1 /***************************************************************************
2 * plugin_katesymbolviewer.cpp - description
3 * -------------------
4 * begin : Apr 2 2003
5 * author : 2003 Massimo Callegari
6 * email : massimocallegari@yahoo.it
7 *
8 * Changes:
9 * Nov 09 2004 v.1.3 - For changelog please refer to KDE CVS
10 * Nov 05 2004 v.1.2 - Choose parser from the current highlight. Minor i18n changes.
11 * Nov 28 2003 v.1.1 - Structured for multilanguage support
12 * Added preliminary Tcl/Tk parser (thanks Rohit). To be improved.
13 * Various bugfixing.
14 * Jun 19 2003 v.1.0 - Removed QTimer (polling is Evil(tm)... )
15 * - Captured documentChanged() event to refresh symbol list
16 * - Tooltips vanished into nowhere...sigh :(
17 * May 04 2003 v 0.6 - Symbol List becomes a K3ListView object. Removed Tooltip class.
18 * Added a QTimer that every 200ms checks:
19 * * if the list width has changed
20 * * if the document has changed
21 * Added an entry in the m_popup menu to switch between List and Tree mode
22 * Various bugfixing.
23 * Apr 24 2003 v 0.5 - Added three check buttons in m_popup menu to show/hide symbols
24 * Apr 23 2003 v 0.4 - "View Symbol" moved in Settings menu. "Refresh List" is no
25 * longer in Kate menu. Moved into a m_popup menu activated by a
26 * mouse right button click. + Bugfixing.
27 * Apr 22 2003 v 0.3 - Added macro extraction + several bugfixing
28 * Apr 19 2003 v 0.2 - Added to CVS. Extract functions and structures
29 * Apr 07 2003 v 0.1 - First version.
30 *
31 * SPDX-FileCopyrightText: 2014, 2018 Kåre Särs <kare.sars@iki.fi>
32 *
33 ***************************************************************************/
34 /***************************************************************************
35 * *
36 * SPDX-License-Identifier: GPL-2.0-or-later
37 * *
38 ***************************************************************************/
39
40 #include "plugin_katesymbolviewer.h"
41
42 #include <KAboutData>
43 #include <KActionCollection>
44 #include <KConfigGroup>
45 #include <KPluginFactory>
46 #include <KSharedConfig>
47 #include <KToggleAction>
48 #include <KXMLGUIFactory>
49 #include <QAction>
50
51 #include <ktexteditor/configinterface.h>
52 #include <ktexteditor/cursor.h>
53
54 #include <QGroupBox>
55 #include <QVBoxLayout>
56
57 #include <QHeaderView>
58 #include <QPainter>
59
60 K_PLUGIN_FACTORY_WITH_JSON(KatePluginSymbolViewerFactory, "katesymbolviewerplugin.json", registerPlugin<KatePluginSymbolViewer>();)
61
KatePluginSymbolViewerView(KatePluginSymbolViewer * plugin,KTextEditor::MainWindow * mw)62 KatePluginSymbolViewerView::KatePluginSymbolViewerView(KatePluginSymbolViewer *plugin, KTextEditor::MainWindow *mw)
63 : QObject(mw)
64 , m_mainWindow(mw)
65 , m_plugin(plugin)
66 {
67 // FIXME KF5 KGlobal::locale()->insertCatalog("katesymbolviewerplugin");
68
69 KXMLGUIClient::setComponentName(QStringLiteral("katesymbolviewer"), i18n("SymbolViewer"));
70 setXMLFile(QStringLiteral("ui.rc"));
71
72 mw->guiFactory()->addClient(this);
73 m_symbols = nullptr;
74
75 // FIXME Let the parser decide which options are available and how they are named
76 // because not all these options are supported by all parsers
77 m_popup = new QMenu(m_symbols);
78 m_treeOn = m_popup->addAction(i18n("Tree Mode"), this, &KatePluginSymbolViewerView::displayOptionChanged);
79 m_treeOn->setCheckable(true);
80 m_expandOn = m_popup->addAction(i18n("Expand Tree"), this, &KatePluginSymbolViewerView::displayOptionChanged);
81 m_expandOn->setCheckable(true);
82 m_sort = m_popup->addAction(i18n("Show Sorted"), this, &KatePluginSymbolViewerView::displayOptionChanged);
83 m_sort->setCheckable(true);
84 m_popup->addSeparator();
85 m_macro = m_popup->addAction(i18n("Show Macros"), this, &KatePluginSymbolViewerView::displayOptionChanged);
86 m_macro->setCheckable(true);
87 m_struct = m_popup->addAction(i18n("Show Structures"), this, &KatePluginSymbolViewerView::displayOptionChanged);
88 m_struct->setCheckable(true);
89 m_func = m_popup->addAction(i18n("Show Functions"), this, &KatePluginSymbolViewerView::displayOptionChanged);
90 m_func->setCheckable(true);
91 m_typesOn = m_popup->addAction(i18n("Show Parameters"), this, &KatePluginSymbolViewerView::displayOptionChanged);
92 m_typesOn->setCheckable(true);
93
94 KConfigGroup config(KSharedConfig::openConfig(), "PluginSymbolViewer");
95 m_typesOn->setChecked(config.readEntry(QStringLiteral("ViewTypes"), false));
96 m_expandOn->setChecked(config.readEntry(QStringLiteral("ExpandTree"), false));
97 m_treeOn->setChecked(config.readEntry(QStringLiteral("TreeView"), false));
98 m_sort->setChecked(config.readEntry(QStringLiteral("SortSymbols"), false));
99
100 m_macro->setChecked(true);
101 m_struct->setChecked(true);
102 m_func->setChecked(true);
103
104 m_expandOn->setEnabled(m_treeOn->isChecked());
105 m_typesOn->setEnabled(m_func->isChecked());
106
107 m_updateTimer.setSingleShot(true);
108 connect(&m_updateTimer, &QTimer::timeout, this, &KatePluginSymbolViewerView::parseSymbols);
109
110 m_currItemTimer.setSingleShot(true);
111 connect(&m_currItemTimer, &QTimer::timeout, this, &KatePluginSymbolViewerView::updateCurrTreeItem);
112
113 QPixmap cls(class_xpm);
114
115 m_toolview = m_mainWindow->createToolView(plugin, QStringLiteral("kate_plugin_symbolviewer"), KTextEditor::MainWindow::Left, cls, i18n("Symbol List"));
116
117 QWidget *container = new QWidget(m_toolview);
118 QHBoxLayout *layout = new QHBoxLayout(container);
119
120 m_symbols = new QTreeWidget();
121 m_symbols->setFocusPolicy(Qt::NoFocus);
122 m_symbols->setLayoutDirection(Qt::LeftToRight);
123 layout->addWidget(m_symbols, 10);
124 layout->setContentsMargins(0, 0, 0, 0);
125
126 connect(m_symbols, &QTreeWidget::itemClicked, this, &KatePluginSymbolViewerView::goToSymbol);
127 connect(m_symbols, &QTreeWidget::customContextMenuRequested, this, &KatePluginSymbolViewerView::slotShowContextMenu);
128 connect(m_symbols, &QTreeWidget::itemExpanded, this, &KatePluginSymbolViewerView::updateCurrTreeItem);
129 connect(m_symbols, &QTreeWidget::itemCollapsed, this, &KatePluginSymbolViewerView::updateCurrTreeItem);
130
131 connect(m_mainWindow, &KTextEditor::MainWindow::viewChanged, this, &KatePluginSymbolViewerView::slotDocChanged);
132
133 QStringList titles;
134 titles << i18nc("@title:column", "Symbols") << i18nc("@title:column", "Position");
135 m_symbols->setColumnCount(2);
136 m_symbols->setHeaderLabels(titles);
137
138 m_symbols->setColumnHidden(1, true);
139 m_symbols->setSortingEnabled(m_sort->isChecked());
140 // Sets the default sorting order:
141 m_symbols->sortByColumn(0, Qt::AscendingOrder);
142 m_symbols->setRootIsDecorated(0);
143 m_symbols->setContextMenuPolicy(Qt::CustomContextMenu);
144 m_symbols->setIndentation(10);
145
146 m_toolview->installEventFilter(this);
147
148 // register view
149 m_plugin->m_views.insert(this);
150 }
151
~KatePluginSymbolViewerView()152 KatePluginSymbolViewerView::~KatePluginSymbolViewerView()
153 {
154 // un-register view
155 m_plugin->m_views.remove(this);
156
157 m_mainWindow->guiFactory()->removeClient(this);
158 delete m_toolview;
159 delete m_popup;
160 }
161
slotDocChanged()162 void KatePluginSymbolViewerView::slotDocChanged()
163 {
164 parseSymbols();
165
166 KTextEditor::View *view = m_mainWindow->activeView();
167 // qDebug()<<"Document changed !!!!" << view;
168 if (view) {
169 connect(view, &KTextEditor::View::cursorPositionChanged, this, &KatePluginSymbolViewerView::cursorPositionChanged, Qt::UniqueConnection);
170
171 if (view->document()) {
172 connect(view->document(), &KTextEditor::Document::textChanged, this, &KatePluginSymbolViewerView::slotDocEdited, Qt::UniqueConnection);
173 }
174 }
175 }
176
slotDocEdited()177 void KatePluginSymbolViewerView::slotDocEdited()
178 {
179 m_currItemTimer.stop(); // Avoid unneeded update
180 m_updateTimer.start(500);
181 }
182
cursorPositionChanged()183 void KatePluginSymbolViewerView::cursorPositionChanged()
184 {
185 if (m_updateTimer.isActive()) {
186 // No need for update, will come anyway
187 return;
188 }
189
190 KTextEditor::View *editView = m_mainWindow->activeView();
191 if (!editView) {
192 return;
193 }
194 int currLine = editView->cursorPositionVirtual().line();
195 if (currLine != m_oldCursorLine) {
196 m_oldCursorLine = currLine;
197 m_currItemTimer.start(100);
198 }
199 }
200
updateCurrTreeItem()201 void KatePluginSymbolViewerView::updateCurrTreeItem()
202 {
203 if (!m_mainWindow) {
204 return;
205 }
206 KTextEditor::View *editView = m_mainWindow->activeView();
207 if (!editView) {
208 return;
209 }
210 KTextEditor::Document *doc = editView->document();
211 if (!doc) {
212 return;
213 }
214
215 int currLine = editView->cursorPositionVirtual().line();
216
217 int newItemLine = 0;
218 QTreeWidgetItem *newItem = nullptr;
219 QTreeWidgetItem *tmp = nullptr;
220 for (int i = 0; i < m_symbols->topLevelItemCount(); i++) {
221 tmp = newActveItem(newItemLine, currLine, m_symbols->topLevelItem(i));
222 if (tmp) {
223 newItem = tmp;
224 }
225 }
226
227 if (!newItem) {
228 return;
229 }
230
231 // check if the item has a parent and if that parent is expanded.
232 // if the parent is not expanded, set the parent as current item in stead of
233 // expanding the tree. The tree was probably collapsed on purpose
234 QTreeWidgetItem *parent = newItem->parent();
235 if (parent && !parent->isExpanded()) {
236 newItem = parent;
237 }
238
239 m_symbols->blockSignals(true);
240 m_symbols->setCurrentItem(newItem);
241 m_symbols->blockSignals(false);
242 }
243
newActveItem(int & newItemLine,int currLine,QTreeWidgetItem * item)244 QTreeWidgetItem *KatePluginSymbolViewerView::newActveItem(int &newItemLine, int currLine, QTreeWidgetItem *item)
245 {
246 QTreeWidgetItem *newItem = nullptr;
247 QTreeWidgetItem *tmp = nullptr;
248 int itemLine = item->data(1, Qt::DisplayRole).toInt();
249 if ((itemLine <= currLine) && (itemLine >= newItemLine)) {
250 newItemLine = itemLine;
251 newItem = item;
252 }
253
254 for (int i = 0; i < item->childCount(); i++) {
255 tmp = newActveItem(newItemLine, currLine, item->child(i));
256 if (tmp) {
257 newItem = tmp;
258 }
259 }
260
261 return newItem;
262 }
263
eventFilter(QObject * obj,QEvent * event)264 bool KatePluginSymbolViewerView::eventFilter(QObject *obj, QEvent *event)
265 {
266 if (event->type() == QEvent::KeyPress) {
267 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
268 if ((obj == m_toolview) && (ke->key() == Qt::Key_Escape)) {
269 m_mainWindow->activeView()->setFocus();
270 event->accept();
271 return true;
272 }
273 }
274 return QObject::eventFilter(obj, event);
275 }
276
slotShowContextMenu(const QPoint &)277 void KatePluginSymbolViewerView::slotShowContextMenu(const QPoint &)
278 {
279 m_popup->popup(QCursor::pos(), m_treeOn);
280 }
281
282 /**
283 * Each popup menu action is connected to this slot which offer the possibility
284 * to modify the menu depended on current settings.
285 */
displayOptionChanged()286 void KatePluginSymbolViewerView::displayOptionChanged()
287 {
288 m_expandOn->setEnabled(m_treeOn->isChecked());
289 m_typesOn->setEnabled(m_func->isChecked());
290 parseSymbols();
291 }
292
parseSymbols()293 void KatePluginSymbolViewerView::parseSymbols()
294 {
295 if (!m_symbols) {
296 return;
297 }
298
299 m_symbols->clear();
300 // Qt docu recommends to populate view with disabled sorting
301 // https://doc.qt.io/qt-5/qtreeview.html#sortingEnabled-prop
302 m_symbols->setSortingEnabled(false);
303 Qt::SortOrder sortOrder = m_symbols->header()->sortIndicatorOrder();
304
305 if (!m_mainWindow->activeView()) {
306 return;
307 }
308
309 KTextEditor::Document *doc = m_mainWindow->activeView()->document();
310
311 // be sure we have some document around !
312 if (!doc) {
313 return;
314 }
315
316 /** Get the current highlighting mode */
317 QString hlModeName = doc->mode();
318
319 if (hlModeName.contains(QLatin1String("C++")) || hlModeName == QLatin1Char('C') || hlModeName == QLatin1String("ANSI C89")) {
320 parseCppSymbols();
321 } else if (hlModeName == QLatin1String("PHP (HTML)")) {
322 parsePhpSymbols();
323 } else if (hlModeName == QLatin1String("Tcl/Tk")) {
324 parseTclSymbols();
325 } else if (hlModeName.contains(QLatin1String("Fortran"))) {
326 parseFortranSymbols();
327 } else if (hlModeName == QLatin1String("Perl")) {
328 parsePerlSymbols();
329 } else if (hlModeName == QLatin1String("Python")) {
330 parsePythonSymbols();
331 } else if (hlModeName == QLatin1String("Ruby")) {
332 parseRubySymbols();
333 } else if (hlModeName == QLatin1String("Java")) {
334 parseCppSymbols();
335 } else if (hlModeName == QLatin1String("Groovy")) {
336 parseCppSymbols();
337 } else if (hlModeName == QLatin1String("xslt")) {
338 parseXsltSymbols();
339 } else if (hlModeName == QLatin1String("XML") || hlModeName == QLatin1String("HTML")) {
340 parseXMLSymbols();
341 } else if (hlModeName == QLatin1String("Bash")) {
342 parseBashSymbols();
343 } else if (hlModeName == QLatin1String("ActionScript 2.0") || hlModeName == QLatin1String("JavaScript") || hlModeName == QLatin1String("QML")) {
344 parseEcmaSymbols();
345 } else {
346 QTreeWidgetItem *node = new QTreeWidgetItem(m_symbols);
347 node->setText(0, i18n("Sorry, not supported yet!"));
348 // Setting invalid line number avoid jump to top of document when clicked
349 node->setText(1, QStringLiteral("-1"));
350 node = new QTreeWidgetItem(m_symbols);
351 node->setText(0, i18n("File type: %1", hlModeName));
352 node->setText(1, QStringLiteral("-1"));
353 }
354
355 m_oldCursorLine = -1;
356 updateCurrTreeItem();
357 if (m_sort->isChecked()) {
358 m_symbols->setSortingEnabled(true);
359 m_symbols->sortItems(0, sortOrder);
360 }
361 }
362
goToSymbol(QTreeWidgetItem * it)363 void KatePluginSymbolViewerView::goToSymbol(QTreeWidgetItem *it)
364 {
365 KTextEditor::View *kv = m_mainWindow->activeView();
366
367 // be sure we really have a view !
368 if (!kv) {
369 return;
370 }
371
372 // qDebug()<<"Slot Activated at pos: "<<m_symbols->indexOfTopLevelItem(it);
373 if (!it || it->text(1).isEmpty()) {
374 return;
375 }
376
377 kv->setCursorPosition(KTextEditor::Cursor(it->text(1).toInt(nullptr, 10), 0));
378 }
379
KatePluginSymbolViewer(QObject * parent,const QList<QVariant> &)380 KatePluginSymbolViewer::KatePluginSymbolViewer(QObject *parent, const QList<QVariant> &)
381 : KTextEditor::Plugin(parent)
382 {
383 // qDebug()<<"KatePluginSymbolViewer";
384 }
385
~KatePluginSymbolViewer()386 KatePluginSymbolViewer::~KatePluginSymbolViewer()
387 {
388 // qDebug()<<"~KatePluginSymbolViewer";
389 }
390
createView(KTextEditor::MainWindow * mainWindow)391 QObject *KatePluginSymbolViewer::createView(KTextEditor::MainWindow *mainWindow)
392 {
393 return new KatePluginSymbolViewerView(this, mainWindow);
394 }
395
configPage(int,QWidget * parent)396 KTextEditor::ConfigPage *KatePluginSymbolViewer::configPage(int, QWidget *parent)
397 {
398 KatePluginSymbolViewerConfigPage *p = new KatePluginSymbolViewerConfigPage(this, parent);
399
400 KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("PluginSymbolViewer"));
401 p->viewReturns->setChecked(config.readEntry(QStringLiteral("ViewTypes"), false));
402 p->expandTree->setChecked(config.readEntry(QStringLiteral("ExpandTree"), false));
403 p->treeView->setChecked(config.readEntry(QStringLiteral("TreeView"), false));
404 p->sortSymbols->setChecked(config.readEntry(QStringLiteral("SortSymbols"), false));
405 connect(p, &KatePluginSymbolViewerConfigPage::configPageApplyRequest, this, &KatePluginSymbolViewer::applyConfig);
406 return static_cast<KTextEditor::ConfigPage *>(p);
407 }
408
applyConfig(KatePluginSymbolViewerConfigPage * p)409 void KatePluginSymbolViewer::applyConfig(KatePluginSymbolViewerConfigPage *p)
410 {
411 KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("PluginSymbolViewer"));
412 config.writeEntry(QStringLiteral("ViewTypes"), p->viewReturns->isChecked());
413 config.writeEntry(QStringLiteral("ExpandTree"), p->expandTree->isChecked());
414 config.writeEntry(QStringLiteral("TreeView"), p->treeView->isChecked());
415 config.writeEntry(QStringLiteral("SortSymbols"), p->sortSymbols->isChecked());
416
417 for (auto view : qAsConst(m_views)) {
418 view->m_typesOn->setChecked(p->viewReturns->isChecked());
419 view->m_expandOn->setChecked(p->expandTree->isChecked());
420 view->m_treeOn->setChecked(p->treeView->isChecked());
421 view->m_sort->setChecked(p->sortSymbols->isChecked());
422
423 view->m_expandOn->setEnabled(view->m_treeOn->isChecked());
424 view->m_typesOn->setEnabled(view->m_func->isChecked());
425 }
426 }
427
428 // BEGIN KatePluginSymbolViewerConfigPage
KatePluginSymbolViewerConfigPage(QObject *,QWidget * parentWidget)429 KatePluginSymbolViewerConfigPage::KatePluginSymbolViewerConfigPage(QObject * /*parent*/ /*= 0L*/, QWidget *parentWidget /*= 0L*/)
430 : KTextEditor::ConfigPage(parentWidget)
431 {
432 QVBoxLayout *lo = new QVBoxLayout(this);
433 // int spacing = KDialog::spacingHint();
434 // lo->setSpacing( spacing );
435
436 viewReturns = new QCheckBox(i18n("Display functions parameters"));
437 expandTree = new QCheckBox(i18n("Automatically expand nodes in tree mode"));
438 treeView = new QCheckBox(i18n("Always display symbols in tree mode"));
439 sortSymbols = new QCheckBox(i18n("Always sort symbols"));
440
441 QGroupBox *parserGBox = new QGroupBox(i18n("Parser Options"), this);
442 QVBoxLayout *top = new QVBoxLayout(parserGBox);
443 top->addWidget(viewReturns);
444 top->addWidget(expandTree);
445 top->addWidget(treeView);
446 top->addWidget(sortSymbols);
447
448 // QGroupBox* generalGBox = new QGroupBox( i18n("General Options"), this);
449 // QVBoxLayout* genLay = new QVBoxLayout(generalGBox);
450 // genLay->addWidget( );
451
452 lo->addWidget(parserGBox);
453 // lo->addWidget( generalGBox );
454 lo->addStretch(1);
455
456 // throw signal changed
457 connect(viewReturns, &QCheckBox::toggled, this, &KatePluginSymbolViewerConfigPage::changed);
458 connect(expandTree, &QCheckBox::toggled, this, &KatePluginSymbolViewerConfigPage::changed);
459 connect(treeView, &QCheckBox::toggled, this, &KatePluginSymbolViewerConfigPage::changed);
460 connect(sortSymbols, &QCheckBox::toggled, this, &KatePluginSymbolViewerConfigPage::changed);
461 }
462
~KatePluginSymbolViewerConfigPage()463 KatePluginSymbolViewerConfigPage::~KatePluginSymbolViewerConfigPage()
464 {
465 }
466
name() const467 QString KatePluginSymbolViewerConfigPage::name() const
468 {
469 return i18n("Symbol Viewer");
470 }
fullName() const471 QString KatePluginSymbolViewerConfigPage::fullName() const
472 {
473 return i18n("Symbol Viewer Configuration Page");
474 }
icon() const475 QIcon KatePluginSymbolViewerConfigPage::icon() const
476 {
477 return QPixmap(class_xpm);
478 }
479
apply()480 void KatePluginSymbolViewerConfigPage::apply()
481 {
482 Q_EMIT configPageApplyRequest(this);
483 }
484 // END KatePluginSymbolViewerConfigPage
485
486 #include "plugin_katesymbolviewer.moc"
487
488 // kate: space-indent on; indent-width 2; replace-tabs on;
489