Commit 7c0f4e8f authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

VCS[SVN, CVS, Perforce]: Implement "Annotate Previous" context menu.

in annotation editor, including base infrastructure in
VCSBaseEditor. Pass signals of VCSBaseEditor to opaque
VCSBaseEditable (IEditor) for convenience.

Task-number: QTCREATORBUG-503
parent 0afaf1f2
......@@ -28,6 +28,7 @@
**************************************************************************/
#include "cvseditor.h"
#include "cvsutils.h"
#include "annotationhighlighter.h"
#include "cvsconstants.h"
......@@ -148,5 +149,13 @@ QString CVSEditor::fileNameFromDiffSpecification(const QTextBlock &inBlock) cons
return QString();
}
QStringList CVSEditor::annotationPreviousVersions(const QString &revision, QString *actionTextFormat) const
{
if (isFirstRevision(revision))
return QStringList();
*actionTextFormat = tr("Annotate revision \"%1\"");
return QStringList(previousRevision(revision));
}
}
}
......@@ -51,6 +51,7 @@ private:
virtual VCSBase::DiffHighlighter *createDiffHighlighter() const;
virtual VCSBase::BaseAnnotationHighlighter *createAnnotationHighlighter(const QSet<QString> &changes) const;
virtual QString fileNameFromDiffSpecification(const QTextBlock &diffFileName) const;
virtual QStringList annotationPreviousVersions(const QString &revision, QString *actionTextFormat) const;
const QRegExp m_revisionAnnotationPattern;
const QRegExp m_revisionLogPattern;
......
......@@ -690,21 +690,33 @@ void CVSPlugin::annotateCurrentFile()
annotate(state.currentFileTopLevel(), state.relativeCurrentFile());
}
void CVSPlugin::annotate(const QString &workingDir, const QString &file)
void CVSPlugin::annotateVersion(const QString &file, const QString &revision, int lineNumber)
{
const QFileInfo fi(file);
annotate(fi.absolutePath(), fi.fileName(), revision, lineNumber);
}
void CVSPlugin::annotate(const QString &workingDir, const QString &file,
const QString &revision /* = QString() */,
int lineNumber /* = -1 */)
{
const QStringList files(file);
QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files);
const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files);
const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files, revision);
const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, file);
QStringList args;
args << QLatin1String("annotate") << file;
args << QLatin1String("annotate");
if (!revision.isEmpty())
args << QLatin1String("-r") << revision;
args << file;
const CVSResponse response = runCVS(workingDir, args, m_settings.timeOutMS(), false, codec);
if (response.result != CVSResponse::Ok)
return;
// Re-use an existing view if possible to support
// the common usage pattern of continuously changing and diffing a file
const int lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor(file);
if (lineNumber < 1)
lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor(file);
if (Core::IEditor *editor = locateEditor("annotateFileName", id)) {
editor->createNew(response.stdOut);
......@@ -714,6 +726,8 @@ void CVSPlugin::annotate(const QString &workingDir, const QString &file)
const QString title = QString::fromLatin1("cvs annotate %1").arg(id);
Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::AnnotateOutput, source, codec);
newEditor->setProperty("annotateFileName", id);
connect(newEditor, SIGNAL(annotatePreviousRequested(QString,QString,int)),
this, SLOT(annotateVersion(QString,QString,int)));
VCSBase::VCSBaseEditor::gotoLineOfEditor(newEditor, lineNumber);
}
}
......@@ -729,22 +743,6 @@ void CVSPlugin::projectStatus()
showOutputInEditor(tr("Project status"), response.stdOut, VCSBase::RegularCommandOutput, state.currentProjectTopLevel(), 0);
}
// Decrement version number "1.2" -> "1.1"
static QString previousRevision(const QString &rev)
{
const int dotPos = rev.lastIndexOf(QLatin1Char('.'));
if (dotPos == -1)
return rev;
const int minor = rev.mid(dotPos + 1).toInt();
return rev.left(dotPos + 1) + QString::number(minor - 1);
}
// Is "[1.2...].1"?
static inline bool isFirstRevision(const QString &r)
{
return r.endsWith(QLatin1String(".1"));
}
void CVSPlugin::slotDescribe(const QString &source, const QString &changeNr)
{
QString errorMessage;
......
......@@ -107,6 +107,7 @@ private slots:
void startCommitCurrentFile();
void filelogCurrentFile();
void annotateCurrentFile();
void annotateVersion(const QString &file, const QString &revision, int lineNumber);
void projectStatus();
void slotDescribe(const QString &source, const QString &changeNr);
void updateProject();
......@@ -129,7 +130,8 @@ private:
bool showStdOutInOutputWindow, QTextCodec *outputCodec = 0,
bool mergeStderr = false);
void annotate(const QString &workingDir, const QString &file);
void annotate(const QString &workingDir, const QString &file,
const QString &revision = QString(), int lineNumber= -1);
bool describe(const QString &source, const QString &changeNr, QString *errorMessage);
bool describe(const QString &toplevel, const QString &source, const QString &changeNr, QString *errorMessage);
bool describe(const QString &repository, QList<CVS_LogEntry> entries, QString *errorMessage);
......
......@@ -234,5 +234,21 @@ StateList parseStatusOutput(const QString &directory, const QString &output)
return changeSet;
}
// Decrement version number "1.2" -> "1.1"
QString previousRevision(const QString &rev)
{
const int dotPos = rev.lastIndexOf(QLatin1Char('.'));
if (dotPos == -1)
return rev;
const int minor = rev.mid(dotPos + 1).toInt();
return rev.left(dotPos + 1) + QString::number(minor - 1);
}
// Is "[1.2...].1"?
bool isFirstRevision(const QString &r)
{
return r.endsWith(QLatin1String(".1"));
}
} // namespace Internal
} // namespace CVS
......@@ -80,6 +80,11 @@ QString fixDiffOutput(QString d);
typedef QList<CVSSubmitEditor::StateFilePair> StateList;
StateList parseStatusOutput(const QString &directory, const QString &output);
// Revision number utilities: Decrement version number "1.2" -> "1.1"
QString previousRevision(const QString &rev);
// Revision number utilities: Is it "[1.2...].1"?
bool isFirstRevision(const QString &r);
} // namespace Internal
} // namespace CVS
......
......@@ -154,5 +154,15 @@ QString PerforceEditor::fileNameFromDiffSpecification(const QTextBlock &inBlock)
return QString();
}
QStringList PerforceEditor::annotationPreviousVersions(const QString &v, QString *actionTextFormat) const
{
bool ok;
const int changeList = v.toInt(&ok);
if (!ok || changeList < 2)
return QStringList();
*actionTextFormat = tr("Annotate change list \"%1\"");
return QStringList(QString::number(changeList - 1));
}
} // namespace Internal
} // namespace Perforce
......@@ -53,6 +53,7 @@ private:
virtual VCSBase::DiffHighlighter *createDiffHighlighter() const;
virtual VCSBase::BaseAnnotationHighlighter *createAnnotationHighlighter(const QSet<QString> &changes) const;
virtual QString fileNameFromDiffSpecification(const QTextBlock &diffFileName) const;
virtual QStringList annotationPreviousVersions(const QString &v, QString *actionTextFormat) const;
const QRegExp m_changeNumberPattern;
PerforcePlugin *m_plugin;
......
......@@ -683,22 +683,40 @@ void PerforcePlugin::annotate()
}
}
void PerforcePlugin::annotate(const QString &workingDir, const QString &fileName)
void PerforcePlugin::annotateVersion(const QString &file, const QString &revision, int lineNumber)
{
const QFileInfo fi(file);
annotate(fi.absolutePath(), fi.fileName(), revision, lineNumber);
}
void PerforcePlugin::annotate(const QString &workingDir,
const QString &fileName,
const QString &changeList /* = QString() */,
int lineNumber /* = -1 */)
{
const QStringList files = QStringList(fileName);
QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files);
const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files);
const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files, changeList);
const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, files);
QStringList args;
args << QLatin1String("annotate") << QLatin1String("-cqi") << fileName;
args << QLatin1String("annotate") << QLatin1String("-cqi");
if (changeList.isEmpty()) {
args << fileName;
} else {
args << (fileName + QLatin1Char('@') + changeList);
}
const PerforceResponse result = runP4Cmd(workingDir, args,
CommandToWindow|StdErrToWindow|ErrorToWindow,
QStringList(), QByteArray(), codec);
if (!result.error) {
const int lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor();
if (lineNumber < 1)
lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor();
const QFileInfo fi(fileName);
Core::IEditor *ed = showOutputInEditor(tr("p4 annotate %1").arg(id),
result.stdOut, VCSBase::AnnotateOutput, codec);
result.stdOut, VCSBase::AnnotateOutput,
source, codec);
connect(ed, SIGNAL(annotatePreviousRequested(QString,QString,int)),
this, SLOT(annotateVersion(QString,QString,int)));
VCSBase::VCSBaseEditor::gotoLineOfEditor(ed, lineNumber);
}
}
......@@ -730,7 +748,10 @@ void PerforcePlugin::filelog(const QString &workingDir, const QStringList &fileN
CommandToWindow|StdErrToWindow|ErrorToWindow,
QStringList(), QByteArray(), codec);
if (!result.error)
showOutputInEditor(tr("p4 filelog %1").arg(id), result.stdOut, VCSBase::LogOutput, codec);
showOutputInEditor(tr("p4 filelog %1").arg(id), result.stdOut,
VCSBase::LogOutput,
VCSBase::VCSBaseEditor::getSource(workingDir, fileNames),
codec);
}
void PerforcePlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
......@@ -1078,7 +1099,9 @@ PerforceResponse PerforcePlugin::runP4Cmd(const QString &workingDir,
}
Core::IEditor * PerforcePlugin::showOutputInEditor(const QString& title, const QString output,
int editorType, QTextCodec *codec)
int editorType,
const QString &source,
QTextCodec *codec)
{
const VCSBase::VCSBaseEditorParameters *params = findType(editorType);
QTC_ASSERT(params, return 0);
......@@ -1090,6 +1113,7 @@ Core::IEditor * PerforcePlugin::showOutputInEditor(const QString& title, const Q
PerforceEditor *e = qobject_cast<PerforceEditor*>(editor->widget());
if (!e)
return 0;
e->setSource(source);
s.replace(QLatin1Char(' '), QLatin1Char('_'));
e->setSuggestedFileName(s);
if (codec)
......@@ -1138,7 +1162,9 @@ void PerforcePlugin::p4Diff(const QString &workingDir, const QStringList &files)
existingEditor->createNew(result.stdOut);
Core::EditorManager::instance()->activateEditor(existingEditor);
} else {
Core::IEditor *editor = showOutputInEditor(tr("p4 diff %1").arg(id), result.stdOut, VCSBase::DiffOutput, codec);
Core::IEditor *editor = showOutputInEditor(tr("p4 diff %1").arg(id), result.stdOut, VCSBase::DiffOutput,
VCSBase::VCSBaseEditor::getSource(workingDir, files),
codec);
editor->file()->setProperty("originalFileName", id);
}
}
......@@ -1151,7 +1177,7 @@ void PerforcePlugin::describe(const QString & source, const QString &n)
const PerforceResponse result = runP4Cmd(m_settings.topLevel(), args, CommandToWindow|StdErrToWindow|ErrorToWindow,
QStringList(), QByteArray(), codec);
if (!result.error)
showOutputInEditor(tr("p4 describe %1").arg(n), result.stdOut, VCSBase::DiffOutput, codec);
showOutputInEditor(tr("p4 describe %1").arg(n), result.stdOut, VCSBase::DiffOutput, source, codec);
}
void PerforcePlugin::submitCurrentLog()
......
......@@ -120,6 +120,7 @@ private slots:
void describeChange();
void annotateCurrentFile();
void annotate();
void annotateVersion(const QString &file, const QString &revision, int lineNumber);
void filelogCurrentFile();
void filelog();
......@@ -138,7 +139,7 @@ private:
typedef QHash<QString, bool> ManagedDirectoryCache;
Core::IEditor *showOutputInEditor(const QString& title, const QString output,
int editorType,
int editorType, const QString &source,
QTextCodec *codec = 0);
// Flags for runP4Cmd.
......@@ -174,7 +175,8 @@ private:
QTextCodec *outputCodec) const;
QString clientFilePath(const QString &serverFilePath);
void annotate(const QString &workingDir, const QString &fileName);
void annotate(const QString &workingDir, const QString &fileName,
const QString &changeList = QString(), int lineNumber = -1);
void filelog(const QString &workingDir, const QStringList &fileNames);
void cleanCommitMessageFile();
bool isCommitEditorOpen() const;
......
......@@ -137,3 +137,13 @@ QString SubversionEditor::fileNameFromDiffSpecification(const QTextBlock &inBloc
}
return QString();
}
QStringList SubversionEditor::annotationPreviousVersions(const QString &v, QString *actionTextFormat) const
{
bool ok;
const int revision = v.toInt(&ok);
if (!ok || revision < 2)
return QStringList();
*actionTextFormat = tr("Annotate revision \"%1\"");
return QStringList(QString::number(revision - 1));
}
......@@ -51,6 +51,7 @@ private:
virtual VCSBase::DiffHighlighter *createDiffHighlighter() const;
virtual VCSBase::BaseAnnotationHighlighter *createAnnotationHighlighter(const QSet<QString> &changes) const;
virtual QString fileNameFromDiffSpecification(const QTextBlock &diffFileName) const;
virtual QStringList annotationPreviousVersions(const QString &, QString *actionTextFormat) const;
const QRegExp m_changeNumberPattern;
const QRegExp m_revisionNumberPattern;
......
......@@ -712,13 +712,25 @@ void SubversionPlugin::annotateCurrentFile()
annotate(state.currentFileTopLevel(), state.relativeCurrentFile());
}
void SubversionPlugin::annotate(const QString &workingDir, const QString &file)
void SubversionPlugin::annotateVersion(const QString &file,
const QString &revision,
int lineNr)
{
const QFileInfo fi(file);
annotate(fi.absolutePath(), fi.fileName(), revision, lineNr);
}
void SubversionPlugin::annotate(const QString &workingDir, const QString &file,
const QString &revision /* = QString() */,
int lineNumber /* = -1 */)
{
QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(file);
QStringList args(QLatin1String("annotate"));
if (m_settings.spaceIgnorantAnnotation)
args << QLatin1String("-x") << QLatin1String("-uw");
if (!revision.isEmpty())
args << QLatin1String("-r") << revision;
args.push_back(QLatin1String("-v"));
args.append(QDir::toNativeSeparators(file));
......@@ -729,8 +741,10 @@ void SubversionPlugin::annotate(const QString &workingDir, const QString &file)
// Re-use an existing view if possible to support
// the common usage pattern of continuously changing and diffing a file
const QString source = workingDir + QLatin1Char('/') + file;
const int lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor(source);
const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, QStringList(file));
if (lineNumber <= 0)
lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor(source);
// Determine id
const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, QStringList(file), revision);
if (Core::IEditor *editor = locateEditor("annotateFileName", id)) {
editor->createNew(response.stdOut);
......@@ -739,6 +753,8 @@ void SubversionPlugin::annotate(const QString &workingDir, const QString &file)
} else {
const QString title = QString::fromLatin1("svn annotate %1").arg(id);
Core::IEditor *newEditor = showOutputInEditor(title, response.stdOut, VCSBase::AnnotateOutput, source, codec);
connect(newEditor, SIGNAL(annotatePreviousRequested(QString,QString,int)),
this, SLOT(annotateVersion(QString,QString,int)));
newEditor->setProperty("annotateFileName", id);
VCSBase::VCSBaseEditor::gotoLineOfEditor(newEditor, lineNumber);
}
......
......@@ -104,6 +104,7 @@ private slots:
void startCommitCurrentFile();
void filelogCurrentFile();
void annotateCurrentFile();
void annotateVersion(const QString &file, const QString &revision, int lineNumber);
void projectStatus();
void describe(const QString &source, const QString &changeNr);
void slotDescribe();
......@@ -111,6 +112,7 @@ private slots:
void submitCurrentLog();
void diffCommitFiles(const QStringList &);
protected:
virtual void updateActions(VCSBase::VCSBasePlugin::ActionState);
virtual bool submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEditor);
......@@ -123,7 +125,8 @@ private:
SubversionResponse runSvn(const QString &workingDir,
const QStringList &arguments, int timeOut,
bool showStdOutInOutputWindow, QTextCodec *outputCodec = 0);
void annotate(const QString &workingDir, const QString &file);
void annotate(const QString &workingDir, const QString &file,
const QString &revision = QString(), int lineNumber = -1);
void filelog(const QString &workingDir, const QStringList &file = QStringList());
bool managesDirectory(const QDir &directory) const;
QString findTopLevelForDirectoryI(const QString &directory) const;
......
......@@ -67,8 +67,11 @@ namespace VCSBase {
// VCSBaseEditorEditable: An editable with no support for duplicates
// Creates a browse combo in the toolbar for diff output.
// It also mirrors the signals of the VCSBaseEditor since the editor
// manager passes the Editable around.
class VCSBaseEditorEditable : public TextEditor::BaseTextEditorEditable
{
Q_OBJECT
public:
VCSBaseEditorEditable(VCSBaseEditor *,
const VCSBaseEditorParameters *type);
......@@ -80,6 +83,10 @@ public:
bool isTemporary() const { return true; }
signals:
void describeRequested(const QString &source, const QString &change);
void annotatePreviousRequested(const QString &source, const QString &change, int line);
private:
const char *m_kind;
QList<int> m_context;
......@@ -247,13 +254,22 @@ bool VCSBaseEditor::isModified() const
TextEditor::BaseTextEditorEditable *VCSBaseEditor::createEditableInterface()
{
if (d->m_parameters->type != DiffOutput)
return new VCSBaseEditorEditable(this, d->m_parameters);
TextEditor::BaseTextEditorEditable *editable = 0;
if (d->m_parameters->type == DiffOutput) {
// Diff: set up diff file browsing
VCSBaseDiffEditorEditable *de = new VCSBaseDiffEditorEditable(this, d->m_parameters);
QComboBox *diffBrowseComboBox = de->diffFileBrowseComboBox();
connect(diffBrowseComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slotDiffBrowse(int)));
return de;
editable = de;
} else {
editable = new VCSBaseEditorEditable(this, d->m_parameters);
}
// Pass on signals.
connect(this, SIGNAL(describeRequested(QString,QString)),
editable, SIGNAL(describeRequested(QString,QString)));
connect(this, SIGNAL(annotatePreviousRequested(QString,QString,int)),
editable, SIGNAL(annotatePreviousRequested(QString,QString,int)));
return editable;
}
void VCSBaseEditor::slotPopulateDiffBrowser()
......@@ -339,6 +355,21 @@ void VCSBaseEditor::contextMenuEvent(QContextMenuEvent *e)
d->m_describeAction->setText(tr("Describe change %1").arg(d->m_currentChange));
menu->addSeparator();
menu->addAction(d->m_describeAction);
// Offer to annotate previous changes
if (contentType() == AnnotateOutput) {
QString actionTextFormat;
const QStringList previousVersions = annotationPreviousVersions(d->m_currentChange, &actionTextFormat);
if (!previousVersions.isEmpty()) {
if (actionTextFormat.isEmpty())
actionTextFormat = tr("Annotate \"%1\"");
menu->addSeparator();
foreach(const QString &pv, previousVersions) {
QAction *a = menu->addAction(actionTextFormat.arg(pv));
a->setData(pv);
connect(a, SIGNAL(triggered()), this, SLOT(slotAnnotatePrevious()));
}
}
}
}
}
menu->exec(e->globalPos());
......@@ -673,17 +704,27 @@ QString VCSBaseEditor::getSource(const QString &workingDirectory,
workingDirectory;
}
QString VCSBaseEditor::getTitleId(const QString &workingDirectory, const QStringList &fileNames)
QString VCSBaseEditor::getTitleId(const QString &workingDirectory,
const QStringList &fileNames,
const QString &revision)
{
QString rc;
switch (fileNames.size()) {
case 0:
return workingDirectory;
rc = workingDirectory;
break;
case 1:
return fileNames.front();
rc = fileNames.front();
break;
default:
rc = fileNames.join(QLatin1String(", "));
break;
}
return fileNames.join(QLatin1String(", "));
if (!revision.isEmpty()) {
rc += QLatin1Char(':');
rc += revision;
}
return rc;
}
// Find the complete file from a diff relative specification.
......@@ -722,4 +763,18 @@ QString VCSBaseEditor::findDiffFile(const QString &f, Core::IVersionControl *con
return QString();
}
void VCSBaseEditor::slotAnnotatePrevious()
{
if (const QAction *a = qobject_cast<const QAction *>(sender()))
emit annotatePreviousRequested(source(), a->data().toString(),
editableInterface()->currentLine());
}
QStringList VCSBaseEditor::annotationPreviousVersions(const QString &, QString *) const
{
return QStringList();
}
} // namespace VCSBase
#include "vcsbaseeditor.moc"
......@@ -145,10 +145,14 @@ public:
static QString getSource(const QString &workingDirectory, const QStringList &fileNames);
// Convenience functions to determine an title/id to identify the editor
// from the arguments (','-joined arguments or directory).
static QString getTitleId(const QString &workingDirectory, const QStringList &fileNames);
static QString getTitleId(const QString &workingDirectory,
const QStringList &fileNames,
const QString &revision = QString());
signals:
// These signals also exist in the opaque editable (IEditor) that is
// handled by the editor manager for convenience.
void describeRequested(const QString &source, const QString &change);
void annotatePreviousRequested(const QString &source, const QString &change, int lineNumber);
public slots:
// Convenience slot to set data read from stdout, will use the
......@@ -173,6 +177,7 @@ private slots:
void slotPopulateDiffBrowser();
void slotDiffBrowse(int);
void slotDiffCursorPositionChanged();
void slotAnnotatePrevious();
protected:
/* A helper that can be used to locate a file in a diff in case it
......@@ -192,6 +197,10 @@ private:
// Implement to return a local file name from the diff file specification
// (text cursor at position above change hunk)
virtual QString fileNameFromDiffSpecification(const QTextBlock &diffFileSpec) const = 0;
// Implement to return the previous version[s] of an annotation change
// for "Annotate previous version" and a format for the action text containing %1
// for the revision
virtual QStringList annotationPreviousVersions(const QString &revision, QString *actionTextFormat) const;
void jumpToChangeFromDiff(QTextCursor cursor);
......
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