File indexing completed on 2024-04-21 03:58:08

0001 /*
0002     SPDX-FileCopyrightText: KDE Developers
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "marks.h"
0008 #include "katedocument.h"
0009 #include "kateview.h"
0010 #include <vimode/inputmodemanager.h>
0011 #include <vimode/modes/normalvimode.h>
0012 
0013 #include <KLocalizedString>
0014 
0015 using namespace KateVi;
0016 
0017 namespace
0018 {
0019 const QChar BeginEditYanked = QLatin1Char('[');
0020 const QChar EndEditYanked = QLatin1Char(']');
0021 const QChar LastChange = QLatin1Char('.');
0022 const QChar InsertStopped = QLatin1Char('^');
0023 const QChar SelectionBegin = QLatin1Char('<');
0024 const QChar SelectionEnd = QLatin1Char('>');
0025 const QChar FirstUserMark = QLatin1Char('a');
0026 const QChar LastUserMark = QLatin1Char('z');
0027 const QChar BeforeJump = QLatin1Char('\'');
0028 const QChar BeforeJumpAlter = QLatin1Char('`');
0029 const QChar UserMarks[] = {QLatin1Char('a'), QLatin1Char('b'), QLatin1Char('c'), QLatin1Char('d'), QLatin1Char('e'), QLatin1Char('f'), QLatin1Char('g'),
0030                            QLatin1Char('h'), QLatin1Char('i'), QLatin1Char('j'), QLatin1Char('k'), QLatin1Char('l'), QLatin1Char('m'), QLatin1Char('n'),
0031                            QLatin1Char('o'), QLatin1Char('p'), QLatin1Char('q'), QLatin1Char('r'), QLatin1Char('s'), QLatin1Char('t'), QLatin1Char('u'),
0032                            QLatin1Char('v'), QLatin1Char('w'), QLatin1Char('x'), QLatin1Char('y'), QLatin1Char('z')};
0033 }
0034 
0035 Marks::Marks(InputModeManager *imm)
0036     : m_inputModeManager(imm)
0037     , m_doc(imm->view()->doc())
0038     , m_settingMark(false)
0039 {
0040     connect(m_doc, &KTextEditor::DocumentPrivate::markChanged, this, &Marks::markChanged);
0041 }
0042 
0043 void Marks::readSessionConfig(const KConfigGroup &config)
0044 {
0045     QStringList marks = config.readEntry("ViMarks", QStringList());
0046     for (int i = 0; i + 2 < marks.size(); i += 3) {
0047         KTextEditor::Cursor c(marks.at(i + 1).toInt(), marks.at(i + 2).toInt());
0048         setMark(marks.at(i).at(0), c);
0049     }
0050 
0051     syncViMarksAndBookmarks();
0052 }
0053 
0054 void Marks::writeSessionConfig(KConfigGroup &config) const
0055 {
0056     QStringList l;
0057     const auto keys = m_marks.keys();
0058     for (QChar key : keys) {
0059         l << key << QString::number(m_marks.value(key)->line()) << QString::number(m_marks.value(key)->column());
0060     }
0061     config.writeEntry("ViMarks", l);
0062 }
0063 
0064 void Marks::setMark(const QChar &_mark, const KTextEditor::Cursor pos)
0065 {
0066     // move on insert is type based, this allows to reuse cursors!
0067     // reuse is important for editing intensive things like replace-all
0068     const bool moveoninsert = _mark != BeginEditYanked;
0069 
0070     m_settingMark = true;
0071 
0072     // ` and ' is the same register (position before jump)
0073     const QChar mark = (_mark == BeforeJumpAlter) ? BeforeJump : _mark;
0074 
0075     // if we have already a cursor for this type: adjust it
0076     bool needToAdjustVisibleMark = true;
0077     if (KTextEditor::MovingCursor *oldCursor = m_marks.value(mark)) {
0078         // cleanup mark display only if line changes
0079         needToAdjustVisibleMark = oldCursor->line() != pos.line();
0080         if (needToAdjustVisibleMark) {
0081             int number_of_marks = 0;
0082             const auto keys = m_marks.keys();
0083             for (QChar c : keys) {
0084                 if (m_marks.value(c)->line() == oldCursor->line()) {
0085                     number_of_marks++;
0086                 }
0087             }
0088             if (number_of_marks == 1) {
0089                 m_doc->removeMark(oldCursor->line(), KTextEditor::Document::markType01);
0090             }
0091         }
0092 
0093         // adjust position
0094         oldCursor->setPosition(pos);
0095     } else {
0096         // if no old mark of that type, create new one
0097         const KTextEditor::MovingCursor::InsertBehavior behavior =
0098             moveoninsert ? KTextEditor::MovingCursor::MoveOnInsert : KTextEditor::MovingCursor::StayOnInsert;
0099         m_marks.insert(mark, m_doc->newMovingCursor(pos, behavior));
0100     }
0101 
0102     // Showing what mark we set, can be skipped if we did not change the line
0103     if (isShowable(mark)) {
0104         if (needToAdjustVisibleMark && !(m_doc->mark(pos.line()) & KTextEditor::Document::markType01)) {
0105             m_doc->addMark(pos.line(), KTextEditor::Document::markType01);
0106         }
0107 
0108         // only show message for active view
0109         if (m_inputModeManager->view()->viewInputMode() == KTextEditor::View::ViInputMode) {
0110             if (m_doc->activeView() == m_inputModeManager->view()) {
0111                 m_inputModeManager->getViNormalMode()->message(i18n("Mark set: %1", mark));
0112             }
0113         }
0114     }
0115 
0116     m_settingMark = false;
0117 }
0118 
0119 KTextEditor::Cursor Marks::getMarkPosition(const QChar &mark) const
0120 {
0121     if (m_marks.contains(mark)) {
0122         KTextEditor::MovingCursor *c = m_marks.value(mark);
0123         return KTextEditor::Cursor(c->line(), c->column());
0124     }
0125 
0126     return KTextEditor::Cursor::invalid();
0127 }
0128 
0129 void Marks::markChanged(KTextEditor::Document *doc, KTextEditor::Mark mark, KTextEditor::Document::MarkChangeAction action)
0130 {
0131     Q_UNUSED(doc)
0132 
0133     if (mark.type != KTextEditor::Document::Bookmark || m_settingMark) {
0134         return;
0135     }
0136 
0137     if (action == KTextEditor::Document::MarkRemoved) {
0138         const auto keys = m_marks.keys();
0139         for (QChar markerChar : keys) {
0140             if (m_marks.value(markerChar)->line() == mark.line) {
0141                 m_marks.remove(markerChar);
0142             }
0143         }
0144     } else if (action == KTextEditor::Document::MarkAdded) {
0145         bool freeMarkerCharFound = false;
0146 
0147         for (const QChar &markerChar : UserMarks) {
0148             if (!m_marks.value(markerChar)) {
0149                 setMark(markerChar, KTextEditor::Cursor(mark.line, 0));
0150                 freeMarkerCharFound = true;
0151                 break;
0152             }
0153         }
0154 
0155         // only show error when we are in Vi input mode
0156         if (!freeMarkerCharFound && m_inputModeManager->view()->viewInputMode() == KTextEditor::View::ViInputMode) {
0157             m_inputModeManager->getViNormalMode()->error(i18n("There are no more chars for the next bookmark."));
0158         }
0159     }
0160 }
0161 
0162 void Marks::syncViMarksAndBookmarks()
0163 {
0164     const QHash<int, KTextEditor::Mark *> &marks = m_doc->marks();
0165 
0166     //  Each bookmark should have a vi mark on the same line.
0167     for (auto mark : marks) {
0168         if (!(mark->type & KTextEditor::Document::markType01)) {
0169             continue;
0170         }
0171 
0172         bool thereIsViMarkForThisLine = false;
0173         for (auto cursor : std::as_const(m_marks)) {
0174             if (cursor->line() == mark->line) {
0175                 thereIsViMarkForThisLine = true;
0176                 break;
0177             }
0178         }
0179 
0180         if (thereIsViMarkForThisLine) {
0181             continue;
0182         }
0183 
0184         for (const QChar &markerChar : UserMarks) {
0185             if (!m_marks.value(markerChar)) {
0186                 setMark(markerChar, KTextEditor::Cursor(mark->line, 0));
0187                 break;
0188             }
0189         }
0190     }
0191 
0192     // For showable vi mark a line should be bookmarked.
0193     const auto keys = m_marks.keys();
0194     for (QChar markChar : keys) {
0195         if (!isShowable(markChar)) {
0196             continue;
0197         }
0198 
0199         bool thereIsKateMarkForThisLine = false;
0200         for (auto mark : marks) {
0201             if (!(mark->type & KTextEditor::Document::markType01)) {
0202                 continue;
0203             }
0204 
0205             if (m_marks.value(markChar)->line() == mark->line) {
0206                 thereIsKateMarkForThisLine = true;
0207                 break;
0208             }
0209         }
0210 
0211         if (!thereIsKateMarkForThisLine) {
0212             m_doc->addMark(m_marks.value(markChar)->line(), KTextEditor::Document::markType01);
0213         }
0214     }
0215 }
0216 
0217 QString Marks::getMarksOnTheLine(int line) const
0218 {
0219     QString res;
0220     const auto keys = m_marks.keys();
0221     for (QChar markerChar : keys) {
0222         if (m_marks.value(markerChar)->line() == line) {
0223             res += markerChar + QLatin1Char(':') + QString::number(m_marks.value(markerChar)->column()) + QLatin1Char(' ');
0224         }
0225     }
0226 
0227     return res;
0228 }
0229 
0230 bool Marks::isShowable(const QChar &mark)
0231 {
0232     return FirstUserMark <= mark && mark <= LastUserMark;
0233 }
0234 
0235 void Marks::setStartEditYanked(const KTextEditor::Cursor pos)
0236 {
0237     setMark(BeginEditYanked, pos);
0238 }
0239 
0240 void Marks::setFinishEditYanked(const KTextEditor::Cursor pos)
0241 {
0242     setMark(EndEditYanked, pos);
0243 }
0244 
0245 void Marks::setLastChange(const KTextEditor::Cursor pos)
0246 {
0247     setMark(LastChange, pos);
0248 }
0249 
0250 void Marks::setInsertStopped(const KTextEditor::Cursor pos)
0251 {
0252     setMark(InsertStopped, pos);
0253 }
0254 
0255 void Marks::setSelectionStart(const KTextEditor::Cursor pos)
0256 {
0257     setMark(SelectionBegin, pos);
0258 }
0259 
0260 void Marks::setSelectionFinish(const KTextEditor::Cursor pos)
0261 {
0262     setMark(SelectionEnd, pos);
0263 }
0264 
0265 void Marks::setUserMark(const QChar &mark, const KTextEditor::Cursor pos)
0266 {
0267     Q_ASSERT(FirstUserMark <= mark && mark <= LastUserMark);
0268     setMark(mark, pos);
0269 }
0270 
0271 KTextEditor::Cursor Marks::getStartEditYanked() const
0272 {
0273     return getMarkPosition(BeginEditYanked);
0274 }
0275 
0276 KTextEditor::Cursor Marks::getFinishEditYanked() const
0277 {
0278     return getMarkPosition(EndEditYanked);
0279 }
0280 
0281 KTextEditor::Cursor Marks::getSelectionStart() const
0282 {
0283     return getMarkPosition(SelectionBegin);
0284 }
0285 
0286 KTextEditor::Cursor Marks::getSelectionFinish() const
0287 {
0288     return getMarkPosition(SelectionEnd);
0289 }
0290 
0291 KTextEditor::Cursor Marks::getLastChange() const
0292 {
0293     return getMarkPosition(LastChange);
0294 }
0295 
0296 KTextEditor::Cursor Marks::getInsertStopped() const
0297 {
0298     return getMarkPosition(InsertStopped);
0299 }