diff --git a/src/filterHotSpots/FileFilter.cpp b/src/filterHotSpots/FileFilter.cpp index 44f8ef952388bd435eb1f3570b10e9478fac946d..f489e77d75b9d87bd556b8e34a0286bf2564a7e0 100644 --- a/src/filterHotSpots/FileFilter.cpp +++ b/src/filterHotSpots/FileFilter.cpp @@ -40,7 +40,7 @@ FileFilter::FileFilter(Session *session) : wordCharacters.append(QLatin1Char('-')); } - static auto re = QRegularExpression( + static const auto re = QRegularExpression( /* First part of the regexp means 'strings with spaces and starting with single quotes' * Second part means "Strings with double quotes" * Last part means "Everything else plus some special chars @@ -48,11 +48,22 @@ FileFilter::FileFilter(Session *session) : * on the HotSpot creation we verify if this is indeed a file, so there's * no problem on testing on random words on the screen. */ - QLatin1String("'[^']+'") // Matches everything between single quotes. - + QStringLiteral(R"RX(|"[^"]+")RX") // Matches everything inside double quotes - + QStringLiteral(R"RX(|[\p{L}\w%1]+)RX").arg(wordCharacters) // matches a contiguous line of alphanumeric characters plus some special ones defined in the profile. - , - QRegularExpression::DontCaptureOption); + QStringLiteral(R"RX('[^'\n]+')RX") // Matches everything between single quotes. + + QStringLiteral(R"RX(|"[^\n"]+")RX") // Matches everything inside double quotes + // Matches a contiguous line of alphanumeric characters plus some special ones + // defined in the profile. With a special case for strings starting with '/' which + // denotes a path on Linux. + // Takes into account line numbers: + // - grep output with line numbers: "/path/to/file:123" + // - compiler error output: ":/path/to/file:123:123" + // + // ([^\n/\[]/) to not match "https://", and urls starting with "[" are matched by the + // next | branch (ctest stuff) + + QStringLiteral(R"RX(|([^\n\s/\[]/)?[\p{L}\w%1]+(:\d+)?(:\d+:)?)RX").arg(wordCharacters) + // - ctest error output: "[/path/to/file(123)]" + + QStringLiteral(R"RX(|\[[/\w%1]+\(\d+\)\])RX").arg(wordCharacters), + QRegularExpression::DontCaptureOption + ); setRegExp(re); } @@ -77,17 +88,27 @@ QSharedPointer FileFilter::newHotSpot(int startLine, int startColumn, i filename.chop(1); } - // Return nullptr if it's not: - // /filename - // /childDir/filename - auto match = std::find_if(_currentDirContents.cbegin(), _currentDirContents.cend(), - [filename](const QString &s) { return filename.startsWith(s); }); + if (filename.startsWith(QLatin1String("[/"))) { // ctest error output + filename.remove(0, 1); + } - if (match == _currentDirContents.cend()) { - return nullptr; + const bool absolute = filename.startsWith(QLatin1Char('/')); + if (!absolute) { + // Return nullptr if it's not: + // /filename + // /childDir/filename + auto match = std::find_if(_currentDirContents.cbegin(), _currentDirContents.cend(), + [filename](const QString &s) { return filename.startsWith(s); }); + + // Create a hotspot if the match starts with '/', which denotes an absolute path + if (match == _currentDirContents.cend()) { + return nullptr; + } } - return QSharedPointer(new FileFilterHotSpot(startLine, startColumn, endLine, endColumn, capturedTexts, _dirPath + filename)); + return QSharedPointer(new FileFilterHotSpot(startLine, startColumn, endLine, endColumn, capturedTexts, + !absolute ? _dirPath + filename : filename, + _session)); } void FileFilter::process() diff --git a/src/filterHotSpots/FileFilterHotspot.cpp b/src/filterHotSpots/FileFilterHotspot.cpp index 11f98e6e17eaa83fcee2621f0f577f1cc86c45cb..ab1d7478bd0c06bf989ccd46f26901df81b69eee 100644 --- a/src/filterHotSpots/FileFilterHotspot.cpp +++ b/src/filterHotSpots/FileFilterHotspot.cpp @@ -16,29 +16,125 @@ #include #include #include +#include +#include +#if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0) #include +#else +#include +#include +#endif + +#include #include #include +#include +#include #include "konsoledebug.h" #include "KonsoleSettings.h" +#include "profile/Profile.h" +#include "session/SessionManager.h" #include "terminalDisplay/TerminalDisplay.h" using namespace Konsole; FileFilterHotSpot::FileFilterHotSpot(int startLine, int startColumn, int endLine, int endColumn, - const QStringList &capturedTexts, const QString &filePath) : - RegExpFilterHotSpot(startLine, startColumn, endLine, endColumn, capturedTexts), - _filePath(filePath) + const QStringList &capturedTexts, const QString &filePath, + Session *session) + : RegExpFilterHotSpot(startLine, startColumn, endLine, endColumn, capturedTexts), + _filePath(filePath), + _session(session) { setType(Link); } void FileFilterHotSpot::activate(QObject *) { - new KRun(QUrl::fromLocalFile(_filePath), QApplication::activeWindow()); + // Used to fallback to opening the url with the system default application + auto openUrl = [](const QString &filePath) { +#if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0) + new KRun(QUrl::fromLocalFile(filePath), QApplication::activeWindow()); +#else + auto *job = new KIO::OpenUrlJob(QUrl::fromLocalFile(filePath)); + job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, QApplication::activeWindow())); + job->setRunExecutables(false); // Always open, e.g. shell scripts, as text + job->start(); +#endif + }; + + // Output of e.g.: + // - grep with line numbers: "path/to/some/file:123:" + // - compiler errors with line/column numbers: "/path/to/file.cpp:123:123:" + // - ctest failing unit tests: "/path/to/file(204)" + const auto re(QRegularExpression(QStringLiteral(R"foo((?:[:\(]?(\d+)(?::?|\)\]))(?:(\d+):)?$)foo"))); + QRegularExpressionMatch match = re.match(_filePath); + if (match.hasMatch()) { + // The file path without the ":123" ... etc bits + const QString path = _filePath.mid(0, match.capturedStart(0)); + + if (!_session) { + openUrl(path); + return; + } + + Profile::Ptr profile = SessionManager::instance()->sessionProfile(_session); + QString editorCmd = profile->textEditorCmd(); + + // If the cmd is empty or PATH and LINE don't exist, then the command + // is malformed, ignore it and open with the default editor + // TODO: show an error message to the user? + if (editorCmd.isEmpty() + || ! (editorCmd.contains(QLatin1String("PATH")) && editorCmd.contains(QLatin1String("LINE")))) { + openUrl(path); + return; + } + + editorCmd.replace(QLatin1String("LINE"), match.captured(1)); + + const QString col = match.captured(2); + + editorCmd.replace(QLatin1String("COLUMN"), !col.isEmpty() ? col : QLatin1String("0")); + + editorCmd.replace(QLatin1String("PATH"), path); + + qCDebug(KonsoleDebug) << "editorCmd:" << editorCmd; + + KService::Ptr service(new KService(QString(), editorCmd, QString())); + +#if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0) + const bool success = KRun::runService(*service, {}, QApplication::activeWindow()); + if (!success) { + openUrl(path); + } +#else + // ApplicationLauncherJob is better at reporting errors to the user than + // CommandLauncherJob; no need to call job->setUrls() because the url is + // already part of editorCmd + auto *job = new KIO::ApplicationLauncherJob(service); + connect(job, &KJob::result, this, [this, path, job, openUrl]() { + if (job->error()) { + // TODO: use KMessageWidget (like the "terminal is read-only" message) + KMessageBox::sorry(QApplication::activeWindow(), + i18n("Could not open file with the text editor specified in the profile settings;\n" + "it will be opened with the system default editor.")); + + openUrl(path); + } + }); + job->start(); +#endif + return; + } + + // There was no match, i.e. regular url "path/to/file", open it with + // the system default editor + // In case the regex above didn't match for any reason, clean up the file path + QString path(_filePath); + path.remove(QRegularExpression(QStringLiteral(R"foo((:\d+[:]?|:)$)foo"), QRegularExpression::DontCaptureOption)); + openUrl(path); } diff --git a/src/filterHotSpots/FileFilterHotspot.h b/src/filterHotSpots/FileFilterHotspot.h index 70d8c3ecbea30de4d7963492c3da413b087b3748..e99dd35eeacd07c3f12df3c868483895d2a49a2c 100644 --- a/src/filterHotSpots/FileFilterHotspot.h +++ b/src/filterHotSpots/FileFilterHotspot.h @@ -24,6 +24,7 @@ class QKeyEvent; class QMouseEvent; namespace Konsole { +class Session; class TerminalDisplay; /** @@ -33,7 +34,7 @@ class FileFilterHotSpot : public RegExpFilterHotSpot { public: FileFilterHotSpot(int startLine, int startColumn, int endLine, int endColumn, - const QStringList &capturedTexts, const QString &filePath); + const QStringList &capturedTexts, const QString &filePath, Session *session); ~FileFilterHotSpot() override; QList actions() override; @@ -58,6 +59,7 @@ public: private: void showThumbnail(const KFileItem& item, const QPixmap& preview); QString _filePath; + Session *_session = nullptr; KFileItemActions _menuActions; QPoint _eventPos; diff --git a/src/profile/Profile.cpp b/src/profile/Profile.cpp index fa0324c28ff7aede6881c49d393aac5e48ede580..ed50c92be7c6d4bf2bc96805edaa84fad3699ebc 100644 --- a/src/profile/Profile.cpp +++ b/src/profile/Profile.cpp @@ -107,6 +107,7 @@ const Profile::PropertyInfo Profile::DefaultPropertyNames[] = { , { UnderlineLinksEnabled , "UnderlineLinksEnabled" , INTERACTION_GROUP , QVariant::Bool } , { UnderlineFilesEnabled , "UnderlineFilesEnabled" , INTERACTION_GROUP , QVariant::Bool } , { OpenLinksByDirectClickEnabled , "OpenLinksByDirectClickEnabled" , INTERACTION_GROUP , QVariant::Bool } + , { TextEditorCmd , "TextEditorCmd" , INTERACTION_GROUP , QVariant::String } , { CtrlRequiredForDrag, "CtrlRequiredForDrag" , INTERACTION_GROUP , QVariant::Bool } , { DropUrlsAsText , "DropUrlsAsText" , INTERACTION_GROUP , QVariant::Bool } , { AutoCopySelectedText , "AutoCopySelectedText" , INTERACTION_GROUP , QVariant::Bool } @@ -192,6 +193,7 @@ void Profile::useFallback() setProperty(UnderlineLinksEnabled, true); setProperty(UnderlineFilesEnabled, false); setProperty(OpenLinksByDirectClickEnabled, false); + setProperty(TextEditorCmd, QStringLiteral("/usr/bin/kate PATH:LINE:COLUMN")); setProperty(CtrlRequiredForDrag, true); setProperty(AutoCopySelectedText, false); setProperty(CopyTextAsHTML, true); diff --git a/src/profile/Profile.h b/src/profile/Profile.h index 346de6d12c5fdd338aafc54dea7219fc0d85001d..c45abd020b9cd8c64c34f0db9428f195afd07841 100644 --- a/src/profile/Profile.h +++ b/src/profile/Profile.h @@ -201,6 +201,16 @@ public: * underlined when hovered by the mouse pointer. */ UnderlineFilesEnabled, + /** + * (QString) Text editor command used to open link/file URLs at a given line/column; + * it should include two placeholders, LINE and COLUMN, which will be + * replaced by the actual line and column numbers, respectively. This is needed as + * each text editor has its own command line options to specify line/column numbers + * when opening a text file. For example: + * "/usr/bin/kate --line LINE --column COLUMN" + * "/usr/bin/gedit +LINE:COLUMN" + */ + TextEditorCmd, /** (bool) If true, links can be opened by direct mouse click.*/ OpenLinksByDirectClickEnabled, /** (bool) If true, control key must be pressed to click and drag selected text. */ @@ -593,6 +603,12 @@ public: return property(Profile::UnderlineFilesEnabled); } + /** Convenience method for property(Profile::TextEditorCmd) */ + QString textEditorCmd() const + { + return property(Profile::TextEditorCmd); + } + bool autoCopySelectedText() const { return property(Profile::AutoCopySelectedText); diff --git a/src/widgets/EditProfileDialog.cpp b/src/widgets/EditProfileDialog.cpp index ed9eda93a4eb085d02f616191f7ee8e7b673c24d..44bb5320df9a714687db6a3f8037cda744843c88 100644 --- a/src/widgets/EditProfileDialog.cpp +++ b/src/widgets/EditProfileDialog.cpp @@ -1687,6 +1687,10 @@ void EditProfileDialog::setupMousePage(const Profile::Ptr &profile) _mouseUi->openLinksByDirectClickButton->setEnabled(_mouseUi->underlineLinksButton->isChecked() || _mouseUi->underlineFilesButton->isChecked()); + _mouseUi->textEditorCmdLineEdit->setText(profile->textEditorCmd()); + connect(_mouseUi->textEditorCmdLineEdit, &QLineEdit::textChanged, + this, &Konsole::EditProfileDialog::textEditorCmdEditLineChanged); + _mouseUi->enableMouseWheelZoomButton->setChecked(profile->mouseWheelZoomEnabled()); connect(_mouseUi->enableMouseWheelZoomButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::toggleMouseWheelZoom); @@ -1806,6 +1810,11 @@ void EditProfileDialog::toggleUnderlineFiles(bool enable) _mouseUi->openLinksByDirectClickButton->setEnabled(enableClick); } +void EditProfileDialog::textEditorCmdEditLineChanged(const QString &text) +{ + updateTempProfileProperty(Profile::TextEditorCmd, text); +} + void EditProfileDialog::toggleCtrlRequiredForDrag(bool enable) { updateTempProfileProperty(Profile::CtrlRequiredForDrag, enable); diff --git a/src/widgets/EditProfileDialog.h b/src/widgets/EditProfileDialog.h index 4d2b6541345add206466c636c9b6c17fc385eaee..623406d77ce678564046db83004d4b2cbc27a5d8 100644 --- a/src/widgets/EditProfileDialog.h +++ b/src/widgets/EditProfileDialog.h @@ -176,6 +176,7 @@ private Q_SLOTS: void toggleUnderlineFiles(bool enable); void toggleUnderlineLinks(bool); void toggleOpenLinksByDirectClick(bool); + void textEditorCmdEditLineChanged(const QString &text); void toggleCtrlRequiredForDrag(bool); void toggleDropUrlsAsText(bool); void toggleCopyTextToClipboard(bool); diff --git a/src/widgets/EditProfileMousePage.ui b/src/widgets/EditProfileMousePage.ui index e83e99b056397e4b975e0346567fe9562cab06b3..7efe9737ea7f8e5a0c7e0ed97abd4d4a6af139c8 100644 --- a/src/widgets/EditProfileMousePage.ui +++ b/src/widgets/EditProfileMousePage.ui @@ -6,8 +6,8 @@ 0 0 - 538 - 516 + 993 + 632 @@ -250,57 +250,14 @@ - 0 + 10 - + - 6 + 10 - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 16 - 20 - - - - - - - - The formats of possible links, like http://, https:// and file:// - - - - - - - Selected text will require control key plus click to drag. - - - Require Ctrl key for drag && drop - - - - - - - Mouse scroll wheel will emulate up/down key presses in programs that use the Alternate Screen buffer (e.g. less) - - - Enable Alternate Screen buffer scrolling - - - - + Text recognized as a link or an email address will be underlined when hovered by the mouse pointer. @@ -310,23 +267,7 @@ - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 16 - 20 - - - - - + Text recognized as a file will be underlined when hovered by the mouse pointer. @@ -336,24 +277,65 @@ - - + + + + + + Text Editor Command: + + + textEditorCmdLineEdit + + + + + + + Command used to open a file PATH at LINE/COLUMN. + + + <html><head/><body> + <p>The text editor command that will be used to open a file starting at the specified line and column.</p> + <p>PATH, LINE and COLUMN are palceholders that will be replaced by the actual path to the file, line number and column number respectively.</p> + <p>If PATH and LINE aren't specified in the command, the file will be opened with the system default text editor.</p> + <p>For example:</p> + <p>/usr/bin/kate PATH:LINE:COMLUMN</p> + <p>/usr/bin/gedit +LINE:COLUMN PATH</p> + </body></html> + + + + + + + + + + 0 + 0 + + - Always paste dropped files and URLs as text without offering move, copy and link actions. + Text recognized as a file, link or an email address can be opened by direct mouse click. - Disable drag && drop menu for files && URLs + Open files/links by direct click - + + + + + Allow escape sequences for links - + <b>WARNING</b>: This has security implications as it allows malicious URLs to be shown as another URL or hidden.<br>Make sure you understand the implications before turning this on. @@ -367,46 +349,74 @@ true - - - - - - Pressing Ctrl+scrollwheel will increase/decrease the text size. - - - Allow Ctrl+scrollwheel to zoom text size + + 30 - - - - Allowed link formats - - - - - - - false - - - - 0 - 0 - - - - Text recognized as a file, link or an email address can be opened by direct mouse click. - - - Open by direct click - - + + + + + + Allowed link formats: + + + 30 + + + + + + + The formats of possible links, like http://, https:// and file:// + + + + + + + + Selected text will require control key plus click to drag. + + + Require Ctrl key for drag && drop + + + + + + + Mouse scroll wheel will emulate up/down key presses in programs that use the Alternate Screen buffer (e.g. less) + + + Enable Alternate Screen buffer scrolling + + + + + + + Always paste dropped files and URLs as text without offering move, copy and link actions. + + + Disable drag && drop menu for files && URLs + + + + + + + Pressing Ctrl+scrollwheel will increase/decrease the text size. + + + Allow Ctrl+scrollwheel to zoom text size + + +