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