/* * This file is part of Code::Blocks Studio, an open-source cross-platform IDE * Copyright (C) 2003 Yiannis An. Mandravellos * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Contact e-mail: Yiannis An. Mandravellos * Program URL : http://www.codeblocks.org * * $Id: nativeparser.cpp,v 1.30.2.1 2005/10/25 07:59:01 mandrav Exp $ * $Date: 2005/10/25 07:59:01 $ */ #include #include "nativeparser.h" #include #include #include #include #include #include #include #include #include #include #include #include "classbrowser.h" #include "parser/parser.h" #include #include BEGIN_EVENT_TABLE(NativeParser, wxEvtHandler) // EVT_MENU(THREAD_START, NativeParser::OnThreadStart) // EVT_MENU(THREAD_END, NativeParser::OnThreadEnd) EVT_MENU(PARSER_END, NativeParser::OnParserEnd) END_EVENT_TABLE() NativeParser::NativeParser() : m_Parsers(1) { //ctor m_pClassBrowser = 0L; } NativeParser::~NativeParser() { RemoveClassBrowser(); ClearParsers(); } void NativeParser::CreateClassBrowser() { if (!m_pClassBrowser) m_pClassBrowser = new ClassBrowser(Manager::Get()->GetNotebook(), this); } void NativeParser::RemoveClassBrowser(bool appShutDown) { if (!appShutDown && m_pClassBrowser) { delete m_pClassBrowser; } m_pClassBrowser = 0L; } void NativeParser::RereadParserOptions() { bool needsReparsing = false; for (ParsersMap::iterator it = m_Parsers.begin(); it != m_Parsers.end(); ++it) { Parser* parser = it->second; if (parser) { ParserOptions opts = parser->Options(); parser->ReadOptions(); if (opts.followLocalIncludes != parser->Options().followLocalIncludes || opts.followGlobalIncludes != parser->Options().followGlobalIncludes || opts.wantPreprocessor != parser->Options().wantPreprocessor) { // important options changed... flag for reparsing needsReparsing = true; } } } if (needsReparsing) { if (wxMessageBox(_("You changed some class parser options. Do you want to " "reparse your projects now, using the new options?"), _("Reparse?"), wxYES | wxNO | wxICON_QUESTION) == wxYES) { cbProject* proj = Manager::Get()->GetProjectManager()->GetActiveProject(); ClearParsers(); ProjectsArray* projects = Manager::Get()->GetProjectManager()->GetProjects(); for (unsigned int i = 0; i < projects->GetCount(); ++i) { AddParser(projects->Item(i)); } if (m_pClassBrowser) m_pClassBrowser->SetParser(m_Parsers[proj]); } } if (m_pClassBrowser) m_pClassBrowser->Update(); } void NativeParser::SetClassBrowserProject(cbProject* project) { if (m_pClassBrowser) m_pClassBrowser->SetParser(m_Parsers[project]); } void NativeParser::SetCBViewMode(const BrowserViewMode& mode) { for (ParsersMap::iterator it = m_Parsers.begin(); it != m_Parsers.end(); ++it) { Parser* parser = it->second; if (parser) { parser->ClassBrowserOptions().showInheritance = mode == bvmInheritance; } } if (m_pClassBrowser) m_pClassBrowser->Update(); } void NativeParser::ClearParsers() { if (m_pClassBrowser) m_pClassBrowser->SetParser(0L); for (ParsersMap::iterator it = m_Parsers.begin(); it != m_Parsers.end(); ++it) { Parser* parser = it->second; if (parser) { delete parser; } } m_Parsers.clear(); } void NativeParser::AddCompilerDirs(Parser* parser, cbProject* project) { if (!parser) return; parser->IncludeDirs().Clear(); wxString base = project->GetBasePath(); parser->IncludeDirs().Add(base); // add project's base path Compiler* compiler = 0; // apply compiler global vars if (CompilerFactory::Compilers.GetCount() > 0 && CompilerFactory::CompilerIndexOK(project->GetCompilerIndex())) { compiler = CompilerFactory::Compilers[project->GetCompilerIndex()]; compiler->GetCustomVars().ApplyVarsToEnvironment(); } // apply project vars project->GetCustomVars().ApplyVarsToEnvironment(); // get project include dirs for (unsigned int i = 0; i < project->GetIncludeDirs().GetCount(); ++i) { wxString out = project->GetIncludeDirs()[i]; Manager::Get()->GetMacrosManager()->ReplaceEnvVars(out); wxFileName dir(out); wxLogNull ln; // hide the error log about "too many ..", if the relative path is invalid if (!dir.IsAbsolute()) dir.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_CASE, base); if (dir.IsOk() && parser->IncludeDirs().Index(dir.GetFullPath()) == wxNOT_FOUND) { parser->IncludeDirs().Add(dir.GetFullPath()); // Manager::Get()->GetMessageManager()->DebugLog("Parser prj dir: " + dir.GetFullPath()); } } // get targets include dirs for (int i = 0; i < project->GetBuildTargetsCount(); ++i) { ProjectBuildTarget* target = project->GetBuildTarget(i); if (target) { // apply target vars target->GetCustomVars().ApplyVarsToEnvironment(); for (unsigned int ti = 0; ti < target->GetIncludeDirs().GetCount(); ++ti) { wxString out = target->GetIncludeDirs()[ti]; Manager::Get()->GetMacrosManager()->ReplaceEnvVars(out); wxFileName dir(out); wxLogNull ln; // hide the error log about "too many ..", if the relative path is invalid if (!dir.IsAbsolute()) dir.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_CASE, base); if (dir.IsOk() && parser->IncludeDirs().Index(dir.GetFullPath()) == wxNOT_FOUND) { parser->IncludeDirs().Add(dir.GetFullPath()); // Manager::Get()->GetMessageManager()->DebugLog("Parser tgt dir: " + dir.GetFullPath()); } } } } // add compiler include dirs if (compiler) { const wxArrayString& dirs = compiler->GetIncludeDirs(); for (unsigned int i = 0; i < dirs.GetCount(); ++i) { //Manager::Get()->GetMessageManager()->Log(mltDevDebug, "Adding %s", dirs[i].c_str()); wxString out = dirs[i]; Manager::Get()->GetMacrosManager()->ReplaceEnvVars(out); wxFileName dir(out); wxLogNull ln; // hide the error log about "too many ..", if the relative path is invalid if (!dir.IsAbsolute()) dir.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_CASE, base); if (dir.IsOk() && parser->IncludeDirs().Index(dir.GetFullPath()) == wxNOT_FOUND) { parser->IncludeDirs().Add(dir.GetFullPath()); // Manager::Get()->GetMessageManager()->DebugLog("Parser cmp dir: " + dir.GetFullPath()); } } } else Manager::Get()->GetMessageManager()->DebugLog(_("No compilers found!")); } void NativeParser::AddParser(cbProject* project, bool useCache) { if (!project) return; // check if we already have a parser for this project if (m_Parsers[project]) return; Manager::Get()->GetMessageManager()->DebugLog(_("Start parsing project %s"), project->GetTitle().c_str()); Parser* parser = new Parser(this); m_Parsers[project] = parser; m_ParsersFilenames[project] = project->GetFilename(); AddCompilerDirs(parser, project); parser->StartTimer(); if (useCache && ConfigManager::Get()->Read(_T("/code_completion/use_cache"), 0L) != 0) { if (LoadCachedData(parser, project)) return; } wxArrayString files; // parse header files first for (int i = 0; i < project->GetFilesCount(); ++i) { ProjectFile* pf = project->GetFile(i); FileType ft = FileTypeOf(pf->relativeFilename); if (ft == ftHeader) // only parse header files { files.Add(pf->file.GetFullPath()); } } // next, parse source files for (int i = 0; i < project->GetFilesCount(); ++i) { ProjectFile* pf = project->GetFile(i); FileType ft = FileTypeOf(pf->relativeFilename); if (ft == ftSource) // only parse source files { files.Add(pf->file.GetFullPath()); } } if (files.IsEmpty()) Manager::Get()->GetMessageManager()->DebugLog(_("End parsing project %s (no files found?)"), project->GetTitle().c_str()); else { parser->BatchParse(files); } } void NativeParser::RemoveParser(cbProject* project, bool useCache) { // check if we already have a parser for this project Parser* parser = m_Parsers[project]; if (!parser) return; if (useCache && ConfigManager::Get()->Read(_T("/code_completion/use_cache"), 0L) != 0) { if (ConfigManager::Get()->Read(_T("/code_completion/update_cache_always"), 0L) != 0 || parser->CacheNeedsUpdate()) { SaveCachedData(parser, m_ParsersFilenames[project]); } } m_Parsers.erase(project); m_ParsersFilenames.erase(project); if (parser) delete parser; if (m_pClassBrowser) m_pClassBrowser->SetParser(0L); Manager::Get()->GetMessageManager()->DebugLog(_("C++ Parser freed")); } void NativeParser::AddFileToParser(cbProject* project, const wxString& filename) { Parser* parser = m_Parsers[project]; if (!parser) return; parser->Parse(filename, true); } void NativeParser::RemoveFileFromParser(cbProject* project, const wxString& filename) { Parser* parser = m_Parsers[project]; if (!parser) return; parser->RemoveFile(filename); } void NativeParser::ForceReparseActiveProject() { cbProject* prj = Manager::Get()->GetProjectManager()->GetActiveProject(); if (prj) { RemoveParser(prj, false); AddParser(prj, false); } } cbProject* NativeParser::FindProjectFromParser(Parser* parser) { for (ParsersMap::iterator it = m_Parsers.begin(); it != m_Parsers.end(); ++it) { if (parser == it->second) return it->first; } return 0L; } cbProject* NativeParser::FindProjectFromEditor(cbEditor* editor) { Parser* parser = FindParserFromEditor(editor); return FindProjectFromParser(parser); } cbProject* NativeParser::FindProjectFromActiveEditor() { EditorManager* edMan = Manager::Get()->GetEditorManager(); if (!edMan) return 0L; cbEditor* ed = edMan->GetBuiltinActiveEditor(); return FindProjectFromEditor(ed); } Parser* NativeParser::FindParserFromEditor(cbEditor* editor) { if (!editor) return 0L; ProjectFile* pf = editor->GetProjectFile(); if (!pf) return 0L; cbProject* project = pf->project; return m_Parsers[project]; } Parser* NativeParser::FindParserFromActiveEditor() { EditorManager* edMan = Manager::Get()->GetEditorManager(); if (!edMan) return 0L; cbEditor* ed = edMan->GetBuiltinActiveEditor(); return FindParserFromEditor(ed); } Parser* NativeParser::FindParserFromActiveProject() { return FindParserFromProject(Manager::Get()->GetProjectManager()->GetActiveProject()); } Parser* NativeParser::FindParserFromProject(cbProject* project) { if (!project) return 0L; return m_Parsers[project]; } bool NativeParser::LoadCachedData(Parser* parser, cbProject* project) { if (!parser || !project) return false; wxFileName projectCache = project->GetFilename(); projectCache.SetExt(_T("cbCache")); wxLogNull ln; wxFile f(projectCache.GetFullPath(), wxFile::read); if (!f.IsOpened()) return false; // read cache file Manager::Get()->GetMessageManager()->DebugLog(_("Using parser's existing cache: %s"), projectCache.GetFullPath().c_str()); bool ret = false; try { ret = parser->ReadFromCache(&f); } catch (cbException& ex) { ex.ShowErrorMessage(true); ret = false; } catch (...) { // eat it wxSafeShowMessage(_("Exception thrown!"),_("ERROR")); ret = false; } DisplayStatus(parser, project); return ret; } bool NativeParser::SaveCachedData(Parser* parser, const wxString& projectFilename) { if (!parser) return false; wxFileName projectCache = projectFilename; projectCache.SetExt(_T("cbCache")); wxLogNull ln; wxFile f(projectCache.GetFullPath(), wxFile::write); if (!f.IsOpened()) { Manager::Get()->GetMessageManager()->DebugLog(_("Failed updating parser's cache: %s"), projectCache.GetFullPath().c_str()); return false; } // write cache file Manager::Get()->GetMessageManager()->DebugLog(_("Updating parser's cache: %s"), projectCache.GetFullPath().c_str()); return parser->WriteToCache(&f); } void NativeParser::DisplayStatus(Parser* parser, cbProject* project) { if (!parser || !project) return; long int tim = parser->GetElapsedTime(); Manager::Get()->GetMessageManager()->DebugLog(_("Done parsing project %s (%d total parsed files, %d tokens in %d.%d seconds)."), project->GetTitle().c_str(), parser->GetFilesCount(), parser->GetTokens().GetCount(), tim / 1000, tim % 1000); } int NativeParser::MarkItemsByAI(bool reallyUseAI) { cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor(); if (!ed) return 0; Parser* parser = FindParserFromActiveEditor(); if (!parser) return 0; if (!parser->Done()) Manager::Get()->GetMessageManager()->DebugLog(_("C++ Parser is still parsing files...")); else { // clear all temporary tokens parser->ClearTemporaries(); bool sort = false; // parse function's arguments wxString _namespace; wxString _procedure; if (FindFunctionNamespace(ed, &_namespace, &_procedure)) { Token* token = parser->FindTokenByName(_procedure, false, tkFunction); if (token) { if (!token->m_Args.IsEmpty() && !token->m_Args.Matches(_T("()"))) { wxString buffer = token->m_Args; buffer.Remove(0, 1); // remove ( buffer.RemoveLast(); // remove ) buffer.Replace(_T(","), _T(";")); // replace commas with semi-colons buffer << _T(';'); // aid parser ;) Manager::Get()->GetMessageManager()->DebugLog(_("Parsing arguments: \"%s\""), buffer.c_str()); if (!parser->ParseBuffer(buffer, false)) Manager::Get()->GetMessageManager()->DebugLog(_("ERROR parsing block:\n%s"), buffer.c_str()); sort = true; } } } else Manager::Get()->GetMessageManager()->DebugLog(_("Could not find current function's namespace...")); // parse current code block int blockStart = FindCurrentBlockStart(ed); if (blockStart != -1) { ++blockStart; // skip { int blockEnd = ed->GetControl()->GetCurrentPos(); wxString buffer = ed->GetControl()->GetTextRange(blockStart, blockEnd); if (!parser->ParseBuffer(buffer, false)) Manager::Get()->GetMessageManager()->DebugLog(_("ERROR parsing block:\n%s"), buffer.c_str()); sort = true; } else Manager::Get()->GetMessageManager()->DebugLog(_("Could not find current block start...")); if (sort) parser->SortAllTokens(); // clear previously marked tokens const TokensArray& tokens = parser->GetTokens(); for (unsigned int i = 0; i < tokens.GetCount(); ++i) { Token* token = tokens[i]; token->m_Bool = !reallyUseAI; } if (!reallyUseAI) return tokens.GetCount(); // AI will mark (m_Bool == true) every token we should include in list return AI(ed, parser); } return 0; } const wxString& NativeParser::GetCodeCompletionItems() { m_CCItems.Clear(); Parser* parser = FindParserFromActiveEditor(); if (!parser) return m_CCItems; int count = MarkItemsByAI(); if (count) { const TokensArray& tokens = parser->GetTokens(); for (unsigned int i = 0; i < tokens.GetCount(); ++i) { Token* token = tokens[i]; if (!token->m_Bool) continue; // not marked by AI token->m_Bool = false; // reset flag for next run if (!m_CCItems.IsEmpty()) m_CCItems << _T(";"); m_CCItems << token->m_Name << token->m_Args;//" " << token->m_Filename << ":" << token->m_Line; } } return m_CCItems; } const wxArrayString& NativeParser::GetCallTips() { m_CallTips.Clear(); cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor(); if (!ed) return m_CallTips; Parser* parser = FindParserFromActiveEditor(); if (!parser) return m_CallTips; if (!parser->Done()) return m_CallTips; int line = ed->GetControl()->GetCurrentLine(); wxString lineText = ed->GetControl()->GetLine(line); int end = lineText.Length(); int nest = 0; while (end > 0) { --end; if (lineText.GetChar(end) == ')') --nest; else if (lineText.GetChar(end) == '(') { ++nest; if (nest != 0) break; } } if (end == 0) return m_CallTips; // no ( lineText.Remove(end); Manager::Get()->GetMessageManager()->DebugLog(_("Sending \"%s\" for call-tip"), lineText.c_str()); // clear previously marked tokens const TokensArray& tokens = parser->GetTokens(); for (unsigned int i = 0; i < tokens.GetCount(); ++i) { Token* token = tokens[i]; token->m_Bool = false; } // AI will mark (m_Bool == true) every token we should include in list if (!AI(ed, parser, lineText, true, true)) return m_CallTips; for (unsigned int i = 0; i < tokens.GetCount(); ++i) { Token* token = tokens[i]; if (token->m_Bool && !token->m_Args.Matches(_T("()"))) { m_CallTips.Add(token->m_Args); token->m_Bool = false; // reset flag for next run } } return m_CallTips; } // helper funcs unsigned int NativeParser::FindCCTokenStart(const wxString& line) { int x = line.Length() - 1; int nest = 0; bool repeat = true; while (repeat) { repeat = false; while (x >= 0 && (isalnum(line.GetChar(x)) || line.GetChar(x) == '_')) --x; if (x > 0 && (line.GetChar(x) == '>' && line.GetChar(x - 1) == '-') || (line.GetChar(x) == ':' && line.GetChar(x - 1) == ':')) { x -= 2; repeat = true; } else if (x >= 0 && line.GetChar(x) == '.') { --x; repeat = true; } if (repeat) { // check for function/cast () if (x >= 0 && line.GetChar(x) == ')') { ++nest; while (--x >= 0 && nest != 0) { switch (line.GetChar(x)) { case ')': ++nest; break; case '(': --nest; break; } } if (x > 0 && (isalnum(line.GetChar(x - 1)) || line.GetChar(x - 1) == '_')) --x; } } } ++x; if (x < 0) x = 0; while (line.GetChar(x) == ' ' || line.GetChar(x) == '\t') ++x; //Manager::Get()->GetMessageManager()->DebugLog("Starting at %d \"%s\"", x, line.Mid(x).c_str()); return x; } wxString NativeParser::GetNextCCToken(const wxString& line, unsigned int& startAt) { wxString res; int nest = 0; if (startAt < line.Length() && line.GetChar(startAt) == '(') { while (startAt < line.Length() && (line.GetChar(startAt) == '*' || line.GetChar(startAt) == '&' || line.GetChar(startAt) == '(')) { if (line.GetChar(startAt) == '(') ++nest; ++startAt; } } //Manager::Get()->GetMessageManager()->DebugLog("at %d (%c): res=%s", startAt, line.GetChar(startAt), res.c_str()); while (startAt < line.Length() && (isalnum(line.GetChar(startAt)) || line.GetChar(startAt) == '_')) { res << line.GetChar(startAt); ++startAt; } while (nest > 0 && startAt < line.Length()) { if (line.GetChar(startAt) == ')') --nest; ++startAt; } //Manager::Get()->GetMessageManager()->DebugLog("Done nest: at %d (%c): res=%s", startAt, line.GetChar(startAt), res.c_str()); if (startAt < line.Length() && line.GetChar(startAt) == '(') { ++nest; while (startAt < line.Length() - 1 && nest != 0) { ++startAt; switch (line.GetChar(startAt)) { case ')': --nest; break; case '(': ++nest; break; } } ++startAt; } //Manager::Get()->GetMessageManager()->DebugLog("Return at %d (%c): res=%s", startAt, line.GetChar(startAt), res.c_str()); return res; } wxString NativeParser::GetCCToken(wxString& line, ParserTokenType& tokenType) { // line contains a string on the following form: // " char* mychar = SomeNamespace::m_SomeVar.SomeMeth" // first we locate the first non-space char starting from the *end*: // // " char* mychar = SomeNamespace::m_SomeVar.SomeMeth" // ^ // then we remove everything before it. // after it, what we do here, is (by this example) return "SomeNamespace" // *and* modify line to become: // m_SomeVar.SomeMeth // so that if we 're called again with the (modified) line, // we 'll return "m_SomeVar" and modify line (again) to become: // SomeMeth // and so on and so forth until we return an empty string... // NOTE: if we find () args in our way, we skip them... tokenType = pttSearchText; if (line.IsEmpty()) return wxEmptyString; unsigned int x = FindCCTokenStart(line); wxString res = GetNextCCToken(line, x); //Manager::Get()->GetMessageManager()->DebugLog("FindCCTokenStart returned %d \"%s\"", x, line.c_str()); //Manager::Get()->GetMessageManager()->DebugLog("GetNextCCToken returned %d \"%s\"", x, res.c_str()); if (x == line.Length()) line.Clear(); else { //Manager::Get()->GetMessageManager()->DebugLog("Left \"%s\"", line.Mid(x).c_str()); if (line.GetChar(x) == '.') { tokenType = pttClass; line.Remove(0, x + 1); } else if ((x < line.Length() - 1 && line.GetChar(x) == '-' && line.GetChar(x + 1) == '>') || (x < line.Length() - 1 && line.GetChar(x) == ':' && line.GetChar(x + 1) == ':')) { if (line.GetChar(x) == ':') tokenType = pttNamespace; else tokenType = pttClass; line.Remove(0, x + 2); } else line.Clear(); } return res; } int NativeParser::AI(cbEditor* editor, Parser* parser, const wxString& lineText, bool noPartialMatch, bool caseSensitive) { int count = 0; int pos = editor->GetControl()->GetCurrentPos(); m_EditorStartWord = editor->GetControl()->WordStartPosition(pos, true); m_EditorEndWord = pos;//editor->GetControl()->WordEndPosition(pos, true); int line = editor->GetControl()->GetCurrentLine(); wxString searchtext; //Manager::Get()->GetMessageManager()->DebugLog("********* START **********"); Token* parentToken = 0L; ParserTokenType tokenType; wxString actual; int col; wxString tabwidth; tabwidth.Pad(editor->GetControl()->GetTabWidth(), ' '); if (lineText.IsEmpty()) { actual = editor->GetControl()->GetLine(line); col = editor->GetControl()->GetColumn(pos); // replace tabs in line by equal-count spaces because col is in spaces! actual.Replace(_T("\t"), tabwidth); actual.Remove(col); actual.Trim(); } else { actual = lineText; col = actual.Length() - 1; } Manager::Get()->GetMessageManager()->DebugLog(_("Doing AI for '%s':"), actual.c_str()); // find current function's namespace wxString procName; wxString scopeName; FindFunctionNamespace(editor, &scopeName, &procName); Token* scopeToken = 0L; if (!scopeName.IsEmpty()) scopeToken = parser->FindTokenByName(scopeName, false, tkNamespace | tkClass); while (1) { // wxString bef = actual; wxString tok = GetCCToken(actual, tokenType); // wxString aft = actual; // Manager::Get()->GetMessageManager()->DebugLog("before='%s', token='%s', after='%s', namespace=%d", bef.c_str(), tok.c_str(), aft.c_str(), tokenType); Manager::Get()->GetMessageManager()->DebugLog(_("tok='%s'"), tok.c_str()); if (tok.IsEmpty()) break; if (actual.IsEmpty() && tokenType == pttSearchText) { searchtext = tok; break; } if (tokenType == pttNamespace) { //parentToken = parser->FindTokenByName(tok); parentToken = parser->FindChildTokenByName(parentToken, tok, true); if (!parentToken) parentToken = parser->FindChildTokenByName(scopeToken, tok, true); // try local scope Manager::Get()->GetMessageManager()->DebugLog(_("Namespace '%s'"), parentToken ? parentToken->m_Name.c_str() : _("unknown")); if (!parentToken) { Manager::Get()->GetMessageManager()->DebugLog(_("Bailing out: namespace not found")); return 0; // fail deliberately } } else { Token* token = 0L; // try #1 - special case if (tok.Matches(_T("this"))) // <-- special case { token = scopeToken; parentToken = scopeToken; } // try #2 - function's class member if (!token) { Manager::Get()->GetMessageManager()->DebugLog(_("Looking for %s under %s"), tok.c_str(), scopeToken ? scopeToken->m_Name.c_str() : _("Unknown")); token = parser->FindChildTokenByName(scopeToken, tok, true); // try local scope } // try #3 - everything else if (!token) { Manager::Get()->GetMessageManager()->DebugLog(_("Looking for %s under %s"), tok.c_str(), parentToken ? parentToken->m_Name.c_str() : _("Unknown")); token = parser->FindChildTokenByName(parentToken, tok, true); } // NOTE: Now that #2 is checked before #3, class member supersedes similarly named // function argument (if any). But if we put #3 before #2, we 'll be checking // global tokens first, which is not what we want... if (token) Manager::Get()->GetMessageManager()->DebugLog(_("Token found %s, type '%s'"), token->m_Name.c_str(), token->m_ActualType.c_str()); if (token && !token->m_ActualType.IsEmpty()) { Manager::Get()->GetMessageManager()->DebugLog(_("actual type is %s"), token->m_ActualType.c_str()); wxString srch = token->m_ActualType; int colon = srch.Find(_T("::")); if (colon != -1) { // type has namespace; break it down and search for it while (!srch.IsEmpty()) { parentToken = parser->FindChildTokenByName(parentToken, srch.Left(colon), true); if (!parentToken) break; Manager::Get()->GetMessageManager()->DebugLog(_("searching under %s"), parentToken->m_DisplayName.c_str()); srch.Remove(0, colon + 2); // plus two to compensate for "::" colon = srch.Find(_T("::")); if (colon == -1) colon = wxSTRING_MAXLEN; } } else { Manager::Get()->GetMessageManager()->DebugLog(_("Locating %s"), token->m_ActualType.c_str()); parentToken = parser->FindTokenByName(token->m_ActualType, true, tkClass | tkNamespace | tkEnum); if (!parentToken) // one more, global, try (might be under a namespace) parentToken = parser->FindTokenByName(token->m_ActualType, false, tkClass | tkNamespace | tkEnum); } } Manager::Get()->GetMessageManager()->DebugLog(_("Class '%s'"), parentToken ? parentToken->m_Name.c_str() : _("unknown")); if (!parentToken) { Manager::Get()->GetMessageManager()->DebugLog(_("Bailing out: class not found")); return 0; // fail deliberately } } //Manager::Get()->GetMessageManager()->Log(mltDevDebug, "parentToken=0x%8.8x", parentToken); } if (noPartialMatch && searchtext.IsEmpty()) return 0; bool bCaseSensitive = parser->Options().caseSensitive || caseSensitive; if (!bCaseSensitive) searchtext.MakeLower(); Manager::Get()->GetMessageManager()->DebugLog(_("Scope='%s'"), scopeToken ? scopeToken->m_Name.c_str() : _("Unknown")); if (parentToken) { Manager::Get()->GetMessageManager()->DebugLog(_("Final parent: '%s' (count=%d, search=%s)"), parentToken->m_Name.c_str(), parentToken->m_Children.GetCount(), searchtext.c_str()); for (unsigned int i = 0; i < parentToken->m_Children.GetCount(); ++i) { Token* token = parentToken->m_Children[i]; if (token->m_IsOperator) continue; // ignore operators wxString name = token->m_Name; if (!bCaseSensitive) name.MakeLower(); bool textCondition; if (noPartialMatch) textCondition = searchtext.IsEmpty() ? false : name.Matches(searchtext); else { if (lineText.IsEmpty()) textCondition = searchtext.IsEmpty() ? true : name.StartsWith(searchtext); else textCondition = searchtext.IsEmpty() ? true : name.Matches(searchtext); } if (token->m_TokenKind == tkEnum) { // enums children (enumerators), are added by default for (unsigned int i2 = 0; i2 < token->m_Children.GetCount(); ++i2) { Token* ctok = token->m_Children[i2]; ctok->m_Bool = true; ++count; } } // scope-based member access :) // display public members // private/protected are displayed only if in same scope... bool scopeCondition = // we can access public members token->m_Scope == tsPublic || // we can access private/protected members of current scope only scopeToken == parentToken; token->m_Bool = textCondition && scopeCondition;// && // token->m_TokenKind != tkConstructor && // ignore constructors // token->m_TokenKind != tkDestructor; // and destructors too if (token->m_Bool) ++count; } // look for inheritance count += DoInheritanceAI(parentToken, scopeToken, searchtext, bCaseSensitive); } else { //Manager::Get()->GetMessageManager()->Log(mltDevDebug, "no token"); // just globals and current function's parent class members here... Manager::Get()->GetMessageManager()->DebugLog(_("Procedure of class '%s' (0x%8.8x), name='%s'"), scopeName.c_str(), scopeToken, procName.c_str()); for (unsigned int i = 0; i < parser->GetTokens().GetCount(); ++i) { Token* token = parser->GetTokens()[i]; if (token->m_IsOperator) continue; // ignore operators wxString name = token->m_Name; if (!bCaseSensitive) name.MakeLower(); bool textCondition; if (!noPartialMatch && lineText.IsEmpty()) textCondition = searchtext.IsEmpty() ? true : name.StartsWith(searchtext); else textCondition = searchtext.IsEmpty() ? false : name.Matches(searchtext); token->m_Bool = textCondition && (!token->m_pParent || // globals token->m_TokenKind == tkEnumerator || // enumerators token->m_pParent == scopeToken || // child of procToken !lineText.IsEmpty()); // locals too if (token->m_Bool) ++count; } // look for inheritance count += DoInheritanceAI(scopeToken, scopeToken, searchtext, bCaseSensitive); } return count; } int NativeParser::DoInheritanceAI(Token* parentToken, Token* scopeToken, const wxString& searchText, bool caseSensitive) { int count = 0; if (!parentToken) return 0; Manager::Get()->GetMessageManager()->DebugLog(_("Checking inheritance of %s"), parentToken->m_Name.c_str()); Manager::Get()->GetMessageManager()->DebugLog(_("- Has %d ancestor(s)"), parentToken->m_Ancestors.GetCount()); for (unsigned int i = 0; i < parentToken->m_Ancestors.GetCount(); ++i) { Token* ancestor = parentToken->m_Ancestors[i]; Manager::Get()->GetMessageManager()->DebugLog(_("- Checking ancestor %s"), ancestor->m_Name.c_str()); int bak = count; for (unsigned int x = 0; x < ancestor->m_Children.GetCount(); ++x) { Token* token = ancestor->m_Children[x]; wxString name = token->m_Name; if (!caseSensitive) name.MakeLower(); bool textCondition = searchText.IsEmpty() ? true : name.StartsWith(searchText); //Manager::Get()->GetMessageManager()->DebugLog("- [%s] %s: %s", searchText.c_str(), name.c_str(), textCondition ? "match" : "no match"); token->m_Bool = textCondition && (token->m_Scope == tsPublic || (scopeToken && scopeToken->InheritsFrom(ancestor))); if (token->m_Bool) count++; } Manager::Get()->GetMessageManager()->DebugLog(_("- %d matches"), count - bak); count += DoInheritanceAI(ancestor, scopeToken, searchText, caseSensitive); } return count; } bool NativeParser::SkipWhitespaceForward(cbEditor* editor, int& pos) { if (!editor) return false; wxChar ch = editor->GetControl()->GetCharAt(pos); int len = editor->GetControl()->GetLength() - 1; if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { while (pos < len && (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')) { ++pos; ch = editor->GetControl()->GetCharAt(pos); } return true; } return false; } bool NativeParser::SkipWhitespaceBackward(cbEditor* editor, int& pos) { if (!editor) return false; wxChar ch = editor->GetControl()->GetCharAt(pos); if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') { while (pos > 0 && (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n')) { --pos; ch = editor->GetControl()->GetCharAt(pos); } return true; } return false; } int NativeParser::FindCurrentBlockStart(cbEditor* editor) { int pos = -1; int line = editor->GetControl()->GetCurrentLine(); while (line >= 0) { int level = editor->GetControl()->GetFoldLevel(line); if ((level & wxSCI_FOLDLEVELHEADERFLAG) && (wxSCI_FOLDLEVELBASE == (level & wxSCI_FOLDLEVELNUMBERMASK))) { // we 're at block start (maybe '{') wxString lineStr = editor->GetControl()->GetLine(line); pos = lineStr.Find(_T("{")); if (pos != wxNOT_FOUND) { pos += editor->GetControl()->PositionFromLine(line); break; } } --line; } return pos; } bool NativeParser::FindFunctionNamespace(cbEditor* editor, wxString* nameSpace, wxString* procName) { if (!nameSpace && !procName) return false; int posOf = FindCurrentBlockStart(editor); if (posOf != wxNOT_FOUND) { // look for :: right before procname and procargs // if we find a }, we failed (probably no class member this function)... bool done = false; int nest = 0; bool passedArgs = false; bool hasNS = false; while (posOf > 0) { --posOf; char ch = editor->GetControl()->GetCharAt(posOf); switch (ch) { case ')' : --nest; passedArgs = false; break; case '(' : ++nest; passedArgs = nest == 0; if (passedArgs) { --posOf; SkipWhitespaceBackward(editor, posOf); } break; } if (passedArgs) { if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == ':') { int bkp = posOf; SkipWhitespaceBackward(editor, posOf); done = true; hasNS = ch == ':' && editor->GetControl()->GetCharAt(posOf - 1) == ':'; posOf = bkp; } } if (done || ch == '}' || ch == ';') break; } Manager::Get()->GetMessageManager()->DebugLog(_("Pos=%d"), posOf); if (done) { if (procName) { int procEnd = editor->GetControl()->WordEndPosition(posOf + 1, true); *procName = editor->GetControl()->GetTextRange(posOf + 1, procEnd); } if (nameSpace && hasNS) { nameSpace->Clear(); posOf -= 2; int scopeStart = editor->GetControl()->WordStartPosition(posOf, true); *nameSpace = editor->GetControl()->GetTextRange(scopeStart, posOf + 1); } Manager::Get()->GetMessageManager()->DebugLog(_("NS: '%s', PROC: '%s'"), nameSpace ? nameSpace->c_str() : _T(""), procName ? procName->c_str() : _T("")); return true; } } else Manager::Get()->GetMessageManager()->DebugLog(_("Can't find block start.")); return false; } // events void NativeParser::OnThreadStart(wxCommandEvent& event) { // nothing for now } void NativeParser::OnThreadEnd(wxCommandEvent& event) { // nothing for now } void NativeParser::OnParserEnd(wxCommandEvent& event) { Parser* parser = (Parser*)event.GetClientData(); if (parser)// && parser->Done()) { cbProject* project = FindProjectFromParser(parser); if (project) DisplayStatus(parser, project); else Manager::Get()->GetMessageManager()->DebugLog(_("Parser aborted (project closed).")); if (project == Manager::Get()->GetProjectManager()->GetActiveProject()) { Manager::Get()->GetMessageManager()->DebugLog(_("Updating class browser...")); if (m_pClassBrowser) { m_pClassBrowser->SetParser(parser); m_pClassBrowser->Update(); } Manager::Get()->GetMessageManager()->DebugLog(_("Class browser updated.")); } } }