File indexing completed on 2024-04-28 05:49:01

0001 /*
0002     SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "lspsemantichighlighting.h"
0007 #include "lspclientprotocol.h"
0008 #include "lspclientservermanager.h"
0009 #include "semantic_tokens_legend.h"
0010 
0011 #include <KTextEditor/Document>
0012 #include <KTextEditor/View>
0013 
0014 #include <ktexteditor_utils.h>
0015 
0016 SemanticHighlighter::SemanticHighlighter(std::shared_ptr<LSPClientServerManager> serverManager, QObject *parent)
0017     : QObject(parent)
0018     , m_serverManager(std::move(serverManager))
0019 {
0020     m_requestTimer.setSingleShot(true);
0021     m_requestTimer.connect(&m_requestTimer, &QTimer::timeout, this, [this]() {
0022         doSemanticHighlighting_impl(m_currentView);
0023     });
0024 }
0025 
0026 void SemanticHighlighter::doSemanticHighlighting(KTextEditor::View *view, bool textChanged)
0027 {
0028     // start the timer
0029     // We dont send the request directly because then there can be too many requests
0030     // which leads to too much load on the server and client/server getting out of sync.
0031     m_currentView = view;
0032     if (textChanged) {
0033         m_requestTimer.start(1000);
0034     } else {
0035         // This is not a textChange, its either the user scrolled or view changed etc
0036         m_requestTimer.start(1);
0037     }
0038 }
0039 
0040 void SemanticHighlighter::doSemanticHighlighting_impl(KTextEditor::View *view)
0041 {
0042     if (!view) {
0043         return;
0044     }
0045 
0046     auto server = m_serverManager->findServer(view);
0047     if (!server) {
0048         return;
0049     }
0050 
0051     const auto &caps = server->capabilities();
0052     const bool serverSupportsSemHighlighting = caps.semanticTokenProvider.full || caps.semanticTokenProvider.fullDelta || caps.semanticTokenProvider.range;
0053     if (!serverSupportsSemHighlighting) {
0054         return;
0055     }
0056 
0057     auto doc = view->document();
0058     if (m_docResultId.count(doc) == 0) {
0059         connect(doc, &KTextEditor::Document::aboutToInvalidateMovingInterfaceContent, this, &SemanticHighlighter::remove, Qt::UniqueConnection);
0060         connect(doc, &KTextEditor::Document::aboutToDeleteMovingInterfaceContent, this, &SemanticHighlighter::remove, Qt::UniqueConnection);
0061     }
0062 
0063     if (caps.semanticTokenProvider.range) {
0064         connect(view, &KTextEditor::View::verticalScrollPositionChanged, this, &SemanticHighlighter::semanticHighlightRange, Qt::UniqueConnection);
0065     }
0066 
0067     //  m_semHighlightingManager.setTypes(server->capabilities().semanticTokenProvider.types);
0068 
0069     QPointer<KTextEditor::View> v = view;
0070     auto h = [this, v, server](const LSPSemanticTokensDelta &st) {
0071         if (v && server) {
0072             const auto legend = &server->capabilities().semanticTokenProvider.legend;
0073             processTokens(st, v, legend);
0074         }
0075     };
0076 
0077     if (caps.semanticTokenProvider.range) {
0078         auto visibleRange = Utils::getVisibleRange(view);
0079         if (visibleRange.start().line() > 8) {
0080             auto s = visibleRange.start();
0081             s.setLine(s.line() - 8);
0082             visibleRange.setStart(s);
0083         }
0084         if (visibleRange.end().line() + 8 < view->document()->lines()) {
0085             auto e = visibleRange.end();
0086             e.setLine(e.line() + 8);
0087             e.setColumn(view->document()->lineLength(e.line()));
0088             visibleRange.setEnd(e);
0089         }
0090         m_currentHighlightedRange = visibleRange;
0091         server->documentSemanticTokensRange(doc->url(), visibleRange, this, h);
0092     } else if (caps.semanticTokenProvider.fullDelta) {
0093         auto prevResultId = previousResultIdForDoc(doc);
0094         server->documentSemanticTokensFullDelta(doc->url(), prevResultId, this, h);
0095     } else {
0096         server->documentSemanticTokensFull(doc->url(), QString(), this, h);
0097     }
0098 }
0099 
0100 void SemanticHighlighter::semanticHighlightRange(KTextEditor::View *view, const KTextEditor::Cursor &)
0101 {
0102     const auto range = Utils::getVisibleRange(view);
0103     if (m_currentHighlightedRange.contains(range)) {
0104         return;
0105     }
0106 
0107     doSemanticHighlighting(view, false);
0108 }
0109 
0110 QString SemanticHighlighter::previousResultIdForDoc(KTextEditor::Document *doc) const
0111 {
0112     auto it = m_docResultId.find(doc);
0113     if (it != m_docResultId.end()) {
0114         return it->second;
0115     }
0116     return QString();
0117 }
0118 
0119 void SemanticHighlighter::processTokens(const LSPSemanticTokensDelta &tokens, KTextEditor::View *view, const SemanticTokensLegend *legend)
0120 {
0121     Q_ASSERT(view);
0122 
0123     for (const auto &semTokenEdit : tokens.edits) {
0124         update(view->document(), tokens.resultId, semTokenEdit.start, semTokenEdit.deleteCount, semTokenEdit.data);
0125     }
0126 
0127     if (!tokens.data.empty()) {
0128         insert(view->document(), tokens.resultId, tokens.data);
0129     }
0130     highlight(view, legend);
0131 }
0132 
0133 void SemanticHighlighter::remove(KTextEditor::Document *doc)
0134 {
0135     m_docResultId.erase(doc);
0136     m_docSemanticInfo.erase(doc);
0137 }
0138 
0139 void SemanticHighlighter::insert(KTextEditor::Document *doc, const QString &resultId, const std::vector<uint32_t> &data)
0140 {
0141     m_docResultId[doc] = resultId;
0142     TokensData &tokensData = m_docSemanticInfo[doc];
0143     tokensData.tokens = data;
0144 }
0145 
0146 /**
0147  * Handle semantic tokens edits
0148  */
0149 void SemanticHighlighter::update(KTextEditor::Document *doc, const QString &resultId, uint32_t start, uint32_t deleteCount, const std::vector<uint32_t> &data)
0150 {
0151     auto toks = m_docSemanticInfo.find(doc);
0152     if (toks == m_docSemanticInfo.end()) {
0153         return;
0154     }
0155 
0156     auto &existingTokens = toks->second.tokens;
0157 
0158     // replace
0159     if (deleteCount > 0) {
0160         existingTokens.erase(existingTokens.begin() + start, existingTokens.begin() + start + deleteCount);
0161     }
0162     existingTokens.insert(existingTokens.begin() + start, data.begin(), data.end());
0163 
0164     //     Update result Id
0165     m_docResultId[doc] = resultId;
0166 }
0167 
0168 void SemanticHighlighter::highlight(KTextEditor::View *view, const SemanticTokensLegend *legend)
0169 {
0170     Q_ASSERT(legend);
0171 
0172     if (!view || !legend) {
0173         return;
0174     }
0175     auto doc = view->document();
0176     TokensData &semanticData = m_docSemanticInfo[doc];
0177     auto &movingRanges = semanticData.movingRanges;
0178     auto &data = semanticData.tokens;
0179 
0180     if (data.size() % 5 != 0) {
0181         qWarning() << "Bad data for doc: " << doc->url() << " skipping";
0182         return;
0183     }
0184 
0185     uint32_t currentLine = 0;
0186     uint32_t start = 0;
0187     auto oldRanges = std::move(movingRanges);
0188 
0189     for (size_t i = 0; i < data.size(); i += 5) {
0190         const uint32_t deltaLine = data[i];
0191         const uint32_t deltaStart = data[i + 1];
0192         const uint32_t len = data[i + 2];
0193         const uint32_t type = data[i + 3];
0194         // auto mod = data[i + 4];
0195 
0196         currentLine += deltaLine;
0197 
0198         if (deltaLine == 0) {
0199             start += deltaStart;
0200         } else {
0201             start = deltaStart;
0202         }
0203 
0204         // QString text = doc->line(currentLine);
0205         // text = text.mid(start, len);
0206 
0207         auto attribute = legend->attributeForTokenType(type);
0208         if (!attribute) {
0209             continue;
0210         }
0211 
0212         KTextEditor::Range r(currentLine, start, currentLine, start + len);
0213         using MovingRangePtr = std::unique_ptr<KTextEditor::MovingRange>;
0214         // Check if we have a moving range for 'r' already available
0215         auto it = std::lower_bound(oldRanges.begin(), oldRanges.end(), r, [](const MovingRangePtr &mr, KTextEditor::Range r) {
0216             // null range is considered less
0217             // it can be null because we 'move' the range from oldRanges whenever we find a matching
0218             return !mr || mr->toRange() < r;
0219         });
0220 
0221         MovingRangePtr range;
0222         if (it != oldRanges.end() && (*it) && (*it)->toRange() == r && (*it)->attribute() == attribute.constData()) {
0223             range = std::move(*it);
0224         } else {
0225             range.reset(doc->newMovingRange(r));
0226             range->setZDepth(-91000.0);
0227             range->setRange(r);
0228             range->setAttribute(std::move(attribute));
0229         }
0230         movingRanges.push_back(std::move(range));
0231     }
0232 }
0233 
0234 #include "moc_lspsemantichighlighting.cpp"