1
#include "clang_code_completion.h"
2
#include "environmentconfig.h"
3
#include "processreaderthread.h"
6
#include "fileextmanager.h"
7
#include <wx/tokenzr.h>
10
#include "workspace.h"
12
#include <wx/filename.h>
14
#include <wx/arrstr.h>
15
#include "procutils.h"
17
static wxStopWatch gStopWatch;
19
BEGIN_EVENT_TABLE(ClangCodeCompletion, wxEvtHandler)
20
EVT_COMMAND(wxID_ANY, wxEVT_PROC_DATA_READ, ClangCodeCompletion::OnProcessOutput)
21
EVT_COMMAND(wxID_ANY, wxEVT_PROC_TERMINATED, ClangCodeCompletion::OnProcessTerminated)
24
ClangCodeCompletion* ClangCodeCompletion::ms_instance = 0;
26
ClangCodeCompletion::ClangCodeCompletion()
29
, m_activationPos (wxNOT_FOUND)
30
, m_activationEditor(NULL)
34
ClangCodeCompletion::~ClangCodeCompletion()
38
void ClangCodeCompletion::Initialize(IManager* manager)
43
ClangCodeCompletion* ClangCodeCompletion::Instance()
45
if(ms_instance == 0) {
46
ms_instance = new ClangCodeCompletion();
51
void ClangCodeCompletion::Release()
59
void ClangCodeCompletion::CodeComplete(IEditor* editor)
61
// TODO :: this code works but for now we disable it until we
62
// add an option in the UI for it
65
if(!editor || !m_manager || !m_manager->GetWorkspace() || !m_manager->IsWorkspaceOpen())
68
// clang process is already running
72
// First, we need to build the command line
73
// try to locate the basic include paths:
74
wxArrayString args = GetStandardIncludePathsArgs();
76
// Next apppend the user include paths
79
// First, we need to find the currently active workspace configuration
80
BuildMatrixPtr matrix = m_manager->GetWorkspace()->GetBuildMatrix();
85
wxString workspaceSelConf = matrix->GetSelectedConfigurationName();
87
// Now that we got the selected workspace configuration, extract the related project configuration
88
ProjectPtr proj = m_manager->GetWorkspace()->FindProjectByName(editor->GetProjectName(), errMsg);
93
wxString currentBuffer = editor->GetTextRange(0, editor->GetCurrentPosition());
94
if(currentBuffer.IsEmpty())
97
wxString projectSelConf = matrix->GetProjectSelectedConf(workspaceSelConf, proj->GetName());
98
BuildConfigPtr dependProjbldConf = m_manager->GetWorkspace()->GetProjBuildConf(proj->GetName(), projectSelConf);
99
if(dependProjbldConf && dependProjbldConf->IsCustomBuild() == false) {
100
// Get the include paths and add them
101
wxString projectIncludePaths = dependProjbldConf->GetIncludePath();
102
wxArrayString projectIncludePathsArr = wxStringTokenize(projectIncludePaths, wxT(";"), wxTOKEN_STRTOK);
103
for(size_t i=0; i<projectIncludePathsArr.GetCount(); i++) {
104
args.Add( wxString::Format(wxT("-I%s"), projectIncludePathsArr[i].c_str()) );
107
// get the compiler options and add them
108
wxString projectCompileOptions = dependProjbldConf->GetCompileOptions();
109
wxArrayString projectCompileOptionsArr = wxStringTokenize(projectCompileOptions, wxT(";"), wxTOKEN_STRTOK);
110
for(size_t i=0; i<projectCompileOptionsArr.GetCount(); i++) {
111
wxString cmpOption (projectCompileOptionsArr.Item(i));
112
cmpOption.Trim().Trim(false);
115
// Expand backticks / $(shell ...) syntax supported by codelite
116
if(cmpOption.StartsWith(wxT("$(shell "), &tmp) || cmpOption.StartsWith(wxT("`"), &tmp)) {
120
if(cmpOption.EndsWith(wxT(")"), &tmp) || cmpOption.EndsWith(wxT("`"), &tmp)) {
124
if(m_backticks.find(cmpOption) == m_backticks.end()) {
126
// Expand the backticks into their value
127
wxArrayString outArr;
128
// Apply the environment before executing the command
129
EnvSetter setter( EnvironmentConfig::Instance() );
130
ProcUtils::SafeExecuteCommand(cmpOption, outArr);
131
wxString expandedValue;
132
for(size_t j=0; j<outArr.size(); j++)
134
expandedValue << outArr.Item(j) << wxT(" ");
136
m_backticks[cmpOption] = expandedValue;
137
cmpOption = expandedValue;
139
cmpOption = m_backticks.find(cmpOption)->second;
142
args.Add( cmpOption );
145
// get the compiler preprocessor and add them as well
146
wxString projectPreps = dependProjbldConf->GetPreprocessor();
147
wxArrayString projectPrepsArr = wxStringTokenize(projectPreps, wxT(";"), wxTOKEN_STRTOK);
148
for(size_t i=0; i<projectPrepsArr.GetCount(); i++) {
149
args.Add( wxString::Format(wxT("-D%s"), projectPrepsArr[i].c_str()) );
154
// Move backward until we found our -> or :: or .
155
for(size_t i=currentBuffer.Length() - 1; i>0; i--) {
156
if(currentBuffer.EndsWith(wxT("->")) || currentBuffer.EndsWith(wxT(".")) || currentBuffer.EndsWith(wxT("::"))) {
159
filterWord.Prepend(currentBuffer.GetChar(i));
160
currentBuffer.Truncate(currentBuffer.Length() - 1);
164
// Get the current line's starting pos
165
int lineStartPos = editor->PosFromLine( editor->GetCurrentLine() );
166
int column = editor->GetCurrentPosition() - lineStartPos + 1;
167
int line = editor->GetCurrentLine() + 1;
169
column -= (int)filterWord.Length();
173
m_tmpfile << editor->GetFileName().GetPath(wxPATH_GET_SEPARATOR|wxPATH_GET_VOLUME) << editor->GetFileName().GetName() << wxT("_clang_tmp") << wxT(".cpp");
177
command << wxT("clang -w -fsyntax-only -Xclang -code-completion-at=");
178
FileExtManager::FileType type = FileExtManager::GetType(editor->GetFileName().GetFullPath());
179
if(type == FileExtManager::TypeSourceC || type == FileExtManager::TypeSourceCpp) {
180
if(!WriteFileWithBackup(m_tmpfile, currentBuffer, false)) {
181
wxLogMessage(wxT("Failed to write temp file: %s"), m_tmpfile.c_str());
184
wxString completefileName;
185
completefileName << editor->GetFileName().GetName() << wxT("_clang_tmp") << wxT(".cpp");
186
command << completefileName << wxT(":") << line << wxT(":") << column << wxT(" ") << completefileName;
190
implFile << wxT("#include <") << editor->GetFileName().GetFullName() << wxT(">\n");
191
if(!WriteFileWithBackup(m_tmpfile, implFile, false)) {
192
wxLogMessage(wxT("Failed to write temp file: %s"), implFile.c_str());
195
wxString completefileName;
196
completefileName << editor->GetFileName().GetName() << wxT("_clang_tmp") << wxT(".cpp");
197
command << editor->GetFileName().GetFullPath() << wxT(":") << line << wxT(":") << column << wxT(" ") << completefileName;
202
for(size_t i=0; i<args.size(); i++) {
203
argString << wxT(" ") << args.Item(i);
206
command << argString;
211
command.Replace(wxT("\n"), wxT(" "));
212
command.Replace(wxT("\r"), wxT(" "));
214
m_process = CreateAsyncProcess(this, command, editor->GetFileName().GetPath(wxPATH_GET_SEPARATOR|wxPATH_GET_VOLUME));
216
wxLogMessage(wxT("Failed to start process: %s"), command.c_str());
220
m_activationEditor = editor;
221
m_activationPos = lineStartPos + column - 1;
228
wxArrayString ClangCodeCompletion::GetStandardIncludePathsArgs()
230
static wxArrayString paths;
232
if(paths.IsEmpty() == false)
235
wxString standardIncludeBase;
236
wxGetEnv(wxT("MINGW_INCL_HOME"), &standardIncludeBase);
237
if (standardIncludeBase.IsEmpty() == false && wxDir::Exists(standardIncludeBase)) {
239
wxString mingwBaseDir (standardIncludeBase );
241
// "C:/MinGW-4.4.1/include"
242
standardIncludeBase.Prepend(wxT("-I"));
243
paths.Add(standardIncludeBase + wxT("\\include"));
244
standardIncludeBase << wxT("\\lib\\gcc\\mingw32\\");
245
mingwBaseDir << wxT("\\lib\\gcc\\mingw32\\");
247
long highestVersion(0);
248
wxString sHighestVersion;
251
if (wxDir::Exists( mingwBaseDir ) ) {
252
wxDir::GetAllFiles(mingwBaseDir, &files, wxEmptyString, wxDIR_DIRS|wxDIR_FILES);
254
//filter out all non-directories
255
for (size_t i=0; i<files.GetCount(); i++) {
256
wxFileName fn(files.Item(i));
258
wxString p = fn.GetPath().Mid( mingwBaseDir.Length() );
260
tmp_p.Replace(wxT("."), wxT(""));
262
tmp_p.ToLong( &number );
263
if (number && number > highestVersion) {
264
sHighestVersion = p.BeforeFirst(wxFileName::GetPathSeparator());
265
highestVersion = number;
269
if (sHighestVersion.IsEmpty() == false) {
270
standardIncludeBase << sHighestVersion << wxT("\\include");
271
paths.Add( standardIncludeBase );
272
paths.Add( standardIncludeBase + wxT("\\c++") );
273
paths.Add( standardIncludeBase + wxT("\\c++\\mingw32") );
283
void ClangCodeCompletion::OnProcessTerminated(wxCommandEvent& e)
285
ProcessEventData *ped = (ProcessEventData*) e.GetClientData();
288
//wxLogMessage(wxT("clang process ended after %d ms"), gStopWatch.Time());
291
if(m_output.IsEmpty() == false) {
297
void ClangCodeCompletion::OnProcessOutput(wxCommandEvent& e)
299
ProcessEventData *ped = (ProcessEventData*) e.GetClientData();
301
m_output << ped->GetData();
307
void ClangCodeCompletion::DoParseOutput()
310
if(m_output.IsEmpty() || !m_activationEditor || m_activationPos == wxNOT_FOUND)
313
wxArrayString entries = wxStringTokenize(m_output, wxT("\n\r"), wxTOKEN_STRTOK);
314
std::vector<TagEntryPtr> tags;
315
tags.reserve( entries.size() );
317
for(size_t i=0; i<entries.GetCount(); i++) {
318
entries.Item(i).Trim().Trim(false);
319
if(entries.Item(i).IsEmpty())
322
TagEntryPtr tag = ClangEntryToTagEntry( entries.Item(i) );
324
tags.push_back( tag );
329
if(tags.empty() == false) {
330
int curline = m_activationEditor->GetCurrentLine();
331
int actline = m_activationEditor->LineFromPos(m_activationPos);
332
if(curline != actline)
334
// we are still on the same line
336
// We need to make sure that the caret is still infront of the completion char
337
if(m_activationEditor->GetCurrentPosition() < m_activationPos)
340
// Get the text between the current position and the activation pos and filter all results
342
wxString filter = m_activationEditor->GetTextRange(m_activationPos, m_activationEditor->GetCurrentPosition());
343
if(filter.IsEmpty() == false) {
346
std::vector<TagEntryPtr> filteredTags;
347
filteredTags.reserve( tags.size() );
348
for(size_t i=0; i<tags.size(); i++) {
349
wxString n = tags.at(i)->GetName();
352
if(n.StartsWith(filter)) {
353
filteredTags.push_back(tags.at(i));
357
if(filteredTags.empty() == false) {
358
m_activationEditor->ShowCompletionBox(filteredTags, filter, NULL);
361
m_activationEditor->ShowCompletionBox(tags, wxEmptyString, NULL);
366
TagEntryPtr ClangCodeCompletion::ClangEntryToTagEntry(const wxString& line)
368
// an example of line:
369
// COMPLETION: OpenFile : [#bool#]OpenFile(<#class wxString const &fileName#>{#, <#class wxString const &projectName#>{#, <#int lineno#>#}#})
371
if(line.StartsWith(wxT("COMPLETION: "), &tmp) == false)
374
if(line.Contains(wxT("(Hidden)")))
377
// Next comes the name of the entry
378
wxString name = tmp.BeforeFirst(wxT(':'));
379
name.Trim().Trim(false);
384
TagEntry *t = new TagEntry();
386
tag->SetName( name );
388
tmp = tmp.AfterFirst(wxT(':'));
389
tmp.Trim().Trim(false);
391
// determine the kind
392
tag->SetKind(wxT("prototype")); // default
395
// this a type (enum/typedef/internal class)
396
tag->SetKind(wxT("class"));
397
} else if(tmp.Contains(wxT("("))) {
398
// definitly a method, do nothing
400
tag->SetKind(wxT("member"));
406
void ClangCodeCompletion::DoCleanUp()
411
// remove the temporary file
412
if(m_tmpfile.IsEmpty() == false) {
414
::unlink( m_tmpfile.mb_str(wxConvUTF8).data() );
416
wxRemoveFile( m_tmpfile );
421
m_activationEditor = NULL;
422
m_activationPos = wxNOT_FOUND;
425
void ClangCodeCompletion::CancelCodeComplete()