From 19a2ecb99ef756addfbcd958a29eefb746354772 Mon Sep 17 00:00:00 2001
From: Spoike <acceptthis@users.sourceforge.net>
Date: Sat, 14 Mar 2020 17:38:55 +0000
Subject: [PATCH] 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
---
 engine/qclib/gui.h         |   1 +
 engine/qclib/qccguiqt.cpp  | 779 +++++++++++++++++++++++++++----------
 engine/qclib/qccguistuff.c |  34 ++
 3 files changed, 612 insertions(+), 202 deletions(-)

diff --git a/engine/qclib/gui.h b/engine/qclib/gui.h
index aa8734b74..2cb31305b 100644
--- a/engine/qclib/gui.h
+++ b/engine/qclib/gui.h
@@ -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];
 
diff --git a/engine/qclib/qccguiqt.cpp b/engine/qclib/qccguiqt.cpp
index 0ef1f92a0..d1cf81169 100644
--- a/engine/qclib/qccguiqt.cpp
+++ b/engine/qclib/qccguiqt.cpp
@@ -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;
@@ -1318,215 +1632,231 @@ public:
 private:
 	void CreateMenus(void)
 	{
-		auto fileMenu = menuBar()->addMenu(tr("&File"));
+		{
+			auto fileMenu = menuBar()->addMenu(tr("&File"));
 
-		//editor UI things
-		auto fileopen = new QAction(tr("Open"), this);
-		fileMenu->addAction(fileopen);
-		fileopen->setShortcuts(QKeySequence::listFromString("Ctrl+O"));
-		connect(fileopen, &QAction::triggered, [=]()
-			{
-				GUIprintf("Ctrl+O hit\n");
-			});
-		auto filesave = new QAction(tr("Save"), this);
-		fileMenu->addAction(filesave);
-		filesave->setShortcuts(QKeySequence::listFromString("Ctrl+S"));
-		connect(filesave, &QAction::triggered, [=]()
-			{
-				if (!docs.saveDocument(NULL))
-					QMessageBox::critical(nullptr, "Error", QString::asprintf("Unable to save"));
-			});
-
-		auto prefs = new QAction(tr("&Preferences"), this);
-		fileMenu->addAction(prefs);
-		prefs->setShortcuts(QKeySequence::Preferences);
-		prefs->setStatusTip(tr("Reconfigure stuff"));
-		connect(prefs, &QAction::triggered, [](){(new optionswindow())->show();});
-
-		auto goline = new QAction(tr("Go to line"), this);
-		fileMenu->addAction(goline);
-		goline->setShortcuts(QKeySequence::listFromString("Ctrl+G"));
-		goline->setStatusTip(tr("Jump to line"));
-		connect(goline, &QAction::triggered, [=]()
-			{
-				struct grepargs_s
+			//editor UI things
+			auto fileopen = new QAction(tr("Open"), this);
+			fileMenu->addAction(fileopen);
+			fileopen->setShortcuts(QKeySequence::listFromString("Ctrl+O"));
+			connect(fileopen, &QAction::triggered, [=]()
 				{
-					guimainwindow *mw;
-					QDialog *d;
-					QLineEdit *t;
-				} args = {this, new QDialog()};
-				args.t = new QLineEdit(QString(""));
-				auto l = new QVBoxLayout;
-				l->addWidget(args.t);
-				args.d->setLayout(l);
-				args.d->setWindowTitle("FTEQCC Go To Line");
-				args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
-					{
-						ctx.mw->docs.EditFile(NULL, atoi(ctx.t->text().toUtf8().data()));
-						ctx.d->done(0);
-					}, args));
-				args.d->show();
-			});
-
-		auto godef = new QAction(tr("Go To Definition"), this);
-		fileMenu->addAction(godef);
-		godef->setShortcuts(QKeySequence::listFromString("F12"));
-		connect(godef, &QAction::triggered, [=]()
-			{
-				struct grepargs_s
+					GUIprintf("Ctrl+O hit\n");
+				});
+			auto filesave = new QAction(tr("Save"), this);
+			fileMenu->addAction(filesave);
+			filesave->setShortcuts(QKeySequence::listFromString("Ctrl+S"));
+			connect(filesave, &QAction::triggered, [=]()
 				{
-					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("Go To Definition");
-				args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
-					{
-						GoToDefinition(ctx.t->text().toUtf8().data());
-						ctx.d->done(0);
-					}, args));
-				args.d->show();
-			});
+					if (!docs.saveDocument(NULL))
+						QMessageBox::critical(nullptr, "Error", QString::asprintf("Unable to save"));
+				});
 
-		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, [=]()
-			{
-				struct grepargs_s
+			auto prefs = new QAction(tr("&Preferences"), this);
+			fileMenu->addAction(prefs);
+			prefs->setShortcuts(QKeySequence::Preferences);
+			prefs->setStatusTip(tr("Reconfigure stuff"));
+			connect(prefs, &QAction::triggered, [](){(new optionswindow())->show();});
+
+			auto goline = new QAction(tr("Go to line"), this);
+			fileMenu->addAction(goline);
+			goline->setShortcuts(QKeySequence::listFromString("Ctrl+G"));
+			goline->setStatusTip(tr("Jump to line"));
+			connect(goline, &QAction::triggered, [=]()
 				{
-					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)
+					struct grepargs_s
 					{
-						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();
-			});
+						guimainwindow *mw;
+						QDialog *d;
+						QLineEdit *t;
+					} args = {this, new QDialog()};
+					args.t = new QLineEdit(QString(""));
+					auto l = new QVBoxLayout;
+					l->addWidget(args.t);
+					args.d->setLayout(l);
+					args.d->setWindowTitle("FTEQCC Go To Line");
+					args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
+						{
+							ctx.mw->docs.EditFile(NULL, atoi(ctx.t->text().toUtf8().data()));
+							ctx.d->done(0);
+						}, args));
+					args.d->show();
+				});
 
-		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
+			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, [=]()
 				{
-					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)
+					struct grepargs_s
 					{
-						ctx.mw->files.GrepAll(ctx.t->text().toUtf8().data());
-						ctx.d->done(0);
-					}, args));
-				args.d->show();
-			});
+						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();
+				});
 
-		//convert to utf-8 chars
-		//convert to Quake chars
+			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 convertunix = new QAction(tr("Convert to Unix Endings"), this);
-		fileMenu->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);
-		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, [=]()
+				{
+					this->close();
+				});
+		}
+		{
+			auto editMenu = menuBar()->addMenu(tr("&Edit"));
 
-		auto quit = new QAction(tr("Quit"), this);
-		fileMenu->addAction(quit);
-		connect(quit, &QAction::triggered, [=]()
-			{
-				this->close();
-			});
+			//undo
+			//redo
 
+			auto godef = new QAction(tr("Go To Definition"), this);
+			editMenu->addAction(godef);
+			godef->setShortcuts(QKeySequence::listFromString("F12"));
+			connect(godef, &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("Go To Definition");
+					args.t->installEventFilter(new keyEnterReceiver<grepargs_s>([](grepargs_s &ctx)
+						{
+							GoToDefinition(ctx.t->text().toUtf8().data());
+							ctx.d->done(0);
+						}, args));
+					args.d->show();
+				});
 
-		auto debugMenu = menuBar()->addMenu(tr("&Debug"));
-		auto debugrebuild = new QAction(tr("Rebuild"), this);
-		debugMenu->addAction(debugrebuild);
-		debugrebuild->setShortcuts(QKeySequence::listFromString("F7"));
-		connect(debugrebuild, &QAction::triggered, [=]()
-			{
-				RunCompiler("", false);
-			});
-		auto debugsetnext = new QAction(tr("Set Next Statement"), this);
-		debugMenu->addAction(debugsetnext);
-		debugsetnext->setShortcuts(QKeySequence::listFromString("F8"));
-		connect(debugsetnext, &QAction::triggered, [=]()
-			{
-				//FIXME
-			});
-		auto debugresume = new QAction(tr("Resume"), this);
-		debugMenu->addAction(debugresume);
-		debugresume->setShortcuts(QKeySequence::listFromString("F5"));
-		connect(debugresume, &QAction::triggered, [=]()
-			{
-				if (!DebuggerSendCommand("qcresume\n"))
-					DebuggerStart();	//unable to send? assume its not running.
-			});
-		auto debugover = new QAction(tr("Step Over"), this);
-		debugMenu->addAction(debugover);
-		debugover->setShortcuts(QKeySequence::listFromString("F10"));
-		connect(debugover, &QAction::triggered, [=]()
-			{
-				if (!DebuggerSendCommand("qcstep over\n"))
-					DebuggerStart();
-			});
-		auto debuginto = new QAction(tr("Step Into"), this);
-		debugMenu->addAction(debuginto);
-		debuginto->setShortcuts(QKeySequence::listFromString("F11"));
-		connect(debuginto, &QAction::triggered, [=]()
-			{
-				if (!DebuggerSendCommand("qcstep into\n"))
-					DebuggerStart();
-			});
-		auto debugout = new QAction(tr("Step Out"), this);
-		debugMenu->addAction(debugout);
-		debugout->setShortcuts(QKeySequence::listFromString("Shift+F11"));
-		connect(debugout, &QAction::triggered, [=]()
-			{
-				if (!DebuggerSendCommand("qcstep out\n"))
-					DebuggerStart();
-			});
-		auto debugbreak = new QAction(tr("Toggle Breakpoint"), this);
-		debugMenu->addAction(debugbreak);
-		debugbreak->setShortcuts(QKeySequence::listFromString("F9"));
-		connect(debugbreak, &QAction::triggered, [=]()
-			{
-				docs.toggleBreak();
-			});
+			auto autocomplete = new QAction(tr("AutoComplete"), this);
+			editMenu->addAction(autocomplete);
+			autocomplete->setShortcuts(QKeySequence::listFromString("Ctrl+Space"));
+			connect(autocomplete, &QAction::triggered, [=]()
+				{
+					docs.autoComplete();
+				});
+			auto convertunix = new QAction(tr("Convert to Unix Endings"), this);
+			editMenu->addAction(convertunix);
+			connect(convertunix, &QAction::triggered, [=]()
+				{
+					s.convertEols(QsciScintilla::EolUnix);
+					s.setEolVisibility(false);
+				});
+			auto convertdos = new QAction(tr("Convert to DOS Endings"), this);
+			editMenu->addAction(convertdos);
+			connect(convertdos, &QAction::triggered, [=]()
+				{
+					s.convertEols(QsciScintilla::EolWindows);
+					s.setEolVisibility(false);
+				});
+
+			//convert to utf-8 chars
+			//convert to Quake chars
+		}
+		{
+			auto debugMenu = menuBar()->addMenu(tr("&Debug"));
+
+			auto debugrebuild = new QAction(tr("Rebuild"), this);
+			debugMenu->addAction(debugrebuild);
+			debugrebuild->setShortcuts(QKeySequence::listFromString("F7"));
+			connect(debugrebuild, &QAction::triggered, [=]()
+				{
+					RunCompiler("", false);
+				});
+			auto debugsetnext = new QAction(tr("Set Next Statement"), this);
+			debugMenu->addAction(debugsetnext);
+			debugsetnext->setShortcuts(QKeySequence::listFromString("F8"));
+			connect(debugsetnext, &QAction::triggered, [=]()
+				{
+					docs.setNextStatement();
+				});
+			auto debugresume = new QAction(tr("Resume"), this);
+			debugMenu->addAction(debugresume);
+			debugresume->setShortcuts(QKeySequence::listFromString("F5"));
+			connect(debugresume, &QAction::triggered, [=]()
+				{
+					if (!DebuggerSendCommand("qcresume\n"))
+						DebuggerStart();	//unable to send? assume its not running.
+				});
+			auto debugover = new QAction(tr("Step Over"), this);
+			debugMenu->addAction(debugover);
+			debugover->setShortcuts(QKeySequence::listFromString("F10"));
+			connect(debugover, &QAction::triggered, [=]()
+				{
+					if (!DebuggerSendCommand("qcstep over\n"))
+						DebuggerStart();
+				});
+			auto debuginto = new QAction(tr("Step Into"), this);
+			debugMenu->addAction(debuginto);
+			debuginto->setShortcuts(QKeySequence::listFromString("F11"));
+			connect(debuginto, &QAction::triggered, [=]()
+				{
+					if (!DebuggerSendCommand("qcstep into\n"))
+						DebuggerStart();
+				});
+			auto debugout = new QAction(tr("Step Out"), this);
+			debugMenu->addAction(debugout);
+			debugout->setShortcuts(QKeySequence::listFromString("Shift+F11"));
+			connect(debugout, &QAction::triggered, [=]()
+				{
+					if (!DebuggerSendCommand("qcstep out\n"))
+						DebuggerStart();
+				});
+			auto debugbreak = new QAction(tr("Toggle Breakpoint"), this);
+			debugMenu->addAction(debugbreak);
+			debugbreak->setShortcuts(QKeySequence::listFromString("F9"));
+			connect(debugbreak, &QAction::triggered, [=]()
+				{
+					docs.toggleBreak();
+				});
+		}
 	}
 
 public:
@@ -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)
diff --git a/engine/qclib/qccguistuff.c b/engine/qclib/qccguistuff.c
index a306d2f89..c7a8d1e1d 100644
--- a/engine/qclib/qccguistuff.c
+++ b/engine/qclib/qccguistuff.c
@@ -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;