Add autocomplete for python help entry

This commit is contained in:
baldurk
2026-08-27 13:43:14 +01:00
parent 9a2ec4aa19
commit b100e05053
5 changed files with 147 additions and 70 deletions
@@ -1783,6 +1783,67 @@ void PythonContext::reflectSource(QString src)
PyGILState_Release(gil);
}
void PythonContext::makeHelpContext()
{
// expand out members of renderdoc and qrenderdoc using the stubs
if(!m_Reflector)
{
for(int i = 0; i < 50 && m_DeferredInit == 0; i++)
QThread::msleep(20);
m_DeferredInit = 1;
if(!m_Reflector)
return;
}
PyGILState_STATE gil = PyGILState_Ensure();
PyObject *alias_modules = PyObject_SafeGetAttrString(m_Reflector, "alias_modules");
PyObject *stub_rd = PyDict_GetItemString(alias_modules, "renderdoc");
PyObject *stub_qrd = PyDict_GetItemString(alias_modules, "qrenderdoc");
PyDict_SetItemString(context_namespace, "stub_rd", stub_rd);
PyDict_SetItemString(context_namespace, "stub_qrd", stub_qrd);
Py_XDECREF(stub_rd);
Py_XDECREF(stub_qrd);
Py_XDECREF(alias_modules);
PyGILState_Release(gil);
executeString(lit(R"(
def publicise_module(mod):
for name in dir(mod):
if name[0] == '_' or '_circular' in name:
continue
obj = getattr(mod, name)
if hasattr(obj, '__module__') and mod.__name__ not in getattr(obj, '__module__'):
print(f"{name} is {obj.__module__} vs {mod.__name__}")
continue
globals()[name] = obj
publicise_module(stub_rd)
publicise_module(stub_qrd)
del publicise_module
)"));
gil = PyGILState_Ensure();
PyDict_DelItemString(context_namespace, "stub_rd");
PyDict_DelItemString(context_namespace, "stub_qrd");
if(PyDict_ContainsString(context_namespace, "pyrenderdoc"))
PyDict_DelItemString(context_namespace, "pyrenderdoc");
if(PyDict_ContainsString(context_namespace, "sys"))
PyDict_DelItemString(context_namespace, "sys");
PyGILState_Release(gil);
reflectSource(QString());
}
QString PythonContext::tooltipForLoc(int line, int col)
{
PyGILState_STATE gil = PyGILState_Ensure();
@@ -118,6 +118,7 @@ public:
static QWidget *QWidgetFromPy(PyObject *widget);
void reflectSource(QString src);
void makeHelpContext();
QString tooltipForLoc(int line, int col);
QList<QPair<QString, QString>> completionOptions(int line, QString expr, int &prefix_len);
QString tryFunctionCompletion(int line, QString expr);
@@ -2143,6 +2143,8 @@ class PyReflector:
prefix_filter = node.id
while curscope is not None:
for k, v in curscope.identifiers.items():
if k.startswith('_renderdoc'):
continue
if any([x.line <= line for x in v]):
ret.append(k)
curscope = curscope.parent
+81 -67
View File
@@ -187,9 +187,10 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
ui->setupUi(this);
QObject::connect(ui->lineInput, &RDLineEdit::keyPress, this, &PythonShell::interactive_keypress);
QObject::connect(ui->helpSearch, &RDLineEdit::keyPress, this, &PythonShell::helpSearch_keypress);
QObject::connect(ui->helpSearch, &RDLineEdit::keyPress, this, &PythonShell::interactive_keypress);
QObject::connect(ui->lineInput, &RDLineEdit::leave, [this]() { hideFunccompleteTooltip(); });
QObject::connect(ui->helpSearch, &RDLineEdit::leave, [this]() { hideFunccompleteTooltip(); });
// we create this up front so its state stays persistent as much as possible.
m_FindReplace = new FindReplace(m_Scintillas, this);
@@ -217,6 +218,7 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
m_FindReplace->setFindAllResultsDisplay(m_FindResults);
ui->lineInput->setFont(Formatter::FixedFont());
ui->helpSearch->setFont(Formatter::FixedFont());
ui->interactiveOutput->setFont(Formatter::FixedFont());
ui->scriptOutput->setFont(Formatter::FixedFont());
ui->helpText->setFont(Formatter::FixedFont());
@@ -226,6 +228,7 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
ui->replGroup->setWindowTitle(tr("Interactive REPL"));
ui->lineInput->setAcceptTabCharacters(true);
ui->helpSearch->setAcceptTabCharacters(true);
// don't repeatedly re-parse for errors. Have a reasonable timeout
m_SyntaxCheckTimer = new QTimer(this);
@@ -282,13 +285,6 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
completionContext = new PythonContext();
// if we're help printing in the completion context, append it to the help text
QObject::connect(completionContext, &PythonContext::textOutput,
[this](const QString &, bool isStdError, const QString &output) {
if(m_HelpPrinting)
appendText(ui->helpText, output);
});
QObject::connect(m_SyntaxCheckTimer, &QTimer::timeout, this, &PythonShell::doSyntaxCheck);
QObject::connect(ui->interactiveOutput, &RDTextEdit::keyPress, [this](QKeyEvent *e) {
@@ -313,7 +309,6 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
m_InteractiveCompleter->popup()->setFont(Formatter::FixedFont());
m_InteractiveCompleter->popup()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
m_InteractiveCompleter->popup()->setTextElideMode(Qt::ElideNone);
m_InteractiveCompleter->setWidget(ui->lineInput);
m_InteractiveCompleter->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
m_InteractiveCompleter->setWrapAround(false);
m_InteractiveCompletionModel = new QStringListModel(this);
@@ -338,15 +333,18 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
QObject::connect(m_InteractiveCompleter,
OverloadedSlot<const QModelIndex &>::of(&QCompleter::activated),
[this](const QModelIndex &idx) {
RDLineEdit *edit = qobject_cast<RDLineEdit *>(m_InteractiveCompleter->widget());
if(!edit)
return;
int i = idx.row();
if(i >= 0 && i < m_InteractiveCompletionModel->rowCount())
{
QString curText = ui->lineInput->text();
QString curText = edit->text();
curText.resize(curText.size() - m_InteractiveCompletionPrefix);
curText += m_InteractiveCompletionModel->stringList()[i];
ui->lineInput->setText(curText);
edit->setText(curText);
ui->lineInput->setCursorPosition(curText.size());
edit->setCursorPosition(curText.size());
}
});
@@ -533,6 +531,15 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent)
QObject::connect(PythonContext::GetExtensionContext(), &PythonContext::extensionLoaded, this,
&PythonShell::extensionLoaded);
helpContext = newContext();
helpContext->makeHelpContext();
QObject::connect(helpContext, &PythonContext::textOutput,
[this](const QString &, bool isStdError, const QString &output) {
appendText(ui->helpText, output);
});
m_Ctx.GetMainWindow()->RegisterShortcut("CTRL+S", this,
[this](QWidget *) { this->on_saveScript_clicked(); });
@@ -566,6 +573,7 @@ PythonShell::~PythonShell()
completionContext->Finish();
interactiveContext->Finish();
helpContext->Finish();
delete ui;
}
@@ -1777,7 +1785,8 @@ bool PythonShell::saveEditor(EditorWrapper *editor, QString filename)
void PythonShell::removeEditor(EditorWrapper *editor)
{
hideFunccompleteTooltip();
m_Watcher->removePath(editor->filename());
if(!editor->filename().isEmpty())
m_Watcher->removePath(editor->filename());
m_Editors.removeOne(editor);
m_Scintillas.removeOne(editor->scintilla());
updateEditorCloseButton();
@@ -2194,9 +2203,7 @@ void PythonShell::refreshCurrentHelp()
ui->helpText->clear();
m_HelpPrinting = true;
completionContext->executeString(lit(R"(
helpContext->executeString(lit(R"(
try:
import keyword
if keyword.iskeyword("%1"):
@@ -2206,15 +2213,17 @@ try:
except ImportError:
help(%1)
)")
.arg(ui->helpSearch->text()));
.arg(ui->helpSearch->text()));
ui->helpText->verticalScrollBar()->setValue(0);
m_HelpPrinting = false;
}
void PythonShell::interactive_keypress(QKeyEvent *event)
{
RDLineEdit *edit = qobject_cast<RDLineEdit *>(QObject::sender());
if(!edit)
return;
bool triggerCompletion = false;
if(m_InteractiveCompleter->popup()->isVisible())
@@ -2224,12 +2233,13 @@ void PythonShell::interactive_keypress(QKeyEvent *event)
// manually trigger a completion with tab
case Qt::Key_Tab:
// allow prefixed tabs to be inserted
if(ui->lineInput->text().trimmed().isEmpty())
if(edit->text().trimmed().isEmpty())
{
ui->lineInput->insert(lit("\t"));
edit->insert(lit("\t"));
}
else
{
m_InteractiveCompleter->setWidget(edit);
m_InteractiveCompleter->activated(
m_InteractiveCompleter->popup()->selectionModel()->currentIndex());
m_InteractiveCompleter->popup()->hide();
@@ -2259,7 +2269,7 @@ void PythonShell::interactive_keypress(QKeyEvent *event)
if(triggerCompletion)
{
QString base = ui->lineInput->text();
QString base = edit->text();
QStringList completions;
int oldCount = m_CompletionTipList.count();
@@ -2267,8 +2277,10 @@ void PythonShell::interactive_keypress(QKeyEvent *event)
if(base.trimmed() != QString())
{
m_CompletionTipList =
interactiveContext->completionOptions(0, base, m_InteractiveCompletionPrefix);
PythonContext *ctx = interactiveContext;
if(edit == ui->helpSearch)
ctx = helpContext;
m_CompletionTipList = ctx->completionOptions(0, base, m_InteractiveCompletionPrefix);
for(const QPair<QString, QString> &item : m_CompletionTipList)
completions << item.first;
@@ -2280,7 +2292,7 @@ void PythonShell::interactive_keypress(QKeyEvent *event)
if(completions.isEmpty())
{
if(event->key() == Qt::Key_Tab)
ui->lineInput->insert(lit("\t"));
edit->insert(lit("\t"));
m_InteractiveCompleter->popup()->hide();
QString prompt = interactiveContext->tryFunctionCompletion(0, base);
@@ -2289,13 +2301,13 @@ void PythonShell::interactive_keypress(QKeyEvent *event)
{
m_ToolTip->configureTip(this, prompt);
QPoint p = ui->lineInput->fontMetrics().boundingRect(base).bottomRight();
p.setY(ui->lineInput->geometry().height());
p = ui->lineInput->mapToGlobal(p);
QPoint p = edit->fontMetrics().boundingRect(base).bottomRight();
p.setY(edit->geometry().height());
p = edit->mapToGlobal(p);
if(!m_ToolTip->isVisible())
m_ToolTip->showTipAtPos(p);
m_FuncTip = true;
m_FuncTipWidget = ui->lineInput;
m_FuncTipWidget = edit;
}
else
{
@@ -2309,8 +2321,8 @@ void PythonShell::interactive_keypress(QKeyEvent *event)
m_InteractiveCompletionModel->setStringList(completions);
QRect r = ui->lineInput->rect();
QFontMetrics fm = ui->lineInput->fontMetrics();
QRect r = edit->rect();
QFontMetrics fm = edit->fontMetrics();
#if(QT_VERSION < QT_VERSION_CHECK(5, 11, 0))
#define horizontalAdvance width
@@ -2325,9 +2337,10 @@ void PythonShell::interactive_keypress(QKeyEvent *event)
base.resize(base.size() - m_InteractiveCompletionPrefix);
r.setLeft(r.left() + fm.horizontalAdvance(base));
r.setWidth(longestWidth + ui->lineInput->style()->pixelMetric(QStyle::PM_ScrollBarExtent) +
ui->lineInput->style()->pixelMetric(QStyle::PM_ButtonMargin));
r.setWidth(longestWidth + edit->style()->pixelMetric(QStyle::PM_ScrollBarExtent) +
edit->style()->pixelMetric(QStyle::PM_ButtonMargin));
m_InteractiveCompleter->setWidget(edit);
m_InteractiveCompleter->complete(r);
m_InteractiveCompleter->popup()->selectionModel()->setCurrentIndex(
m_InteractiveCompletionModel->index(0), QItemSelectionModel::ClearAndSelect);
@@ -2337,45 +2350,46 @@ void PythonShell::interactive_keypress(QKeyEvent *event)
if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
{
on_execute_clicked();
if(edit == ui->lineInput)
on_execute_clicked();
else if(edit == ui->helpSearch)
refreshCurrentHelp();
}
bool moved = false;
if(event->key() == Qt::Key_Down && historyidx > -1)
// only line input has history
if(edit == ui->lineInput)
{
historyidx--;
bool moved = false;
moved = true;
if(event->key() == Qt::Key_Down && historyidx > -1)
{
historyidx--;
moved = true;
}
QString workingtext;
if(event->key() == Qt::Key_Up && historyidx + 1 < history.count())
{
if(historyidx == -1)
workingtext = ui->lineInput->text();
historyidx++;
moved = true;
}
if(moved)
{
if(historyidx == -1)
ui->lineInput->setText(workingtext);
else
ui->lineInput->setText(history[historyidx]);
ui->lineInput->deselect();
}
}
QString workingtext;
if(event->key() == Qt::Key_Up && historyidx + 1 < history.count())
{
if(historyidx == -1)
workingtext = ui->lineInput->text();
historyidx++;
moved = true;
}
if(moved)
{
if(historyidx == -1)
ui->lineInput->setText(workingtext);
else
ui->lineInput->setText(history[historyidx]);
ui->lineInput->deselect();
}
}
void PythonShell::helpSearch_keypress(QKeyEvent *e)
{
if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter)
refreshCurrentHelp();
}
QString PythonShell::scriptHeader()
+2 -3
View File
@@ -149,7 +149,6 @@ private slots:
// manual slots
void interactive_keypress(QKeyEvent *e);
void helpSearch_keypress(QKeyEvent *e);
void traceLine(const QString &file, int line);
void exception(const QString &extension, const QString &type, const QString &value, int finalLine,
QList<QString> frames);
@@ -176,7 +175,6 @@ private:
QWidget *m_FuncTipWidget = NULL;
intptr_t m_FuncTipLine = 0;
bool m_ContextMenuVisible = false;
bool m_HelpPrinting = false;
bool m_IgnoreRecovered = false;
@@ -205,7 +203,8 @@ private:
int m_CurLine = 0;
QTimer *m_CurLineTimer = NULL;
PythonContext *interactiveContext = NULL, *scriptContext = NULL, *completionContext = NULL;
PythonContext *interactiveContext = NULL, *scriptContext = NULL, *completionContext = NULL,
*helpContext = NULL;
QList<QString> history;
int historyidx = -1;