mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-09-25 23:25:41 +00:00
Show syntax errors when editing in python script editor
This commit is contained in:
@@ -2275,6 +2275,64 @@ void PythonContext::LaunchDebugger(QWidget *window, PersistantConfig &config, QS
|
||||
});
|
||||
}
|
||||
|
||||
PyParseError PythonContext::CheckPyParse(const QByteArray &script, const rdcstr &scriptNameForErrors)
|
||||
{
|
||||
PyParseError parseError;
|
||||
|
||||
PyGILState_STATE gil = PyGILState_Ensure();
|
||||
|
||||
PyObject *ast = PyImport_ImportModule("ast");
|
||||
|
||||
PyObject *scriptText = PyUnicode_FromStringAndSize(script.data(), script.size());
|
||||
PyObject *scriptName = PyUnicode_FromString(scriptNameForErrors.c_str());
|
||||
|
||||
PyObject *parseResult = PyObject_CallMethod(ast, "parse", "OO", scriptText, scriptName);
|
||||
|
||||
if(!parseResult)
|
||||
{
|
||||
PyObject *exObj = NULL, *valueObj = NULL, *tracebackObj = NULL;
|
||||
|
||||
PyErr_Fetch(&exObj, &valueObj, &tracebackObj);
|
||||
PyErr_NormalizeException(&exObj, &valueObj, &tracebackObj);
|
||||
|
||||
{
|
||||
PyObject *a;
|
||||
|
||||
PyObject_GetOptionalAttrString(valueObj, "lineno", &a);
|
||||
parseError.lineno = PyLong_AsInt(a);
|
||||
Py_XDECREF(a);
|
||||
|
||||
PyObject_GetOptionalAttrString(valueObj, "offset", &a);
|
||||
parseError.offset = PyLong_AsInt(a);
|
||||
Py_XDECREF(a);
|
||||
|
||||
PyObject *repr = PyObject_Str(valueObj);
|
||||
PyObject *utf8 = PyUnicode_AsUTF8String(repr);
|
||||
parseError.errStr = PyBytes_AsString(utf8);
|
||||
for(int i = 1; i < parseError.offset - 1; i++)
|
||||
parseError.errStr.insert(0, ' ');
|
||||
parseError.errStr.insert(parseError.offset - 2, "^\n");
|
||||
|
||||
Py_XDECREF(repr);
|
||||
Py_XDECREF(utf8);
|
||||
}
|
||||
|
||||
Py_XDECREF(exObj);
|
||||
Py_XDECREF(valueObj);
|
||||
Py_XDECREF(tracebackObj);
|
||||
}
|
||||
|
||||
Py_XDECREF(scriptText);
|
||||
Py_XDECREF(scriptName);
|
||||
Py_XDECREF(parseResult);
|
||||
|
||||
Py_XDECREF(ast);
|
||||
|
||||
PyGILState_Release(gil);
|
||||
|
||||
return parseError;
|
||||
}
|
||||
|
||||
extern "C" PyThreadState *GetExecutingThreadState(PyObject *global_handle)
|
||||
{
|
||||
OutputRedirector *redirector = (OutputRedirector *)global_handle;
|
||||
|
||||
@@ -37,6 +37,12 @@ typedef struct _object PyObject;
|
||||
typedef struct _frame PyFrameObject;
|
||||
typedef struct _ts PyThreadState;
|
||||
|
||||
struct PyParseError
|
||||
{
|
||||
int lineno = -1, offset = -1;
|
||||
rdcstr errStr;
|
||||
};
|
||||
|
||||
class PythonContext : public QObject
|
||||
{
|
||||
private:
|
||||
@@ -77,6 +83,8 @@ public:
|
||||
static bool WaitForDebugger();
|
||||
static void LaunchDebugger(QWidget *window, PersistantConfig &config, QString context_location);
|
||||
|
||||
PyParseError CheckPyParse(const QByteArray &script, const rdcstr &scriptNameForErrors);
|
||||
|
||||
bool CheckInterfaces(rdcstr &log);
|
||||
|
||||
QString versionString();
|
||||
|
||||
@@ -952,7 +952,42 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
|
||||
|
||||
scriptEditor = new ScintillaEdit(this);
|
||||
|
||||
// don't repeatedly re-parse for errors. Have a reasonable timeout
|
||||
QTimer *checkTimer = new QTimer(this);
|
||||
checkTimer->setSingleShot(true);
|
||||
checkTimer->setInterval(1200);
|
||||
|
||||
QObject::connect(checkTimer, &QTimer::timeout, [this]() {
|
||||
PythonContext *context = new PythonContext();
|
||||
|
||||
setGlobals(context);
|
||||
|
||||
QByteArray script = scriptEditor->getText(scriptEditor->textLength() + 1);
|
||||
PyParseError parseError = context->CheckPyParse(script, "script.py");
|
||||
|
||||
if(parseError.lineno >= 0)
|
||||
{
|
||||
sptr_t end = scriptEditor->lineLength(parseError.lineno - 1);
|
||||
sptr_t linePos = scriptEditor->positionFromLine(parseError.lineno - 1);
|
||||
while(QChar(QLatin1Char(script[int(linePos + end - 1)])).isSpace())
|
||||
end--;
|
||||
scriptEditor->setIndicatorCurrent(0);
|
||||
scriptEditor->indicatorFillRange(linePos + parseError.offset - 1, end + 1 - parseError.offset);
|
||||
|
||||
scriptEditor->annotationSetText(parseError.lineno - 1, parseError.errStr.c_str());
|
||||
scriptEditor->annotationSetVisible(ANNOTATION_BOXED);
|
||||
scriptEditor->annotationSetStyle(parseError.lineno - 1, 100);
|
||||
}
|
||||
|
||||
context->Finish();
|
||||
});
|
||||
|
||||
scriptEditor->indicSetFore(0, 0x0000ff);
|
||||
|
||||
scriptEditor->styleSetFont(STYLE_DEFAULT, Formatter::FixedFont().family().toUtf8().data());
|
||||
scriptEditor->styleSetFont(100, Formatter::FixedFont().family().toUtf8().data());
|
||||
scriptEditor->styleSetBack(
|
||||
100, IsDarkTheme() ? SCINTILLA_COLOUR(175, 70, 70) : SCINTILLA_COLOUR(255, 150, 150));
|
||||
|
||||
scriptEditor->setMarginLeft(4.0);
|
||||
scriptEditor->setMarginWidthN(0, 32.0);
|
||||
@@ -992,12 +1027,21 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
|
||||
});
|
||||
|
||||
QObject::connect(scriptEditor, &ScintillaEdit::modified,
|
||||
[this](int type, int, int, int, const QByteArray &, int, int, int) {
|
||||
[this, checkTimer](int type, int, int, int, const QByteArray &, int, int, int) {
|
||||
if(type & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT | SC_MOD_BEFOREINSERT |
|
||||
SC_MOD_BEFOREDELETE))
|
||||
{
|
||||
scriptEditor->markerDeleteAll(CURRENT_MARKER);
|
||||
scriptEditor->markerDeleteAll(CURRENT_MARKER + 1);
|
||||
|
||||
// always remove errors immediately
|
||||
scriptEditor->setIndicatorCurrent(0);
|
||||
scriptEditor->indicatorClearRange(0, scriptEditor->textLength() + 1);
|
||||
scriptEditor->annotationClearAll();
|
||||
|
||||
// we'll reparse when this timer finishes (it will be re-started on every
|
||||
// change, so only N ms after the last change
|
||||
checkTimer->start();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user