Python editor: Add buttons & actions for opening REPL

Opens interactive Python, optionally with the current file imported, for
testing and experimentation.

Change-Id: Ieb120e3698bdba77a1445c40fe7fda533773a0cf
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Eike Ziller
2020-03-10 16:26:20 +01:00
parent be9d5fa0d9
commit 3b5fc296f2
6 changed files with 135 additions and 2 deletions

View File

@@ -136,6 +136,11 @@ void ConsoleProcess::setCommand(const CommandLine &command)
d->m_commandLine = command; d->m_commandLine = command;
} }
CommandLine ConsoleProcess::command() const
{
return d->m_commandLine;
}
void ConsoleProcess::setSettings(QSettings *settings) void ConsoleProcess::setSettings(QSettings *settings)
{ {
d->m_settings = settings; d->m_settings = settings;

View File

@@ -65,6 +65,7 @@ public:
~ConsoleProcess() override; ~ConsoleProcess() override;
void setCommand(const Utils::CommandLine &command); void setCommand(const Utils::CommandLine &command);
Utils::CommandLine command() const;
void setAbortOnMetaChars(bool abort); void setAbortOnMetaChars(bool abort);
void setWorkingDirectory(const QString &dir); void setWorkingDirectory(const QString &dir);

View File

@@ -37,6 +37,10 @@ const char C_EDITOR_DISPLAY_NAME[] =
const char C_PYTHONOPTIONS_PAGE_ID[] = "PythonEditor.OptionsPage"; const char C_PYTHONOPTIONS_PAGE_ID[] = "PythonEditor.OptionsPage";
const char C_PYTHON_SETTINGS_CATEGORY[] = "P.Python"; const char C_PYTHON_SETTINGS_CATEGORY[] = "P.Python";
const char PYTHON_OPEN_REPL[] = "Python.OpenRepl";
const char PYTHON_OPEN_REPL_IMPORT[] = "Python.OpenReplImport";
const char PYTHON_OPEN_REPL_IMPORT_TOPLEVEL[] = "Python.OpenReplImportToplevel";
/******************************************************************************* /*******************************************************************************
* MIME type * MIME type
******************************************************************************/ ******************************************************************************/

View File

@@ -27,16 +27,81 @@
#include "pythonconstants.h" #include "pythonconstants.h"
#include "pythonhighlighter.h" #include "pythonhighlighter.h"
#include "pythonindenter.h" #include "pythonindenter.h"
#include "pythonsettings.h"
#include "pythonutils.h" #include "pythonutils.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/commandbutton.h>
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <texteditor/texteditoractionhandler.h> #include <texteditor/texteditoractionhandler.h>
#include <QAction>
#include <QMenu>
namespace Python { namespace Python {
namespace Internal { namespace Internal {
static QAction *createAction(QObject *parent, ReplType type)
{
QAction *action = new QAction(parent);
switch (type) {
case ReplType::Unmodified:
action->setText(QCoreApplication::translate("Python", "REPL"));
action->setToolTip(QCoreApplication::translate("Python", "Open interactive Python."));
break;
case ReplType::Import:
action->setText(QCoreApplication::translate("Python", "REPL Import File"));
action->setToolTip(
QCoreApplication::translate("Python", "Open interactive Python and import file."));
break;
case ReplType::ImportToplevel:
action->setText(QCoreApplication::translate("Python", "REPL Import *"));
action->setToolTip(
QCoreApplication::translate("Python",
"Open interactive Python and import * from file."));
break;
}
QObject::connect(action, &QAction::triggered, parent, [type] {
Core::IDocument *doc = Core::EditorManager::currentDocument();
openPythonRepl(doc ? doc->filePath() : Utils::FilePath(), type);
});
return action;
}
static void registerReplAction(QObject *parent)
{
Core::ActionManager::registerAction(createAction(parent, ReplType::Unmodified),
Constants::PYTHON_OPEN_REPL);
Core::ActionManager::registerAction(createAction(parent, ReplType::Import),
Constants::PYTHON_OPEN_REPL_IMPORT);
Core::ActionManager::registerAction(createAction(parent, ReplType::ImportToplevel),
Constants::PYTHON_OPEN_REPL_IMPORT_TOPLEVEL);
}
static QWidget *createEditorWidget()
{
auto widget = new TextEditor::TextEditorWidget;
auto replButton = new QToolButton(widget);
replButton->setProperty("noArrow", true);
replButton->setText(QCoreApplication::translate("Python", "REPL"));
replButton->setPopupMode(QToolButton::InstantPopup);
auto menu = new QMenu(replButton);
replButton->setMenu(menu);
menu->addAction(Core::ActionManager::command(Constants::PYTHON_OPEN_REPL)->action());
menu->addSeparator();
menu->addAction(Core::ActionManager::command(Constants::PYTHON_OPEN_REPL_IMPORT)->action());
menu->addAction(
Core::ActionManager::command(Constants::PYTHON_OPEN_REPL_IMPORT_TOPLEVEL)->action());
widget->insertExtraToolBarWidget(TextEditor::TextEditorWidget::Left, replButton);
return widget;
}
PythonEditorFactory::PythonEditorFactory() PythonEditorFactory::PythonEditorFactory()
{ {
registerReplAction(this);
setId(Constants::C_PYTHONEDITOR_ID); setId(Constants::C_PYTHONEDITOR_ID);
setDisplayName( setDisplayName(
QCoreApplication::translate("OpenWith::Editors", Constants::C_EDITOR_DISPLAY_NAME)); QCoreApplication::translate("OpenWith::Editors", Constants::C_EDITOR_DISPLAY_NAME));
@@ -48,6 +113,7 @@ PythonEditorFactory::PythonEditorFactory()
| TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor); | TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor);
setDocumentCreator([] { return new TextEditor::TextDocument(Constants::C_PYTHONEDITOR_ID); }); setDocumentCreator([] { return new TextEditor::TextDocument(Constants::C_PYTHONEDITOR_ID); });
setEditorWidgetCreator(createEditorWidget);
setIndenterCreator([](QTextDocument *doc) { return new PythonIndenter(doc); }); setIndenterCreator([](QTextDocument *doc) { return new PythonIndenter(doc); });
setSyntaxHighlighterCreator([] { return new PythonHighlighter; }); setSyntaxHighlighterCreator([] { return new PythonHighlighter; });
setCommentDefinition(Utils::CommentDefinition::HashStyle); setCommentDefinition(Utils::CommentDefinition::HashStyle);

View File

@@ -43,6 +43,8 @@
#include <texteditor/textdocument.h> #include <texteditor/textdocument.h>
#include <utils/consoleprocess.h>
#include <utils/mimetypes/mimedatabase.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/runextensions.h> #include <utils/runextensions.h>
#include <utils/synchronousprocess.h> #include <utils/synchronousprocess.h>
@@ -170,7 +172,9 @@ static FilePath detectPython(const FilePath &documentPath)
{ {
FilePath python; FilePath python;
PythonProject *project = qobject_cast<PythonProject *>( PythonProject *project = documentPath.isEmpty()
? nullptr
: qobject_cast<PythonProject *>(
ProjectExplorer::SessionManager::projectForFile(documentPath)); ProjectExplorer::SessionManager::projectForFile(documentPath));
if (!project) if (!project)
project = qobject_cast<PythonProject *>(ProjectExplorer::SessionManager::startupProject()); project = qobject_cast<PythonProject *>(ProjectExplorer::SessionManager::startupProject());
@@ -480,6 +484,55 @@ PyLSConfigureAssistant::PyLSConfigureAssistant(QObject *parent)
}); });
} }
static QStringList replImportArgs(const FilePath &pythonFile, ReplType type)
{
using MimeTypes = QList<MimeType>;
const MimeTypes mimeTypes = pythonFile.isEmpty() || type == ReplType::Unmodified
? MimeTypes()
: mimeTypesForFileName(pythonFile.toString());
const bool isPython = Utils::anyOf(mimeTypes, [](const MimeType &mt) {
return mt.inherits("text/x-python") || mt.inherits("text/x-python3");
});
if (type == ReplType::Unmodified || !isPython)
return {};
const auto import = type == ReplType::Import
? QString("import %1").arg(pythonFile.toFileInfo().completeBaseName())
: QString("from %1 import *")
.arg(pythonFile.toFileInfo().completeBaseName());
return {"-c", QString("%1; print('Running \"%1\"')").arg(import)};
}
void openPythonRepl(const FilePath &file, ReplType type)
{
static const auto workingDir = [](const FilePath &file) {
if (file.isEmpty()) {
if (ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject())
return project->projectDirectory().toFileInfo().filePath();
return QDir::currentPath();
}
return file.toFileInfo().path();
};
const auto args = QStringList{"-i"} + replImportArgs(file, type);
auto process = new ConsoleProcess;
const FilePath pythonCommand = detectPython(file);
process->setCommand({pythonCommand, args});
process->setWorkingDirectory(workingDir(file));
const QString commandLine = process->command().toUserOutput();
QObject::connect(process,
&ConsoleProcess::processError,
process,
[process, commandLine](const QString &errorString) {
Core::MessageManager::write(
QCoreApplication::translate("Python",
"Failed to run Python (%1): \"%2\".")
.arg(commandLine, errorString));
process->deleteLater();
});
QObject::connect(process, &ConsoleProcess::stubStopped, process, &QObject::deleteLater);
process->start();
}
} // namespace Internal } // namespace Internal
} // namespace Python } // namespace Python

View File

@@ -40,6 +40,10 @@ namespace TextEditor { class TextDocument; }
namespace Python { namespace Python {
namespace Internal { namespace Internal {
enum class ReplType { Unmodified, Import, ImportToplevel };
void openPythonRepl(const Utils::FilePath &file, ReplType type);
struct PythonLanguageServerState; struct PythonLanguageServerState;
class PyLSConfigureAssistant : public QObject class PyLSConfigureAssistant : public QObject