kde-extraapps/kdevplatform/language/codegen/coderepresentation.cpp

412 lines
12 KiB
C++
Raw Normal View History

/*
Copyright 2008 David Nolden <david.nolden.kdevelop@art-master.de>
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 "coderepresentation.h"
#include <QtCore/qfile.h>
#include <KDE/KTextEditor/Document>
#include <language/duchain/indexedstring.h>
#include <interfaces/idocumentcontroller.h>
#include <interfaces/icore.h>
#include <editor/modificationrevision.h>
#include <ktexteditor/movinginterface.h>
#include <ktexteditor/configinterface.h>
namespace KDevelop {
static bool onDiskChangesForbidden = false;
QString CodeRepresentation::rangeText(const KTextEditor::Range& range) const
{
Q_ASSERT(range.end().line() < lines());
//Easier for single line ranges which should happen most of the time
if(range.onSingleLine())
return QString( line( range.start().line() ).mid( range.start().column(), range.columnWidth() ) );
//Add up al the requested lines
QString rangedText = line(range.start().line()).mid(range.start().column());
for(int i = range.start().line() + 1; i <= range.end().line(); ++i)
rangedText += '\n' + ((i == range.end().line()) ? line(i).left(range.end().column()) : line(i));
return rangedText;
}
static void grepLine(const QString& identifier, const QString& lineText, int lineNumber, QVector<SimpleRange>& ret, bool surroundedByBoundary)
{
if (identifier.isEmpty())
return;
int pos = 0;
while(true)
{
pos = lineText.indexOf(identifier, pos);
if(pos == -1)
break;
int start = pos;
pos += identifier.length();
int end = pos;
if(!surroundedByBoundary || ( (end == lineText.length() || !lineText[end].isLetterOrNumber() || lineText[end] != '_')
&& (start-1 < 0 || !lineText[start-1].isLetterOrNumber() || lineText[start-1] != '_')) )
{
ret << SimpleRange(lineNumber, start, lineNumber, end);
}
}
}
//NOTE: this is ugly, but otherwise kate might remove tabs again :-/
// see also: https://bugs.kde.org/show_bug.cgi?id=291074
struct EditorDisableReplaceTabs {
EditorDisableReplaceTabs(KTextEditor::Document* document) : m_iface(qobject_cast<KTextEditor::ConfigInterface*>(document)), m_count(0) {
}
void start() {
++m_count;
if( m_count > 1 )
return;
if ( m_iface ) {
m_oldReplaceTabs = m_iface->configValue( "replace-tabs" );
m_iface->setConfigValue( "replace-tabs", false );
}
}
void end() {
--m_count;
if( m_count > 0 )
return;
Q_ASSERT( m_count == 0 );
if (m_iface)
m_iface->setConfigValue("replace-tabs", m_oldReplaceTabs);
}
KTextEditor::ConfigInterface* m_iface;
int m_count;
QVariant m_oldReplaceTabs;
};
class EditorCodeRepresentation : public DynamicCodeRepresentation {
public:
EditorCodeRepresentation(KTextEditor::Document* document) : m_document(document), m_replaceTabs(document) {
m_url = IndexedString(m_document->url());
}
virtual QVector< SimpleRange > grep ( const QString& identifier, bool surroundedByBoundary ) const {
QVector< SimpleRange > ret;
if (identifier.isEmpty())
return ret;
for(int line = 0; line < m_document->lines(); ++line)
grepLine(identifier, m_document->line(line), line, ret, surroundedByBoundary);
return ret;
}
QString line(int line) const {
if(line < 0 || line >= m_document->lines())
return QString();
return m_document->line(line);
}
virtual int lines() const {
return m_document->lines();
}
QString text() const {
return m_document->text();
}
bool setText(const QString& text) {
startEdit();
bool ret = m_document->setText(text);
endEdit();
ModificationRevision::clearModificationCache(m_url);
return ret;
}
bool fileExists(){
return QFile(m_document->url().path()).exists();
}
void startEdit() {
m_document->startEditing();
m_replaceTabs.start();
}
void endEdit() {
m_document->endEditing();
m_replaceTabs.end();
}
bool replace(const KTextEditor::Range& range, const QString& oldText,
const QString& newText, bool ignoreOldText) {
QString old = m_document->text(range);
if(oldText != old && !ignoreOldText) {
return false;
}
startEdit();
bool ret = m_document->replaceText(range, newText);
endEdit();
ModificationRevision::clearModificationCache(m_url);
return ret;
}
virtual QString rangeText(const KTextEditor::Range& range) const {
return m_document->text(range);
}
private:
KTextEditor::Document* m_document;
IndexedString m_url;
EditorDisableReplaceTabs m_replaceTabs;
};
class FileCodeRepresentation : public CodeRepresentation {
public:
FileCodeRepresentation(const IndexedString& document) : m_document(document) {
QString localFile(document.toUrl().toLocalFile());
QFile file( localFile );
if ( file.open(QIODevice::ReadOnly) ) {
data = QString::fromLocal8Bit(file.readAll());
lineData = data.split('\n');
}
m_exists = file.exists();
}
QString line(int line) const {
if(line < 0 || line >= lineData.size())
return QString();
return lineData.at(line);
}
virtual QVector< SimpleRange > grep ( const QString& identifier, bool surroundedByBoundary ) const {
QVector< SimpleRange > ret;
if (identifier.isEmpty())
return ret;
for(int line = 0; line < lineData.count(); ++line)
grepLine(identifier, lineData.at(line), line, ret, surroundedByBoundary);
return ret;
}
virtual int lines() const {
return lineData.count();
}
QString text() const {
return data;
}
bool setText(const QString& text) {
Q_ASSERT(!onDiskChangesForbidden);
QString localFile(m_document.toUrl().toLocalFile());
QFile file( localFile );
if ( file.open(QIODevice::WriteOnly) )
{
QByteArray data = text.toLocal8Bit();
if(file.write(data) == data.size())
{
ModificationRevision::clearModificationCache(m_document);
return true;
}
}
return false;
}
bool fileExists(){
return m_exists;
}
private:
//We use QByteArray, because the column-numbers are measured in utf-8
IndexedString m_document;
bool m_exists;
QStringList lineData;
QString data;
};
class ArtificialStringData : public QSharedData {
public:
ArtificialStringData(const QString& data) {
setData(data);
}
void setData(const QString& data) {
m_data = data;
m_lineData = m_data.split('\n');
}
QString data() const {
return m_data;
}
const QStringList& lines() const {
return m_lineData;
}
private:
QString m_data;
QStringList m_lineData;
};
class StringCodeRepresentation : public CodeRepresentation {
public:
StringCodeRepresentation(KSharedPtr<ArtificialStringData> _data) : data(_data) {
Q_ASSERT(data);
}
QString line(int line) const {
if(line < 0 || line >= data->lines().size())
return QString();
return data->lines().at(line);
}
virtual int lines() const {
return data->lines().count();
}
QString text() const {
return data->data();
}
bool setText(const QString& text) {
data->setData(text);
return true;
}
bool fileExists(){
return false;
}
virtual QVector< SimpleRange > grep ( const QString& identifier, bool surroundedByBoundary ) const {
QVector< SimpleRange > ret;
if (identifier.isEmpty())
return ret;
for(int line = 0; line < data->lines().count(); ++line)
grepLine(identifier, data->lines().at(line), line, ret, surroundedByBoundary);
return ret;
}
private:
KSharedPtr<ArtificialStringData> data;
};
static QHash<IndexedString, KSharedPtr<ArtificialStringData> > artificialStrings;
//Return the representation for the given URL if it exists, or an empty pointer otherwise
static KSharedPtr<ArtificialStringData> representationForPath(const IndexedString& path)
{
if(artificialStrings.contains(path))
return artificialStrings[path];
else
{
IndexedString constructedPath(CodeRepresentation::artificialPath(path.str()));
if(artificialStrings.contains(constructedPath))
return artificialStrings[constructedPath];
else
return KSharedPtr<ArtificialStringData>();
}
}
bool artificialCodeRepresentationExists(const IndexedString& path)
{
return !representationForPath(path).isNull();
}
CodeRepresentation::Ptr createCodeRepresentation(const IndexedString& path) {
if(artificialCodeRepresentationExists(path))
return CodeRepresentation::Ptr(new StringCodeRepresentation(representationForPath(path)));
IDocument* document = ICore::self()->documentController()->documentForUrl(path.toUrl());
if(document && document->textDocument())
return CodeRepresentation::Ptr(new EditorCodeRepresentation(document->textDocument()));
else
return CodeRepresentation::Ptr(new FileCodeRepresentation(path));
}
void CodeRepresentation::setDiskChangesForbidden(bool changesForbidden)
{
onDiskChangesForbidden = changesForbidden;
}
QString CodeRepresentation::artificialPath(const QString& name)
{
KUrl url = KUrl::fromPath(name);
url.cleanPath();
return "/kdev-artificial/" + url.path();
}
InsertArtificialCodeRepresentation::InsertArtificialCodeRepresentation(const IndexedString& file,
const QString& text)
: m_file(file)
{
if(m_file.toUrl().isRelative())
{
m_file = IndexedString(CodeRepresentation::artificialPath(file.str()));
int idx = 0;
while(artificialStrings.contains(m_file))
{
++idx;
m_file = IndexedString(CodeRepresentation::artificialPath(QString("%1_%2").arg(idx).arg(file.str())));
}
}
Q_ASSERT(!artificialStrings.contains(m_file));
artificialStrings.insert(m_file, KSharedPtr<ArtificialStringData>(new ArtificialStringData(text)));
}
IndexedString InsertArtificialCodeRepresentation::file()
{
return m_file;
}
InsertArtificialCodeRepresentation::~InsertArtificialCodeRepresentation() {
Q_ASSERT(artificialStrings.contains(m_file));
artificialStrings.remove(m_file);
}
void InsertArtificialCodeRepresentation::setText(const QString& text) {
Q_ASSERT(artificialStrings.contains(m_file));
artificialStrings[m_file]->setData(text);
}
QString InsertArtificialCodeRepresentation::text() {
Q_ASSERT(artificialStrings.contains(m_file));
return artificialStrings[m_file]->data();
}
}