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 }