Attempt to add autocomplete+calltips to the qt version of fteqccgui.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5649 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2020-03-14 17:38:55 +00:00
parent d0b7519242
commit 19a2ecb99e
3 changed files with 612 additions and 202 deletions

View file

@ -19,6 +19,7 @@ void GUI_RevealOptions(void);
int GUIprintf(const char *msg, ...);
pbool GenBuiltinsList(char *buffer, int buffersize);
pbool GenAutoCompleteList(char *prefix, char *buffer, int buffersize);
extern char parameters[16384];

View file

@ -3,8 +3,6 @@
/*Todo (in no particular order):
tooltips for inspecting variables/types.
variables/watch list.
calltips for argument info
autocompletion calltips, for people who can't remember function names
initial open project prompt
shpuld's styling
decompiler output saving
@ -47,6 +45,9 @@ extern int fl_tabsize;
extern char enginebinary[MAX_OSPATH];
extern char enginebasedir[MAX_OSPATH];
extern char enginecommandline[8192];
extern QCC_def_t *sourcefilesdefs[];
extern int sourcefilesnumdefs;
};
static char *cmdlineargs;
@ -60,7 +61,7 @@ template<class T> inline T cpprealloc(T p, size_t s) {return static_cast<T>(real
#define STRINGIFY2(s) #s
#define STRINGIFY(s) STRINGIFY2(s)
static QProcess *qcdebugger;
static void DebuggerStop(void);
static bool DebuggerSendCommand(const char *msg, ...);
static void DebuggerStart(void);
@ -407,9 +408,120 @@ public:
}
};
class WrappedQsciScintilla:public QsciScintilla
{
public:
WrappedQsciScintilla(QWidget *parent):QsciScintilla(parent){}
char *WordUnderCursor(char *word, int wordsize, char *term, int termsize, int position)
{
unsigned char linebuf[1024];
long charidx;
long lineidx;
long len;
pbool noafter = (position==-2);
if (position == -3)
{
len = SendScintilla(QsciScintillaBase::SCI_GETSELTEXT, NULL);
if (len > 0 && len < wordsize)
{
len = SendScintilla(QsciScintillaBase::SCI_GETSELTEXT, word);
if (len>0)
return word;
}
}
if (position < 0)
position = SendScintilla(QsciScintillaBase::SCI_GETCURRENTPOS);
lineidx = SendScintilla(QsciScintillaBase::SCI_LINEFROMPOSITION, position);
charidx = position - SendScintilla(QsciScintillaBase::SCI_POSITIONFROMLINE, lineidx);
len = SendScintilla(QsciScintillaBase::SCI_LINELENGTH, lineidx);
if (len >= sizeof(linebuf))
{
*word = 0;
return word;
}
len = SendScintilla(QsciScintillaBase::SCI_GETLINE, lineidx, linebuf);
linebuf[len] = 0;
if (charidx >= len)
charidx = len-1;
if (noafter) //truncate it here if we're not meant to be reading anything after.
linebuf[charidx] = 0;
if (word)
{
//skip back to the start of the word
while(charidx > 0 && (
(linebuf[charidx-1] >= 'a' && linebuf[charidx-1] <= 'z') ||
(linebuf[charidx-1] >= 'A' && linebuf[charidx-1] <= 'Z') ||
(linebuf[charidx-1] >= '0' && linebuf[charidx-1] <= '9') ||
linebuf[charidx-1] == '_' || linebuf[charidx-1] == ':' ||
linebuf[charidx-1] >= 128
))
{
charidx--;
}
//copy the result out
lineidx = 0;
wordsize--;
while (wordsize && (
(linebuf[charidx] >= 'a' && linebuf[charidx] <= 'z') ||
(linebuf[charidx] >= 'A' && linebuf[charidx] <= 'Z') ||
(linebuf[charidx] >= '0' && linebuf[charidx] <= '9') ||
linebuf[charidx] == '_' || linebuf[charidx] == ':' ||
linebuf[charidx] >= 128
))
{
word[lineidx++] = linebuf[charidx++];
wordsize--;
}
word[lineidx++] = 0;
}
if (term)
{
//skip back to the start of the word
while(charidx > 0 && (
(linebuf[charidx-1] >= 'a' && linebuf[charidx-1] <= 'z') ||
(linebuf[charidx-1] >= 'A' && linebuf[charidx-1] <= 'Z') ||
(linebuf[charidx-1] >= '0' && linebuf[charidx-1] <= '9') ||
linebuf[charidx-1] == '_' || linebuf[charidx-1] == ':' || linebuf[charidx-1] == '.' ||
linebuf[charidx-1] == '[' || linebuf[charidx-1] == ']' ||
linebuf[charidx-1] >= 128
))
{
charidx--;
}
//copy the result out
lineidx = 0;
termsize--;
while (termsize && (
(linebuf[charidx] >= 'a' && linebuf[charidx] <= 'z') ||
(linebuf[charidx] >= 'A' && linebuf[charidx] <= 'Z') ||
(linebuf[charidx] >= '0' && linebuf[charidx] <= '9') ||
linebuf[charidx] == '_' || linebuf[charidx] == ':' || linebuf[charidx] == '.' ||
linebuf[charidx] == '[' || linebuf[charidx] == ']' ||
linebuf[charidx] >= 128
))
{
term[lineidx++] = linebuf[charidx++];
termsize--;
}
term[lineidx++] = 0;
}
return word;
}
protected:
void contextMenuEvent(QContextMenuEvent *event);
};
class documentlist : public QAbstractListModel
{
QsciScintilla *s; //this is the widget that we load our documents into
WrappedQsciScintilla *s; //this is the widget that we load our documents into
int numdocuments;
struct document_s
@ -480,7 +592,7 @@ class documentlist : public QAbstractListModel
}
void UpdateTitle(void);
public:
documentlist(QsciScintilla *editor)
documentlist(WrappedQsciScintilla *editor)
{
s = editor;
numdocuments = 0;
@ -868,6 +980,18 @@ public:
break;
}
connect(s, &QsciScintillaBase::SCN_CHARADDED, [=](int charadded)
{
if (charadded == '(')
{
int pos = s->SendScintilla(QsciScintillaBase::SCI_GETCURRENTPOS);
char *tooltext = GetCalltipForLine(pos-1);
//tooltip_editor = NULL;
if (tooltext)
s->SendScintilla(QsciScintillaBase::SCI_CALLTIPSHOW, pos, tooltext);
}
});
s->setUtf8(ed->savefmt != UTF_ANSI);
s->setText(QString(file));
@ -927,6 +1051,155 @@ public:
return ret;
}
char *GetCalltipForLine(int cursorpos) //calltips are used for tooltips too, so there's some extra weirdness in here
{
static char buffer[1024];
char wordbuf[256], *text;
char term[256];
char *defname;
defname = s->WordUnderCursor(wordbuf, sizeof(wordbuf), term, sizeof(term), cursorpos);
if (!*defname)
return NULL;
else if (globalstable.numbuckets)
{
QCC_def_t *def;
int fno;
int line;
int best, bestline;
char *macro = QCC_PR_CheckCompConstTooltip(defname, buffer, buffer + sizeof(buffer));
if (macro && *macro)
return macro;
/*if (dwell)
{
tooltip_editor = NULL;
*tooltip_variable = 0;
tooltip_position = 0;
*tooltip_type = 0;
*tooltip_comment = 0;
}*/
line = s->SendScintilla(QsciScintillaBase::SCI_LINEFROMPOSITION, cursorpos);
for (best = 0,bestline=0, fno = 1; fno < numfunctions; fno++)
{
if (line > functions[fno].line && bestline < functions[fno].line)
{
if (!strcmp(curdoc->fname, functions[fno].filen))
{
best = fno;
bestline = functions[fno].line;
}
}
}
if (best)
{
if (strstr(functions[best].name, "::"))
{
QCC_type_t *type;
char tmp[256];
char *c;
QC_strlcpy(tmp, functions[best].name, sizeof(tmp));
c = strstr(tmp, "::");
if (c)
*c = 0;
type = QCC_TypeForName(tmp);
if (type->type == ev_entity)
{
QCC_def_t *def;
QC_snprintfz(tmp, sizeof(tmp), "%s::__m%s", type->name, term);
for (fno = 0, def = NULL; fno < sourcefilesnumdefs && !def; fno++)
{
for (def = sourcefilesdefs[fno]; def; def = def->next)
{
if (def->scope && def->scope != &functions[best])
continue;
// OutputDebugString(def->name);
// OutputDebugString("\n");
if (!strcmp(def->name, tmp))
{
//FIXME: look at the scope's function to find the start+end of the function and filter based upon that, to show locals
break;
}
}
}
if (def && def->type->type == ev_field)
{
// QC_strlcpy(tmp, term, sizeof(tmp));
QC_snprintfz(term, sizeof(term), "self.%s", tmp);
}
else
{
for (fno = 0, def = NULL; fno < sourcefilesnumdefs && !def; fno++)
{
for (def = sourcefilesdefs[fno]; def; def = def->next)
{
if (def->scope && def->scope != &functions[best])
continue;
if (!strcmp(def->name, term))
{
//FIXME: look at the scope's function to find the start+end of the function and filter based upon that, to show locals
break;
}
}
}
if (def && def->type->type == ev_field)
{
QC_strlcpy(tmp, term, sizeof(tmp));
QC_snprintfz(term, sizeof(term), "self.%s", tmp);
}
}
}
}
}
//FIXME: we may need to display types too
for (fno = 0, def = NULL; fno < sourcefilesnumdefs && !def; fno++)
{
for (def = sourcefilesdefs[fno]; def; def = def->next)
{
if (def->scope)
continue;
if (!strcmp(def->name, defname))
{
//FIXME: look at the scope's function to find the start+end of the function and filter based upon that, to show locals
break;
}
}
}
if (def)
{
char typebuf[1024];
char valuebuf[1024];
if (def->constant && def->type->type == ev_float)
QC_snprintfz(valuebuf, sizeof(valuebuf), " = %g", def->symboldata[def->ofs]._float);
else if (def->constant && def->type->type == ev_integer)
QC_snprintfz(valuebuf, sizeof(valuebuf), " = %i", def->symboldata[def->ofs]._int);
else if (def->constant && def->type->type == ev_vector)
QC_snprintfz(valuebuf, sizeof(valuebuf), " = '%g %g %g'", def->symboldata[def->ofs].vector[0], def->symboldata[def->ofs].vector[1], def->symboldata[def->ofs].vector[2]);
else
*valuebuf = 0;
//note function argument names do not persist beyond the function def. we might be able to read the function's localdefs for them, but that's unreliable/broken with builtins where they're most needed.
if (def->comment)
QC_snprintfz(buffer, sizeof(buffer)-1, "%s %s%s\r\n%s", TypeName(def->type, typebuf, sizeof(typebuf)), def->name, valuebuf, def->comment);
else
QC_snprintfz(buffer, sizeof(buffer)-1, "%s %s%s", TypeName(def->type, typebuf, sizeof(typebuf)), def->name, valuebuf);
text = buffer;
}
else
text = NULL;
return text;
}
else
return NULL;//"Type info not available. Compile first.";
}
bool annotate(const char *line)
{
auto filename = line+6;
@ -1124,6 +1397,47 @@ public:
DebuggerSendCommand("qcbreakpoint %i \"%s\" %i\n", mode, curdoc->fname, curdoc->cursorline);
}
/*void setMarkerLine(int linenum)
{ //moves the 'current-line' marker to the active document and current line
for (int i = 0; i < numdocuments; i++)
{
docstacklock lock(this, docs[i]);
s->SendScintilla(QsciScintillaBase::SCI_MARKERDELETEALL, 1);
s->SendScintilla(QsciScintillaBase::SCI_MARKERDELETEALL, 2);
}
if (linenum <= 0)
return;
s->SendScintilla(QsciScintillaBase::SCI_MARKERADD, linenum-1, 1);
s->SendScintilla(QsciScintillaBase::SCI_MARKERADD, linenum-1, 2);
}*/
void setNextStatement(void)
{
if (!curdoc)
return;
if (!qcdebugger)
return; //can't move it if we're not running.
DebuggerSendCommand("qcjump \"%s\" %i\n", curdoc->fname, curdoc->cursorline);
}
void autoComplete(void)
{
if (!curdoc)
return;
char word[256];
char suggestions[65536];
s->WordUnderCursor(word, sizeof(word), NULL, 0, -2);
if (*word && GenAutoCompleteList(word, suggestions, sizeof(suggestions)))
{
s->SendScintilla(QsciScintillaBase::SCI_AUTOCSETFILLUPS, ".,[<>(*/+-=\t\n");
s->SendScintilla(QsciScintillaBase::SCI_AUTOCSHOW, strlen(word), suggestions);
}
//s->SendScintilla(QsciScintillaBase::SCI_CALLTIPSHOW, (int)0, "hello world");
//SCI_CALLTIPSHOW pos, "text"
}
void setNext(void)
{
if (!curdoc)
@ -1304,7 +1618,7 @@ public:
static class guimainwindow : public QMainWindow
{
public:
QsciScintilla s;
WrappedQsciScintilla s;
QSplitter leftrightsplit;
QSplitter logsplit;
QSplitter leftsplit;
@ -1317,6 +1631,7 @@ public:
private:
void CreateMenus(void)
{
{
auto fileMenu = menuBar()->addMenu(tr("&File"));
@ -1368,8 +1683,76 @@ private:
args.d->show();
});
auto find = new QAction(tr("Find In File"), this);
fileMenu->addAction(find);
find->setShortcuts(QKeySequence::listFromString("Ctrl+F"));
find->setStatusTip(tr("Search for a token in the current file"));
connect(find, &QAction::triggered, [=]()
{
struct grepargs_s
{
guimainwindow *mw;
QDialog *d;
QLineEdit *t;
} args = {this, new QDialog()};
args.t = new QLineEdit(this->s.selectedText());
auto l = new QVBoxLayout;
l->addWidget(args.t);
args.d->setLayout(l);
args.d->setWindowTitle("Find In File");
args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
{
ctx.mw->s.findFirst(ctx.t->text(), false, false, false, true);
ctx.d->done(0);
}, args));
args.d->show();
});
connect(new QShortcut(QKeySequence(tr("F3", "File|FindNext")), this), &QShortcut::activated,
[=]()
{
s.findNext();
});
auto grep = new QAction(tr("Find In Project"), this);
fileMenu->addAction(grep);
grep->setShortcuts(QKeySequence::listFromString("Ctrl+Shift+F"));
grep->setStatusTip(tr("Search through all project files"));
connect(grep, &QAction::triggered, [=]()
{
struct grepargs_s
{
guimainwindow *mw;
QDialog *d;
QLineEdit *t;
} args = {this, new QDialog()};
args.t = new QLineEdit(this->s.selectedText());
auto l = new QVBoxLayout;
l->addWidget(args.t);
args.d->setLayout(l);
args.d->setWindowTitle("Find In Project");
args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
{
ctx.mw->files.GrepAll(ctx.t->text().toUtf8().data());
ctx.d->done(0);
}, args));
args.d->show();
});
auto quit = new QAction(tr("Quit"), this);
fileMenu->addAction(quit);
connect(quit, &QAction::triggered, [=]()
{
this->close();
});
}
{
auto editMenu = menuBar()->addMenu(tr("&Edit"));
//undo
//redo
auto godef = new QAction(tr("Go To Definition"), this);
fileMenu->addAction(godef);
editMenu->addAction(godef);
godef->setShortcuts(QKeySequence::listFromString("F12"));
connect(godef, &QAction::triggered, [=]()
{
@ -1392,88 +1775,34 @@ private:
args.d->show();
});
auto find = new QAction(tr("Find"), this);
fileMenu->addAction(find);
find->setShortcuts(QKeySequence::listFromString("Ctrl+F"));
find->setStatusTip(tr("Search through all project files"));
connect(find, &QAction::triggered, [=]()
auto autocomplete = new QAction(tr("AutoComplete"), this);
editMenu->addAction(autocomplete);
autocomplete->setShortcuts(QKeySequence::listFromString("Ctrl+Space"));
connect(autocomplete, &QAction::triggered, [=]()
{
struct grepargs_s
{
guimainwindow *mw;
QDialog *d;
QLineEdit *t;
} args = {this, new QDialog()};
args.t = new QLineEdit(this->s.selectedText());
auto l = new QVBoxLayout;
l->addWidget(args.t);
args.d->setLayout(l);
args.d->setWindowTitle("FTEQCC Find");
args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
{
ctx.mw->s.findFirst(ctx.t->text(), false, false, false, true);
ctx.d->done(0);
}, args));
args.d->show();
docs.autoComplete();
});
connect(new QShortcut(QKeySequence(tr("F3", "File|FindNext")), this), &QShortcut::activated,
[=]()
{
s.findNext();
});
auto grep = new QAction(tr("Grep"), this);
fileMenu->addAction(grep);
grep->setShortcuts(QKeySequence::listFromString("Ctrl+Shift+G"));
grep->setStatusTip(tr("Search through all project files"));
connect(grep, &QAction::triggered, [=]()
{
struct grepargs_s
{
guimainwindow *mw;
QDialog *d;
QLineEdit *t;
} args = {this, new QDialog()};
args.t = new QLineEdit(this->s.selectedText());
auto l = new QVBoxLayout;
l->addWidget(args.t);
args.d->setLayout(l);
args.d->setWindowTitle("FTEQCC Grep Project Files");
args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
{
ctx.mw->files.GrepAll(ctx.t->text().toUtf8().data());
ctx.d->done(0);
}, args));
args.d->show();
});
//convert to utf-8 chars
//convert to Quake chars
auto convertunix = new QAction(tr("Convert to Unix Endings"), this);
fileMenu->addAction(convertunix);
editMenu->addAction(convertunix);
connect(convertunix, &QAction::triggered, [=]()
{
s.convertEols(QsciScintilla::EolUnix);
s.setEolVisibility(false);
});
auto convertdos = new QAction(tr("Convert to DOS Endings"), this);
fileMenu->addAction(convertdos);
editMenu->addAction(convertdos);
connect(convertdos, &QAction::triggered, [=]()
{
s.convertEols(QsciScintilla::EolWindows);
s.setEolVisibility(false);
});
auto quit = new QAction(tr("Quit"), this);
fileMenu->addAction(quit);
connect(quit, &QAction::triggered, [=]()
//convert to utf-8 chars
//convert to Quake chars
}
{
this->close();
});
auto debugMenu = menuBar()->addMenu(tr("&Debug"));
auto debugrebuild = new QAction(tr("Rebuild"), this);
debugMenu->addAction(debugrebuild);
debugrebuild->setShortcuts(QKeySequence::listFromString("F7"));
@ -1486,7 +1815,7 @@ private:
debugsetnext->setShortcuts(QKeySequence::listFromString("F8"));
connect(debugsetnext, &QAction::triggered, [=]()
{
//FIXME
docs.setNextStatement();
});
auto debugresume = new QAction(tr("Resume"), this);
debugMenu->addAction(debugresume);
@ -1528,6 +1857,7 @@ private:
docs.toggleBreak();
});
}
}
public:
~guimainwindow()
@ -1597,6 +1927,52 @@ public:
}
} *mainwnd;
void WrappedQsciScintilla::contextMenuEvent(QContextMenuEvent *event)
{
QMenu *menu = createStandardContextMenu();
static char blah[256];
if (*WordUnderCursor(blah, sizeof(blah), NULL, 0, -3))
{
menu->addSeparator();
connect(menu->addAction(tr("Go to definition")), &QAction::triggered, [=]()
{
GoToDefinition(blah);
});
connect(menu->addAction(tr("Find Usages")), &QAction::triggered, [=]()
{
mainwnd->files.GrepAll(blah);
});
}
menu->addSeparator();
connect(menu->addAction(tr("Toggle Breakpoint")), &QAction::triggered, [=]()
{
mainwnd->docs.toggleBreak();
});
if (qcdebugger)
{
connect(menu->addAction(tr("Set Next Statement")), &QAction::triggered, [=]()
{
mainwnd->docs.setNextStatement();
});
connect(menu->addAction(tr("Resume")), &QAction::triggered, [=]()
{
});
}
else
{
connect(menu->addAction(tr("Start Debugging")), &QAction::triggered, [=]()
{
DebuggerStart();
});
}
menu->exec(event->globalPos());
delete menu;
}
//called when progssrcname has changed.
//progssrcname should already have been set.
void UpdateFileList(void)
@ -2043,7 +2419,6 @@ int GUIFileSize(const char *fname)
static QProcess *qcdebugger;
static void DebuggerStop(void)
{
if (qcdebugger)

View file

@ -201,6 +201,40 @@ void GoToDefinition(const char *name)
}
}
pbool GenAutoCompleteList(char *prefix, char *buffer, int buffersize)
{
QCC_def_t *def;
int prefixlen = strlen(prefix);
int usedbuffer = 0;
int l;
int fno;
for (fno = 0; fno < sourcefilesnumdefs; fno++)
{
for (def = sourcefilesdefs[fno]; def; def = def->next)
{
if (def->scope)
continue; //ignore locals, because we don't know where we are, and they're probably irrelevent.
//make sure it has the right prefix
if (!strncmp(def->name, prefix, prefixlen))
//but ignore it if its one of those special things that you're not meant to know about.
if (strcmp(def->name, "IMMEDIATE") && !strchr(def->name, ':') && !strchr(def->name, '.') && !strchr(def->name, '*') && !strchr(def->name, '['))
{
l = strlen(def->name);
if (l && usedbuffer+2+l < buffersize)
{
if (usedbuffer)
buffer[usedbuffer++] = ' ';
memcpy(buffer+usedbuffer, def->name, l);
usedbuffer += l;
}
}
}
}
buffer[usedbuffer] = 0;
return usedbuffer>0;
}
static void GUI_WriteConfigLine(FILE *file, char *part1, char *part2, char *part3, char *desc)
{
int align = 0;