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"