mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 06:05:58 -06:00
402 lines
11 KiB
C++
402 lines
11 KiB
C++
#include "ide.h"
|
|
|
|
static String lpath;
|
|
static Vector<String> line;
|
|
|
|
void Ide::ResetFileLine()
|
|
{
|
|
lpath.Clear();
|
|
}
|
|
|
|
String Ide::GetFileLine(const String& path, int linei)
|
|
{ // slightly cached fetching of line from file
|
|
if(path == editfile && !designer)
|
|
return linei >= 0 && linei < editor.GetLineCount() ? editor.GetUtf8Line(linei)
|
|
: String();
|
|
if(path != lpath) {
|
|
lpath = path;
|
|
FileIn in(path);
|
|
line.Clear();
|
|
if(in.GetSize() < 10000000)
|
|
while(!in.IsEof())
|
|
line.Add(in.GetLine());
|
|
}
|
|
return linei >= 0 && linei < line.GetCount() ? line[linei] : String();
|
|
}
|
|
|
|
void Ide::AddReferenceLine(const String& path, Point mpos, const String& name, Index<String>& unique)
|
|
{
|
|
String ln = GetFileLine(path, mpos.y);
|
|
int count = 0;
|
|
int pos = -1;
|
|
if(name.GetCount()) {
|
|
pos = FindId(ln.Mid(mpos.x), name);
|
|
if(pos < 0) {
|
|
pos = FindId(ln.Mid(mpos.x), "With" + name);
|
|
if(pos >= 0) // special case for WithNameLayout
|
|
pos += 4;
|
|
}
|
|
if(pos >= 0) {
|
|
count = name.GetCount();
|
|
pos += mpos.x;
|
|
}
|
|
}
|
|
String h = String() << path << '\t' << mpos.y << '\t' << ln << '\t' << pos << '\t' << count;
|
|
if(unique.Find(h) < 0) {
|
|
unique.Add(h);
|
|
AddFoundFile(path, mpos.y + 1, ln, pos, count);
|
|
}
|
|
}
|
|
|
|
String ScopeWorkaround(const char *s)
|
|
{ // we are sometimes getting incorrect signatures with missing param qualifiers ([Upp::]CodeEditor::MouseTip
|
|
StringBuffer r;
|
|
while(*s) {
|
|
const char *b = s;
|
|
if(iscib(*s)) {
|
|
s++;
|
|
while(iscid(*s))
|
|
s++;
|
|
if(s[0] == ':' && s[1] == ':')
|
|
s += 2;
|
|
else
|
|
r.Cat(b, s);
|
|
}
|
|
else {
|
|
while(*s && !iscib(*s))
|
|
s++;
|
|
r.Cat(b, s);
|
|
}
|
|
}
|
|
return String(r);
|
|
}
|
|
|
|
void GatherBaseVirtuals(const String& cls, const String& signature, Index<String>& ids, Index<String>& visited)
|
|
{ // find all ancestor classes that contain signature
|
|
if(IsNull(cls) || visited.Find(cls) >= 0)
|
|
return;
|
|
visited.Add(cls);
|
|
for(const auto& f : ~CodeIndex()) // check base classes
|
|
for(const AnnotationItem& m : f.value.items)
|
|
if(IsStruct(m.kind) && m.id == cls)
|
|
// we cheat with With..<TopWindow> by splitting it to With... and TopWindow as
|
|
// two bases
|
|
for(String bcls : Split(m.bases, [](int c) { return iscid(c) || c == ':' ? 0 : 1; }))
|
|
GatherBaseVirtuals(bcls, signature, ids, visited);
|
|
|
|
|
|
for(const auto& f : ~CodeIndex()) // now check virtual methods of this cls
|
|
for(const AnnotationItem& m : f.value.items) {
|
|
if(m.nest == cls && IsFunction(m.kind) && m.isvirtual && ScopeWorkaround(m.id.Mid(m.nest.GetCount())) == signature) {
|
|
ids.FindAdd(cls); // found virtual method in the class
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GatherVirtuals(const VectorMap<String, String>& bases, const String& cls,
|
|
const String& signature, Index<String>& ids, Index<String>& visited)
|
|
{ // find all virtual methods with the same signature
|
|
if(IsNull(cls) || visited.Find(cls) >= 0)
|
|
return;
|
|
|
|
visited.Add(cls);
|
|
|
|
for(int q = bases.Find(cls); q >= 0; q = bases.FindNext(q)) {
|
|
GatherVirtuals(bases, bases[q], signature, ids, visited);
|
|
}
|
|
|
|
for(const auto& f : ~CodeIndex()) // now check virtual methods of this cls
|
|
for(const AnnotationItem& m : f.value.items) {
|
|
if(m.nest == cls && IsFunction(m.kind) && m.isvirtual && ScopeWorkaround(m.id.Mid(m.nest.GetCount())) == signature) {
|
|
ids.FindAdd(m.id); // found virtual method in the class
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Ide::GetGlobalUsageIds(const String& id, bool& isvirtual, bool& isstatic, bool& istype, Index<String>& ids)
|
|
{
|
|
isvirtual = false;
|
|
isstatic = false; // to limit file static variables to single file
|
|
istype = false;
|
|
String cls;
|
|
for(const auto& f : ~CodeIndex())
|
|
for(const AnnotationItem& m : f.value.items) {
|
|
if(m.id == id) {
|
|
if(m.isvirtual) {
|
|
isvirtual = true;
|
|
cls = m.nest;
|
|
break;
|
|
}
|
|
if(IsStruct(m.kind)) {
|
|
istype = true;
|
|
cls = m.nest;
|
|
break;
|
|
}
|
|
if(m.id == id && m.isstatic && f.key == editfile &&
|
|
m.nest.GetCount() == m.nspace.GetCount()) // ignore class variables
|
|
isstatic = true;
|
|
}
|
|
}
|
|
|
|
ids.FindAdd(id);
|
|
|
|
if(isvirtual) {
|
|
Index<String> visited;
|
|
String signature = ScopeWorkaround(id.Mid(cls.GetCount()));
|
|
Index<String> base_id;
|
|
GatherBaseVirtuals(cls, signature, base_id, visited);
|
|
|
|
VectorMap<String, String> bases;
|
|
for(const auto& f : ~CodeIndex()) // check derived classes
|
|
for(const AnnotationItem& m : f.value.items)
|
|
if(IsStruct(m.kind))
|
|
for(String bcls : Split(m.bases, [](int c) { return iscid(c) || c == ':' ? 0 : 1; }))
|
|
bases.Add(bcls, m.id);
|
|
|
|
visited.Clear();
|
|
for(const String& cls : base_id)
|
|
GatherVirtuals(bases, cls, signature, ids, visited);
|
|
}
|
|
}
|
|
|
|
bool Ide::IsLocalAtCursor(const String& id, Point ref_pos)
|
|
{
|
|
int li = editor.GetCursorLine();
|
|
bool local = false;
|
|
AnnotationItem cm = editor.FindCurrentAnnotation(); // what function body are we in?
|
|
if(IsFunction(cm.kind)) // do local variables
|
|
for(const AnnotationItem& lm : editor.locals)
|
|
if(lm.id == id && lm.pos.y >= cm.pos.y && lm.pos.y <= li && ref_pos == lm.pos)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void Ide::Usage(const String& id, const String& name, Point ref_pos)
|
|
{
|
|
if(IsNull(id))
|
|
return;
|
|
|
|
ResetFileLine();
|
|
NewFFound();
|
|
|
|
Progress pi("Indexing files");
|
|
while(Indexer::IsRunning()) {
|
|
if(pi.StepCanceled())
|
|
break;
|
|
GuiSleep(10);
|
|
}
|
|
|
|
Index<String> unique;
|
|
if(IsLocalAtCursor(id, ref_pos)) {
|
|
AddReferenceLine(editfile, ref_pos, name, unique);
|
|
for(const ReferenceItem& lm : editor.references)
|
|
if(lm.id == id && lm.ref_pos == ref_pos)
|
|
AddReferenceLine(editfile, lm.pos, name, unique);
|
|
}
|
|
else {
|
|
bool isvirtual;
|
|
bool isstatic; // to limit file static variables to single file
|
|
bool istype;
|
|
Index<String> ids;
|
|
|
|
GetGlobalUsageIds(id, isvirtual, isstatic, istype, ids);
|
|
|
|
UsageId(name, id, ids, istype, isstatic, unique);
|
|
}
|
|
|
|
UsageFinish();
|
|
}
|
|
|
|
void Ide::UsageId(const String& name, const String& id, const Index<String>& ids, bool istype, bool isstatic, Index<String>& unique)
|
|
{
|
|
// DLOG("UsageId " << name << " " << id << " " << ids << " istype: " << istype << " " << unique << " static " << isstatic);
|
|
int q = id.ReverseFind("::");
|
|
String constructor = id + "::" + (q >= 0 ? id.Mid(q + 2) : id) + "(";
|
|
String destructor = id + "::~(";
|
|
int kind = Null;
|
|
for(int src = 0; src < 2; src++)
|
|
for(const auto& f : ~CodeIndex()) {
|
|
if(!isstatic || f.key == editfile)
|
|
if((findarg(GetFileExt(f.key), ".h", "") < 0) == src) { // headers first
|
|
auto Add = [&](Point mpos) {
|
|
AddReferenceLine(f.key, mpos, name, unique);
|
|
};
|
|
for(const AnnotationItem& m : f.value.items) {
|
|
if(m.id == id)
|
|
kind = m.kind;
|
|
if(ids.Find(m.id) >= 0 || istype && (m.id.StartsWith(constructor) || m.id.StartsWith(destructor)))
|
|
Add(m.pos);
|
|
}
|
|
for(const ReferenceItem& m : f.value.refs)
|
|
if(ids.Find(m.id) >= 0)
|
|
Add(m.pos);
|
|
}
|
|
}
|
|
|
|
if(!IsNull(kind))
|
|
FFoundSetIcon(CxxIcon(kind));
|
|
}
|
|
|
|
void Ide::UsageFinish()
|
|
{
|
|
FFound().Sort(3, [](const Value& va, const Value& vb)->int {
|
|
const ListLineInfo& a = va.To<ListLineInfo>();
|
|
const ListLineInfo& b = vb.To<ListLineInfo>();
|
|
return CombineCompare(GetFileName(a.file), GetFileName(b.file))
|
|
(a.file, b.file)
|
|
(a.lineno, b.lineno)
|
|
(a.linepos, b.linepos);
|
|
});
|
|
FFoundFinish();
|
|
}
|
|
|
|
void Ide::Usage()
|
|
{
|
|
if(designer)
|
|
return;
|
|
if(!editor.WaitCurrentFile())
|
|
return;
|
|
AnnotationItem cm = editor.FindCurrentAnnotation();
|
|
Usage(cm.id, cm.name, cm.pos);
|
|
}
|
|
|
|
void Ide::IdUsage()
|
|
{
|
|
SortByKey(CodeIndex());
|
|
if(designer)
|
|
return;
|
|
if(editfile.EndsWith(".lay")) {
|
|
String layout, item;
|
|
if(GetLayoutItem(layout, item)) {
|
|
if(item.GetCount())
|
|
FindDesignerItemReferences("With" + layout + "::" + item, item);
|
|
else
|
|
FindDesignerItemReferences("With" + layout, layout);
|
|
}
|
|
return;
|
|
}
|
|
if(editfile.EndsWith(".key")) {
|
|
CParser p(editor.GetUtf8Line(editor.GetCursorLine()));
|
|
try {
|
|
p.PassId("KEY");
|
|
p.PassChar('(');
|
|
String id = "AK_" + p.ReadId();
|
|
String path = NormalizePath(editfile);
|
|
for(const auto& f : ~CodeIndex())
|
|
for(const AnnotationItem& m : f.value.items)
|
|
if(m.name == id) {
|
|
Index<String> ids, unique;
|
|
ids.Add(m.id);
|
|
NewFFound();
|
|
UsageId(id, id, ids, IsStruct(m.kind), false, unique);
|
|
UsageFinish();
|
|
return;
|
|
}
|
|
}
|
|
catch(CParser::Error) {}
|
|
return;
|
|
}
|
|
String name;
|
|
Point ref_pos;
|
|
String ref_id;
|
|
for(int pass = 0; pass < 2; pass++) { // bit of heuristics - if stored info has correct id at current pos, do not wait for rescan
|
|
ref_id = GetRefId(editor.GetCursor(), name, ref_pos);
|
|
if(ref_id.GetCount())
|
|
break;
|
|
if(!editor.WaitCurrentFile())
|
|
return;
|
|
}
|
|
Usage(ref_id, name, ref_pos);
|
|
}
|
|
|
|
void Ide::DoUsageTree(String parent_id, String id, String fn, Point mpos, String name, Index<String>& unique, int& lvl, String& current, Index<String>& done)
|
|
{
|
|
/* AddReferenceLine(fn, mpos, name, unique);
|
|
if(parent_id != current) {
|
|
current = parent_id;
|
|
if(lvl < 40 && !IsNull(parent_id) && done.Find(parent_id) < 0 && done.GetCount() < 100) {
|
|
done.Add(parent_id);
|
|
lvl++;
|
|
for(int pass = 0; pass < 2; pass++)
|
|
for(const auto& f : ~CodeIndex()) {
|
|
for(const AnnotationItem& m : f.value.items) {
|
|
if(m.id == parent_id && m.definition != (bool)pass) {
|
|
String current;
|
|
DLOG(lvl << " " << parent_id);
|
|
Usage(parent_id, m.name, m.pos, [&](String parent_id, String id, String fn, Point mpos, String name) {
|
|
DoUsageTree(parent_id, id, fn, mpos, name, unique, lvl, current, done);
|
|
});
|
|
lvl--;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
lvl--;
|
|
}
|
|
}
|
|
*/}
|
|
|
|
void Ide::UsageTree()
|
|
{
|
|
/* Index<String> unique, done;
|
|
int lvl = 0;
|
|
String current;
|
|
IdUsage([&](String parent_id, String id, String fn, Point mpos, String name) {
|
|
DoUsageTree(parent_id, id, fn, mpos, name, unique, lvl, current, done);
|
|
});
|
|
*/}
|
|
|
|
void Ide::FindDesignerItemReferences(const String& id, const String& name)
|
|
{
|
|
SaveFile();
|
|
ResetFileLine();
|
|
SortByKey(CodeIndex());
|
|
String path = NormalizePath(editfile);
|
|
int q = CodeIndex().Find(path);
|
|
if(q < 0)
|
|
return;
|
|
|
|
auto DoUsage = [&](const AnnotationItem& m) {
|
|
Index<String> ids, unique;
|
|
ids.Add(m.id);
|
|
NewFFound();
|
|
UsageId(name, m.id, ids, IsStruct(m.kind), false, unique);
|
|
UsageFinish();
|
|
};
|
|
|
|
for(const AnnotationItem& m : CodeIndex()[q].items) {
|
|
if(m.id.EndsWith(id) &&
|
|
(m.id.GetCount() <= id.GetCount() || !iscid(m.id[m.id.GetCount() - id.GetCount() - 1]))) {
|
|
DoUsage(m);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Probably untyped layout item, need to find all classes using the layout
|
|
q = id.ReverseFind("::");
|
|
if(q < 0) return;
|
|
String bclst = id.Mid(0, q);
|
|
String nbclst = ":" + id.Mid(0, q);
|
|
String var = id.Mid(q + 2);
|
|
Index<String> vars;
|
|
for(const auto& f : ~CodeIndex())
|
|
for(const AnnotationItem& m : f.value.items) {
|
|
if(IsStruct(m.kind) && m.bases.GetCount()) {
|
|
for(String bcls : Split(m.bases, [](int c) { return iscid(c) || c == ':' ? 0 : 1; })) {
|
|
if(bcls == bclst || bcls.EndsWith(nbclst)) {
|
|
vars.FindAdd(m.id + "::" + var);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(const auto& f : ~CodeIndex())
|
|
for(const AnnotationItem& m : f.value.items) {
|
|
if(IsVariable(m.kind) && vars.Find(m.id) >= 0) {
|
|
DoUsage(m);
|
|
}
|
|
}
|
|
}
|
|
|