From f0d1786c8ccaee61fb66eaa33a1f892616fc6f71 Mon Sep 17 00:00:00 2001 From: Ahmad Samir Date: Thu, 10 Sep 2020 00:41:26 +0200 Subject: [PATCH 1/4] WIP: FileFilter: add line/column numbers to file urls; also match absolute paths Since there is no universal way to tell a text editor about line/column on the cli (each editor seems to have its own scheme), there is a profile option to let the user specify the command to use to open local text files, e.g.: /usr/bin/kate PATH:LINE:COLUMN /usr/bin/gedit +LINE:COLUMN PATH then we replace PATH LINE and COLUMN to create the cmd to pass on to KIO::ApplicationLauncherJob. Current use cases, opening a file at: - specific line, from the ouput of "grep -n": "path/to/some/file:123" - specific line/column, from the output of gcc compiler errors: "/path/to/file:123:123" - specific line, from the ouput of ctest, when there is a compilation error: "[/path/to/some/file(123)]" Change the regex to also match absolute file paths, this is useful in general and also compilation errors usually use absolute file paths. Tweak the regex matching text between two single/double quotes so that it doesn't match newline characters. Port some usage of KRun to OpenUrlJob/ApplicationLauncherJob, bump minimum required KF5 version to 5.71 as that's where those classes were added to KIO. Refactor the UI file to use QBoxLayouts, this way each related set of options can be put together, which makes future changes slightly easier. --- CMakeLists.txt | 2 +- src/filterHotSpots/FileFilter.cpp | 49 +++-- src/filterHotSpots/FileFilterHotspot.cpp | 88 ++++++++- src/filterHotSpots/FileFilterHotspot.h | 4 +- src/profile/Profile.cpp | 2 + src/profile/Profile.h | 16 ++ src/widgets/EditProfileDialog.cpp | 9 + src/widgets/EditProfileDialog.h | 1 + src/widgets/EditProfileMousePage.ui | 224 ++++++++++++----------- 9 files changed, 267 insertions(+), 128 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c130b99e..18a24b69a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ cmake_minimum_required (VERSION 3.0 FATAL_ERROR) set (QT_MIN_VERSION "5.12.0") # Ubuntu 20.04 LTS only has KF 5.68 and Qt 5.12 -set (KF5_MIN_VERSION "5.68.0") +set (KF5_MIN_VERSION "5.71.0") # Release script will create bugzilla versions project(konsole VERSION ${RELEASE_SERVICE_VERSION}) diff --git a/src/filterHotSpots/FileFilter.cpp b/src/filterHotSpots/FileFilter.cpp index 44f8ef952..f489e77d7 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 11f98e6e1..40ec07574 100644 --- a/src/filterHotSpots/FileFilterHotspot.cpp +++ b/src/filterHotSpots/FileFilterHotspot.cpp @@ -16,29 +16,107 @@ #include #include #include +#include -#include +#include +#include +#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()); + auto openUrl = [](const QString filePath) { + 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(); + }; + + // 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())); + // 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(); + + 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 70d8c3ecb..e99dd35ee 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 fa0324c28..ed50c92be 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 346de6d12..c45abd020 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 ed9eda93a..44bb5320d 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 4d2b65413..623406d77 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 e83e99b05..dfe26f235 100644 --- a/src/widgets/EditProfileMousePage.ui +++ b/src/widgets/EditProfileMousePage.ui @@ -6,8 +6,8 @@ 0 0 - 538 - 516 + 993 + 632 @@ -26,7 +26,7 @@ - 0 + 1 @@ -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 + + + -- GitLab From 9a74127f859475a712b25f4969f46e6eeea3b430 Mon Sep 17 00:00:00 2001 From: Ahmad Samir Date: Mon, 9 Nov 2020 07:02:42 +0200 Subject: [PATCH 2/4] Keep it working with older KF --- CMakeLists.txt | 2 +- src/filterHotSpots/FileFilterHotspot.cpp | 24 +++++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 18a24b69a..3c130b99e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ cmake_minimum_required (VERSION 3.0 FATAL_ERROR) set (QT_MIN_VERSION "5.12.0") # Ubuntu 20.04 LTS only has KF 5.68 and Qt 5.12 -set (KF5_MIN_VERSION "5.71.0") +set (KF5_MIN_VERSION "5.68.0") # Release script will create bugzilla versions project(konsole VERSION ${RELEASE_SERVICE_VERSION}) diff --git a/src/filterHotSpots/FileFilterHotspot.cpp b/src/filterHotSpots/FileFilterHotspot.cpp index 40ec07574..5c292740c 100644 --- a/src/filterHotSpots/FileFilterHotspot.cpp +++ b/src/filterHotSpots/FileFilterHotspot.cpp @@ -18,9 +18,15 @@ #include #include +#include +#if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0) +#include +#else #include -#include #include +#endif + +#include #include #include #include @@ -47,11 +53,16 @@ FileFilterHotSpot::FileFilterHotSpot(int startLine, int startColumn, int endLine void FileFilterHotSpot::activate(QObject *) { - auto openUrl = [](const QString filePath) { + // 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.: @@ -92,6 +103,13 @@ void FileFilterHotSpot::activate(QObject *) 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 @@ -107,7 +125,7 @@ void FileFilterHotSpot::activate(QObject *) } }); job->start(); - +#endif return; } -- GitLab From 1503b63ff4b32d01fe4ab128dbe50d0d710c942d Mon Sep 17 00:00:00 2001 From: Ahmad Samir Date: Tue, 17 Nov 2020 22:05:25 +0200 Subject: [PATCH 3/4] Strip the last character in "/path/to/file:" --- src/filterHotSpots/FileFilterHotspot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filterHotSpots/FileFilterHotspot.cpp b/src/filterHotSpots/FileFilterHotspot.cpp index 5c292740c..ab1d7478b 100644 --- a/src/filterHotSpots/FileFilterHotspot.cpp +++ b/src/filterHotSpots/FileFilterHotspot.cpp @@ -133,7 +133,7 @@ void FileFilterHotSpot::activate(QObject *) // 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)); + path.remove(QRegularExpression(QStringLiteral(R"foo((:\d+[:]?|:)$)foo"), QRegularExpression::DontCaptureOption)); openUrl(path); } -- GitLab From d0e2fe85bdde32481333ea96ca60b89543932bda Mon Sep 17 00:00:00 2001 From: Ahmad Samir Date: Tue, 17 Nov 2020 22:23:37 +0200 Subject: [PATCH 4/4] Restore the default index in the .ui file to 0 --- src/widgets/EditProfileMousePage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/EditProfileMousePage.ui b/src/widgets/EditProfileMousePage.ui index dfe26f235..7efe9737e 100644 --- a/src/widgets/EditProfileMousePage.ui +++ b/src/widgets/EditProfileMousePage.ui @@ -26,7 +26,7 @@ - 1 + 0 -- GitLab