mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-25 03:12:53 +00:00
220 lines
7.7 KiB
C++
220 lines
7.7 KiB
C++
/*
|
|
Copyright 2010 Olivier de Gaalon <olivier.jg@gmail.com>
|
|
Copyright 2014 Kevin Funk <kfunk@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License version 2 as published by the Free Software Foundation.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "renameassistant.h"
|
|
|
|
#include "renameaction.h"
|
|
#include "renamefileaction.h"
|
|
#include "../codegen/basicrefactoring.h"
|
|
#include "../codegen/documentchangeset.h"
|
|
#include "../duchain/duchain.h"
|
|
#include "../duchain/duchainlock.h"
|
|
#include "../duchain/duchainutils.h"
|
|
#include "../duchain/declaration.h"
|
|
#include "../duchain/functiondefinition.h"
|
|
#include "../duchain/classfunctiondeclaration.h"
|
|
|
|
#include <interfaces/icore.h>
|
|
#include <interfaces/ilanguagecontroller.h>
|
|
|
|
#include <ktexteditor/view.h>
|
|
#include <ktexteditor/document.h>
|
|
|
|
#include <klocalizedstring.h>
|
|
|
|
using namespace KDevelop;
|
|
|
|
namespace {
|
|
|
|
bool rangesConnect(const KTextEditor::Range& firstRange, const KTextEditor::Range& secondRange)
|
|
{
|
|
return !firstRange.intersect(secondRange + KTextEditor::Range(0, -1, 0, +1)).isEmpty();
|
|
}
|
|
|
|
Declaration* getDeclarationForChangedRange(KTextEditor::View* view, const KTextEditor::Range& changed)
|
|
{
|
|
SimpleCursor cursor(changed.start());
|
|
Declaration* declaration = DUChainUtils::itemUnderCursor(view->document()->url(), cursor);
|
|
|
|
//If it's null we could be appending, but there's a case where appending gives a wrong decl
|
|
//and not a null declaration ... "type var(init)", so check for that too
|
|
if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision().textRange(), changed)) {
|
|
declaration = DUChainUtils::itemUnderCursor(view->document()->url(), SimpleCursor(cursor.line, --cursor.column));
|
|
}
|
|
|
|
//In this case, we may either not have a decl at the cursor, or we got a decl, but are editing its use.
|
|
//In either of those cases, give up and return 0
|
|
if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision().textRange(), changed)) {
|
|
return 0;
|
|
}
|
|
|
|
return declaration;
|
|
}
|
|
|
|
}
|
|
|
|
struct RenameAssistant::Private
|
|
{
|
|
Private(RenameAssistant* qq)
|
|
: q(qq)
|
|
, m_isUseful(false)
|
|
, m_renameFile(false)
|
|
{
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
q->doHide();
|
|
q->clearActions();
|
|
|
|
m_oldDeclarationName = Identifier();
|
|
m_newDeclarationRange.clear();
|
|
m_oldDeclarationUses.clear();
|
|
m_isUseful = false;
|
|
m_renameFile = false;
|
|
}
|
|
|
|
RenameAssistant* q;
|
|
|
|
KDevelop::Identifier m_oldDeclarationName;
|
|
QString m_newDeclarationName;
|
|
KDevelop::PersistentMovingRange::Ptr m_newDeclarationRange;
|
|
QVector<RevisionedFileRanges> m_oldDeclarationUses;
|
|
|
|
bool m_isUseful;
|
|
bool m_renameFile;
|
|
};
|
|
|
|
RenameAssistant::RenameAssistant(ILanguageSupport* supportedLanguage)
|
|
: StaticAssistant(supportedLanguage)
|
|
, d(new Private(this))
|
|
{
|
|
}
|
|
|
|
QString RenameAssistant::title() const
|
|
{
|
|
return tr("Rename");
|
|
}
|
|
|
|
bool RenameAssistant::isUseful() const
|
|
{
|
|
return d->m_isUseful;
|
|
}
|
|
|
|
void RenameAssistant::textChanged(KTextEditor::View* view, const KTextEditor::Range& invocationRange, const QString& removedText)
|
|
{
|
|
clearActions();
|
|
|
|
if (!supportedLanguage()->refactoring()) {
|
|
kWarning() << "Refactoring not supported. Aborting.";
|
|
return;
|
|
}
|
|
|
|
if (!view)
|
|
return;
|
|
|
|
//If the inserted text isn't valid for a variable name, consider the editing ended
|
|
QRegExp validDeclName("^[0-9a-zA-Z_]*$");
|
|
if (removedText.isEmpty() && !validDeclName.exactMatch(view->document()->text(invocationRange))) {
|
|
d->reset();
|
|
return;
|
|
}
|
|
|
|
const KUrl url = view->document()->url();
|
|
const IndexedString indexedUrl(url);
|
|
DUChainReadLocker lock;
|
|
|
|
//If we've stopped editing m_newDeclarationRange or switched the view,
|
|
// reset and see if there's another declaration being edited
|
|
if (!d->m_newDeclarationRange.data() || !rangesConnect(d->m_newDeclarationRange->range().textRange(), invocationRange)
|
|
|| d->m_newDeclarationRange->document() != indexedUrl) {
|
|
d->reset();
|
|
|
|
Declaration* declAtCursor = getDeclarationForChangedRange(view, invocationRange);
|
|
if (!declAtCursor) {
|
|
// not editing a declaration
|
|
return;
|
|
}
|
|
|
|
if (supportedLanguage()->refactoring()->shouldRenameUses(declAtCursor)) {
|
|
QMap< IndexedString, QList<RangeInRevision> > declUses = declAtCursor->uses();
|
|
if (declUses.isEmpty()) {
|
|
// new declaration is use-less
|
|
return;
|
|
}
|
|
|
|
for(QMap< IndexedString, QList< RangeInRevision > >::const_iterator it = declUses.constBegin();
|
|
it != declUses.constEnd(); ++it)
|
|
{
|
|
foreach(const RangeInRevision& range, it.value())
|
|
{
|
|
SimpleRange currentRange = declAtCursor->transformFromLocalRevision(range);
|
|
if(currentRange.isEmpty() || view->document()->text(currentRange.textRange()) != declAtCursor->identifier().identifier().str())
|
|
return; // One of the uses is invalid. Maybe the replacement has already been performed.
|
|
}
|
|
}
|
|
d->m_oldDeclarationUses = RevisionedFileRanges::convert(declUses);
|
|
} else if (supportedLanguage()->refactoring()->shouldRenameFile(declAtCursor)) {
|
|
d->m_renameFile = true;
|
|
} else {
|
|
// not a valid declaration
|
|
return;
|
|
}
|
|
|
|
d->m_oldDeclarationName = declAtCursor->identifier();
|
|
KTextEditor::Range newRange = declAtCursor->rangeInCurrentRevision().textRange();
|
|
if (removedText.isEmpty() && newRange.intersect(invocationRange).isEmpty()) {
|
|
newRange = newRange.encompass(invocationRange); //if text was added to the ends, encompass it
|
|
}
|
|
|
|
d->m_newDeclarationRange.attach(new PersistentMovingRange(newRange, indexedUrl, true));
|
|
}
|
|
|
|
//Unfortunately this happens when you make a selection including one end of the decl's range and replace it
|
|
if (removedText.isEmpty() && d->m_newDeclarationRange->range().textRange().intersect(invocationRange).isEmpty()) {
|
|
d->m_newDeclarationRange.attach(new PersistentMovingRange(
|
|
d->m_newDeclarationRange->range().textRange().encompass(invocationRange), indexedUrl, true));
|
|
}
|
|
|
|
d->m_newDeclarationName = view->document()->text(d->m_newDeclarationRange->range().textRange());
|
|
if (d->m_newDeclarationName == d->m_oldDeclarationName.toString()) {
|
|
d->reset();
|
|
return;
|
|
}
|
|
|
|
if (d->m_renameFile && supportedLanguage()->refactoring()->newFileName(url, d->m_newDeclarationName) == url.fileName()) {
|
|
// no change, don't do anything
|
|
return;
|
|
}
|
|
|
|
d->m_isUseful = true;
|
|
|
|
IAssistantAction::Ptr action;
|
|
if (d->m_renameFile) {
|
|
action.attach(new RenameFileAction(supportedLanguage()->refactoring(), url, d->m_newDeclarationName));
|
|
} else {
|
|
action.attach(new RenameAction(d->m_oldDeclarationName, d->m_newDeclarationName,
|
|
d->m_oldDeclarationUses));
|
|
}
|
|
connect(action.data(), SIGNAL(executed(IAssistantAction*)), SLOT(reset()));
|
|
addAction(action);
|
|
emit actionsChanged();
|
|
}
|
|
|
|
#include "moc_renameassistant.cpp"
|