From 09db13ff3c828844ae325b7c000bd98be2cd9a16 Mon Sep 17 00:00:00 2001 From: baldurk Date: Mon, 25 Dec 2017 16:05:04 +0000 Subject: [PATCH] Add a help lookup panel to the python script editor --- qrenderdoc/Windows/PythonShell.cpp | 182 +++++++++++++++++++++++++++-- qrenderdoc/Windows/PythonShell.h | 7 ++ qrenderdoc/Windows/PythonShell.ui | 91 +++++++++++++-- 3 files changed, 266 insertions(+), 14 deletions(-) diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index ac206d398..dd7374381 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -25,6 +25,7 @@ #include "PythonShell.h" #include #include +#include #include #include "3rdparty/scintilla/include/SciLexer.h" #include "3rdparty/scintilla/include/qt/ScintillaEdit.h" @@ -435,10 +436,12 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent) m_ThreadCtx = new CaptureContextInvoker(m_Ctx); QObject::connect(ui->lineInput, &RDLineEdit::keyPress, this, &PythonShell::interactive_keypress); + QObject::connect(ui->helpSearch, &RDLineEdit::keyPress, this, &PythonShell::helpSearch_keypress); ui->lineInput->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui->interactiveOutput->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui->scriptOutput->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + ui->helpText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); ui->lineInput->setAcceptTabCharacters(true); @@ -460,6 +463,12 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent) scriptEditor->autoCSetMaxHeight(10); + scriptEditor->usePopUp(SC_POPUP_NEVER); + + scriptEditor->setContextMenuPolicy(Qt::CustomContextMenu); + QObject::connect(scriptEditor, &ScintillaEdit::customContextMenuRequested, this, + &PythonShell::editor_contextMenu); + ConfigureSyntax(scriptEditor, SCLEX_PYTHON); scriptEditor->setTabWidth(4); @@ -490,6 +499,17 @@ PythonShell::PythonShell(ICaptureContext &ctx, QWidget *parent) { startAutocomplete(); } + + if(ev->key() == Qt::Key_F1) + { + QString curWord = getDottedWordAtPoint(scriptEditor->currentPos()); + + if(!curWord.isEmpty()) + { + ui->helpSearch->setText(curWord); + refreshCurrentHelp(); + } + } }); ui->scriptSplitter->insertWidget(0, scriptEditor); @@ -633,6 +653,8 @@ void PythonShell::on_runScript_clicked() ANALYTIC_SET(UIFeatures.PythonInterop, true); + ui->outputHelpTabs->setCurrentIndex(0); + ui->scriptOutput->clear(); QString script = QString::fromUtf8(scriptEditor->getText(scriptEditor->textLength() + 1)); @@ -707,6 +729,139 @@ void PythonShell::textOutput(bool isStdError, const QString &output) appendText(out, output); } +void PythonShell::editor_contextMenu(const QPoint &pos) +{ + int scintillaPos = scriptEditor->positionFromPoint(pos.x(), pos.y()); + + QMenu contextMenu(this); + + QString curWord = getDottedWordAtPoint(scintillaPos); + + bool valid = !curWord.isEmpty(); + + QAction help(valid ? tr("Help for '%1'").arg(curWord) : tr("Help"), this); + + QObject::connect(&help, &QAction::triggered, [this, curWord] { selectedHelp(curWord); }); + + help.setEnabled(valid); + + contextMenu.addAction(&help); + contextMenu.addSeparator(); + + QAction undo(tr("Undo"), this); + QAction redo(tr("Redo"), this); + + QObject::connect(&undo, &QAction::triggered, [this] { scriptEditor->undo(); }); + QObject::connect(&redo, &QAction::triggered, [this] { scriptEditor->redo(); }); + + undo.setEnabled(scriptEditor->canUndo()); + redo.setEnabled(scriptEditor->canRedo()); + + contextMenu.addAction(&undo); + contextMenu.addAction(&redo); + contextMenu.addSeparator(); + + QAction cutText(tr("Cut"), this); + QAction copyText(tr("Copy"), this); + QAction pasteText(tr("Paste"), this); + QAction deleteText(tr("Delete"), this); + + QObject::connect(&cutText, &QAction::triggered, [this] { scriptEditor->cut(); }); + + QObject::connect(©Text, &QAction::triggered, [this] { + scriptEditor->copyRange(scriptEditor->selectionStart(), scriptEditor->selectionEnd()); + }); + + QObject::connect(&pasteText, &QAction::triggered, [this] { scriptEditor->paste(); }); + + QObject::connect(&deleteText, &QAction::triggered, [this] { + scriptEditor->deleteRange(scriptEditor->selectionStart(), scriptEditor->selectionEnd()); + }); + + contextMenu.addAction(&cutText); + contextMenu.addAction(©Text); + contextMenu.addAction(&pasteText); + contextMenu.addAction(&deleteText); + contextMenu.addSeparator(); + + if(scriptEditor->selectionEmpty()) + { + cutText.setEnabled(false); + copyText.setEnabled(false); + deleteText.setEnabled(false); + } + + pasteText.setEnabled(scriptEditor->canPaste()); + + QAction selectAll(tr("Select All"), this); + QObject::connect(&selectAll, &QAction::triggered, [this] { scriptEditor->selectAll(); }); + contextMenu.addAction(&selectAll); + + RDDialog::show(&contextMenu, scriptEditor->viewport()->mapToGlobal(pos)); +} + +QString PythonShell::getDottedWordAtPoint(int scintillaPos) +{ + QByteArray wordChars = scriptEditor->wordChars(); + + QByteArray wordCharsAndDot = wordChars; + if(wordCharsAndDot.indexOf('.') < 0) + wordCharsAndDot.append('.'); + + scriptEditor->setWordChars(wordCharsAndDot.data()); + + sptr_t start = scriptEditor->wordStartPosition(scintillaPos, true); + sptr_t end = scriptEditor->wordEndPosition(scintillaPos, true); + + scriptEditor->setWordChars(wordChars.data()); + + QString curWord = QString::fromUtf8(scriptEditor->textRange(start, end)); + + bool valid = true; + + if(curWord.isEmpty() || (!curWord[0].isLetterOrNumber() && curWord[0] != QLatin1Char('_'))) + valid = false; + + for(QChar c : curWord) + { + if(!c.isLetterOrNumber() && c != QLatin1Char('_') && c != QLatin1Char('.')) + valid = false; + } + + return valid ? curWord : QString(); +} + +void PythonShell::selectedHelp(QString word) +{ + ui->helpSearch->setText(word); + + refreshCurrentHelp(); +} + +void PythonShell::refreshCurrentHelp() +{ + PythonContext *context = newImportedDummyContext(); + + ui->helpText->clear(); + + QObject::connect( + context, &PythonContext::textOutput, + [this](bool isStdError, const QString &output) { appendText(ui->helpText, output); }); + + context->executeString(lit(R"( +try: + import keyword + if keyword.iskeyword("%1"): + help("%1") + else: + help(%1) +except ImportError: + help(%1) +)").arg(ui->helpSearch->text())); + + context->Finish(); +} + void PythonShell::interactive_keypress(QKeyEvent *event) { if(event->key() == Qt::Key_Tab) @@ -817,6 +972,12 @@ void PythonShell::interactive_keypress(QKeyEvent *event) } } +void PythonShell::helpSearch_keypress(QKeyEvent *e) +{ + if(e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) + refreshCurrentHelp(); +} + QString PythonShell::scriptHeader() { return tr(R"(RenderDoc Python console, powered by python %1. @@ -866,6 +1027,19 @@ void PythonShell::startAutocomplete() QString comp = QString::fromUtf8(lineText.mid(start, end - start + 1)); + PythonContext *context = newImportedDummyContext(); + + QStringList completions = context->completionOptions(comp); + + context->Finish(); + + scriptEditor->autoCShow(comp.count(), completions.join(QLatin1Char(' ')).toUtf8().data()); +} + +PythonContext *PythonShell::newImportedDummyContext() +{ + sptr_t pos = scriptEditor->currentPos(); + PythonContext *context = new PythonContext(); setGlobals(context); @@ -895,13 +1069,7 @@ void PythonShell::startAutocomplete() offs = newline + 1; } - QStringList completions = context->completionOptions(comp); - - context->Finish(); - - qDebug() << "auto complete from" << comp; - - scriptEditor->autoCShow(comp.count(), completions.join(QLatin1Char(' ')).toUtf8().data()); + return context; } PythonContext *PythonShell::newContext() diff --git a/qrenderdoc/Windows/PythonShell.h b/qrenderdoc/Windows/PythonShell.h index 0f6854966..b70761172 100644 --- a/qrenderdoc/Windows/PythonShell.h +++ b/qrenderdoc/Windows/PythonShell.h @@ -61,9 +61,11 @@ 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 &type, const QString &value, int finalLine, QList frames); void textOutput(bool isStdError, const QString &output); + void editor_contextMenu(const QPoint &pos); private: Ui::PythonShell *ui; @@ -81,10 +83,15 @@ private: QString m_storedLines; + QString getDottedWordAtPoint(int scintillaPos); + PythonContext *newContext(); + PythonContext *newImportedDummyContext(); void setGlobals(PythonContext *ret); void startAutocomplete(); + void selectedHelp(QString word); + void refreshCurrentHelp(); QString scriptHeader(); void appendText(QTextEdit *output, const QString &text); diff --git a/qrenderdoc/Windows/PythonShell.ui b/qrenderdoc/Windows/PythonShell.ui index d201e42bd..14873fcb1 100644 --- a/qrenderdoc/Windows/PythonShell.ui +++ b/qrenderdoc/Windows/PythonShell.ui @@ -62,6 +62,9 @@ true + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + @@ -275,18 +278,92 @@ - Qt::Horizontal + Qt::Vertical false - - - Qt::ScrollBarAlwaysOn - - - true + + + 0 + + + Output + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Qt::ScrollBarAlwaysOn + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + Help + + + + 2 + + + 2 + + + 2 + + + 2 + + + 4 + + + + + Search + + + + + + + + + + Qt::ScrollBarAlwaysOn + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + +