Commit 923f8d14 authored by Ahmad Samir's avatar Ahmad Samir Committed by Kurt Hindenburg
Browse files

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.

Refactor the UI file to use QBoxLayouts, this way each related set of
options can be put together, which makes future changes slightly easier.
parent 3631e42f
......@@ -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<HotSpot> FileFilter::newHotSpot(int startLine, int startColumn, i
filename.chop(1);
}
// Return nullptr if it's not:
// <current dir>/filename
// <current dir>/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:
// <current dir>/filename
// <current dir>/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<HotSpot>(new FileFilterHotSpot(startLine, startColumn, endLine, endColumn, capturedTexts, _dirPath + filename));
return QSharedPointer<HotSpot>(new FileFilterHotSpot(startLine, startColumn, endLine, endColumn, capturedTexts,
!absolute ? _dirPath + filename : filename,
_session));
}
void FileFilter::process()
......
......@@ -16,29 +16,125 @@
#include <QToolTip>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QRegularExpression>
#include <kio_version.h>
#if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0)
#include <KRun>
#else
#include <KIO/ApplicationLauncherJob>
#include <KIO/OpenUrlJob>
#endif
#include <KIO/JobUiDelegate>
#include <KLocalizedString>
#include <KFileItemListProperties>
#include <KMessageBox>
#include <KShell>
#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);
}
......
......@@ -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<QAction *> 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;
......
......@@ -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);
......
......@@ -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<bool>(Profile::UnderlineFilesEnabled);
}
/** Convenience method for property<QString>(Profile::TextEditorCmd) */
QString textEditorCmd() const
{
return property<QString>(Profile::TextEditorCmd);
}
bool autoCopySelectedText() const
{
return property<bool>(Profile::AutoCopySelectedText);
......
......@@ -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);
......
......@@ -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);
......
......@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>538</width>
<height>516</height>
<width>993</width>
<height>632</height>
</rect>
</property>
<layout class="QVBoxLayout">
......@@ -250,57 +250,14 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
<number>10</number>
</property>
<item>
<layout class="QGridLayout" columnstretch="0,0,0,0">
<layout class="QVBoxLayout" name="underlineURILayout">
<property name="spacing">
<number>6</number>
<number>10</number>
</property>
<item row="1" column="0">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>16</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="2">
<widget class="QLineEdit" name="linkEscapeSequenceTexts">
<property name="toolTip">
<string>The formats of possible links, like http://, https:// and file://</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="4">
<widget class="QCheckBox" name="ctrlRequiredForDragButton">
<property name="toolTip">
<string>Selected text will require control key plus click to drag.</string>
</property>
<property name="text">
<string>Require Ctrl key for drag &amp;&amp; drop</string>
</property>
</widget>
</item>
<item row="10" column="0" colspan="4">
<widget class="QCheckBox" name="enableAlternateScrollingButton">
<property name="toolTip">
<string>Mouse scroll wheel will emulate up/down key presses in programs that use the Alternate Screen buffer (e.g. less)</string>
</property>
<property name="text">
<string>Enable Alternate Screen buffer scrolling</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="4">
<item>
<widget class="QCheckBox" name="underlineLinksButton">
<property name="toolTip">
<string>Text recognized as a link or an email address will be underlined when hovered by the mouse pointer.</string>
......@@ -310,23 +267,7 @@
</property>
</widget>
</item>
<item row="4" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>16</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0" colspan="4">
<item>
<widget class="QCheckBox" name="underlineFilesButton">
<property name="toolTip">
<string>Text recognized as a file will be underlined when hovered by the mouse pointer.</string>
......@@ -336,24 +277,65 @@
</property>
</widget>
</item>
<item row="8" column="0" colspan="4">
<widget class="QCheckBox" name="dropUrlsAsText">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="textEditorCommandLabel">
<property name="text">
<string>Text Editor Command: </string>
</property>
<property name="buddy">
<cstring>textEditorCmdLineEdit</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="textEditorCmdLineEdit">
<property name="toolTip">
<string comment="@info:tooltip">Command used to open a file PATH at LINE/COLUMN.</string>
</property>
<property name="whatsThis">
<string comment="@info:whatsthis">&lt;html&gt;&lt;head/&gt;&lt;body&gt;
&lt;p&gt;The text editor command that will be used to open a file starting at the specified line and column.&lt;/p&gt;
&lt;p&gt;PATH, LINE and COLUMN are palceholders that will be replaced by the actual path to the file, line number and column number respectively.&lt;/p&gt;
&lt;p&gt;If PATH and LINE aren't specified in the command, the file will be opened with the system default text editor.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;/usr/bin/kate PATH:LINE:COMLUMN&lt;/p&gt;
&lt;p&gt;/usr/bin/gedit +LINE:COLUMN PATH&lt;/p&gt;
&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="openLinksByDirectClickButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Always paste dropped files and URLs as text without offering move, copy and link actions.</string>
<string>Text recognized as a file, link or an email address can be opened by direct mouse click.</string>
</property>
<property name="text">
<string>Disable drag &amp;&amp; drop menu for files &amp;&amp; URLs</string>
<string>Open files/links by direct click</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="4">
</layout>
</item>
<item>
<layout class="QVBoxLayout">
<item>
<widget class="QCheckBox" name="allowLinkEscapeSequenceButton">
<property name="text">
<string>Allow escape sequences for links</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="3">
<item>
<widget class="QLabel" name="allowLinkEscapeSequenceButtonWarning">
<property name="text">
<string>&lt;b&gt;WARNING&lt;/b&gt;: This has security implications as it allows malicious URLs to be shown as another URL or hidden.&lt;br&gt;Make sure you understand the implications before turning this on.</string>
......@@ -367,46 +349,74 @@
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="9" column="0" colspan="4">
<widget class="QCheckBox" name="enableMouseWheelZoomButton">
<property name="toolTip">
<string>Pressing Ctrl+scrollwheel will increase/decrease the text size.</string>
</property>
<property name="text">
<string>Allow Ctrl+scrollwheel to zoom text size</string>
<property name="indent">
<number>30</number>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Allowed link formats</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QCheckBox" name="openLinksByDirectClickButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Text recognized as a file, link or an email address can be opened by direct mouse click.</string>
</property>
<property name="text">
<string>Open by direct click</string>
</property>
</widget>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Allowed link formats: </string>
</property>
<property name="indent">
<number>30</number>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="linkEscapeSequenceTexts">
<property name="toolTip">
<string>The formats of possible links, like http://, https:// and file://</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="ctrlRequiredForDragButton">
<property name="toolTip">
<string>Selected text will require control key plus click to drag.</string>
</property>
<property name="text">
<string>Require Ctrl key for drag &amp;&amp; drop</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="enableAlternateScrollingButton">
<property name="toolTip">
<string>Mouse scroll wheel will emulate up/down key presses in programs that use the Alternate Screen buffer (e.g. less)</string>
</property>
<property name="text">
<string>Enable Alternate Screen buffer scrolling</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="dropUrlsAsText">
<property name="toolTip">
<string>Always paste dropped files and URLs as text without offering move, copy and link actions.</string>
</property>
<property name="text">
<string>Disable drag &amp;&amp; drop menu for files &amp;&amp; URLs</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="enableMouseWheelZoomButton">
<property name="toolTip">
<string>Pressing Ctrl+scrollwheel will increase/decrease the text size.</string>
</property>
<property name="text">
<string>Allow Ctrl+scrollwheel to zoom text size</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment