File indexing completed on 2024-04-28 15:30:52

0001 /*
0002     SPDX-FileCopyrightText: 2002, 2003, 2004 Anders Lund <anders.lund@lund.tdcadsl.dk>
0003     SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "katebookmarks.h"
0009 
0010 #include "kateabstractinputmode.h"
0011 #include "katedocument.h"
0012 #include "kateview.h"
0013 
0014 #include <KActionCollection>
0015 #include <KActionMenu>
0016 #include <KGuiItem>
0017 #include <KLocalizedString>
0018 #include <KStringHandler>
0019 #include <KToggleAction>
0020 #include <KXMLGUIClient>
0021 #include <KXMLGUIFactory>
0022 
0023 #include <QEvent>
0024 #include <QRegularExpression>
0025 #include <QVector>
0026 
0027 namespace KTextEditor
0028 {
0029 class Document;
0030 }
0031 
0032 KateBookmarks::KateBookmarks(KTextEditor::ViewPrivate *view, Sorting sort)
0033     : QObject(view)
0034     , m_view(view)
0035     , m_bookmarkClear(nullptr)
0036     , m_sorting(sort)
0037 {
0038     setObjectName(QStringLiteral("kate bookmarks"));
0039     connect(view->doc(), &KTextEditor::DocumentPrivate::marksChanged, this, &KateBookmarks::marksChanged);
0040     _tries = 0;
0041     m_bookmarksMenu = nullptr;
0042 }
0043 
0044 KateBookmarks::~KateBookmarks() = default;
0045 
0046 void KateBookmarks::createActions(KActionCollection *ac)
0047 {
0048     m_bookmarkToggle = new KToggleAction(i18n("Set &Bookmark"), this);
0049     ac->addAction(QStringLiteral("bookmarks_toggle"), m_bookmarkToggle);
0050     m_bookmarkToggle->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-new")));
0051     ac->setDefaultShortcut(m_bookmarkToggle, Qt::CTRL + Qt::Key_B);
0052     m_bookmarkToggle->setWhatsThis(i18n("If a line has no bookmark then add one, otherwise remove it."));
0053     connect(m_bookmarkToggle, &QAction::triggered, this, &KateBookmarks::toggleBookmark);
0054 
0055     m_bookmarkClear = new QAction(i18n("Clear &All Bookmarks"), this);
0056     ac->addAction(QStringLiteral("bookmarks_clear"), m_bookmarkClear);
0057     m_bookmarkClear->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-remove")));
0058     m_bookmarkClear->setWhatsThis(i18n("Remove all bookmarks of the current document."));
0059     connect(m_bookmarkClear, &QAction::triggered, this, &KateBookmarks::clearBookmarks);
0060 
0061     m_goNext = new QAction(i18n("Next Bookmark"), this);
0062     ac->addAction(QStringLiteral("bookmarks_next"), m_goNext);
0063     m_goNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search")));
0064     ac->setDefaultShortcut(m_goNext, Qt::ALT + Qt::Key_PageDown);
0065     m_goNext->setWhatsThis(i18n("Go to the next bookmark."));
0066     connect(m_goNext, &QAction::triggered, this, &KateBookmarks::goNext);
0067 
0068     m_goPrevious = new QAction(i18n("Previous Bookmark"), this);
0069     ac->addAction(QStringLiteral("bookmarks_previous"), m_goPrevious);
0070     m_goPrevious->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search")));
0071     ac->setDefaultShortcut(m_goPrevious, Qt::ALT + Qt::Key_PageUp);
0072     m_goPrevious->setWhatsThis(i18n("Go to the previous bookmark."));
0073     connect(m_goPrevious, &QAction::triggered, this, &KateBookmarks::goPrevious);
0074 
0075     KActionMenu *actionMenu = new KActionMenu(i18n("&Bookmarks"), this);
0076     actionMenu->setPopupMode(QToolButton::InstantPopup);
0077     actionMenu->setIcon(QIcon::fromTheme(QStringLiteral("bookmarks")));
0078     ac->addAction(QStringLiteral("bookmarks"), actionMenu);
0079     m_bookmarksMenu = actionMenu->menu();
0080 
0081     connect(m_bookmarksMenu, &QMenu::aboutToShow, this, &KateBookmarks::bookmarkMenuAboutToShow);
0082     // Ensure the bookmarks menu is populated with at least the basic actions, otherwise macOS will not show it in the global menu bar.
0083     bookmarkMenuAboutToShow();
0084 
0085     marksChanged();
0086 
0087     // Always want the actions with shortcuts plugged into something so their shortcuts can work
0088     m_view->addAction(m_bookmarkToggle);
0089     m_view->addAction(m_bookmarkClear);
0090     m_view->addAction(m_goNext);
0091     m_view->addAction(m_goPrevious);
0092 }
0093 
0094 void KateBookmarks::toggleBookmark()
0095 {
0096     uint mark = m_view->doc()->mark(m_view->cursorPosition().line());
0097     if (mark & KTextEditor::MarkInterface::markType01) {
0098         m_view->doc()->removeMark(m_view->cursorPosition().line(), KTextEditor::MarkInterface::markType01);
0099     } else {
0100         m_view->doc()->addMark(m_view->cursorPosition().line(), KTextEditor::MarkInterface::markType01);
0101     }
0102 }
0103 
0104 void KateBookmarks::clearBookmarks()
0105 {
0106     // work on a COPY of the hash, the removing will modify it otherwise!
0107     const auto hash = m_view->doc()->marks();
0108     for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
0109         m_view->doc()->removeMark(it.value()->line, KTextEditor::MarkInterface::markType01);
0110     }
0111 }
0112 
0113 void KateBookmarks::insertBookmarks(QMenu &menu)
0114 {
0115     const int line = m_view->cursorPosition().line();
0116     static const QRegularExpression re(QStringLiteral("&(?!&)"));
0117     int next = -1; // -1 means next bookmark doesn't exist
0118     int prev = -1; // -1 means previous bookmark doesn't exist
0119 
0120     // reference ok, not modified
0121     const auto &hash = m_view->doc()->marks();
0122     if (hash.isEmpty()) {
0123         return;
0124     }
0125 
0126     QVector<int> bookmarkLineArray; // Array of line numbers which have bookmarks
0127 
0128     // Find line numbers where bookmarks are set & store those line numbers in bookmarkLineArray
0129     for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
0130         if (it.value()->type & KTextEditor::MarkInterface::markType01) {
0131             bookmarkLineArray.append(it.value()->line);
0132         }
0133     }
0134 
0135     if (m_sorting == Position) {
0136         std::sort(bookmarkLineArray.begin(), bookmarkLineArray.end());
0137     }
0138 
0139     QAction *firstNewAction = menu.addSeparator();
0140     // Consider each line with a bookmark one at a time
0141     for (int i = 0; i < bookmarkLineArray.size(); ++i) {
0142         const int lineNo = bookmarkLineArray.at(i);
0143         // Get text in this particular line in a QString
0144         QFontMetrics fontMetrics(menu.fontMetrics());
0145         QString bText = fontMetrics.elidedText(m_view->doc()->line(lineNo), Qt::ElideRight, fontMetrics.maxWidth() * 32);
0146         bText.replace(re, QStringLiteral("&&")); // kill undesired accellerators!
0147         bText.replace(QLatin1Char('\t'), QLatin1Char(' ')); // kill tabs, as they are interpreted as shortcuts
0148 
0149         QAction *before = nullptr;
0150         if (m_sorting == Position) {
0151             // 3 actions already present
0152             if (menu.actions().size() <= i + 3) {
0153                 before = nullptr;
0154             } else {
0155                 before = menu.actions().at(i + 3);
0156             }
0157         }
0158 
0159         const QString actionText(QStringLiteral("%1  %2  - \"%3\"").arg(QString::number(lineNo + 1), m_view->currentInputMode()->bookmarkLabel(lineNo), bText));
0160         // Adding action for this bookmark in menu
0161         if (before) {
0162             QAction *a = new QAction(actionText, &menu);
0163             menu.insertAction(before, a);
0164             connect(a, &QAction::triggered, this, [this, lineNo]() {
0165                 gotoLine(lineNo);
0166             });
0167 
0168             if (!firstNewAction) {
0169                 firstNewAction = a;
0170             }
0171         } else {
0172             menu.addAction(actionText, this, [this, lineNo]() {
0173                 gotoLine(lineNo);
0174             });
0175         }
0176 
0177         // Find the line number of previous & next bookmark (if present) in relation to the cursor
0178         if (lineNo < line) {
0179             if (prev == -1 || prev < lineNo) {
0180                 prev = lineNo;
0181             }
0182         } else if (lineNo > line) {
0183             if (next == -1 || next > lineNo) {
0184                 next = lineNo;
0185             }
0186         }
0187     }
0188 
0189     if (next != -1) {
0190         // Insert action for next bookmark
0191         m_goNext->setText(i18n("&Next: %1 - \"%2\"", next + 1, KStringHandler::rsqueeze(m_view->doc()->line(next), 24)));
0192         menu.insertAction(firstNewAction, m_goNext);
0193         firstNewAction = m_goNext;
0194     }
0195     if (prev != -1) {
0196         // Insert action for previous bookmark
0197         m_goPrevious->setText(i18n("&Previous: %1 - \"%2\"", prev + 1, KStringHandler::rsqueeze(m_view->doc()->line(prev), 24)));
0198         menu.insertAction(firstNewAction, m_goPrevious);
0199         firstNewAction = m_goPrevious;
0200     }
0201 
0202     if (next != -1 || prev != -1) {
0203         menu.insertSeparator(firstNewAction);
0204     }
0205 }
0206 
0207 void KateBookmarks::gotoLine(int line)
0208 {
0209     m_view->setCursorPosition(KTextEditor::Cursor(line, 0));
0210 }
0211 
0212 void KateBookmarks::bookmarkMenuAboutToShow()
0213 {
0214     m_bookmarksMenu->clear();
0215     m_bookmarkToggle->setChecked(m_view->doc()->mark(m_view->cursorPosition().line()) & KTextEditor::MarkInterface::markType01);
0216     m_bookmarksMenu->addAction(m_bookmarkToggle);
0217     m_bookmarksMenu->addAction(m_bookmarkClear);
0218 
0219     m_goNext->setText(i18n("Next Bookmark"));
0220     m_goPrevious->setText(i18n("Previous Bookmark"));
0221 
0222     insertBookmarks(*m_bookmarksMenu);
0223 }
0224 
0225 void KateBookmarks::goNext()
0226 {
0227     // reference ok, not modified
0228     const auto &hash = m_view->doc()->marks();
0229     if (hash.isEmpty()) {
0230         return;
0231     }
0232 
0233     int line = m_view->cursorPosition().line();
0234     int found = -1;
0235 
0236     for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
0237         const int markLine = it.value()->line;
0238         if (markLine > line && (found == -1 || found > markLine)) {
0239             found = markLine;
0240         }
0241     }
0242 
0243     if (found != -1) {
0244         gotoLine(found);
0245     }
0246 }
0247 
0248 void KateBookmarks::goPrevious()
0249 {
0250     // reference ok, not modified
0251     const auto &hash = m_view->doc()->marks();
0252     if (hash.isEmpty()) {
0253         return;
0254     }
0255 
0256     int line = m_view->cursorPosition().line();
0257     int found = -1;
0258 
0259     for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
0260         const int markLine = it.value()->line;
0261         if (markLine < line && (found == -1 || found < markLine)) {
0262             found = markLine;
0263         }
0264     }
0265 
0266     if (found != -1) {
0267         gotoLine(found);
0268     }
0269 }
0270 
0271 void KateBookmarks::marksChanged()
0272 {
0273     if (m_bookmarkClear) {
0274         m_bookmarkClear->setEnabled(!m_view->doc()->marks().isEmpty());
0275     }
0276 }
0277 
0278 #include "moc_katebookmarks.cpp"