1 /*
2  * This file licensed under the GNU General Public License, version 3
3  * http://www.gnu.org/licenses/gpl-3.0.html
4  *
5  */
6 
7 #include "nativeparserf.h"
8 
9 #include <sdk.h>
10 #ifndef CB_PRECOMP
11     #include <wx/regex.h>
12     #include <wx/log.h>
13     #include <wx/string.h>
14     #include <wx/tokenzr.h>
15     #include <wx/dir.h>
16     #include <wx/wfstream.h>
17     #include <wx/stopwatch.h>
18 
19     #include <manager.h>
20     #include <configmanager.h>
21     #include <editormanager.h>
22     #include <projectmanager.h>
23     #include <pluginmanager.h>
24     #include <logmanager.h>
25     #include <cbauibook.h>
26     #include <cbeditor.h>
27     #include <cbproject.h>
28     #include <cbexception.h>
29     #include <projectloader_hooks.h>
30     #include <cbstyledtextctrl.h>
31     #include <tinyxml/tinyxml.h>
32 #endif
33 #include <cctype>
34 
35 #include "workspacebrowserf.h"
36 #include "workspacebrowserbuilder.h"
37 #include "parserf.h"
38 #include "makefilegen.h"
39 #include "bufferparserthread.h"
40 #include "adddirparserthread.h"
41 
42 static wxCriticalSection s_CurrentBufferCritSect;
43 
44 
45 int idWSPThreadEvent          = wxNewId();
46 int idADirPThreadEvent        = wxNewId();
47 int idBPThreadEvent           = wxNewId();
48 int idWorkspaceReparseTimer   = wxNewId();
49 int idASearchDirsReparseTimer = wxNewId();
BEGIN_EVENT_TABLE(NativeParserF,wxEvtHandler)50 BEGIN_EVENT_TABLE(NativeParserF, wxEvtHandler)
51     EVT_COMMAND(idWSPThreadEvent, wxEVT_COMMAND_ENTER, NativeParserF::OnUpdateWorkspaceBrowser)
52     EVT_COMMAND(idADirPThreadEvent, wxEVT_COMMAND_ENTER, NativeParserF::OnUpdateADirTokens)
53     EVT_COMMAND(idBPThreadEvent, wxEVT_COMMAND_ENTER, NativeParserF::OnUpdateCurrentFileTokens)
54     EVT_TIMER(idWorkspaceReparseTimer, NativeParserF::OnReparseWorkspaceTimer)
55     EVT_TIMER(idASearchDirsReparseTimer, NativeParserF::OnASearchDirsReparseTimer)
56 END_EVENT_TABLE()
57 
58 NativeParserF::NativeParserF(FortranProject* forproj)
59     : m_pWorkspaceBrowser(0),
60       m_WorkspaceBrowserIsFloating(false),
61       m_pFortranProject(forproj),
62       m_WorkspaceReparseTimer(this, idWorkspaceReparseTimer),
63       m_ThreadPool(this, wxNewId(), 2, 2 * 1024 * 1024),
64       m_ASearchDirsReparseTimer(this, idASearchDirsReparseTimer)
65 {
66 }
67 
~NativeParserF()68 NativeParserF::~NativeParserF()
69 {
70     RemoveWorkspaceBrowser();
71     ClearParser();
72 }
73 
CreateWorkspaceBrowser()74 void NativeParserF::CreateWorkspaceBrowser()
75 {
76     ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("fortran_project"));
77     m_WorkspaceBrowserIsFloating = cfg->ReadBool(_T("/as_floating_window"), false);
78 
79     if (cfg->ReadBool(_T("/use_symbols_browser"), true))
80     {
81         if (!m_pWorkspaceBrowser)
82         {
83             if (!m_WorkspaceBrowserIsFloating)
84             {
85                 // make this a tab in projectmanager notebook
86                 cbAuiNotebook* bk = Manager::Get()->GetProjectManager()->GetUI().GetNotebook();
87                 m_pWorkspaceBrowser = new WorkspaceBrowserF(bk, this, &m_Parser);
88                 Manager::Get()->GetProjectManager()->GetUI().GetNotebook()->AddPage(m_pWorkspaceBrowser, _("FSymbols"));
89             }
90             else
91             {
92                 // make this a free floating/docking window
93                 m_pWorkspaceBrowser = new WorkspaceBrowserF(Manager::Get()->GetAppWindow(), this, &m_Parser);
94                 CodeBlocksDockEvent evt(cbEVT_ADD_DOCK_WINDOW);
95 
96                 evt.name = _T("FSymbolsBrowser");
97                 evt.title = _("FSymbols browser");
98                 evt.pWindow = m_pWorkspaceBrowser;
99                 evt.dockSide = CodeBlocksDockEvent::dsRight;
100                 evt.desiredSize.Set(200, 250);
101                 evt.floatingSize.Set(200, 250);
102                 evt.minimumSize.Set(150, 150);
103                 evt.shown = true;
104                 evt.hideable = true;
105                 Manager::Get()->ProcessEvent(evt);
106             }
107             m_pWorkspaceBrowser->UpdateSash();
108         }
109     }
110 }
111 
GetWorkspaceBrowser()112 WorkspaceBrowserF* NativeParserF::GetWorkspaceBrowser()
113 {
114     return m_pWorkspaceBrowser;
115 }
116 
RemoveWorkspaceBrowser()117 void NativeParserF::RemoveWorkspaceBrowser()
118 {
119     if (m_pWorkspaceBrowser)
120     {
121         if (!m_WorkspaceBrowserIsFloating)
122         {
123             int idx = Manager::Get()->GetProjectManager()->GetUI().GetNotebook()->GetPageIndex(m_pWorkspaceBrowser);
124             if (idx != -1)
125                 Manager::Get()->GetProjectManager()->GetUI().GetNotebook()->RemovePage(idx);
126         }
127         else
128         {
129             CodeBlocksDockEvent evt(cbEVT_REMOVE_DOCK_WINDOW);
130             evt.pWindow = m_pWorkspaceBrowser;
131             Manager::Get()->ProcessEvent(evt);
132         }
133         m_pWorkspaceBrowser->Destroy();
134     }
135     m_pWorkspaceBrowser = 0L;
136 }
137 
UpdateWorkspaceBrowser(bool selectCurrentSymbol)138 void NativeParserF::UpdateWorkspaceBrowser(bool selectCurrentSymbol)
139 {
140     if (m_pWorkspaceBrowser && !Manager::IsAppShuttingDown())
141     {
142         wxCriticalSectionLocker locker(s_CritSect);
143 
144         m_pWorkspaceBrowser->UpdateView();
145     }
146     MarkCurrentSymbol(selectCurrentSymbol);
147 }
148 
AddParser(cbProject * project)149 void NativeParserF::AddParser(cbProject* project)
150 {
151     if (!project)
152         return;
153 
154     ParseProject(project);
155 }
156 
ClearParser()157 void NativeParserF::ClearParser()
158 {
159     m_Parser.Clear();
160 }
161 
RemoveFromParser(cbProject * project)162 void NativeParserF::RemoveFromParser(cbProject* project)
163 {
164     if (Manager::Get()->GetProjectManager()->GetProjects()->GetCount() == 0)
165     {
166         m_Parser.Clear();
167         UpdateWorkspaceBrowser();
168 
169         return;
170     }
171     if (!project)
172         return;
173 
174     for (FilesList::iterator it = project->GetFilesList().begin(); it != project->GetFilesList().end(); ++it)
175     {
176         ProjectFile* pf = *it;
177         m_Parser.RemoveFile(pf->file.GetFullPath());
178     }
179     RemoveProjectFilesDependency(project);
180 }
181 
IsFileFortran(const wxString & filename)182 bool NativeParserF::IsFileFortran(const wxString& filename)
183 {
184     FortranSourceForm fsForm;
185     return IsFileFortran(filename, fsForm);
186 }
187 
IsFileFortran(const wxString & filename,FortranSourceForm & fsForm)188 bool NativeParserF::IsFileFortran(const wxString& filename, FortranSourceForm& fsForm)
189 {
190    return m_Parser.IsFileFortran(filename, fsForm);
191 }
192 
AddFileToParser(const wxString & projectFilename,const wxString & filename)193 void NativeParserF::AddFileToParser(const wxString& projectFilename, const wxString& filename)
194 {
195     FortranSourceForm fsForm;
196     if (IsFileFortran(filename, fsForm))
197     {
198         m_Parser.Reparse(projectFilename, filename, fsForm);
199     }
200 }
201 
RemoveFileFromParser(const wxString & filename)202 void NativeParserF::RemoveFileFromParser(const wxString& filename)
203 {
204     m_Parser.RemoveFile(filename);
205 }
206 
ParseProject(cbProject * project)207 void NativeParserF::ParseProject(cbProject* project)
208 {
209     wxArrayString files;
210     FortranSourceForm fsForm;
211     ArrayOfFortranSourceForm fileForms;
212     wxArrayString prFilenameArr;
213     wxString prFName = project->GetFilename();
214 
215     for (FilesList::iterator it = project->GetFilesList().begin(); it != project->GetFilesList().end(); ++it)
216     {
217         ProjectFile* pf = *it;
218 
219         if (IsFileFortran(pf->relativeFilename, fsForm))
220         {
221             files.Add(pf->file.GetFullPath());
222             fileForms.push_back(fsForm);
223             prFilenameArr.Add(prFName);
224         }
225     }
226     if (!files.IsEmpty())
227     {
228         m_Parser.BatchParse(prFilenameArr, files, fileForms);
229     }
230 }
231 
ReparseFile(const wxString & projectFilename,const wxString & filename)232 void NativeParserF::ReparseFile(const wxString& projectFilename, const wxString& filename)
233 {
234     FortranSourceForm fsForm;
235     if (IsFileFortran(filename, fsForm))
236         m_Parser.Reparse(projectFilename, filename, fsForm);
237 }
238 
ReparseProject(cbProject * project)239 void NativeParserF::ReparseProject(cbProject* project)
240 {
241     wxStopWatch sw;
242 
243     if (project && !Manager::IsAppShuttingDown())
244     {
245         wxString projectFilename = project->GetFilename();
246         for (FilesList::iterator it = project->GetFilesList().begin(); it != project->GetFilesList().end(); ++it)
247         {
248             ProjectFile* pf = *it;
249             ReparseFile(projectFilename, pf->file.GetFullPath());
250         }
251     }
252 
253     Manager::Get()->GetLogManager()->DebugLog(F(_T("NativeParserF::ReparseProject: Reparse poject took %d ms."), sw.Time()));
254 }
255 
ForceReparseWorkspace()256 void NativeParserF::ForceReparseWorkspace()
257 {
258     if (Manager::IsAppShuttingDown())
259         return;
260 
261     cbProject* project = Manager::Get()->GetProjectManager()->GetActiveProject();
262     if (project && m_pWorkspaceBrowser)
263         m_pWorkspaceBrowser->SetActiveProject(project);
264     m_WorkspaceReparseTimer.Start(500, wxTIMER_ONE_SHOT);
265 }
266 
OnReparseWorkspaceTimer(wxTimerEvent & event)267 void NativeParserF::OnReparseWorkspaceTimer(wxTimerEvent& event)
268 {
269     if (Manager::IsAppShuttingDown())
270         return;
271 
272     if (s_WorkspaceParserMutex.TryLock() == wxMUTEX_NO_ERROR)
273     {
274         MakeWSFileList();
275         s_WorkspaceParserMutex.Unlock();
276 
277         WorkspaceParserThread* thread = new WorkspaceParserThread(this, idWSPThreadEvent);
278         m_ThreadPool.AddTask(thread, true);
279     }
280 
281     OnASearchDirsReparseTimer(event);
282 }
283 
MakeWSFileList()284 void NativeParserF::MakeWSFileList()
285 {
286     FortranSourceForm fsForm;
287     m_WSFiles.clear();
288     m_WSFileForms.clear();
289 
290     ProjectsArray* projects = Manager::Get()->GetProjectManager()->GetProjects();
291     for (size_t i = 0; i < projects->GetCount(); ++i)
292     {
293         cbProject* proj = projects->Item(i);
294         wxString prFName = proj->GetFilename();
295 
296         for (FilesList::iterator it = proj->GetFilesList().begin(); it != proj->GetFilesList().end(); ++it)
297         {
298             ProjectFile* pf = *it;
299 
300             if (IsFileFortran(pf->relativeFilename, fsForm))
301             {
302                 m_WSFiles.Add(pf->file.GetFullPath());
303                 m_WSFileForms.push_back(fsForm);
304                 m_WSFilePFN.push_back(prFName);
305             }
306         }
307     }
308 }
309 
MakeADirFileList()310 void NativeParserF::MakeADirFileList()
311 {
312     FortranSourceForm fsForm;
313 
314     m_ADirFiles.clear();
315     m_ADirFileForms.clear();
316     m_ADirFNameToProjMap.clear();
317 
318     for (auto it=m_ASearchDirs.begin(); it != m_ASearchDirs.end(); ++it)
319     {
320         wxArrayString files;
321         wxArrayString* pDirs = &it->second;
322         for (size_t i=0; i<pDirs->size(); ++i)
323         {
324             wxDir::GetAllFiles(pDirs->Item(i), &files, wxEmptyString, wxDIR_FILES);
325         }
326 
327         size_t nfiles = files.size();
328         for (size_t i=0; i<nfiles; i++)
329         {
330             if (IsFileFortran(files.Item(i), fsForm))
331             {
332                 if (m_ADirFNameToProjMap.count(files.Item(i)) == 0)
333                 {
334                     m_ADirFiles.Add(files.Item(i));
335                     m_ADirFileForms.push_back(fsForm);
336 
337                     wxArrayString prarr;
338                     prarr.Add(it->first);
339                     m_ADirFNameToProjMap[files.Item(i)] = prarr;
340                 }
341                 else
342                 {
343                     wxArrayString* prarr = &m_ADirFNameToProjMap[files.Item(i)];
344                     prarr->Add(it->first);
345                 }
346             }
347         }
348     }
349 }
350 
GetWSFiles()351 wxArrayString* NativeParserF::GetWSFiles()
352 {
353     return &m_WSFiles;
354 }
355 
GetWSFileForms()356 ArrayOfFortranSourceForm* NativeParserF::GetWSFileForms()
357 {
358     return &m_WSFileForms;
359 }
360 
GetWSFileProjFilenames()361 wxArrayString* NativeParserF::GetWSFileProjFilenames()
362 {
363     return &m_WSFilePFN;
364 }
365 
GetADirFiles()366 wxArrayString* NativeParserF::GetADirFiles()
367 {
368     return &m_ADirFiles;
369 }
370 
GetADirFileForms()371 ArrayOfFortranSourceForm* NativeParserF::GetADirFileForms()
372 {
373     return &m_ADirFileForms;
374 }
375 
OnUpdateWorkspaceBrowser(wxCommandEvent &)376 void NativeParserF::OnUpdateWorkspaceBrowser(wxCommandEvent& /*event*/)
377 {
378     m_Parser.ConnectToNewTokens();
379     UpdateWorkspaceBrowser();
380 }
381 
OnUpdateADirTokens(wxCommandEvent &)382 void NativeParserF::OnUpdateADirTokens(wxCommandEvent& /*event*/)
383 {
384     m_Parser.ConnectToNewADirTokens();
385 }
386 
OnProjectActivated(cbProject * prj)387 void NativeParserF::OnProjectActivated(cbProject* prj)
388 {
389     if (!m_pWorkspaceBrowser)
390         return;
391 
392     m_pWorkspaceBrowser->SetActiveProject(prj);
393     UpdateWorkspaceBrowser();
394 }
395 
OnEditorActivated(EditorBase * editor)396 void NativeParserF::OnEditorActivated(EditorBase* editor)
397 {
398     if (!m_pWorkspaceBrowser)
399         return;
400     cbEditor* ed = editor && editor->IsBuiltinEditor() ? static_cast<cbEditor*>(editor) : 0;
401     if (ed)
402     {
403         wxString filename = ed->GetFilename();
404         if (m_pWorkspaceBrowser->GetBrowserDisplayFilter() == bdfFile && !m_pWorkspaceBrowser->GetActiveFilename().IsSameAs(filename))
405         {
406             UpdateWorkspaceBrowser(true);
407         }
408     }
409 }
410 
OnEditorClose(EditorBase * editor)411 void NativeParserF::OnEditorClose(EditorBase* editor)
412 {
413     cbEditor* ed = editor && editor->IsBuiltinEditor() ? static_cast<cbEditor*>(editor) : 0;
414     if (ed)
415     {
416         m_Parser.RemoveBuffer(ed->GetFilename());
417     }
418 }
419 
UpdateWorkspaceFilesDependency()420 void NativeParserF::UpdateWorkspaceFilesDependency()
421 {
422     ClearWSDependency();
423     ProjectsArray* projects = Manager::Get()->GetProjectManager()->GetProjects();
424 
425     for (size_t i = 0; i < projects->GetCount(); ++i)
426     {
427         cbProject* proj = projects->Item(i);
428         if (!proj->IsMakefileCustom())
429             UpdateProjectFilesDependency(proj);
430     }
431 }
432 
UpdateProjectFilesDependency(cbProject * project)433 void NativeParserF::UpdateProjectFilesDependency(cbProject* project)
434 {
435     project->SaveAllFiles();
436 
437     ProjectFilesArray pfs;
438     for (FilesList::iterator it = project->GetFilesList().begin(); it != project->GetFilesList().end(); ++it)
439     {
440         ProjectFile* pf = *it;
441         if (IsFileFortran(pf->relativeFilename))
442         {
443             pfs.push_back(pf);
444         }
445     }
446 
447     wxString fn = project->GetFilename();
448     WSDependencyMap::iterator pos;
449     pos = m_WSDependency.find(fn);
450     if (pos == m_WSDependency.end())
451     {
452         pos = m_WSDependency.insert(std::make_pair(fn,new ProjectDependencies(project))).first;
453     }
454     if (pfs.size() > 0)
455     {
456         pos->second->MakeProjectFilesDependencies(pfs, m_Parser);
457         pos->second->EnsureUpToDateObjs();
458 
459         for (size_t i=0; i<pfs.size(); i++)
460         {
461             wxString fn2 = pfs[i]->file.GetFullPath();
462             unsigned short int wt = pos->second->GetFileWeight(fn2);
463             pfs[i]->weight = wt;
464         }
465         if (pos->second->HasInfiniteDependences())
466         {
467             wxString msg = _T("Warning. FortranProject plugin:\n");
468             msg << _T("     'It seems you have a circular dependency in Fortran files. Check your USE or INCLUDE statements.'");
469             Manager::Get()->GetLogManager()->Log(msg);
470             cbMessageBox(_("It seems you have a circular dependency in Fortran files. Check your USE or INCLUDE statements."),
471                          _("Warning"));
472         }
473     }
474 }
475 
ClearWSDependency()476 void NativeParserF::ClearWSDependency()
477 {
478     WSDependencyMap::iterator pos=m_WSDependency.begin();
479     while(pos != m_WSDependency.end())
480     {
481         ProjectDependencies* pd = pos->second;
482         pd->Clear();
483         delete pd;
484         pos++;
485     }
486     m_WSDependency.clear();
487 }
488 
RemoveProjectFilesDependency(cbProject * project)489 void NativeParserF::RemoveProjectFilesDependency(cbProject* project)
490 {
491     if (m_WSDependency.count(project->GetFilename()))
492     {
493         ProjectDependencies* pd = m_WSDependency[project->GetFilename()];
494         pd->Clear();
495         delete pd;
496     }
497 }
498 
GetParser()499 ParserF* NativeParserF::GetParser()
500 {
501     return &m_Parser;
502 }
503 
GetTokenKindImageIdx(TokenF * token)504 int NativeParserF::GetTokenKindImageIdx(TokenF* token)
505 {
506     if (m_pWorkspaceBrowser)
507         return m_pWorkspaceBrowser->GetTokenKindImageIdx(token);
508     return 0;
509 }
510 
511 // count commas in lineText (nesting parentheses)
CountCommas(const wxString & lineText,int start,bool nesting)512 int NativeParserF::CountCommas(const wxString& lineText, int start, bool nesting)
513 {
514     int commas = 0;
515     int nest   = 0;
516     bool inA  = false;
517     bool inDA = false;
518     while (true)
519     {
520         wxChar c = lineText.GetChar(start);
521         start++;
522         if (c == '\0')
523             break;
524         else if (nesting && (c == '(' || c == '[') && !inA && !inDA)
525             ++nest;
526         else if (nesting && (c == ')' || c == ']') && !inA && !inDA)
527         {
528             --nest;
529             if (nest < 0)
530                 break;
531         }
532         else if (c == '\'' && !inA && !inDA)
533             inA = true;
534         else if (c == '\'' && inA)
535             inA = false;
536         else if (c == '"' && !inA && !inDA)
537             inDA = true;
538         else if (c == '"' && inDA)
539             inDA = false;
540         else if (c == ',' && nest == 0 && !inA && !inDA)
541             ++commas;
542     }
543     return commas;
544 }
545 
GetLastName(const wxString & line)546 wxString NativeParserF::GetLastName(const wxString& line)
547 {
548     wxString name;
549     wxString tmp = line;
550     tmp.Trim();
551     if (tmp.IsEmpty())
552         return name;
553     int cur = tmp.Len() - 1;
554 
555     while (cur >= 0)
556     {
557         wxChar cch = tmp.GetChar(cur);
558         if (!isalnum(cch) && (cch != '_'))
559         {
560             cur++;
561             break;
562         }
563         else
564             cur--;
565     }
566     if (cur < 0)
567         cur = 0;
568     name = tmp.Mid(cur);
569 
570     return name;
571 }
572 
CollectInformationForCallTip(int & commasAll,int & commasUntilPos,wxString & argNameUnderCursor,wxString & lastName,bool & isAfterPercent,int & argsPos,TokensArrayFlat * result)573 void NativeParserF::CollectInformationForCallTip(int& commasAll, int& commasUntilPos, wxString& argNameUnderCursor, wxString& lastName,
574                                                  bool& isAfterPercent, int& argsPos, TokensArrayFlat* result)
575 {
576     wxString lineText; // string before '('
577     CountCommasInEditor(commasAll, commasUntilPos, lastName, lineText, argsPos);
578     if (lastName.IsEmpty())
579         return;
580 
581     lineText.Trim();
582     wxString lineTextMinus = lineText.Mid(0,lineText.Len()-lastName.Len());
583     wxString beforLast = GetLastName(lineTextMinus);
584     if (beforLast.IsSameAs(_T("subroutine"),false) || beforLast.IsSameAs(_T("function"),false))
585     {
586         lastName = _T("");
587         return; // we don't want calltips during procedure declaration
588     }
589 
590     isAfterPercent = false;
591     cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
592     if(!ed)
593         return;
594 
595     GetDummyVarName(ed, argNameUnderCursor);
596 
597     m_Parser.ChangeLineIfRequired(ed, lineText);
598 
599     lineText.Trim();
600     TokensArrayFlatClass tokensTemp;
601     TokensArrayFlat* resultTemp = tokensTemp.GetTokens();
602     if (!m_Parser.FindMatchTypeComponents(ed, lineText, *resultTemp, false, false, isAfterPercent, true))
603         return;
604     if (resultTemp->GetCount() > 0)
605     {
606         TokenFlat* token = resultTemp->Item(0); // we take only first added item
607         result->Add( new TokenFlat(token) );
608         if (token->m_TokenKind == tkProcedure)
609         {
610             wxString tokName;
611             if (!token->m_PartLast.IsEmpty())
612                 tokName = token->m_PartLast;
613             else
614                 tokName = token->m_Name;
615 
616             TokensArrayFlatClass tokensTmp;
617             TokensArrayFlat* resultTmp = tokensTmp.GetTokens();
618             int kindMask = tkFunction | tkSubroutine;
619             int noInChildren = tkInterface | tkFunction | tkSubroutine;
620             bool found = m_Parser.FindMatchTokenInSameModule(token, tokName, *resultTmp, kindMask, noInChildren);
621             if (!found)
622                 m_Parser.FindMatchTokensDeclared(tokName, *resultTmp, kindMask, false, noInChildren);
623             if (resultTmp->GetCount() > 0)
624                 result->Add( new TokenFlat(resultTmp->Item(0)) );
625         }
626         else if (token->m_TokenKind == tkInterface)
627         {
628             m_Parser.FindGenericTypeBoudComponents(token, *result);
629             for (size_t i=1; i<resultTemp->GetCount(); i++)
630             {
631                 if (resultTemp->Item(i)->m_TokenKind == tkInterface)
632                 {
633                     result->Add( new TokenFlat(resultTemp->Item(i)));
634                     m_Parser.FindGenericTypeBoudComponents(resultTemp->Item(i), *result);
635                 }
636             }
637         }
638     }
639 
640 }
641 
642 
CountCommasInEditor(int & commasAll,int & commasUntilPos,wxString & lastName,wxString & lineText,int & pos)643 void NativeParserF::CountCommasInEditor(int& commasAll, int& commasUntilPos, wxString& lastName, wxString& lineText, int &pos)
644 {
645     commasAll = 0;
646     commasUntilPos = 0;
647     lastName = wxEmptyString;
648     int end = 0;
649     cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
650     if(!ed)
651         return;
652 
653     cbStyledTextCtrl* control = ed->GetControl();
654     if (!control)
655         return;
656     int line = control->GetCurrentLine();
657     lineText = control->GetLine(line);
658     pos = control->PositionFromLine(line);
659     end = control->GetCurrentPos() - pos;
660 
661     lineText = lineText.BeforeFirst('!');
662     if (int(lineText.Len()) < end)
663         return; // we are in comments
664     //join lines first, if we are in the continuation line
665     FortranSourceForm fsForm;
666     IsFileFortran(ed->GetShortName(), fsForm);
667 
668     if (fsForm == fsfFree)
669     {
670         int line2 = line - 1;
671         while (line2 > 0)
672         {
673             wxString lineTextPast = control->GetLine(line2).BeforeFirst('!');
674             lineTextPast = lineTextPast.Trim();
675             if (!lineTextPast.IsEmpty())
676             {
677                 int idx = lineTextPast.Find('&', true);
678                 if (idx == wxNOT_FOUND)
679                 {
680                     break;
681                 }
682                 else
683                 {
684                     lineText = lineTextPast.Mid(0,idx) + lineText;
685                     end += idx;
686                     pos = control->PositionFromLine(line2);
687                 }
688             }
689             line2--;
690         }
691     }
692     else //fsfFixed
693     {
694         if (lineText.Len() >= 6)
695         {
696             wxChar contS = lineText.GetChar(5);
697             if (contS != ' ' && contS != '0')
698             {
699                 lineText = lineText.Mid(6);
700                 pos += 6;
701                 end -= 6;
702                 int line2 = line - 1;
703                 while (line2 > 0)
704                 {
705                     wxString lineTextPast = control->GetLine(line2).BeforeFirst('!');
706                     lineTextPast = lineTextPast.Trim();
707                     if (!lineTextPast.IsEmpty())
708                     {
709                         lineText = lineTextPast + lineText;
710                         end += lineTextPast.Len();
711                         pos = control->PositionFromLine(line2);
712                         if (lineTextPast.Len() >= 6)
713                         {
714                             wxChar contS2 = lineTextPast.GetChar(5);
715                             if (contS2 == ' ' || contS2 == '0')
716                                 break;
717                             else
718                             {
719                                 lineText = lineText.Mid(6);
720                                 pos += 6;
721                                 end -= 6;
722                             }
723                         }
724                         else
725                             break;
726                     }
727                     line2--;
728                 }
729             }
730         }
731         else
732         {
733             return;
734         }
735     }
736 
737     wxString lineTextUntilPos = lineText.Mid(0,end);
738     int nest = 0;
739 
740     while (end > 0)
741     {
742         --end;
743         if (lineText.GetChar(end) == ')')
744             --nest;
745         else if (lineText.GetChar(end) == '(')
746         {
747             ++nest;
748             if (nest > 0)
749             {
750                 // count commas (nesting parentheses again) to see how far we 're in arguments
751                 commasAll = CountCommas(lineText, end + 1);
752                 commasUntilPos = CountCommas(lineTextUntilPos, end + 1);
753                 break;
754             }
755         }
756     }
757     if (!end)
758         return;
759 
760     lineText.Truncate(end);
761     pos += lineText.Len();
762     lastName = GetLastName(lineText);
763 }
764 
GetDummyVarName(cbEditor * ed,wxString & lastDummyVar)765 void NativeParserF::GetDummyVarName(cbEditor* ed, wxString& lastDummyVar)
766 {
767     // Get dummy arg name like 'vnam' in 'call sub1(a, b, vnam=...'
768     cbStyledTextCtrl* control = ed->GetControl();
769     if (!control)
770         return;
771     int clin = control->GetCurrentLine();
772     int lpos = control->PositionFromLine(clin);
773     int cpos = control->GetCurrentPos();
774     cpos = control->WordEndPosition(cpos, true);
775     while (cpos < control->GetLength())
776     {
777         wxChar c = control->GetCharAt(cpos);
778         if (c == ' ' || c == '=')
779             cpos++;
780         else
781             break;
782     }
783     wxString line = control->GetTextRange(lpos, cpos);
784 
785     if (line.Find('!') != wxNOT_FOUND)
786         return;
787 
788     int asig = line.Find('=', true);
789     if (asig == wxNOT_FOUND)
790         return;
791 
792     int endIdx = 0;
793     int nest = 0;
794     bool inA  = false;
795     bool inDA = false;
796     for (int i=line.Len()-1; i>=0; --i)
797     {
798         wxChar c = line.GetChar(i);
799         if (c == '\'' && !inA && !inDA)
800             inA = true;
801         else if (c == '\'' && inA)
802             inA = false;
803         else if (c == '"' && !inA && !inDA)
804             inDA = true;
805         else if (c == '"' && inDA)
806             inDA = false;
807         else if ((c == ')' || c == ']') && !inA && !inDA)
808             nest++;
809         else if ((c == '(' || c == '[') && nest == 0 && !inA && !inDA)
810             break;
811         else if ((c == '(' || c == '[') && !inA && !inDA)
812             nest--;
813         else if (c == ',' && nest == 0 && !inA && !inDA)
814             break;
815         else if (c == '=' && nest == 0 && !inA && !inDA)
816         {
817             endIdx = i;
818             break;
819         }
820     }
821 
822     if (endIdx == 0)
823         return;
824 
825     lastDummyVar = GetLastName(line.Mid(0, endIdx));
826 }
827 
GetCallTips(const wxString & name,bool onlyUseAssoc,bool onlyPublicNames,wxArrayString & callTips,TokensArrayFlat * result)828 void NativeParserF::GetCallTips(const wxString& name, bool onlyUseAssoc, bool onlyPublicNames, wxArrayString& callTips, TokensArrayFlat* result)
829 {
830     int tokKind;
831     if (Manager::Get()->GetConfigManager(_T("fortran_project"))->ReadBool(_T("/call_tip_arrays"), true))
832         tokKind = tkFunction | tkSubroutine | tkInterface | tkType | tkVariable;
833     else
834         tokKind = tkFunction | tkSubroutine | tkInterface | tkType;
835 
836     int resCountOld = result->GetCount();
837     if (onlyUseAssoc)
838     {
839         cbEditor* ed =  Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
840         if (!ed)
841             return;
842         m_Parser.FindUseAssociatedTokens(onlyPublicNames, ed, name, false, *result, tokKind, false);
843         int noChildrenOf = tkInterface | tkModule | tkSubmodule | tkFunction | tkSubroutine | tkProgram;
844         m_Parser.FindMatchTokensDeclared(name, *result, tokKind, false, noChildrenOf, false, true); // take global procedures only
845 
846         if (tokKind & tkVariable)
847         {
848             TokensArrayFlatClass tokensTmp;
849             TokensArrayFlat* resultTmp = tokensTmp.GetTokens();
850             m_Parser.FindMatchDeclarationsInCurrentScope(name, ed, *resultTmp, false);
851             for (size_t i=0; i<resultTmp->GetCount(); i++)
852             {
853                 if (resultTmp->Item(i)->m_TokenKind == tkVariable)
854                     result->Add(new TokenFlat(resultTmp->Item(i)));
855             }
856         }
857     }
858     else
859     {
860         int noChildrenOf = tkInterface | tkFunction | tkSubroutine | tkProgram;
861         m_Parser.FindMatchTokensDeclared(name, *result, tokKind, false, noChildrenOf, onlyPublicNames);
862     }
863 
864     int tokkindFS = tkFunction | tkSubroutine;
865     int resCount = result->GetCount();
866     for (int i=resCountOld; i<resCount; ++i)
867     {
868         if (result->Item(i)->m_ParentTokenKind == tkSubmodule && (result->Item(i)->m_TokenKind & tokkindFS))
869         {
870             for (int j=i+1; j<resCount; ++j)
871             {
872                 if (result->Item(j)->m_ParentTokenKind == tkInterfaceExplicit &&
873                     result->Item(j)->m_TokenKind == result->Item(i)->m_TokenKind &&
874                     result->Item(j)->m_Name.IsSameAs(result->Item(i)->m_Name) )
875                 {
876                     result->RemoveAt(i);
877                     resCount--;
878                     i--;
879                     break;
880                 }
881             }
882         }
883     }
884 
885     resCount = result->GetCount();
886     for (int i=resCountOld; i<resCount; ++i)
887     {
888         if (result->Item(i)->m_TokenKind == tkInterface)
889         {
890             m_Parser.FindChildrenOfInterface(result->Item(i), *result);
891             result->RemoveAt(i);
892             resCount--;
893             i--;
894         }
895     }
896 
897     resCount = result->GetCount();
898     for (int i=resCountOld; i<resCount; ++i)
899     {
900         if (result->Item(i)->m_TokenKind == tkVariable)
901         {
902             wxString callTipArr;
903             GetCallTipsForVariable(result->Item(i), callTipArr);
904             if (!callTipArr.IsEmpty())
905                 callTips.Add(callTipArr);
906         }
907         else if (result->Item(i)->m_TokenKind == tkType)
908         {
909             if (resCountOld+1 != int(result->GetCount()))
910             {
911                 // remove 'type' if it is not unique
912                 result->RemoveAt(i);
913                 resCount--;
914                 i--;
915             }
916             else
917             {
918                 // Default structure-constructor
919                 wxString callTipType;
920                 GetCallTipsForType(result->Item(i), callTipType);
921                 if (!callTipType.IsEmpty())
922                     callTips.Add(callTipType);
923                 else
924                 {
925                     result->RemoveAt(i);
926                     resCount--;
927                     i--;
928                 }
929             }
930         }
931         else
932             callTips.Add(result->Item(i)->m_Args);
933     }
934 }
935 
GetCallTipsForGenericTypeBoundProc(TokensArrayFlat * result,wxArrayString & callTips,wxArrayInt & idxFuncSub)936 void NativeParserF::GetCallTipsForGenericTypeBoundProc(TokensArrayFlat* result, wxArrayString& callTips, wxArrayInt& idxFuncSub)
937 {
938     if (result->GetCount() >= 3 && result->Item(0)->m_TokenKind == tkInterface)
939     {
940         int tokKind = tkFunction | tkSubroutine;
941         for (size_t i=1; i<result->GetCount()-1; i+=2)
942         {
943             if (result->Item(i)->m_TokenKind == tkInterface)
944                 i++;
945             if (i+1 >= result->GetCount())
946                 return;
947             if (result->Item(i)->m_TokenKind != tkProcedure || !(result->Item(i+1)->m_TokenKind & tokKind))
948                 return;
949 
950             TokensArrayFlatClass tokensTmpCl;
951             TokensArrayFlat* tokensTmp = tokensTmpCl.GetTokens();
952             tokensTmp->Add(new TokenFlat(result->Item(i)));
953             tokensTmp->Add(new TokenFlat(result->Item(i+1)));
954             GetCallTipsForTypeBoundProc(tokensTmp, callTips);
955             idxFuncSub.Add(i+1);
956         }
957     }
958 }
959 
GetCallTipsForTypeBoundProc(TokensArrayFlat * result,wxArrayString & callTips)960 void NativeParserF::GetCallTipsForTypeBoundProc(TokensArrayFlat* result, wxArrayString& callTips)
961 {
962     if (result->GetCount() != 2)
963         return;
964     if (!(result->Item(0)->m_TokenKind == tkProcedure))
965         return;
966 
967     int tokKind = tkFunction | tkSubroutine;
968     if (!(result->Item(1)->m_TokenKind & tokKind))
969         return;
970 
971     TokenFlat tbProcTok(result->Item(0));
972     m_Parser.ChangeArgumentsTypeBoundProc(tbProcTok, result->Item(1));
973     callTips.Add(tbProcTok.m_Args);
974 }
975 
GetCallTipsForVariable(TokenFlat * token,wxString & callTip)976 void NativeParserF::GetCallTipsForVariable(TokenFlat* token, wxString& callTip)
977 {
978     callTip = wxEmptyString;
979     if (!(token->m_TokenKind == tkVariable))
980         return;
981 
982     int dstart = token->m_TypeDefinition.Lower().Find(_T("dimension"));
983     if (dstart != wxNOT_FOUND)
984     {
985         wxString dim = token->m_TypeDefinition.Mid(dstart+9);
986         if (dim.size() > 0 && dim[0] == '(')
987         {
988             int last = dim.Find(')');
989             if (last != wxNOT_FOUND)
990                 callTip = dim.Mid(0,last+1);
991         }
992     }
993     else if (token->m_Args.StartsWith(_T("(")))
994     {
995         int last = token->m_Args.Find(')');
996         if (last != wxNOT_FOUND)
997             callTip = token->m_Args.Mid(0,last+1);
998     }
999 }
1000 
GetCallTipsForType(TokenFlat * token,wxString & callTip)1001 void NativeParserF::GetCallTipsForType(TokenFlat* token, wxString& callTip)
1002 {
1003     callTip = wxEmptyString;
1004     if (!(token->m_TokenKind == tkType))
1005         return;
1006 
1007     if (token->m_IsAbstract || !token->m_ExtendsType.IsEmpty())  // no default constructor for Abstract or Extended type
1008         return;
1009     TokensArrayFlatClass tokensTmp;
1010     TokensArrayFlat* resultTmp = tokensTmp.GetTokens();
1011     m_Parser.GetTypeComponentsInFile(token->m_Filename, token->m_LineStart, token->m_Name, resultTmp);
1012 
1013     wxString names;
1014     for (size_t i=0; i<resultTmp->GetCount(); i++)
1015     {
1016         if (resultTmp->Item(i)->m_TokenKind != tkVariable)
1017             continue;
1018 
1019         names << resultTmp->Item(i)->m_DisplayName << _T(", ");
1020     }
1021 
1022     if (!names.IsEmpty())
1023     {
1024         callTip << _T("(") << names.Mid(0,names.Length()-2) << _T(")");
1025     }
1026 }
1027 
1028 // set start and end to the calltip highlight region, based on commasWas (calculated in GetCallTips())
GetCallTipHighlight(const wxString & calltip,int commasWas,int & start,int & end)1029 void NativeParserF::GetCallTipHighlight(const wxString& calltip, int commasWas, int& start, int& end)
1030 {
1031     m_Parser.GetCallTipHighlight(calltip, commasWas, start, end);
1032 }
1033 
MarkCurrentSymbol(bool selectCurrentSymbol)1034 void NativeParserF::MarkCurrentSymbol(bool selectCurrentSymbol)
1035 {
1036     if (!m_pWorkspaceBrowser || Manager::IsAppShuttingDown())
1037         return;
1038 
1039     cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
1040     if (!ed)
1041         return;
1042     wxString activeFilename = ed->GetFilename();
1043     if (activeFilename.IsEmpty())
1044         return;
1045     if (!IsFileFortran(activeFilename))
1046         return;
1047     cbStyledTextCtrl* control = ed->GetControl();
1048     int currentLine = control->GetCurrentLine() + 1;
1049 
1050     wxCriticalSectionLocker locker(s_CritSect);
1051     wxString fname = UnixFilename(activeFilename);
1052     m_pWorkspaceBrowser->MarkSymbol(fname, currentLine);
1053     if (selectCurrentSymbol)
1054         m_pWorkspaceBrowser->SelectSymbol(fname, currentLine);
1055 }
1056 
RereadOptions()1057 void NativeParserF::RereadOptions()
1058 {
1059     ConfigManager* cfg = Manager::Get()->GetConfigManager(_T("fortran_project"));
1060     // disabled?
1061     if (cfg->ReadBool(_("/use_symbols_browser"), true))
1062     {
1063         if (!m_pWorkspaceBrowser)
1064         {
1065             CreateWorkspaceBrowser();
1066         }
1067         // change class-browser docking settings
1068         else if (m_WorkspaceBrowserIsFloating != cfg->ReadBool(_T("/as_floating_window"), false))
1069         {
1070             RemoveWorkspaceBrowser();
1071             CreateWorkspaceBrowser();
1072         }
1073         else
1074         {
1075             m_pWorkspaceBrowser->RereadOptions();
1076         }
1077         UpdateWorkspaceBrowser();
1078     }
1079     else if (m_pWorkspaceBrowser)
1080     {
1081         RemoveWorkspaceBrowser();
1082     }
1083     else
1084     {
1085         //m_pWorkspaceBrowser->RereadOptions();
1086     }
1087 
1088     m_Parser.RereadOptions();
1089 }
1090 
GetJumpTracker()1091 JumpTracker* NativeParserF::GetJumpTracker()
1092 {
1093     return &m_JumpTracker;
1094 }
1095 
GetFortranProject()1096 FortranProject* NativeParserF::GetFortranProject()
1097 {
1098     return m_pFortranProject;
1099 }
1100 
GenMakefile()1101 void NativeParserF::GenMakefile()
1102 {
1103     cbProject* project = Manager::Get()->GetProjectManager()->GetActiveProject();
1104     if (!project)
1105     {
1106         Manager::Get()->GetLogManager()->Log(_T("No active project was found. Makefile was not generated."));
1107         cbMessageBox(_("No active project was found.\nMakefile was not generated."), _("Error"), wxICON_ERROR);
1108         return;
1109     }
1110 
1111     UpdateProjectFilesDependency(project);
1112 
1113     wxString fn = project->GetFilename();
1114     WSDependencyMap::iterator pos;
1115     pos = m_WSDependency.find(fn);
1116     if (pos == m_WSDependency.end())
1117         return;
1118 
1119     if (pos->second->GetSizeFiles() > 0)
1120         MakefileGen::GenerateMakefile(project, pos->second, this);
1121     else
1122     {
1123         Manager::Get()->GetLogManager()->Log(_T("Active project doesn't have Fortran files."));
1124         cbMessageBox(_("Active project doesn't have Fortran files.\nMakefile was not generated."), _("Information"), wxICON_INFORMATION);
1125     }
1126 }
1127 
GetCurrentBuffer(wxString & buffer,wxString & filename,wxString & projFilename)1128 void NativeParserF::GetCurrentBuffer(wxString& buffer, wxString& filename, wxString& projFilename)
1129 {
1130     wxCriticalSectionLocker locker(s_CurrentBufferCritSect);
1131     buffer   = m_CurrentEditorBuffer;
1132     filename = m_CurrentEditorFilename;
1133     projFilename = m_CurrentEditorProjectFN;
1134 }
1135 
ReparseCurrentEditor()1136 void NativeParserF::ReparseCurrentEditor()
1137 {
1138     cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
1139     if(!ed)
1140         return;
1141     cbStyledTextCtrl* control = ed->GetControl();
1142     if (!control)
1143         return;
1144 
1145     {
1146         wxCriticalSectionLocker locker(s_CurrentBufferCritSect);
1147         m_CurrentEditorBuffer = control->GetText();
1148         m_CurrentEditorFilename = ed->GetFilename();
1149 
1150         ProjectFile* pf = ed->GetProjectFile();
1151         if (pf)
1152         {
1153             cbProject* pr = pf->GetParentProject();
1154             if (pr)
1155             {
1156                 m_CurrentEditorProjectFN = pr->GetFilename();
1157             }
1158         }
1159         else
1160             m_CurrentEditorProjectFN = _T("");
1161     }
1162 
1163     if (BufferParserThread::s_BPTInstances <= 1)
1164     {
1165         BufferParserThread* thread = new BufferParserThread(this, idBPThreadEvent);
1166         m_ThreadPool.AddTask(thread, true);
1167     }
1168 }
1169 
OnUpdateCurrentFileTokens(wxCommandEvent &)1170 void NativeParserF::OnUpdateCurrentFileTokens(wxCommandEvent& /*event*/)
1171 {
1172     m_Parser.ConnectToNewCurrentTokens();
1173 }
1174 
GetProjectSearchDirs(cbProject * project)1175 wxArrayString NativeParserF::GetProjectSearchDirs(cbProject* project)
1176 {
1177     wxArrayString dirs;
1178     if (!project)
1179         return dirs;
1180     wxString pfn = project->GetFilename();
1181     if (m_ASearchDirs.count(pfn) == 0)
1182         return dirs;
1183 
1184     return m_ASearchDirs[pfn];
1185 }
1186 
SetProjectSearchDirs(cbProject * project,wxArrayString & searchDirs)1187 void NativeParserF::SetProjectSearchDirs(cbProject* project, wxArrayString& searchDirs)
1188 {
1189     if (!project)
1190         return;
1191 
1192     m_ASearchDirs[project->GetFilename()] = searchDirs;
1193 }
1194 
HasFortranFiles(cbProject * project)1195 bool NativeParserF::HasFortranFiles(cbProject* project)
1196 {
1197     if (!project)
1198         return false;
1199 
1200     wxString pfn = project->GetFilename();
1201     for (size_t i=0; i<m_WSFilePFN.size(); ++i)
1202     {
1203         if (m_WSFilePFN[i] == pfn)
1204             return true;
1205     }
1206     return false;
1207 }
1208 
DelProjectSearchDirs(cbProject * project)1209 void NativeParserF::DelProjectSearchDirs(cbProject* project)
1210 {
1211     if (!project)
1212         return;
1213 
1214     m_ASearchDirs.erase(project->GetFilename());
1215 }
1216 
ForceReparseProjectSearchDirs()1217 void NativeParserF::ForceReparseProjectSearchDirs()
1218 {
1219     if (Manager::IsAppShuttingDown())
1220         return;
1221 
1222     m_ASearchDirsReparseTimer.Start(1500, wxTIMER_ONE_SHOT);
1223 }
1224 
OnASearchDirsReparseTimer(wxTimerEvent &)1225 void NativeParserF::OnASearchDirsReparseTimer(wxTimerEvent& /*event*/)
1226 {
1227     if (Manager::IsAppShuttingDown())
1228         return;
1229 
1230     if (s_AdditionalDirParserMutex.TryLock() == wxMUTEX_NO_ERROR)
1231     {
1232         MakeADirFileList();
1233         s_AdditionalDirParserMutex.Unlock();
1234 
1235         ADirParserThread* thread = new ADirParserThread(this, idADirPThreadEvent);
1236         m_ThreadPool.AddTask(thread, true);
1237     }
1238 }
1239