Commit 730fd82a authored by Friedemann Kleint's avatar Friedemann Kleint

VCS: Add "Open file" context menu action to VCS log pane

to be used for status/opened output. enabling convenient opening.
Append repository as block data to log text to be able to resolve
relative paths.
parent 30373284
......@@ -251,7 +251,11 @@ void GitClient::status(const QString &workingDirectory)
// @TODO: Use "--no-color" once it is supported
QStringList statusArgs(QLatin1String("status"));
statusArgs << QLatin1String("-u");
executeGit(workingDirectory, statusArgs, 0, true);
VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
outwin->setRepository(workingDirectory);
GitCommand *command = executeGit(workingDirectory, statusArgs, 0, true);
connect(command, SIGNAL(finished(bool,QVariant)), outwin, SLOT(clearRepository()),
Qt::QueuedConnection);
}
void GitClient::log(const QString &workingDirectory, const QStringList &fileNames)
......
......@@ -250,7 +250,11 @@ void MercurialClient::status(const QString &workingDir, const QString &file)
QStringList args(QLatin1String("status"));
if (!file.isEmpty())
args.append(file);
VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
outwin->setRepository(workingDir);
QSharedPointer<HgTask> job(new HgTask(workingDir, args, false));
connect(job.data(), SIGNAL(succeeded(QVariant)), outwin, SLOT(clearRepository()),
Qt::QueuedConnection);
enqueueJob(job);
}
......
......@@ -145,7 +145,7 @@ QString PerforceEditor::fileNameFromDiffSpecification(const QTextBlock &inBlock)
if (revisionPos != -1 && revisionPos < diffFileName.length() - 1)
diffFileName.truncate(revisionPos);
// Ask plugin to map back
const QString fileName = m_plugin->fileNameFromPerforceName(diffFileName.trimmed(), &errorMessage);
const QString fileName = m_plugin->fileNameFromPerforceName(diffFileName.trimmed(), false, &errorMessage);
if (fileName.isEmpty())
qWarning("%s", qPrintable(errorMessage));
return fileName;
......
......@@ -544,8 +544,29 @@ void PerforcePlugin::updateCheckout(const QString &workingDir, const QStringList
void PerforcePlugin::printOpenedFileList()
{
runP4Cmd(m_settings.topLevel(), QStringList(QLatin1String("opened")),
CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
const PerforceResponse perforceResponse
= runP4Cmd(m_settings.topLevel(), QStringList(QLatin1String("opened")),
CommandToWindow|StdErrToWindow|ErrorToWindow);
if (perforceResponse.error || perforceResponse.stdOut.isEmpty())
return;
// reformat "//depot/file.cpp#1 - description" into "file.cpp # - description"
// for context menu opening to work. This produces absolute paths, then.
VCSBase::VCSBaseOutputWindow *outWin = VCSBase::VCSBaseOutputWindow::instance();
QString errorMessage;
QString mapped;
const QChar delimiter = QLatin1Char('#');
foreach (const QString &line, perforceResponse.stdOut.split(QLatin1Char('\n'))) {
mapped.clear();
const int delimiterPos = line.indexOf(delimiter);
if (delimiterPos > 0)
mapped = fileNameFromPerforceName(line.left(delimiterPos), true, &errorMessage);
if (mapped.isEmpty()) {
outWin->appendSilently(line);
} else {
outWin->appendSilently(mapped + QLatin1Char(' ') + line.mid(delimiterPos));
}
}
outWin->popup();
}
void PerforcePlugin::startSubmitProject()
......@@ -1282,6 +1303,7 @@ static inline QString msgWhereFailed(const QString & file, const QString &why)
// Map a perforce name "//xx" to its real name in the file system
QString PerforcePlugin::fileNameFromPerforceName(const QString& perforceName,
bool quiet,
QString *errorMessage) const
{
// All happy, already mapped
......@@ -1290,8 +1312,10 @@ QString PerforcePlugin::fileNameFromPerforceName(const QString& perforceName,
// "where" remaps the file to client file tree
QStringList args;
args << QLatin1String("where") << perforceName;
const PerforceResponse response = runP4Cmd(m_settings.topLevelSymLinkTarget(), args,
RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow);
unsigned flags = RunFullySynchronous;
if (!quiet)
flags |= CommandToWindow|StdErrToWindow|ErrorToWindow;
const PerforceResponse response = runP4Cmd(m_settings.topLevelSymLinkTarget(), args, flags);
if (response.error) {
*errorMessage = msgWhereFailed(perforceName, response.message);
return QString::null;
......
......@@ -96,7 +96,9 @@ public:
void setSettings(const Settings &s);
// Map a perforce name "//xx" to its real name in the file system
QString fileNameFromPerforceName(const QString& perforceName, QString *errorMessage) const;
QString fileNameFromPerforceName(const QString& perforceName,
bool quiet,
QString *errorMessage) const;
public slots:
void describe(const QString &source, const QString &n);
......
......@@ -748,7 +748,10 @@ void SubversionPlugin::projectStatus()
QTC_ASSERT(state.hasProject(), return);
QStringList args(QLatin1String("status"));
args += state.relativeCurrentProject();
VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
outwin->setRepository(state.currentProjectTopLevel());
runSvn(state.currentProjectTopLevel(), args, m_settings.timeOutMS(), true);
outwin->clearRepository();
}
void SubversionPlugin::describe(const QString &source, const QString &changeNr)
......
......@@ -30,28 +30,44 @@
#include "vcsbaseoutputwindow.h"
#include <utils/qtcassert.h>
#include <coreplugin/editormanager/editormanager.h>
#include <QtGui/QPlainTextEdit>
#include <QtGui/QTextCharFormat>
#include <QtGui/QContextMenuEvent>
#include <QtGui/QTextBlock>
#include <QtGui/QMenu>
#include <QtGui/QAction>
#include <QtGui/QTextDocument>
#include <QtGui/QTextBlockUserData>
#include <QtCore/QPointer>
#include <QtCore/QTextCodec>
#include <QtCore/QTime>
#include <QtCore/QPoint>
#include <QtCore/QFileInfo>
namespace VCSBase {
namespace Internal {
// Store repository along with text blocks
class RepositoryUserData : public QTextBlockUserData {
public:
explicit RepositoryUserData(const QString &repo) : m_repository(repo) {}
const QString &repository() const { return m_repository; }
private:
const QString m_repository;
};
// A plain text edit with a special context menu containing "Clear" and
// and functions to append specially formatted entries.
class OutputWindowPlainTextEdit : public QPlainTextEdit {
public:
explicit OutputWindowPlainTextEdit(QWidget *parent);
void appendLines(QString s);
void appendLines(QString s, const QString &repository = QString());
// Append red error text and pop up.
void appendError(const QString &text);
// Append warning error text and pop up.
......@@ -63,6 +79,8 @@ protected:
virtual void contextMenuEvent(QContextMenuEvent *event);
private:
QString identifierUnderCursor(const QPoint &pos, QString *repository = 0) const;
const QTextCharFormat m_defaultFormat;
QTextCharFormat m_errorFormat;
QTextCharFormat m_warningFormat;
......@@ -83,27 +101,100 @@ OutputWindowPlainTextEdit::OutputWindowPlainTextEdit(QWidget *parent) :
m_commandFormat.setFontWeight(QFont::Bold);
}
// Search back for beginning of word
static inline int firstWordCharacter(const QString &s, int startPos)
{
for ( ; startPos >= 0 ; startPos--) {
if (s.at(startPos).isSpace())
return startPos + 1;
}
return 0;
}
QString OutputWindowPlainTextEdit::identifierUnderCursor(const QPoint &widgetPos, QString *repository) const
{
if (repository)
repository->clear();
// Get the blank-delimited word under cursor. Note that
// using "SelectWordUnderCursor" does not work since it breaks
// at delimiters like '/'. Get the whole line
QTextCursor cursor = cursorForPosition(widgetPos);
const int cursorDocumentPos = cursor.position();
cursor.select(QTextCursor::BlockUnderCursor);
if (!cursor.hasSelection())
return QString();
QString block = cursor.selectedText();
// Determine cursor position within line and find blank-delimited word
const int cursorPos = cursorDocumentPos - cursor.block().position();
const int blockSize = block.size();
if (cursorPos < 0 || cursorPos >= blockSize || block.at(cursorPos).isSpace())
return QString();
// Retrieve repository if desired
if (repository)
if (QTextBlockUserData *data = cursor.block().userData())
*repository = static_cast<const RepositoryUserData*>(data)->repository();
// Find first non-space character of word and find first non-space character past
const int startPos = firstWordCharacter(block, cursorPos);
int endPos = cursorPos;
for ( ; endPos < blockSize && !block.at(endPos).isSpace(); endPos++) ;
return endPos > startPos ? block.mid(startPos, endPos - startPos) : QString();
}
void OutputWindowPlainTextEdit::contextMenuEvent(QContextMenuEvent *event)
{
QMenu *menu = createStandardContextMenu();
// Add 'open file'
QString repository;
const QString token = identifierUnderCursor(event->pos(), &repository);
QAction *openAction = 0;
if (!token.isEmpty()) {
// Check for a file, expand via repository if relative
QFileInfo fi(token);
if (!repository.isEmpty() && !fi.isFile() && fi.isRelative())
fi = QFileInfo(repository + QLatin1Char('/') + token);
if (fi.isFile()) {
menu->addSeparator();
openAction = menu->addAction(VCSBaseOutputWindow::tr("Open \"%1\"").arg(fi.fileName()));
openAction->setData(fi.absoluteFilePath());
}
}
// Add 'clear'
menu->addSeparator();
QAction *clearAction = menu->addAction(VCSBaseOutputWindow::tr("Clear"));
connect(clearAction, SIGNAL(triggered()), this, SLOT(clear()));
menu->exec(event->globalPos());
// Run
QAction *action = menu->exec(event->globalPos());
if (action) {
if (action == clearAction) {
clear();
return;
}
if (action == openAction) {
const QString fileName = action->data().toString();
Core::EditorManager::instance()->openEditor(fileName);
}
}
delete menu;
}
void OutputWindowPlainTextEdit::appendLines(QString s)
void OutputWindowPlainTextEdit::appendLines(QString s, const QString &repository)
{
if (s.isEmpty())
return;
// Avoid additional new line character generated by appendPlainText
if (s.endsWith(QLatin1Char('\n')))
s.truncate(s.size() - 1);
const int previousLineCount = document()->lineCount();
appendPlainText(s);
// Scroll down
moveCursor(QTextCursor::End);
ensureCursorVisible();
if (!repository.isEmpty()) {
// Associate repository with new data.
QTextBlock block = document()->findBlockByLineNumber(previousLineCount);
for ( ; block.isValid(); block = block.next())
block.setUserData(new RepositoryUserData(repository));
}
}
void OutputWindowPlainTextEdit::appendError(const QString &text)
......@@ -135,6 +226,7 @@ void OutputWindowPlainTextEdit::appendCommand(const QString &text)
struct VCSBaseOutputWindowPrivate {
static VCSBaseOutputWindow *instance;
QPointer<Internal::OutputWindowPlainTextEdit> plainTextEdit;
QString repository;
};
VCSBaseOutputWindow *VCSBaseOutputWindowPrivate::instance = 0;
......@@ -236,7 +328,7 @@ void VCSBaseOutputWindow::setData(const QByteArray &data)
void VCSBaseOutputWindow::appendSilently(const QString &text)
{
QTC_ASSERT(d->plainTextEdit, return)
d->plainTextEdit->appendLines(text);
d->plainTextEdit->appendLines(text, d->repository);
}
void VCSBaseOutputWindow::append(const QString &text)
......@@ -292,4 +384,19 @@ VCSBaseOutputWindow *VCSBaseOutputWindow::instance()
return VCSBaseOutputWindowPrivate::instance;
}
QString VCSBaseOutputWindow::repository() const
{
return d->repository;
}
void VCSBaseOutputWindow::setRepository(const QString &r)
{
d->repository = r;
}
void VCSBaseOutputWindow::clearRepository()
{
d->repository.clear();
}
} // namespace VCSBase
......@@ -41,11 +41,16 @@ struct VCSBaseOutputWindowPrivate;
/* Common OutputWindow for Version Control System command and other output.
* Installed by the base plugin and accessible for the other plugins
* via static instance()-accessor. Provides slots to append output with
* special formatting. */
* special formatting.
* It is possible to associate a repository with plain log text, enabling
* an "Open" context menu action over relative file name tokens in the text
* (absolute paths will also work). This can be used for "status" logs,
* showing modified file names, allowing the user to open them. */
class VCSBASE_EXPORT VCSBaseOutputWindow : public Core::IOutputPane
{
Q_OBJECT
Q_PROPERTY(QString repository READ repository WRITE setRepository)
public:
virtual ~VCSBaseOutputWindow();
......@@ -70,7 +75,12 @@ public:
static VCSBaseOutputWindow *instance();
QString repository() const;
public slots:
void setRepository(const QString &);
void clearRepository();
// Set the whole text.
void setText(const QString &text);
// Set text from QProcess' output data using the Locale's converter.
......
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