/*
* 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 <mandrav@codeblocks.org>
* Program URL : http://www.codeblocks.org
*
* $Id: codecompletion.cpp,v 1.20.2.1 2005/10/25 07:59:01 mandrav Exp $
* $Date: 2005/10/25 07:59:01 $
*/
#include <sdk.h>
#include "codecompletion.h"
#include <wx/mimetype.h>
#include <wx/filename.h>
#include <wx/regex.h>
#include <wx/xrc/xmlres.h>
#include <wx/fs_zip.h>
#include <wx/msgdlg.h>
#include <manager.h>
#include <configmanager.h>
#include <messagemanager.h>
#include <projectmanager.h>
#include <editormanager.h>
#include <sdk_events.h>
#include <incrementalselectlistdlg.h>
#include "insertclassmethoddlg.h"
#include "ccoptionsdlg.h"
#include "parser/parser.h"
#include "cclist.h"
CB_IMPLEMENT_PLUGIN(CodeCompletion);
// menu IDS
// just because we don't know other plugins' used identifiers,
// we use wxNewId() to generate a guaranteed unique ID ;), instead of enum
// (don't forget that, especially in a plugin)
int idMenuCodeComplete = wxNewId();
int idMenuShowCallTip = wxNewId();
int idMenuGotoFunction = wxNewId();
int idEditorSubMenu = wxNewId();
int idClassMethod = wxNewId();
int idGotoDeclaration = wxNewId();
int idOpenIncludeFile = wxNewId();
BEGIN_EVENT_TABLE(CodeCompletion, cbCodeCompletionPlugin)
EVT_UPDATE_UI_RANGE(idMenuCodeComplete, idMenuGotoFunction, CodeCompletion::OnUpdateUI)
EVT_MENU(idMenuCodeComplete, CodeCompletion::OnCodeComplete)
EVT_MENU(idMenuShowCallTip, CodeCompletion::OnShowCallTip)
EVT_MENU(idMenuGotoFunction, CodeCompletion::OnGotoFunction)
EVT_MENU(idClassMethod, CodeCompletion::OnClassMethod)
EVT_MENU(idGotoDeclaration, CodeCompletion::OnGotoDeclaration)
EVT_MENU(idOpenIncludeFile, CodeCompletion::OnOpenIncludeFile)
EVT_EDITOR_AUTOCOMPLETE(CodeCompletion::OnCodeComplete)
EVT_EDITOR_CALLTIP(CodeCompletion::OnShowCallTip)
EVT_EDITOR_USERLIST_SELECTION(CodeCompletion::OnUserListSelection)
EVT_EDITOR_SAVE(CodeCompletion::OnReparseActiveEditor)
EVT_PROJECT_OPEN(CodeCompletion::OnProjectOpened)
EVT_PROJECT_ACTIVATE(CodeCompletion::OnProjectActivated)
EVT_PROJECT_CLOSE(CodeCompletion::OnProjectClosed)
EVT_PROJECT_FILE_ADDED(CodeCompletion::OnProjectFileAdded)
EVT_PROJECT_FILE_REMOVED(CodeCompletion::OnProjectFileRemoved)
EVT_CCLIST_CODECOMPLETE(CodeCompletion::OnCodeComplete)
END_EVENT_TABLE()
CodeCompletion::CodeCompletion()
{
wxFileSystem::AddHandler(new wxZipFSHandler);
wxXmlResource::Get()->InitAllHandlers();
wxString resPath = ConfigManager::Get()->Read(_T("data_path"), wxEmptyString);
wxXmlResource::Get()->Load(resPath + _T("/code_completion.zip#zip:*.xrc"));
m_PluginInfo.name = _T("CodeCompletion");
m_PluginInfo.title = _("Code completion");
m_PluginInfo.version = _T("0.1");
m_PluginInfo.description = _("This plugin provides a class browser for your projects "
"and code-completion inside the editor\n\n"
"Note: Only C/C++ language is supported by this plugin...");
m_PluginInfo.author = _T("Yiannis An. Mandravellos");
m_PluginInfo.authorEmail = _T("info@codeblocks.org");
m_PluginInfo.authorWebsite = _T("www.codeblocks.org");
m_PluginInfo.thanksTo = _T("");
m_PluginInfo.hasConfigure = true;
m_PageIndex = -1;
m_EditMenu = 0L;
m_SearchMenu = 0L;
ConfigManager::AddConfiguration(m_PluginInfo.title, _T("/code_completion"));
}
CodeCompletion::~CodeCompletion()
{
}
int CodeCompletion::Configure()
{
CCOptionsDlg dlg(Manager::Get()->GetAppWindow());
if (dlg.ShowModal() == wxID_OK)
{
m_NativeParsers.RereadParserOptions();
}
return 0;
}
void CodeCompletion::BuildMenu(wxMenuBar* menuBar)
{
// if not attached, exit
if (!m_IsAttached)
return;
// if (m_EditMenu)
// return; // already set-up
int pos = menuBar->FindMenu(_("&Edit"));
if (pos != wxNOT_FOUND)
{
m_EditMenu = menuBar->GetMenu(pos);
m_EditMenu->AppendSeparator();
m_EditMenu->Append(idMenuCodeComplete, _("Complete code\tCtrl-Space"));
m_EditMenu->Append(idMenuShowCallTip, _("Show call tip\tCtrl-Shift-Space"));
}
else
Manager::Get()->GetMessageManager()->DebugLog(_("Could not find Edit menu!"));
pos = menuBar->FindMenu(_("Sea&rch"));
if (pos != wxNOT_FOUND)
{
m_SearchMenu = menuBar->GetMenu(pos);
m_SearchMenu->Append(idMenuGotoFunction, _("Goto function...\tCtrl-Alt-G"));
}
else
Manager::Get()->GetMessageManager()->DebugLog(_("Could not find Search menu!"));
}
void CodeCompletion::BuildModuleMenu(const ModuleType type, wxMenu* menu, const wxString& arg)
{
// if not attached, exit
if (!menu || !m_IsAttached)
return;
if (type == mtEditorManager)
{
cbStyledTextCtrl* control = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor()->GetControl();
if (control)
{
int pos = control->GetCurrentPos();
wxString line = control->GetLine(control->LineFromPosition(pos));
wxRegEx reg(_T("^[ \t]*#[ \t]*include[ \t]+[\"<]([^\">]+)[\">]"));
wxString inc;
if (reg.Matches(line))
inc = reg.GetMatch(line, 1);
m_LastIncludeFile.Clear();
if (!inc.IsEmpty())
{
wxString msg;
msg.Printf(_("Open #include file: '%s'"), inc.c_str());
menu->Insert(0, idOpenIncludeFile, msg);
menu->Insert(1, wxID_SEPARATOR, wxEmptyString);
m_LastIncludeFile = inc;
}
else // either #include or keyword-search
{
int ws = control->WordStartPosition(pos, true);
int we = control->WordEndPosition(pos, true);
wxString txt = control->GetTextRange(ws, we);
m_LastKeyword.Clear();
if (!txt.IsEmpty())
{
wxString msg;
msg.Printf(_("Find declaration of: '%s'"), txt.c_str());
menu->Insert(0, idGotoDeclaration, msg);
menu->Insert(1, wxID_SEPARATOR, wxEmptyString);
m_LastKeyword = txt;
}
}
}
int insertId = menu->FindItem(_("Insert..."));
if (insertId != wxNOT_FOUND)
{
wxMenuItem* insertMenu = menu->FindItem(insertId, NULL);
if (insertMenu)
{
wxMenu* subMenu = insertMenu->GetSubMenu();
if (subMenu)
{
subMenu->Append(idClassMethod, _("Class method declaration/implementation..."));
}
else
Manager::Get()->GetMessageManager()->DebugLog(_("Could not find Insert menu 3!"));
}
else
Manager::Get()->GetMessageManager()->DebugLog(_("Could not find Insert menu 2!"));
}
else
Manager::Get()->GetMessageManager()->DebugLog(_("Could not find Insert menu!"));
}
}
bool CodeCompletion::BuildToolBar(wxToolBar* toolBar)
{
// no need for toolbar items
return false;
}
void CodeCompletion::OnAttach()
{
m_NativeParsers.CreateClassBrowser();
// parse all active projects
ProjectManager* prjMan = Manager::Get()->GetProjectManager();
for (unsigned int i = 0; i < prjMan->GetProjects()->GetCount(); ++i)
m_NativeParsers.AddParser(prjMan->GetProjects()->Item(i));
}
void CodeCompletion::OnRelease(bool appShutDown)
{
m_NativeParsers.RemoveClassBrowser(appShutDown);
m_NativeParsers.ClearParsers();
CCList::Free();
/* TODO (mandrav#1#): Delete separator line too... */
if (m_EditMenu)
{
m_EditMenu->Delete(idMenuCodeComplete);
m_EditMenu->Delete(idMenuShowCallTip);
}
if (m_SearchMenu)
m_SearchMenu->Delete(idMenuGotoFunction);
}
int CodeCompletion::CodeComplete()
{
if (!m_IsAttached)
return -1;
EditorManager* edMan = Manager::Get()->GetEditorManager();
if (!edMan)
return -2;
cbEditor* ed = edMan->GetBuiltinActiveEditor();
if (!ed)
return -3;
FileType ft = FileTypeOf(ed->GetShortName());
if ( ft != ftHeader && ft != ftSource) // only parse source/header files
return -4;
Parser* parser = m_NativeParsers.FindParserFromActiveEditor();
if (!parser)
{
Manager::Get()->GetMessageManager()->DebugLog(_("Active editor has no associated parser ?!?"));
return -4;
}
if (m_NativeParsers.MarkItemsByAI(parser->Options().useSmartSense))
{
CCList::Free(); // free any previously open cc list
CCList::Get(this, ed->GetControl(), parser)->Show();
return 0;
}
return -5;
}
void CodeCompletion::CodeCompleteIncludes()
{
if (!m_IsAttached)
return;
cbProject* pPrj = Manager::Get()->GetProjectManager()->GetActiveProject();
if (!pPrj)
return;
EditorManager* edMan = Manager::Get()->GetEditorManager();
if (!edMan)
return;
cbEditor* ed = edMan->GetBuiltinActiveEditor();
if (!ed)
return;
Parser* parser = m_NativeParsers.FindParserFromActiveEditor();
bool caseSens = parser ? parser->Options().caseSensitive : false;
FileType ft = FileTypeOf(ed->GetShortName());
if ( ft != ftHeader && ft != ftSource) // only parse source/header files
return;
int pos = ed->GetControl()->GetCurrentPos();
int lineStartPos = ed->GetControl()->PositionFromLine(ed->GetControl()->GetCurrentLine());
wxString line = ed->GetControl()->GetLine(ed->GetControl()->GetCurrentLine());
//Manager::Get()->GetMessageManager()->DebugLog("#include cc for \"%s\"", line.c_str());
line.Trim();
if (line.IsEmpty() || !line.StartsWith(_T("#include")))
return;
// find opening quote (" or <)
int idx = pos - lineStartPos;
int found = -1;
wxString filename;
while (idx > 0)
{
wxChar c = line.GetChar(idx);
if (c == _T('>'))
return; // the quote is closed already...
else if (c == _T('"') || c == _T('<'))
{
if (found != -1)
return; // the already found quote was the closing one...
found = idx + 1;
}
else if (c != _T(' ') && c != _T('\t') && !found)
filename << c;
--idx;
}
// take care: found might point at the end of the string (out of bounds)
// in this case: #include "(<-code-completion at this point)
//Manager::Get()->GetMessageManager()->DebugLog("#include using \"%s\" (starting at %d)", filename.c_str(), found);
if (found == -1)
return;
// fill a list of matching project files
wxArrayString files;
for (int i = 0; i < pPrj->GetFilesCount(); ++i)
{
ProjectFile* pf = pPrj->GetFile(i);
if (pf && FileTypeOf(pf->relativeFilename) == ftHeader)
{
wxFileName fname(pf->relativeFilename);
files.Add(pf->relativeFilename);
files.Add(fname.GetFullName());
}
}
if (files.GetCount() != 0)
{
files.Sort();
ed->GetControl()->AutoCompSetIgnoreCase(caseSens);
ed->GetControl()->AutoCompShow(pos - lineStartPos - found, GetStringFromArray(files, _T(" ")));
}
}
wxArrayString CodeCompletion::GetCallTips()
{
if (!m_IsAttached)
{
wxArrayString items;
return items;
}
return m_NativeParsers.GetCallTips();
}
void CodeCompletion::ShowCallTip()
{
if (!m_IsAttached)
return;
if (!Manager::Get()->GetEditorManager())
return;
cbEditor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
if (!ed)
return;
wxArrayString items = GetCallTips();
wxString definition;
for (unsigned int i = 0; i < items.GetCount(); ++i)
{
if (!items[i].IsEmpty())
{
if (i != 0)
definition << _T('\n'); // add new-line, except for the first line
definition << items[i];
}
}
if (!definition.IsEmpty())
ed->GetControl()->CallTipShow(ed->GetControl()->GetCurrentPos(), definition);
}
int CodeCompletion::DoClassMethodDeclImpl()
{
if (!m_IsAttached)
return -1;
EditorManager* edMan = Manager::Get()->GetEditorManager();
if (!edMan)
return -2;
cbEditor* ed = edMan->GetBuiltinActiveEditor();
if (!ed)
return -3;
FileType ft = FileTypeOf(ed->GetShortName());
if ( ft != ftHeader && ft != ftSource) // only parse source/header files
return -4;
Parser* parser = m_NativeParsers.FindParserFromActiveEditor();
if (!parser)
{
Manager::Get()->GetMessageManager()->DebugLog(_("Active editor has no associated parser ?!?"));
return -4;
}
wxString filename = ed->GetFilename();
InsertClassMethodDlg dlg(Manager::Get()->GetAppWindow(), parser, filename);
if (dlg.ShowModal() == wxID_OK)
{
int pos = ed->GetControl()->GetCurrentPos();
int line = ed->GetControl()->LineFromPosition(pos);
ed->GetControl()->GotoPos(ed->GetControl()->PositionFromLine(line));
wxArrayString result = dlg.GetCode();
for (unsigned int i = 0; i < result.GetCount(); ++i)
{
pos = ed->GetControl()->GetCurrentPos();
line = ed->GetControl()->LineFromPosition(pos);
wxString str = ed->GetLineIndentString(line - 1) + result[i];
ed->GetControl()->SetTargetStart(pos);
ed->GetControl()->SetTargetEnd(pos);
ed->GetControl()->ReplaceTarget(str);
ed->GetControl()->GotoPos(pos + str.Length());// - 3);
}
return 0;
}
return -5;
}
void CodeCompletion::DoCodeComplete()
{
EditorManager* edMan = Manager::Get()->GetEditorManager();
if (!edMan)
return;
cbEditor* ed = edMan->GetBuiltinActiveEditor();
if (!ed)
return;
int style = ed->GetControl()->GetStyleAt(ed->GetControl()->GetCurrentPos());
// Manager::Get()->GetMessageManager()->DebugLog(_("Style at %d is %d"), ed->GetControl()->GetCurrentPos(), style);
// Manager::Get()->GetMessageManager()->DebugLog(_("wxSCI_C_PREPROCESSOR is %d"), wxSCI_C_PREPROCESSOR);
if (style == wxSCI_C_PREPROCESSOR)
{
CodeCompleteIncludes();
return;
}
if (style != wxSCI_C_DEFAULT && style != wxSCI_C_OPERATOR && style != wxSCI_C_IDENTIFIER)
return;
CodeComplete();
}
void CodeCompletion::DoInsertCodeCompleteToken(wxString tokName)
{
// remove arguments
int pos = tokName.Find(_T("("));
if (pos != wxNOT_FOUND)
tokName.Remove(pos);
EditorManager* edMan = Manager::Get()->GetEditorManager();
if (!edMan)
return;
cbEditor* ed = edMan->GetBuiltinActiveEditor();
if (!ed)
return;
int end = ed->GetControl()->GetCurrentPos() > m_NativeParsers.GetEditorEndWord() ? ed->GetControl()->GetCurrentPos() : m_NativeParsers.GetEditorEndWord();
ed->GetControl()->SetSelection(m_NativeParsers.GetEditorStartWord(), end);
ed->GetControl()->ReplaceSelection(_T(""));
ed->GetControl()->InsertText(m_NativeParsers.GetEditorStartWord(), tokName);
ed->GetControl()->GotoPos(m_NativeParsers.GetEditorStartWord() + tokName.Length());
}
void CodeCompletion::OnProjectOpened(CodeBlocksEvent& event)
{
if (m_IsAttached)
m_NativeParsers.AddParser(event.GetProject());
event.Skip();
}
void CodeCompletion::OnProjectActivated(CodeBlocksEvent& event)
{
if (m_IsAttached)
m_NativeParsers.SetClassBrowserProject(event.GetProject());
event.Skip();
}
void CodeCompletion::OnProjectClosed(CodeBlocksEvent& event)
{
if (m_IsAttached)
m_NativeParsers.RemoveParser(event.GetProject());
event.Skip();
}
void CodeCompletion::OnProjectFileAdded(CodeBlocksEvent& event)
{
if (m_IsAttached)
m_NativeParsers.AddFileToParser(event.GetProject(), event.GetString());
event.Skip();
}
void CodeCompletion::OnProjectFileRemoved(CodeBlocksEvent& event)
{
if (m_IsAttached)
m_NativeParsers.RemoveFileFromParser(event.GetProject(), event.GetString());
event.Skip();
}
void CodeCompletion::OnUserListSelection(CodeBlocksEvent& event)
{
if (m_IsAttached)
{
wxString tokName = event.GetString();
DoInsertCodeCompleteToken(event.GetString());
}
event.Skip();
}
void CodeCompletion::OnReparseActiveEditor(CodeBlocksEvent& event)
{
if (m_IsAttached)
{
cbEditor* ed = event.GetEditor();
if (!ed)
return;
Parser* parser = m_NativeParsers.FindParserFromActiveEditor();
if (!parser || !parser->Done())
return;
parser->StartTimer();
parser->Reparse(ed->GetFilename());
}
event.Skip();
}
// events
void CodeCompletion::OnUpdateUI(wxUpdateUIEvent& event)
{
bool hasEd = Manager::Get()->GetEditorManager()->GetActiveEditor() != 0;
if (m_EditMenu)
{
m_EditMenu->Enable(idMenuCodeComplete, hasEd);
m_EditMenu->Enable(idMenuShowCallTip, hasEd);
}
if (m_SearchMenu)
{
m_SearchMenu->Enable(idMenuGotoFunction, hasEd);
}
// must do...
event.Skip();
}
void CodeCompletion::OnCodeComplete(wxCommandEvent& event)
{
if (ConfigManager::Get()->Read(_T("/code_completion/use_code_completion"), 1L) == 0)
return;
if (m_IsAttached)
DoCodeComplete();
event.Skip();
}
void CodeCompletion::OnShowCallTip(wxCommandEvent& event)
{
if (m_IsAttached)
ShowCallTip();
event.Skip();
}
void CodeCompletion::OnGotoFunction(wxCommandEvent& event)
{
EditorManager* edMan = Manager::Get()->GetEditorManager();
if (!edMan)
return;
cbEditor* ed = edMan->GetBuiltinActiveEditor();
if (!ed)
return;
Parser parser(this);
parser.ParseBufferForFunctions(ed->GetControl()->GetText());
wxArrayString funcs;
const TokensArray& tokens = parser.GetTokens();
for (unsigned int i = 0; i < tokens.GetCount(); ++i)
{
funcs.Add(tokens[i]->m_DisplayName);// token->m_Name);
}
if (!funcs.GetCount())
{
wxMessageBox(_("No functions parsed in this file..."));
return;
}
IncrementalSelectListDlg dlg(Manager::Get()->GetAppWindow(), funcs, _("Select function..."), _("Please select function to go to:"));
if (dlg.ShowModal() == wxID_OK)
{
wxString sel = dlg.GetStringSelection();
for (unsigned int i = 0; i < tokens.GetCount(); ++i)
{
Token* token = tokens[i];
if (token && token->m_DisplayName.Matches(sel))
{
Manager::Get()->GetMessageManager()->DebugLog(_("Token found at line %d"), token->m_Line);
ed->GetControl()->GotoLine(token->m_Line - 1);
}
}
}
}
void CodeCompletion::OnClassMethod(wxCommandEvent& event)
{
DoClassMethodDeclImpl();
}
void CodeCompletion::OnGotoDeclaration(wxCommandEvent& event)
{
EditorManager* edMan = Manager::Get()->GetEditorManager();
if (!edMan)
return;
wxString txt = m_LastKeyword;
// Manager::Get()->GetMessageManager()->DebugLog(_("Go to decl for '%s'"), txt.c_str());
Parser* parser = m_NativeParsers.FindParserFromActiveEditor();
if (!parser)
parser = m_NativeParsers.FindParserFromActiveProject(); // get parser of active project, then
if (!parser)
return;
Token* token = parser->FindTokenByName(txt, false);
if (token)
{
cbEditor* ed = edMan->Open(token->m_Filename);
if (ed)
{
ed->GetControl()->GotoLine(token->m_Line - 1);
return;
}
}
wxMessageBox(wxString::Format(_("Not found: %s"), txt.c_str()), _("Warning"), wxICON_WARNING);
}
void CodeCompletion::OnOpenIncludeFile(wxCommandEvent& event)
{
Parser* parser = m_NativeParsers.FindParserFromActiveEditor();
if (!parser)
parser = m_NativeParsers.FindParserFromActiveProject(); // get parser of active project, then
if (!parser)
return;
wxString inc = m_LastIncludeFile;
// Manager::Get()->GetMessageManager()->DebugLog(_("Looking for #include '%s' (%d dirs)"), inc.c_str(), parser->IncludeDirs().GetCount());
for (unsigned int i = 0; i < parser->IncludeDirs().GetCount(); ++i)
{
wxString base = parser->IncludeDirs()[i];
wxFileName tmp = inc;
tmp.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_CASE, base);
// Manager::Get()->GetMessageManager()->DebugLog(_("Searching '%s'"), tmp.GetFullPath().c_str());
if (wxFileExists(tmp.GetFullPath()))
{
EditorManager* edMan = Manager::Get()->GetEditorManager();
edMan->Open(tmp.GetFullPath());
return;
}
}
wxMessageBox(wxString::Format(_("Not found: %s"), inc.c_str()), _("Warning"), wxICON_WARNING);
}
syntax highlighted by Code2HTML, v. 0.9.1