we can have a look at this function in editormanager.cpp
bool EditorManager::SwapActiveHeaderSource()
{
cbEditor* ed = GetBuiltinEditor(GetActiveEditor());
if (!ed)
return false;
FileType ft = FileTypeOf(ed->GetFilename());
if (ft != ftHeader && ft != ftSource)
return false;
// because ft == ftHeader || ftSource, the extension has at least 1 character
bool extStartsWithCapital = wxIsupper(wxFileName(ed->GetFilename()).GetExt()[0]);
// create a list of search dirs
wxArrayString dirs;
cbProject* project = 0;
// if the file in question belongs to a different open project,
// then use that project instead.
// this fixes locating the file's pair in a workspace when the active file
// does not belong to the active project.
ProjectFile* opf = ed->GetProjectFile();
if (opf)
project = opf->GetParentProject();
// if we didn't get a valid project, try the active one
if (!project)
project = Manager::Get()->GetProjectManager()->GetActiveProject();
if (project)
{
// get project's include dirs
dirs = project->GetIncludeDirs();
if (opf)
{
wxString const &activeName = opf->file.GetName();
// first try to find the file among the opened files
for (int i = 0; i < GetEditorsCount(); ++i)
{
cbEditor* edit = GetBuiltinEditor(GetEditor(i));
if (!edit)
continue;
ProjectFile* pf = edit->GetProjectFile();
if (!pf)
continue;
if (pf->file.GetName() == activeName)
{
wxFileName const & fname = pf->file;
FileType ft_other = FileTypeOf(fname.GetFullName());
if ( ( ((ft == ftHeader) && (ft_other == ftSource))
|| ((ft == ftSource) && (ft_other == ftHeader)) )
&& (wxIsupper(fname.GetExt()[0]) == extStartsWithCapital) )
{
if (fname.FileExists())
{
cbEditor* newEd = Open(fname.GetFullPath());
if (newEd!=0L) // we found and were able to open it
return true; // --> RETURN
}
}
}
}
// second try to find in the project files - at the same time
// build the directory list for further searching if not
// successful now
for (int i = 0; i < project->GetFilesCount(); ++i)
{
ProjectFile* pf = project->GetFile(i);
if (!pf)
continue;
wxString dir = pf->file.GetPath(wxPATH_GET_VOLUME);
if (dirs.Index(dir) == wxNOT_FOUND)
dirs.Add(dir);
if (pf->file.GetName() == activeName)
{
wxFileName const & fname = pf->file;
FileType ft_other = FileTypeOf(fname.GetFullName());
if ( ( ((ft == ftHeader) && (ft_other == ftSource))
|| ((ft == ftSource) && (ft_other == ftHeader)) )
&& (wxIsupper(fname.GetExt()[0]) == extStartsWithCapital) )
{
if (fname.FileExists())
{
cbEditor* newEd = Open(fname.GetFullPath());
if (newEd!=0L) // we found and were able to open it
return true; // --> RETURN
}
}
}
}
}
else // no opf
{
// build the directory list for further searching if opf not available
for (int i = 0; i < project->GetFilesCount(); ++i)
{
ProjectFile* pf = project->GetFile(i);
if (!pf)
continue;
wxString dir = pf->file.GetPath(wxPATH_GET_VOLUME);
if (dirs.Index(dir) == wxNOT_FOUND)
dirs.Add(dir);
}
}
// if not found, continue building the list of directories for further searching
// get targets include dirs
for (int i = 0; i < project->GetBuildTargetsCount(); ++i)
{
ProjectBuildTarget* target = project->GetBuildTarget(i);
if (target)
{
for (unsigned int ti = 0; ti < target->GetIncludeDirs().GetCount(); ++ti)
{
// TODO (Morten#5#): target include dirs might override project include dirs, take append/prepend option into account
wxString dir = target->GetIncludeDirs()[ti];
if (dirs.Index(dir) == wxNOT_FOUND)
dirs.Add(dir);
}
}
}
} // project
wxFileName fname;
wxFileName fn(ed->GetFilename());
dirs.Insert(fn.GetPath(wxPATH_GET_VOLUME), 0); // add file's dir
for (unsigned int i = 0; i < dirs.GetCount(); ++i)
{
ProjectManager *pm = Manager::Get()->GetProjectManager();
if ( !pm )
break;
wxString dir = dirs[i]; // might contain macros -> replace them
Manager::Get()->GetMacrosManager()->ReplaceMacros(dir);
fname.Assign(dir + wxFileName::GetPathSeparator() + fn.GetFullName());
// Manager::Get()->GetLogManager()->DebugLog(F(_T("Looking for '%s', dir='%s'."), fname.GetFullPath().c_str(), dir.c_str()));
if (!fname.IsAbsolute() && project)
{
fname.Normalize(wxPATH_NORM_ALL & ~wxPATH_NORM_CASE, project->GetBasePath());
// Manager::Get()->GetLogManager()->DebugLog(F(_T("Normalizing dir to '%s'."), fname.GetFullPath().c_str()));
}
wxString HeaderSource = pm->GetHeaderSource(fname);
if (!HeaderSource.IsEmpty())
{
fname.SetFullName(HeaderSource);
// Manager::Get()->GetLogManager()->DebugLog(F(_T("Located '%s'."), fname.GetFullPath().c_str()));
break;
}
}
if (fname.FileExists())
{
//Manager::Get()->GetLogManager()->DebugLog("ed=%s, pair=%s", ed->GetFilename().c_str(), pair.c_str());
cbEditor* newEd = Open(fname.GetFullPath());
if (newEd!=0L) // we found and were able to open it
return true; // --> RETURN;
}
// We couldn't find the file, maybe it does not exist. Ask the user if we
// should create it:
if (cbMessageBox(_("The file seems not to exist. Do you want to create it?"),
_("Error"), wxICON_QUESTION | wxYES_NO) == wxID_YES)
{
cbProject* project = Manager::Get()->GetProjectManager()->GetActiveProject();
if (project)
wxSetWorkingDirectory(project->GetBasePath());
// Create a suggestion for the new file name:
if (ft == ftHeader)
fn.SetExt(FileFilters::CPP_EXT);
else if (ft == ftSource)
fn.SetExt(FileFilters::H_EXT);
// else? Well, if the filename is not changed we could possibly
// overwrite an existing file with our suggestion.
cbEditor* newEd = New(fn.GetFullPath());
if (project)
{
if (cbMessageBox(_("Do you want to add this new file in the active project?"),
_("Add file to project"),
wxYES_NO | wxICON_QUESTION) == wxID_YES)
{
wxArrayInt targets;
if (Manager::Get()->GetProjectManager()->AddFileToProject(newEd->GetFilename(), project, targets) != 0)
{
ProjectFile* pf = project->GetFileByFilename(newEd->GetFilename(), false);
newEd->SetProjectFile(pf);
Manager::Get()->GetProjectManager()->RebuildTree();
}
}
}
// verify that the open files are still in sync
// the new file might have overwritten an existing one)
Manager::Get()->GetEditorManager()->CheckForExternallyModifiedFiles();
}
return false;
}
Looking at the code again, it actually already searches the include path if it doesn't find it in the list of currently open editors or in the project files (i.e. in the same directory):
// get targets include dirs
for (int i = 0; i < project->GetBuildTargetsCount(); ++i)
{
ProjectBuildTarget* target = project->GetBuildTarget(i);
if (target)
{
for (unsigned int ti = 0; ti < target->GetIncludeDirs().GetCount(); ++ti)
{
// TODO (Morten#5#): target include dirs might override project include dirs, take append/prepend option into account
wxString dir = target->GetIncludeDirs()[ti];
if (dirs.Index(dir) == wxNOT_FOUND)
dirs.Add(dir);
}
}
}
With my patch applied, would it be possible for you to validate if my patch actually breaks the scenario you proposed?
As for the scenario where the file doesn't exist, I wouldn't suggest any automation in terms of guessing where the file should be created. I would leave it up to the developer and simply provide a file dialogue to create the file.
Looking at the code again, it actually already searches the include path if it doesn't find it in the list of currently open editors or in the project files (i.e. in the same directory):
That is a part of the problem. Why the treat open files differently from non-open files? If it only works if the file isn't open, the algorithm is already broken.
With my patch applied, would it be possible for you to validate if my patch actually breaks the scenario you proposed?
Guess it will be less work, if you try it out. Please note that I have uploaded a test project, that demonstrates the problem (a few posts above).
Anyway, I couldn't sleep, so I wrote a new algorithm. Hope you can understand my python-pseudo-code. No guarantees about the correctness though (despite not being able to get to sleep I am awful sleepy already):
listCandidates (file):
e = extension (file)
if e in header_extension_list:
extension_list = source_extension_list
elseif e in source_extension_list:
extension_list = header_extension_list
else
throw error (unknown extension)
l = leaf_without_extension (file)
for f in project: # iterate over all files in the project
if leaf_without_extension (f)==l and extension (f) in extension_list:
add f to list
return list
findCandidate (file):
list = listCandidates (file)
n = len (file) # len (file) is the number of path elements
if n>1:
for f in list if len (f)==n:
for i in range (0, n-2):
if f[i]==file[i]:
return f # success
else
for f in list if len (f)==1:
return f # success (top level directory)
# maybe add other methods to determine the file in case the above fails
return null # no file found; either does match no known pattern or it does not exist
swap (file):
c = findCandidate (file):
if c!=null:
open c
return
# file does not exist
parent = file[0:n-2] # one level up (the directory where the file is located)
for f in parent if f!=file: # iterating over the files in the directory
c = findCandidate (f)
if c!=null:
create corresponding file in directory c[0:n-2] and open it
return
# give up; either ask the user for a directory or report that the file can't be created
There we go ...
listCandidates (file):
e = extension (file)
if e in header_extension_list:
extension_list = source_extension_list
elif e in source_extension_list:
extension_list = header_extension_list
else:
raise error (unknown extension)
l = leaf_without_extension (file)
for f in project: # iterate over all files in the project
if leaf_without_extension (f)==l and extension (f) in extension_list:
add f to list
return list
findCandidate (file, list):
if not strictMode and len (list)==1:
return list[0]
n = len (file) # len (file) is the number of path elements
if n>1: # search for shared path elements
for f in list if len (f)==n:
for i in range (n-1): # for loop over the first n-1 integers starting with 0
if f[i]==file[i]:
return f # success
else # name consists of leaf elements only
for f in list if len (f)==1:
return f # success (top level directory)
# maybe add other methods to determine the file in case the above fails
return null # no file found; either does match no known pattern or it does not exist
swap (file):
list = listCandidates (file)
c = findCandidate (file, list):
if c!=null:
open c
return
# file does not exist -> try to find another file pair
parent = file[0:n-2] # one level up (the directory where the file is located)
for f in parent if f!=file: # iterating over the files in the directory
c = findCandidate (f, list)
if c!=null:
# may want to include a sanity check here: warn or abort if file already exists
create corresponding file in directory c[0:n-2] and open it
return
# give up; either ask the user for a directory or report that the file can't be created
Optimized, better documented and with a little bug fix. This should work.
Regarding the strictMode variable: This should be either a Code.:Blocks-wide or a project-wide setting. In strict mode the algorithm will not handle some less symmetric source layouts correctly. In non-strict mode multiple files with the same name won't be handles correctly, if one of the files still needs to be created. I think that is a good compromise.
Edit: File names are assumed to be relative to the project directory.
Maybe something like this? (Warning, not yet heavily tested.)
Index: src/sdk/editormanager.cpp
===================================================================
--- src/sdk/editormanager.cpp (revision 8646)
+++ src/sdk/editormanager.cpp (working copy)
@@ -1014,8 +1014,8 @@
bool EditorManager::IsHeaderSource(const wxFileName& candidateFile, const wxFileName& activeFile, FileType ftActive)
{
- // Verify the base name mathes
- if (candidateFile.GetName() == activeFile.GetName())
+ // Verify the base name matches
+ if (candidateFile.GetName().CmpNoCase(activeFile.GetName()) == 0)
{
// Verify:
// If looking for a header we have a source OR
I will see if I can pull something together.
I believe this meets the criteria, and it passes my test cases. oBFusCATed, does it work on your project?
Index: src/sdk/editormanager.cpp
===================================================================
--- src/sdk/editormanager.cpp (revision 8669)
+++ src/sdk/editormanager.cpp (working copy)
@@ -1012,11 +1012,14 @@
m_isCheckingForExternallyModifiedFiles = false;
}
-bool EditorManager::IsHeaderSource(const wxFileName& candidateFile, const wxFileName& activeFile, FileType ftActive)
+bool EditorManager::IsHeaderSource(const wxFileName& candidateFile, const wxFileName& activeFile, FileType ftActive, bool& isCandidate)
{
- // Verify the base name mathes
- if (candidateFile.GetName() == activeFile.GetName())
+ // Verify the base name matches
+ if (candidateFile.GetName().CmpNoCase(activeFile.GetName()) == 0)
{
+ // non-Windows platforms: case-insensitive match -> isCandidate
+ isCandidate = !( platform::windows || (candidateFile.GetName() == activeFile.GetName()) );
+
// Verify:
// If looking for a header we have a source OR
// If looking for a source we have a header
@@ -1056,13 +1059,12 @@
{
wxFileName currentCandidateFile(candidateFilesArray[i]);
- if (IsHeaderSource(currentCandidateFile, activeFile, ftActive))
+ if (IsHeaderSource(currentCandidateFile, activeFile, ftActive, isCandidate))
{
bool isUpper = wxIsupper(currentCandidateFile.GetExt()[0]);
- if (isUpper == extStartsWithCapital)
+ if (isUpper == extStartsWithCapital && !isCandidate)
{
// we definitely found the header/source we were searching for
- isCandidate = false;
return currentCandidateFile;
}
else
Index: src/include/editormanager.h
===================================================================
--- src/include/editormanager.h (revision 8669)
+++ src/include/editormanager.h (working copy)
@@ -194,7 +194,7 @@
int FindInFiles(cbFindReplaceData* data);
int Replace(cbStyledTextCtrl* control, cbFindReplaceData* data);
int ReplaceInFiles(cbFindReplaceData* data);
- bool IsHeaderSource(const wxFileName& candidateFile, const wxFileName& activeFile, FileType ftActive);
+ bool IsHeaderSource(const wxFileName& candidateFile, const wxFileName& activeFile, FileType ftActive, bool& isCandidate);
wxFileName FindHeaderSource(const wxArrayString& candidateFilesArray, const wxFileName& activeFile, bool& isCandidate);
cbAuiNotebook* m_pNotebook;