forked from qt-creator/qt-creator
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:
@@ -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;
|
||||||
|
@@ -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);
|
||||||
|
@@ -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
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
@@ -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);
|
||||||
|
@@ -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,8 +172,10 @@ static FilePath detectPython(const FilePath &documentPath)
|
|||||||
{
|
{
|
||||||
FilePath python;
|
FilePath python;
|
||||||
|
|
||||||
PythonProject *project = qobject_cast<PythonProject *>(
|
PythonProject *project = documentPath.isEmpty()
|
||||||
ProjectExplorer::SessionManager::projectForFile(documentPath));
|
? nullptr
|
||||||
|
: qobject_cast<PythonProject *>(
|
||||||
|
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
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user