File indexing completed on 2024-04-28 05:49:01
0001 /* 0002 SPDX-FileCopyrightText: 2019 Mark Nauwelaerts <mark.nauwelaerts@gmail.com> 0003 SPDX-FileCopyrightText: 2019 Christoph Cullmann <cullmann@kde.org> 0004 0005 SPDX-License-Identifier: MIT 0006 */ 0007 0008 #include "lspclientsymbolview.h" 0009 0010 #include <KFuzzyMatcher> 0011 #include <KLineEdit> 0012 #include <KLocalizedString> 0013 #include <QSortFilterProxyModel> 0014 0015 #include <KTextEditor/Document> 0016 #include <KTextEditor/Editor> 0017 #include <KTextEditor/MainWindow> 0018 #include <KTextEditor/View> 0019 0020 #include <QFrame> 0021 #include <QHBoxLayout> 0022 #include <QHeaderView> 0023 #include <QIdentityProxyModel> 0024 #include <QMenu> 0025 #include <QPointer> 0026 #include <QStandardItemModel> 0027 #include <QTimer> 0028 #include <QTreeView> 0029 0030 #include <drawing_utils.h> 0031 #include <memory> 0032 #include <utility> 0033 0034 // TODO: Make this globally available in shared/ 0035 enum SymbolViewRoles { SymbolRange = Qt::UserRole, ScoreRole, IsPlaceholder }; 0036 0037 class LSPClientViewTrackerImpl : public LSPClientViewTracker 0038 { 0039 Q_OBJECT 0040 0041 typedef LSPClientViewTrackerImpl self_type; 0042 0043 LSPClientPlugin *m_plugin; 0044 KTextEditor::MainWindow *m_mainWindow; 0045 // timers to delay some todo's 0046 QTimer m_changeTimer; 0047 int m_change; 0048 QTimer m_motionTimer; 0049 int m_motion; 0050 int m_oldCursorLine = -1; 0051 0052 public: 0053 LSPClientViewTrackerImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, int change_ms, int motion_ms) 0054 : m_plugin(plugin) 0055 , m_mainWindow(mainWin) 0056 , m_change(change_ms) 0057 , m_motion(motion_ms) 0058 { 0059 Q_UNUSED(m_plugin); 0060 // get updated 0061 m_changeTimer.setSingleShot(true); 0062 auto ch = [this]() { 0063 Q_EMIT newState(m_mainWindow->activeView(), TextChanged); 0064 }; 0065 connect(&m_changeTimer, &QTimer::timeout, this, ch); 0066 0067 m_motionTimer.setSingleShot(true); 0068 auto mh = [this]() { 0069 Q_EMIT newState(m_mainWindow->activeView(), LineChanged); 0070 }; 0071 connect(&m_motionTimer, &QTimer::timeout, this, mh); 0072 0073 // track views 0074 connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &self_type::viewChanged); 0075 } 0076 0077 void viewChanged(KTextEditor::View *view) 0078 { 0079 m_motionTimer.stop(); 0080 m_changeTimer.stop(); 0081 0082 if (view) { 0083 if (m_motion) { 0084 connect(view, &KTextEditor::View::cursorPositionChanged, this, &self_type::cursorPositionChanged, Qt::UniqueConnection); 0085 } 0086 if (m_change > 0 && view->document()) { 0087 connect(view->document(), &KTextEditor::Document::textChanged, this, &self_type::textChanged, Qt::UniqueConnection); 0088 } 0089 Q_EMIT newState(view, ViewChanged); 0090 m_oldCursorLine = view->cursorPosition().line(); 0091 } 0092 } 0093 0094 void textChanged() 0095 { 0096 m_motionTimer.stop(); 0097 m_changeTimer.start(m_change); 0098 } 0099 0100 void cursorPositionChanged(KTextEditor::View *view, const KTextEditor::Cursor &newPosition) 0101 { 0102 if (m_changeTimer.isActive()) { 0103 // change trumps motion 0104 return; 0105 } 0106 0107 if (view && newPosition.line() != m_oldCursorLine) { 0108 m_oldCursorLine = newPosition.line(); 0109 m_motionTimer.start(m_motion); 0110 } 0111 } 0112 }; 0113 0114 LSPClientViewTracker *LSPClientViewTracker::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, int change_ms, int motion_ms) 0115 { 0116 return new LSPClientViewTrackerImpl(plugin, mainWin, change_ms, motion_ms); 0117 } 0118 0119 class LSPClientSymbolViewFilterProxyModel : public QSortFilterProxyModel 0120 { 0121 public: 0122 LSPClientSymbolViewFilterProxyModel(QObject *parent = nullptr) 0123 : QSortFilterProxyModel(parent) 0124 { 0125 } 0126 0127 void setFilterString(const QString &string) 0128 { 0129 beginResetModel(); 0130 m_pattern = string; 0131 endResetModel(); 0132 } 0133 0134 protected: 0135 bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override 0136 { 0137 // make sure to honour configured sort-order (if no scoring applies) 0138 if (m_pattern.isEmpty()) { 0139 return QSortFilterProxyModel::lessThan(sourceLeft, sourceRight); 0140 } 0141 0142 const int l = sourceLeft.data(SymbolViewRoles::ScoreRole).toInt(); 0143 const int r = sourceRight.data(SymbolViewRoles::ScoreRole).toInt(); 0144 return l < r; 0145 } 0146 0147 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override 0148 { 0149 if (m_pattern.isEmpty()) { 0150 return true; 0151 } 0152 0153 const auto idx = sourceModel()->index(sourceRow, 0, sourceParent); 0154 const QString symbol = idx.data().toString(); 0155 const auto res = KFuzzyMatcher::match(m_pattern, symbol); 0156 sourceModel()->setData(idx, res.score, SymbolViewRoles::ScoreRole); 0157 return res.matched; 0158 } 0159 0160 private: 0161 QString m_pattern; 0162 }; 0163 0164 class SymbolViewProxyModel : public QIdentityProxyModel 0165 { 0166 Q_OBJECT 0167 public: 0168 using QIdentityProxyModel::QIdentityProxyModel; 0169 0170 int columnCount(const QModelIndex &) const override 0171 { 0172 return 1; 0173 } 0174 }; 0175 0176 /* 0177 * Instantiates and manages the symbol outline toolview. 0178 */ 0179 class LSPClientSymbolViewImpl : public QObject, public LSPClientSymbolView 0180 { 0181 Q_OBJECT 0182 0183 typedef LSPClientSymbolViewImpl self_type; 0184 0185 LSPClientPlugin *m_plugin; 0186 KTextEditor::MainWindow *m_mainWindow; 0187 std::shared_ptr<LSPClientServerManager> m_serverManager; 0188 std::unique_ptr<QWidget> m_toolview; 0189 // parent ownership 0190 QPointer<QTreeView> m_symbols; 0191 QPointer<KLineEdit> m_filter; 0192 std::unique_ptr<QMenu> m_popup; 0193 // initialized/updated from plugin settings 0194 // managed by context menu later on 0195 // parent ownership 0196 QAction *m_detailsOn; 0197 QAction *m_expandOn; 0198 QAction *m_treeOn; 0199 QAction *m_sortOn; 0200 // view tracking 0201 std::unique_ptr<LSPClientViewTracker> m_viewTracker; 0202 // outstanding request 0203 LSPClientServer::RequestHandle m_handle; 0204 // magic request tracking cookie 0205 int m_requestCnt = 0; 0206 // cached outline models 0207 struct ModelData { 0208 QPointer<KTextEditor::Document> document; 0209 qint64 revision; 0210 std::shared_ptr<QStandardItemModel> model; 0211 }; 0212 QList<ModelData> m_models; 0213 // max number to cache 0214 static constexpr int MAX_MODELS = 10; 0215 // last outline model we constructed 0216 std::shared_ptr<QStandardItemModel> m_outline; 0217 // filter model, setup once 0218 LSPClientSymbolViewFilterProxyModel m_filterModel; 0219 0220 SymbolViewProxyModel *m_identityModel; 0221 0222 // cached icons for model 0223 QIcon m_icon_pkg = QIcon::fromTheme(QStringLiteral("code-block")); 0224 QIcon m_icon_class = QIcon::fromTheme(QStringLiteral("code-class")); 0225 QIcon m_icon_typedef = QIcon::fromTheme(QStringLiteral("code-typedef")); 0226 QIcon m_icon_function = QIcon::fromTheme(QStringLiteral("code-function")); 0227 QIcon m_icon_var = QIcon::fromTheme(QStringLiteral("code-variable")); 0228 0229 public: 0230 LSPClientSymbolViewImpl(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, std::shared_ptr<LSPClientServerManager> manager) 0231 : m_plugin(plugin) 0232 , m_mainWindow(mainWin) 0233 , m_serverManager(std::move(manager)) 0234 , m_outline(new QStandardItemModel()) 0235 , m_identityModel(new SymbolViewProxyModel(this)) 0236 { 0237 m_toolview.reset(m_mainWindow->createToolView(plugin, 0238 QStringLiteral("lspclient_symbol_outline"), 0239 KTextEditor::MainWindow::Left, 0240 QIcon::fromTheme(QStringLiteral("quickopen-class")), 0241 i18n("Symbol Outline"))); 0242 0243 m_symbols = new QTreeView(m_toolview.get()); 0244 m_symbols->setFocusPolicy(Qt::NoFocus); 0245 m_symbols->setLayoutDirection(Qt::LeftToRight); 0246 m_toolview->layout()->setContentsMargins(0, 0, 0, 0); 0247 0248 auto separator = new QFrame(m_toolview.get()); 0249 separator->setFrameShape(QFrame::HLine); 0250 separator->setEnabled(false); 0251 m_toolview->layout()->addWidget(separator); 0252 0253 m_toolview->layout()->addWidget(m_symbols); 0254 m_toolview->layout()->setSpacing(0); 0255 0256 // setup filter line edit 0257 m_filter = new KLineEdit(m_toolview.get()); 0258 m_toolview->layout()->addWidget(m_filter); 0259 m_filter->setPlaceholderText(i18n("Filter...")); 0260 m_filter->setClearButtonEnabled(true); 0261 m_filter->setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags{Qt::TopEdge})); 0262 connect(m_filter, &KLineEdit::textChanged, this, &self_type::filterTextChanged); 0263 0264 m_symbols->setContextMenuPolicy(Qt::CustomContextMenu); 0265 m_symbols->setIndentation(10); 0266 m_symbols->setEditTriggers(QAbstractItemView::NoEditTriggers); 0267 m_symbols->setAllColumnsShowFocus(true); 0268 0269 // init filter model once, later we only swap the source model! 0270 QItemSelectionModel *m = m_symbols->selectionModel(); 0271 m_filterModel.setFilterCaseSensitivity(Qt::CaseInsensitive); 0272 m_filterModel.setSortCaseSensitivity(Qt::CaseInsensitive); 0273 m_filterModel.setSourceModel(m_outline.get()); 0274 m_filterModel.setRecursiveFilteringEnabled(true); 0275 m_symbols->setModel(&m_filterModel); 0276 delete m; 0277 0278 m_identityModel->setSourceModel(m_outline.get()); 0279 0280 connect(m_symbols, &QTreeView::customContextMenuRequested, this, &self_type::showContextMenu); 0281 connect(m_symbols, &QTreeView::activated, this, &self_type::goToSymbol); 0282 connect(m_symbols, &QTreeView::clicked, this, &self_type::goToSymbol); 0283 0284 // context menu 0285 m_popup.reset(new QMenu(m_symbols)); 0286 m_treeOn = m_popup->addAction(i18n("Tree Mode"), this, &self_type::displayOptionChanged); 0287 m_treeOn->setCheckable(true); 0288 m_expandOn = m_popup->addAction(i18n("Automatically Expand Tree"), this, &self_type::displayOptionChanged); 0289 m_expandOn->setCheckable(true); 0290 m_sortOn = m_popup->addAction(i18n("Sort Alphabetically"), this, &self_type::displayOptionChanged); 0291 m_sortOn->setCheckable(true); 0292 m_detailsOn = m_popup->addAction(i18n("Show Details"), this, &self_type::displayOptionChanged); 0293 m_detailsOn->setCheckable(true); 0294 m_popup->addSeparator(); 0295 m_popup->addAction(i18n("Expand All"), m_symbols.data(), &QTreeView::expandAll); 0296 m_popup->addAction(i18n("Collapse All"), m_symbols.data(), &QTreeView::collapseAll); 0297 0298 // sync with plugin settings if updated 0299 connect(m_plugin, &LSPClientPlugin::update, this, &self_type::configUpdated); 0300 0301 // get updated 0302 m_viewTracker.reset(LSPClientViewTracker::new_(plugin, mainWin, 500, 100)); 0303 connect(m_viewTracker.get(), &LSPClientViewTracker::newState, this, &self_type::onViewState); 0304 connect(m_serverManager.get(), &LSPClientServerManager::serverChanged, this, [this]() { 0305 refresh(false, false); 0306 }); 0307 0308 // limit cached models; will not go beyond capacity set here 0309 m_models.reserve(MAX_MODELS + 1); 0310 0311 // recolor icons 0312 QObject::connect(KTextEditor::Editor::instance(), &KTextEditor::Editor::configChanged, this, [this](KTextEditor::Editor *e) { 0313 colorIcons(e); 0314 }); 0315 colorIcons(KTextEditor::Editor::instance()); 0316 0317 // initial trigger of symbols view update 0318 configUpdated(); 0319 } 0320 0321 void displayOptionChanged() 0322 { 0323 m_expandOn->setEnabled(m_treeOn->isChecked()); 0324 refresh(false, false); 0325 } 0326 0327 void configUpdated() 0328 { 0329 m_treeOn->setChecked(m_plugin->m_symbolTree); 0330 m_detailsOn->setChecked(m_plugin->m_symbolDetails); 0331 m_expandOn->setChecked(m_plugin->m_symbolExpand); 0332 m_sortOn->setChecked(m_plugin->m_symbolSort); 0333 displayOptionChanged(); 0334 } 0335 0336 void showContextMenu(const QPoint &pos) 0337 { 0338 m_popup->popup(m_symbols->viewport()->mapToGlobal(pos), m_treeOn); 0339 } 0340 0341 void onViewState(KTextEditor::View *, LSPClientViewTracker::State newState) 0342 { 0343 switch (newState) { 0344 case LSPClientViewTracker::ViewChanged: 0345 refresh(true); 0346 break; 0347 case LSPClientViewTracker::TextChanged: 0348 refresh(false); 0349 break; 0350 case LSPClientViewTracker::LineChanged: 0351 updateCurrentTreeItem(); 0352 break; 0353 } 0354 } 0355 0356 void makeNodes(const std::list<LSPSymbolInformation> &symbols, bool tree, bool show_detail, QStandardItemModel *model, QStandardItem *parent, bool &details) 0357 { 0358 const QIcon *icon = nullptr; 0359 for (const auto &symbol : symbols) { 0360 switch (symbol.kind) { 0361 case LSPSymbolKind::File: 0362 case LSPSymbolKind::Module: 0363 case LSPSymbolKind::Namespace: 0364 case LSPSymbolKind::Package: 0365 if (symbol.children.empty()) { 0366 continue; 0367 } 0368 icon = &m_icon_pkg; 0369 break; 0370 case LSPSymbolKind::Class: 0371 case LSPSymbolKind::Interface: 0372 icon = &m_icon_class; 0373 break; 0374 case LSPSymbolKind::Enum: 0375 icon = &m_icon_typedef; 0376 break; 0377 case LSPSymbolKind::Method: 0378 case LSPSymbolKind::Function: 0379 case LSPSymbolKind::Constructor: 0380 icon = &m_icon_function; 0381 break; 0382 // all others considered/assumed Variable 0383 case LSPSymbolKind::Variable: 0384 case LSPSymbolKind::Constant: 0385 case LSPSymbolKind::String: 0386 case LSPSymbolKind::Number: 0387 case LSPSymbolKind::Property: 0388 case LSPSymbolKind::Field: 0389 default: 0390 // skip local variable 0391 // property, field, etc unlikely in such case anyway 0392 if (parent && parent->icon().cacheKey() == m_icon_function.cacheKey()) { 0393 continue; 0394 } 0395 icon = &m_icon_var; 0396 } 0397 0398 auto node = new QStandardItem(); 0399 auto line = new QStandardItem(); 0400 if (parent && tree) { 0401 parent->appendRow({node, line}); 0402 } else { 0403 model->appendRow({node, line}); 0404 } 0405 0406 if (!symbol.detail.isEmpty()) { 0407 details = true; 0408 } 0409 auto detail = show_detail && !symbol.detail.isEmpty() ? QStringLiteral(" [%1]").arg(symbol.detail) : QString(); 0410 node->setText(symbol.name + detail); 0411 node->setIcon(*icon); 0412 node->setData(QVariant::fromValue<KTextEditor::Range>(symbol.range), SymbolViewRoles::SymbolRange); 0413 static const QChar prefix = QChar::fromLatin1('0'); 0414 line->setText(QStringLiteral("%1").arg(symbol.range.start().line(), 7, 10, prefix)); 0415 // recurse children 0416 makeNodes(symbol.children, tree, show_detail, model, node, details); 0417 } 0418 } 0419 0420 void onDocumentSymbols(const std::list<LSPSymbolInformation> &outline) 0421 { 0422 onDocumentSymbolsOrProblem(outline, QString(), true); 0423 } 0424 0425 void onDocumentSymbolsOrProblem(const std::list<LSPSymbolInformation> &outline, const QString &problem = QString(), bool cache = false) 0426 { 0427 if (!m_symbols) { 0428 return; 0429 } 0430 0431 // construct new model for data 0432 auto newModel = std::make_shared<QStandardItemModel>(); 0433 0434 // if we have some problem, just report that, else construct model 0435 bool details = false; 0436 if (problem.isEmpty()) { 0437 makeNodes(outline, m_treeOn->isChecked(), m_detailsOn->isChecked(), newModel.get(), nullptr, details); 0438 if (cache) { 0439 // last request has been placed at head of model list 0440 Q_ASSERT(!m_models.isEmpty()); 0441 m_models[0].model = newModel; 0442 } 0443 } else { 0444 auto item = new QStandardItem(problem); 0445 item->setData(true, SymbolViewRoles::IsPlaceholder); 0446 newModel->appendRow(item); 0447 } 0448 0449 // cache detail info with model 0450 newModel->invisibleRootItem()->setData(details); 0451 0452 // fixup headers 0453 QStringList headers{i18n("Symbols")}; 0454 newModel->setHorizontalHeaderLabels(headers); 0455 0456 setModel(newModel); 0457 } 0458 0459 void setModel(const std::shared_ptr<QStandardItemModel> &newModel) 0460 { 0461 Q_ASSERT(newModel); 0462 0463 // update filter model, do this before the assignment below deletes the old model! 0464 m_filterModel.setSourceModel(newModel.get()); 0465 0466 // delete old outline if there, keep our new one alive 0467 m_outline = newModel; 0468 0469 // fixup sorting 0470 if (m_sortOn->isChecked()) { 0471 m_symbols->setSortingEnabled(true); 0472 m_symbols->sortByColumn(0, Qt::AscendingOrder); 0473 m_symbols->header()->setSectionsClickable(true); 0474 } else { 0475 // most servers provide items in reasonable file/input order 0476 // however sadly not all, so let's sort by hidden line number column to make sure 0477 m_symbols->setSortingEnabled(true); 0478 m_symbols->sortByColumn(1, Qt::AscendingOrder); 0479 m_symbols->header()->setSectionsClickable(false); 0480 } 0481 // no need to show internal info 0482 m_symbols->setColumnHidden(1, true); 0483 0484 // handle auto-expansion 0485 if (m_expandOn->isChecked()) { 0486 m_symbols->expandAll(); 0487 } 0488 0489 // recover detail info from model data 0490 bool details = newModel->invisibleRootItem()->data().toBool(); 0491 0492 // disable detail setting if no such info available 0493 // (as an indication there is nothing to show anyway) 0494 m_detailsOn->setEnabled(details); 0495 0496 // current item tracking 0497 updateCurrentTreeItem(); 0498 0499 m_identityModel->setSourceModel(m_outline.get()); 0500 } 0501 0502 void refresh(bool clear, bool allow_cache = true, int retry = 0) 0503 { 0504 // cancel old request! 0505 m_handle.cancel(); 0506 0507 // check if we have some server for the current view => trigger request 0508 auto view = m_mainWindow->activeView(); 0509 if (auto server = m_serverManager->findServer(view)) { 0510 // clear current model in any case 0511 // this avoids that we show stuff not matching the current view 0512 // but let's only do it if needed, e.g. when changing view 0513 // so as to avoid unhealthy flickering in other cases 0514 if (clear) { 0515 onDocumentSymbolsOrProblem({}, QString(), false); 0516 } 0517 0518 // check (valid) cache 0519 auto doc = view->document(); 0520 auto revision = m_serverManager->revision(doc); 0521 auto it = m_models.begin(); 0522 for (; it != m_models.end();) { 0523 if (it->document == doc) { 0524 break; 0525 } 0526 if (!it->document) { 0527 it = m_models.erase(it); 0528 continue; 0529 } 0530 ++it; 0531 } 0532 if (it != m_models.end()) { 0533 // move to most recently used head 0534 m_models.move(it - m_models.begin(), 0); 0535 auto &model = m_models.front(); 0536 // re-use if possible 0537 // reloaded document recycles revision number, so avoid stale cache 0538 // (clear := view switch) 0539 if (revision == model.revision && model.model && (clear || revision > 0) && allow_cache) { 0540 setModel(model.model); 0541 return; 0542 } 0543 it->revision = revision; 0544 } else { 0545 m_models.insert(0, {doc, revision, nullptr}); 0546 if (m_models.size() > MAX_MODELS) { 0547 m_models.pop_back(); 0548 } 0549 } 0550 0551 // a cancelled request or modified content might result in error response, 0552 // so arrange to process it as an error rather than an empty result, 0553 // since the latter would (temporarily) clear the symbol outline 0554 // and lead to flicker until the next/final request has a proper result again 0555 auto oldRequestCnt = ++m_requestCnt; 0556 auto eh = [this, clear, retry, oldRequestCnt](const LSPResponseError &err) { 0557 switch (err.code) { 0558 case LSPErrorCode::ContentModified: 0559 case LSPErrorCode::RequestCancelled: 0560 break; 0561 default: 0562 // also try to avoid flicker here 0563 // never mind the request if another one has already been launched 0564 // but if this is the last request standing, go for retry 0565 if (m_requestCnt == oldRequestCnt) { 0566 if (retry < 4) { 0567 // if we got here, cache was not used 0568 refresh(clear, false, retry + 1); 0569 } else { 0570 // clear old/stale situation and show that the server has lost track 0571 onDocumentSymbols({}); 0572 } 0573 } 0574 break; 0575 } 0576 }; 0577 0578 m_handle = server->documentSymbols(doc->url(), this, utils::mem_fun(&self_type::onDocumentSymbols, this), eh); 0579 0580 return; 0581 } 0582 0583 // else: inform that no server is there 0584 onDocumentSymbolsOrProblem({}, i18n("No LSP server for this document.")); 0585 } 0586 0587 // returns (covering item, closest child of covering item after line) 0588 // if distance non-null, then (output) *distance: 0589 // = irrelevant if first element of return value is non-null 0590 // < 0 if line is after item range 0591 // > 0 if line is before item range = distance from line to item range start 0592 std::pair<QStandardItem *, QStandardItem *> getCurrentItem(QStandardItem *item, int line, int *distance = nullptr) 0593 { 0594 // first traverse the child items to have deepest match! 0595 // only do this if our stuff is expanded 0596 QStandardItem *minItem = nullptr; 0597 if (item == m_outline->invisibleRootItem() || m_symbols->isExpanded(m_filterModel.mapFromSource(m_outline->indexFromItem(item)))) { 0598 int minDistance = std::numeric_limits<int>::max(); 0599 for (int i = 0; i < item->rowCount(); i++) { 0600 int dist = 0; 0601 auto child = item->child(i); 0602 auto citem = getCurrentItem(child, line, &dist); 0603 if (citem.first) { 0604 return citem; 0605 } else if (dist > 0 && dist < minDistance) { 0606 minDistance = dist; 0607 minItem = child; 0608 } 0609 } 0610 } 0611 0612 // does the line match our item? 0613 auto range = item->data(SymbolViewRoles::SymbolRange).value<KTextEditor::Range>(); 0614 if (range.overlapsLine(line)) { 0615 return {item, minItem}; 0616 } else { 0617 if (distance) { 0618 auto startline = range.start().line(); 0619 *distance = line < startline ? startline - line : -1; 0620 } 0621 return {nullptr, minItem}; 0622 } 0623 } 0624 0625 void updateCurrentTreeItem() 0626 { 0627 KTextEditor::View *editView = m_mainWindow->activeView(); 0628 if (!editView || !m_symbols) { 0629 return; 0630 } 0631 0632 /** 0633 * get item if any 0634 */ 0635 auto items = getCurrentItem(m_outline->invisibleRootItem(), editView->cursorPositionVirtual().line()); 0636 if (!items.first) { 0637 return; 0638 } 0639 0640 /** 0641 * select it; 0642 * however, in a typical (e.g. class/namespace) case, the areas between (e.g. method) children 0643 * select the parent (class) as item. To aid navigation in such case, the closest (following) 0644 * child is also considered (as the parent item may in fact be quite far away in the symbol tree). 0645 */ 0646 QModelIndex coverIndex = m_filterModel.mapFromSource(m_outline->indexFromItem(items.first)); 0647 QModelIndex closestIndex = m_filterModel.mapFromSource(m_outline->indexFromItem(items.second ? items.second : items.first)); 0648 // select covering (parent) item 0649 m_symbols->selectionModel()->setCurrentIndex(coverIndex, QItemSelectionModel::Clear | QItemSelectionModel::Select); 0650 // add child to selection and move view there 0651 m_symbols->selectionModel()->setCurrentIndex(closestIndex, QItemSelectionModel::Select); 0652 m_symbols->scrollTo(closestIndex); 0653 } 0654 0655 void goToSymbol(const QModelIndex &index) 0656 { 0657 KTextEditor::View *kv = m_mainWindow->activeView(); 0658 const auto range = index.data(SymbolViewRoles::SymbolRange).value<KTextEditor::Range>(); 0659 if (kv && range.isValid()) { 0660 kv->setCursorPosition(range.start()); 0661 } 0662 } 0663 0664 QAbstractItemModel *documentSymbolsModel() override 0665 { 0666 return m_identityModel; 0667 } 0668 0669 private: 0670 void colorIcons(KTextEditor::Editor *e) 0671 { 0672 using KSyntaxHighlighting::Theme; 0673 auto theme = e->theme(); 0674 auto varColor = QColor::fromRgba(theme.textColor(Theme::Variable)); 0675 m_icon_var = Utils::colorIcon(m_icon_var, varColor); 0676 0677 auto typeColor = QColor::fromRgba(theme.textColor(Theme::DataType)); 0678 m_icon_class = Utils::colorIcon(m_icon_class, typeColor); 0679 0680 auto enColor = QColor::fromRgba(theme.textColor(Theme::Constant)); 0681 m_icon_typedef = Utils::colorIcon(m_icon_typedef, enColor); 0682 0683 auto funcColor = QColor::fromRgba(theme.textColor(Theme::Function)); 0684 m_icon_function = Utils::colorIcon(m_icon_function, funcColor); 0685 0686 auto blockColor = QColor::fromRgba(theme.textColor(Theme::Import)); 0687 m_icon_pkg = Utils::colorIcon(m_icon_pkg, blockColor); 0688 } 0689 0690 private Q_SLOTS: 0691 /** 0692 * React on filter change 0693 * @param filterText new filter text 0694 */ 0695 void filterTextChanged(const QString &filterText) 0696 { 0697 if (!m_symbols) { 0698 return; 0699 } 0700 0701 /** 0702 * filter 0703 */ 0704 m_filterModel.setFilterString(filterText); 0705 0706 /** 0707 * expand 0708 */ 0709 if (!filterText.isEmpty()) { 0710 QTimer::singleShot(100, m_symbols, &QTreeView::expandAll); 0711 } 0712 } 0713 }; 0714 0715 LSPClientSymbolView *LSPClientSymbolView::new_(LSPClientPlugin *plugin, KTextEditor::MainWindow *mainWin, std::shared_ptr<LSPClientServerManager> manager) 0716 { 0717 return new LSPClientSymbolViewImpl(plugin, mainWin, std::move(manager)); 0718 } 0719 0720 LSPClientSymbolView::~LSPClientSymbolView() = default; 0721 0722 #include "lspclientsymbolview.moc" 0723 #include "moc_lspclientsymbolview.cpp"