diff --git a/src/libs/utils/consoleprocess.cpp b/src/libs/utils/consoleprocess.cpp index f4cde33a876..3095c5ee9d7 100644 --- a/src/libs/utils/consoleprocess.cpp +++ b/src/libs/utils/consoleprocess.cpp @@ -136,6 +136,11 @@ void ConsoleProcess::setCommand(const CommandLine &command) d->m_commandLine = command; } +CommandLine ConsoleProcess::command() const +{ + return d->m_commandLine; +} + void ConsoleProcess::setSettings(QSettings *settings) { d->m_settings = settings; diff --git a/src/libs/utils/consoleprocess.h b/src/libs/utils/consoleprocess.h index af1ce1017c4..6af30c1f84b 100644 --- a/src/libs/utils/consoleprocess.h +++ b/src/libs/utils/consoleprocess.h @@ -65,6 +65,7 @@ public: ~ConsoleProcess() override; void setCommand(const Utils::CommandLine &command); + Utils::CommandLine command() const; void setAbortOnMetaChars(bool abort); void setWorkingDirectory(const QString &dir); diff --git a/src/plugins/python/pythonconstants.h b/src/plugins/python/pythonconstants.h index 0fba0603e92..eb01dc5fab2 100644 --- a/src/plugins/python/pythonconstants.h +++ b/src/plugins/python/pythonconstants.h @@ -37,6 +37,10 @@ const char C_EDITOR_DISPLAY_NAME[] = const char C_PYTHONOPTIONS_PAGE_ID[] = "PythonEditor.OptionsPage"; 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 ******************************************************************************/ diff --git a/src/plugins/python/pythoneditor.cpp b/src/plugins/python/pythoneditor.cpp index cfd86233639..9b98493c83f 100644 --- a/src/plugins/python/pythoneditor.cpp +++ b/src/plugins/python/pythoneditor.cpp @@ -27,16 +27,81 @@ #include "pythonconstants.h" #include "pythonhighlighter.h" #include "pythonindenter.h" +#include "pythonsettings.h" #include "pythonutils.h" +#include +#include + #include #include +#include +#include + namespace Python { 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() { + registerReplAction(this); + setId(Constants::C_PYTHONEDITOR_ID); setDisplayName( QCoreApplication::translate("OpenWith::Editors", Constants::C_EDITOR_DISPLAY_NAME)); @@ -48,6 +113,7 @@ PythonEditorFactory::PythonEditorFactory() | TextEditor::TextEditorActionHandler::FollowSymbolUnderCursor); setDocumentCreator([] { return new TextEditor::TextDocument(Constants::C_PYTHONEDITOR_ID); }); + setEditorWidgetCreator(createEditorWidget); setIndenterCreator([](QTextDocument *doc) { return new PythonIndenter(doc); }); setSyntaxHighlighterCreator([] { return new PythonHighlighter; }); setCommentDefinition(Utils::CommentDefinition::HashStyle); diff --git a/src/plugins/python/pythonutils.cpp b/src/plugins/python/pythonutils.cpp index e584a90e4d3..de52e9413a1 100644 --- a/src/plugins/python/pythonutils.cpp +++ b/src/plugins/python/pythonutils.cpp @@ -43,6 +43,8 @@ #include +#include +#include #include #include #include @@ -170,8 +172,10 @@ static FilePath detectPython(const FilePath &documentPath) { FilePath python; - PythonProject *project = qobject_cast( - ProjectExplorer::SessionManager::projectForFile(documentPath)); + PythonProject *project = documentPath.isEmpty() + ? nullptr + : qobject_cast( + ProjectExplorer::SessionManager::projectForFile(documentPath)); if (!project) project = qobject_cast(ProjectExplorer::SessionManager::startupProject()); @@ -480,6 +484,55 @@ PyLSConfigureAssistant::PyLSConfigureAssistant(QObject *parent) }); } +static QStringList replImportArgs(const FilePath &pythonFile, ReplType type) +{ + using MimeTypes = QList; + 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 Python diff --git a/src/plugins/python/pythonutils.h b/src/plugins/python/pythonutils.h index 28d1faf6074..9bedd9f6cf0 100644 --- a/src/plugins/python/pythonutils.h +++ b/src/plugins/python/pythonutils.h @@ -40,6 +40,10 @@ namespace TextEditor { class TextDocument; } namespace Python { namespace Internal { +enum class ReplType { Unmodified, Import, ImportToplevel }; + +void openPythonRepl(const Utils::FilePath &file, ReplType type); + struct PythonLanguageServerState; class PyLSConfigureAssistant : public QObject