ultimatepp/uppsrc/ide/Usage.cpp
2025-03-30 10:39:04 +02:00

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);
}
}
}