File indexing completed on 2024-05-12 04:37:47

0001 /*
0002     SPDX-FileCopyrightText: 2008 David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "coderepresentation.h"
0008 
0009 #include <QFile>
0010 #include <KTextEditor/Document>
0011 
0012 #include <serialization/indexedstring.h>
0013 #include <interfaces/idocumentcontroller.h>
0014 #include <interfaces/icore.h>
0015 #include <editor/modificationrevision.h>
0016 
0017 namespace KDevelop {
0018 static bool onDiskChangesForbidden = false;
0019 
0020 QString CodeRepresentation::rangeText(const KTextEditor::Range& range) const
0021 {
0022     Q_ASSERT(range.end().line() < lines());
0023 
0024     //Easier for single line ranges which should happen most of the time
0025     if (range.onSingleLine())
0026         return QString(line(range.start().line()).mid(range.start().column(), range.columnWidth()));
0027 
0028     //Add up al the requested lines
0029     QString rangedText = line(range.start().line()).mid(range.start().column());
0030 
0031     for (int i = range.start().line() + 1; i <= range.end().line(); ++i)
0032         rangedText += QLatin1Char('\n') + ((i == range.end().line()) ? line(i).left(range.end().column()) : line(i));
0033 
0034     return rangedText;
0035 }
0036 
0037 static void grepLine(const QString& identifier, const QString& lineText, int lineNumber,
0038                      QVector<KTextEditor::Range>& ret, bool surroundedByBoundary)
0039 {
0040     if (identifier.isEmpty())
0041         return;
0042 
0043     int pos = 0;
0044     while (true) {
0045         pos = lineText.indexOf(identifier, pos);
0046         if (pos == -1)
0047             break;
0048         int start = pos;
0049         pos += identifier.length();
0050         int end = pos;
0051 
0052         if (!surroundedByBoundary ||
0053             ((end == lineText.length() || !lineText[end].isLetterOrNumber() || lineText[end] != QLatin1Char('_'))
0054              && (start - 1 < 0 || !lineText[start - 1].isLetterOrNumber() ||
0055                  lineText[start - 1] != QLatin1Char('_')))) {
0056             ret << KTextEditor::Range(lineNumber, start, lineNumber, end);
0057         }
0058     }
0059 }
0060 
0061 class EditorCodeRepresentation
0062     : public DynamicCodeRepresentation
0063 {
0064 public:
0065     explicit EditorCodeRepresentation(KTextEditor::Document* document) : m_document(document)
0066     {
0067         m_url = IndexedString(m_document->url());
0068     }
0069 
0070     QVector<KTextEditor::Range> grep(const QString& identifier, bool surroundedByBoundary) const override
0071     {
0072         QVector<KTextEditor::Range> ret;
0073 
0074         if (identifier.isEmpty())
0075             return ret;
0076 
0077         for (int line = 0; line < m_document->lines(); ++line)
0078             grepLine(identifier, m_document->line(line), line, ret, surroundedByBoundary);
0079 
0080         return ret;
0081     }
0082 
0083     KDevEditingTransaction::Ptr makeEditTransaction() override
0084     {
0085         return KDevEditingTransaction::Ptr(new KDevEditingTransaction(m_document));
0086     }
0087 
0088     QString line(int line) const override
0089     {
0090         if (line < 0 || line >= m_document->lines())
0091             return QString();
0092         return m_document->line(line);
0093     }
0094 
0095     int lines() const override
0096     {
0097         return m_document->lines();
0098     }
0099 
0100     QString text() const override
0101     {
0102         return m_document->text();
0103     }
0104 
0105     bool setText(const QString& text) override
0106     {
0107         bool ret;
0108         {
0109             KDevEditingTransaction t(m_document);
0110             ret = m_document->setText(text);
0111         }
0112         ModificationRevision::clearModificationCache(m_url);
0113         return ret;
0114     }
0115 
0116     bool fileExists() const override
0117     {
0118         return QFile(m_document->url().path()).exists();
0119     }
0120 
0121     bool replace(const KTextEditor::Range& range, const QString& oldText,
0122                  const QString& newText, bool ignoreOldText) override
0123     {
0124         QString old = m_document->text(range);
0125         if (oldText != old && !ignoreOldText) {
0126             return false;
0127         }
0128 
0129         bool ret;
0130         {
0131             KDevEditingTransaction t(m_document);
0132             ret = m_document->replaceText(range, newText);
0133         }
0134 
0135         ModificationRevision::clearModificationCache(m_url);
0136 
0137         return ret;
0138     }
0139 
0140     QString rangeText(const KTextEditor::Range& range) const override
0141     {
0142         return m_document->text(range);
0143     }
0144 
0145 private:
0146     KTextEditor::Document* m_document;
0147     IndexedString m_url;
0148 };
0149 
0150 class FileCodeRepresentation
0151     : public CodeRepresentation
0152 {
0153 public:
0154     explicit FileCodeRepresentation(const IndexedString& document) : m_document(document)
0155     {
0156         QString localFile(document.toUrl().toLocalFile());
0157 
0158         QFile file(localFile);
0159         if (file.open(QIODevice::ReadOnly)) {
0160             data = QString::fromLocal8Bit(file.readAll());
0161             lineData = data.split(QLatin1Char('\n'));
0162         }
0163         m_exists = file.exists();
0164     }
0165 
0166     QString line(int line) const override
0167     {
0168         if (line < 0 || line >= lineData.size())
0169             return QString();
0170 
0171         return lineData.at(line);
0172     }
0173 
0174     QVector<KTextEditor::Range> grep(const QString& identifier, bool surroundedByBoundary) const override
0175     {
0176         QVector<KTextEditor::Range> ret;
0177 
0178         if (identifier.isEmpty())
0179             return ret;
0180 
0181         for (int line = 0; line < lineData.count(); ++line)
0182             grepLine(identifier, lineData.at(line), line, ret, surroundedByBoundary);
0183 
0184         return ret;
0185     }
0186 
0187     int lines() const override
0188     {
0189         return lineData.count();
0190     }
0191 
0192     QString text() const override
0193     {
0194         return data;
0195     }
0196 
0197     bool setText(const QString& text) override
0198     {
0199         Q_ASSERT(!onDiskChangesForbidden);
0200         QString localFile(m_document.toUrl().toLocalFile());
0201 
0202         QFile file(localFile);
0203         if (file.open(QIODevice::WriteOnly)) {
0204             QByteArray data = text.toLocal8Bit();
0205 
0206             if (file.write(data) == data.size()) {
0207                 ModificationRevision::clearModificationCache(m_document);
0208                 return true;
0209             }
0210         }
0211         return false;
0212     }
0213 
0214     bool fileExists() const override
0215     {
0216         return m_exists;
0217     }
0218 
0219 private:
0220     //We use QByteArray, because the column-numbers are measured in utf-8
0221     IndexedString m_document;
0222     bool m_exists;
0223     QStringList lineData;
0224     QString data;
0225 };
0226 
0227 class ArtificialStringData
0228     : public QSharedData
0229 {
0230 public:
0231     explicit ArtificialStringData(const QString& data)
0232     {
0233         setData(data);
0234     }
0235     void setData(const QString& data)
0236     {
0237         m_data = data;
0238         m_lineData = m_data.split(QLatin1Char('\n'));
0239     }
0240     QString data() const
0241     {
0242         return m_data;
0243     }
0244     const QStringList& lines() const
0245     {
0246         return m_lineData;
0247     }
0248 
0249 private:
0250     QString m_data;
0251     QStringList m_lineData;
0252 };
0253 
0254 class StringCodeRepresentation
0255     : public CodeRepresentation
0256 {
0257 public:
0258     explicit StringCodeRepresentation(const QExplicitlySharedDataPointer<ArtificialStringData>& _data)
0259         : data(_data)
0260     {
0261         Q_ASSERT(data);
0262     }
0263 
0264     QString line(int line) const override
0265     {
0266         if (line < 0 || line >= data->lines().size())
0267             return QString();
0268 
0269         return data->lines().at(line);
0270     }
0271 
0272     int lines() const override
0273     {
0274         return data->lines().count();
0275     }
0276 
0277     QString text() const override
0278     {
0279         return data->data();
0280     }
0281 
0282     bool setText(const QString& text) override
0283     {
0284         data->setData(text);
0285         return true;
0286     }
0287 
0288     bool fileExists() const override
0289     {
0290         return false;
0291     }
0292 
0293     QVector<KTextEditor::Range> grep(const QString& identifier, bool surroundedByBoundary) const override
0294     {
0295         QVector<KTextEditor::Range> ret;
0296 
0297         if (identifier.isEmpty())
0298             return ret;
0299 
0300         for (int line = 0; line < data->lines().count(); ++line)
0301             grepLine(identifier, data->lines().at(line), line, ret, surroundedByBoundary);
0302 
0303         return ret;
0304     }
0305 
0306 private:
0307     QExplicitlySharedDataPointer<ArtificialStringData> data;
0308 };
0309 
0310 static QHash<IndexedString, QExplicitlySharedDataPointer<ArtificialStringData>> artificialStrings;
0311 
0312 //Return the representation for the given URL if it exists, or an empty pointer otherwise
0313 static QExplicitlySharedDataPointer<ArtificialStringData> representationForPath(const IndexedString& path)
0314 {
0315     const auto artificialStringIt = artificialStrings.constFind(path);
0316     if (artificialStringIt != artificialStrings.constEnd())
0317         return *artificialStringIt;
0318     else
0319     {
0320         IndexedString constructedPath(CodeRepresentation::artificialPath(path.str()));
0321         const auto artificialStringIt = artificialStrings.constFind(constructedPath);
0322         if (artificialStringIt != artificialStrings.constEnd())
0323             return *artificialStringIt;
0324         else
0325             return QExplicitlySharedDataPointer<ArtificialStringData>();
0326     }
0327 }
0328 
0329 bool artificialCodeRepresentationExists(const IndexedString& path)
0330 {
0331     return representationForPath(path);
0332 }
0333 
0334 CodeRepresentation::Ptr createCodeRepresentation(const IndexedString& path)
0335 {
0336     if (artificialCodeRepresentationExists(path))
0337         return CodeRepresentation::Ptr(new StringCodeRepresentation(representationForPath(path)));
0338 
0339     IDocument* document = ICore::self()->documentController()->documentForUrl(path.toUrl());
0340     if (document && document->textDocument())
0341         return CodeRepresentation::Ptr(new EditorCodeRepresentation(document->textDocument()));
0342     else
0343         return CodeRepresentation::Ptr(new FileCodeRepresentation(path));
0344 }
0345 
0346 void CodeRepresentation::setDiskChangesForbidden(bool changesForbidden)
0347 {
0348     onDiskChangesForbidden = changesForbidden;
0349 }
0350 
0351 QString CodeRepresentation::artificialPath(const QString& name)
0352 {
0353     QUrl url = QUrl::fromLocalFile(name);
0354     return QLatin1String("/kdev-artificial/") + url.adjusted(QUrl::NormalizePathSegments).path();
0355 }
0356 
0357 InsertArtificialCodeRepresentation::InsertArtificialCodeRepresentation(const IndexedString& file,
0358                                                                        const QString& text)
0359     : m_file(file)
0360 {
0361     // make it simpler to use this by converting relative strings into artificial paths
0362     if (QUrl(m_file.str()).isRelative()) {
0363         m_file = IndexedString(CodeRepresentation::artificialPath(file.str()));
0364 
0365         int idx = 0;
0366         while (artificialStrings.contains(m_file)) {
0367             ++idx;
0368             m_file =
0369                 IndexedString(CodeRepresentation::artificialPath(QStringLiteral("%1_%2").arg(idx).arg(file.str())));
0370         }
0371     }
0372 
0373     Q_ASSERT(!artificialStrings.contains(m_file));
0374 
0375     artificialStrings.insert(m_file,
0376                              QExplicitlySharedDataPointer<ArtificialStringData>(new ArtificialStringData(text)));
0377 }
0378 
0379 IndexedString InsertArtificialCodeRepresentation::file()
0380 {
0381     return m_file;
0382 }
0383 
0384 InsertArtificialCodeRepresentation::~InsertArtificialCodeRepresentation()
0385 {
0386     Q_ASSERT(artificialStrings.contains(m_file));
0387     artificialStrings.remove(m_file);
0388 }
0389 
0390 void InsertArtificialCodeRepresentation::setText(const QString& text)
0391 {
0392     Q_ASSERT(artificialStrings.contains(m_file));
0393     artificialStrings[m_file]->setData(text);
0394 }
0395 
0396 QString InsertArtificialCodeRepresentation::text() const
0397 {
0398     Q_ASSERT(artificialStrings.contains(m_file));
0399     return artificialStrings[m_file]->data();
0400 }
0401 }