File indexing completed on 2024-05-05 16:18:20

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2009 Michel Ludwig <michel.ludwig@kdemail.net>
0004     SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
0005     SPDX-FileCopyrightText: 2003 Hamish Rodda <rodda@kde.org>
0006     SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
0007     SPDX-FileCopyrightText: 2001-2004 Christoph Cullmann <cullmann@kde.org>
0008     SPDX-FileCopyrightText: 2001-2010 Joseph Wenninger <jowenn@kde.org>
0009     SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
0010 
0011     SPDX-License-Identifier: LGPL-2.0-only
0012 */
0013 
0014 // BEGIN includes
0015 #include "kateview.h"
0016 
0017 #include "clipboardhistorydialog.h"
0018 #include "export/exporter.h"
0019 #include "inlinenotedata.h"
0020 #include "kateabstractinputmode.h"
0021 #include "kateautoindent.h"
0022 #include "katebookmarks.h"
0023 #include "katebuffer.h"
0024 #include "katecompletionwidget.h"
0025 #include "kateconfig.h"
0026 #include "katedialogs.h"
0027 #include "katedocument.h"
0028 #include "kateglobal.h"
0029 #include "katehighlight.h"
0030 #include "katehighlightmenu.h"
0031 #include "katekeywordcompletion.h"
0032 #include "katelayoutcache.h"
0033 #include "katemessagewidget.h"
0034 #include "katemodemenu.h"
0035 #include "katepartdebug.h"
0036 #include "katestatusbar.h"
0037 #include "katetemplatehandler.h"
0038 #include "katetextline.h"
0039 #include "kateundomanager.h"
0040 #include "kateviewhelpers.h"
0041 #include "kateviewinternal.h"
0042 #include "katewordcompletion.h"
0043 #include "printing/kateprinter.h"
0044 #include "script/katescriptaction.h"
0045 #include "script/katescriptmanager.h"
0046 #include "spellcheck/spellcheck.h"
0047 #include "spellcheck/spellcheckdialog.h"
0048 #include "spellcheck/spellingmenu.h"
0049 
0050 #include <KTextEditor/Message>
0051 #include <ktexteditor/inlinenoteprovider.h>
0052 
0053 #include <KParts/Event>
0054 
0055 #include <KActionCollection>
0056 #include <KConfig>
0057 #include <KConfigGroup>
0058 #include <KCursor>
0059 #include <KMessageBox>
0060 #include <KSelectAction>
0061 #include <KStandardAction>
0062 #include <KStandardShortcut>
0063 #include <KToggleAction>
0064 #include <KXMLGUIFactory>
0065 
0066 #include <QActionGroup>
0067 #include <QApplication>
0068 #include <QClipboard>
0069 #include <QFileDialog>
0070 #include <QFileInfo>
0071 #include <QFont>
0072 #include <QInputDialog>
0073 #include <QKeyEvent>
0074 #include <QLayout>
0075 #include <QMimeData>
0076 #include <QPainter>
0077 #include <QRegularExpression>
0078 #include <QToolTip>
0079 
0080 //#define VIEW_RANGE_DEBUG
0081 
0082 // END includes
0083 
0084 namespace
0085 {
0086 bool hasCommentInFirstLine(KTextEditor::DocumentPrivate *doc)
0087 {
0088     const Kate::TextLine &line = doc->kateTextLine(0);
0089     Q_ASSERT(line);
0090     return doc->isComment(0, line->firstChar());
0091 }
0092 
0093 }
0094 
0095 void KTextEditor::ViewPrivate::blockFix(KTextEditor::Range &range)
0096 {
0097     if (range.start().column() > range.end().column()) {
0098         int tmp = range.start().column();
0099         range.setStart(KTextEditor::Cursor(range.start().line(), range.end().column()));
0100         range.setEnd(KTextEditor::Cursor(range.end().line(), tmp));
0101     }
0102 }
0103 
0104 KTextEditor::ViewPrivate::ViewPrivate(KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow)
0105     : KTextEditor::View(this, parent)
0106     , m_completionWidget(nullptr)
0107     , m_annotationModel(nullptr)
0108     , m_hasWrap(false)
0109     , m_doc(doc)
0110     , m_textFolding(doc->buffer())
0111     , m_config(new KateViewConfig(this))
0112     , m_renderer(new KateRenderer(doc, m_textFolding, this))
0113     , m_viewInternal(new KateViewInternal(this))
0114     , m_spell(new KateSpellCheckDialog(this))
0115     , m_bookmarks(new KateBookmarks(this))
0116     , m_topSpacer(new QSpacerItem(0, 0))
0117     , m_leftSpacer(new QSpacerItem(0, 0))
0118     , m_rightSpacer(new QSpacerItem(0, 0))
0119     , m_bottomSpacer(new QSpacerItem(0, 0))
0120     , m_startingUp(true)
0121     , m_updatingDocumentConfig(false)
0122     , m_selection(m_doc->buffer(), KTextEditor::Range::invalid(), Kate::TextRange::ExpandLeft, Kate::TextRange::AllowEmpty)
0123     , blockSelect(false)
0124     , m_bottomViewBar(nullptr)
0125     , m_gotoBar(nullptr)
0126     , m_dictionaryBar(nullptr)
0127     , m_spellingMenu(new KateSpellingMenu(this))
0128     , m_userContextMenuSet(false)
0129     , m_lineToUpdateRange(KTextEditor::LineRange::invalid())
0130     , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there!
0131     , m_statusBar(nullptr)
0132     , m_temporaryAutomaticInvocationDisabled(false)
0133     , m_autoFoldedFirstLine(false)
0134 {
0135     // queued connect to collapse view updates for range changes, INIT THIS EARLY ENOUGH!
0136     connect(this, &KTextEditor::ViewPrivate::delayedUpdateOfView, this, &KTextEditor::ViewPrivate::slotDelayedUpdateOfView, Qt::QueuedConnection);
0137 
0138     m_delayedUpdateTimer.setSingleShot(true);
0139     m_delayedUpdateTimer.setInterval(0);
0140     connect(&m_delayedUpdateTimer, &QTimer::timeout, this, &KTextEditor::ViewPrivate::delayedUpdateOfView);
0141 
0142     KXMLGUIClient::setComponentName(KTextEditor::EditorPrivate::self()->aboutData().componentName(),
0143                                     KTextEditor::EditorPrivate::self()->aboutData().displayName());
0144 
0145     // selection if for this view only and will invalidate if becoming empty
0146     m_selection.setView(this);
0147 
0148     // use z depth defined in moving ranges interface
0149     m_selection.setZDepth(-100000.0);
0150 
0151     KTextEditor::EditorPrivate::self()->registerView(this);
0152 
0153     // try to let the main window, if any, create a view bar for this view
0154     QWidget *bottomBarParent = m_mainWindow->createViewBar(this);
0155 
0156     m_bottomViewBar = new KateViewBar(bottomBarParent != nullptr, bottomBarParent ? bottomBarParent : this, this);
0157 
0158     // ugly workaround:
0159     // Force the layout to be left-to-right even on RTL desktop, as discussed
0160     // on the mailing list. This will cause the lines and icons panel to be on
0161     // the left, even for Arabic/Hebrew/Farsi/whatever users.
0162     setLayoutDirection(Qt::LeftToRight);
0163 
0164     m_bottomViewBar->installEventFilter(m_viewInternal);
0165 
0166     // add KateMessageWidget for KTE::MessageInterface immediately above view
0167     m_messageWidgets[KTextEditor::Message::AboveView] = new KateMessageWidget(this);
0168     m_messageWidgets[KTextEditor::Message::AboveView]->hide();
0169 
0170     // add KateMessageWidget for KTE::MessageInterface immediately above view
0171     m_messageWidgets[KTextEditor::Message::BelowView] = new KateMessageWidget(this);
0172     m_messageWidgets[KTextEditor::Message::BelowView]->hide();
0173 
0174     // add bottom viewbar...
0175     if (bottomBarParent) {
0176         m_mainWindow->addWidgetToViewBar(this, m_bottomViewBar);
0177     }
0178 
0179     // add layout for floating message widgets to KateViewInternal
0180     m_notificationLayout = new KateMessageLayout(m_viewInternal);
0181     m_notificationLayout->setContentsMargins(20, 20, 20, 20);
0182     m_viewInternal->setLayout(m_notificationLayout);
0183 
0184     // this really is needed :)
0185     m_viewInternal->updateView();
0186 
0187     doc->addView(this);
0188 
0189     setFocusProxy(m_viewInternal);
0190     setFocusPolicy(Qt::StrongFocus);
0191 
0192     setXMLFile(QStringLiteral("katepart5ui.rc"));
0193 
0194     setupConnections();
0195     setupActions();
0196 
0197     // auto word completion
0198     new KateWordCompletionView(this, actionCollection());
0199 
0200     // update the enabled state of the undo/redo actions...
0201     slotUpdateUndo();
0202 
0203     // create the status bar of this view
0204     // do this after action creation, we use some of them!
0205     toggleStatusBar();
0206 
0207     m_startingUp = false;
0208     updateConfig();
0209 
0210     slotHlChanged();
0211     KCursor::setAutoHideCursor(m_viewInternal, true);
0212 
0213     for (auto messageWidget : m_messageWidgets) {
0214         if (messageWidget) {
0215             // user interaction (scrolling) starts notification auto-hide timer
0216             connect(this, &KTextEditor::ViewPrivate::displayRangeChanged, messageWidget, &KateMessageWidget::startAutoHideTimer);
0217 
0218             // user interaction (cursor navigation) starts notification auto-hide timer
0219             connect(this, &KTextEditor::ViewPrivate::cursorPositionChanged, messageWidget, &KateMessageWidget::startAutoHideTimer);
0220         }
0221     }
0222 
0223     // folding restoration on reload
0224     connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::saveFoldingState);
0225     connect(m_doc, &KTextEditor::DocumentPrivate::reloaded, this, &KTextEditor::ViewPrivate::applyFoldingState);
0226 
0227     connect(m_doc, &KTextEditor::DocumentPrivate::reloaded, this, &KTextEditor::ViewPrivate::slotDocumentReloaded);
0228     connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::slotDocumentAboutToReload);
0229 
0230     // update highlights on scrolling and co
0231     connect(this, &KTextEditor::ViewPrivate::displayRangeChanged, this, &KTextEditor::ViewPrivate::createHighlights);
0232 
0233     // clear highlights on reload
0234     connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::clearHighlights);
0235 
0236     // setup layout
0237     setupLayout();
0238 }
0239 
0240 KTextEditor::ViewPrivate::~ViewPrivate()
0241 {
0242     // de-register views early from global collections
0243     // otherwise we might "use" them again during destruction in a half-valid state
0244     // see e.g. bug 422546
0245     // Kate::TextBuffer::notifyAboutRangeChange will access views() in a chain during
0246     // deletion of m_viewInternal
0247     doc()->removeView(this);
0248     KTextEditor::EditorPrivate::self()->deregisterView(this);
0249 
0250     // remove from xmlgui factory, to be safe
0251     if (factory()) {
0252         factory()->removeClient(this);
0253     }
0254 
0255     // delete internal view before view bar!
0256     delete m_viewInternal;
0257 
0258     // remove view bar again, if needed
0259     m_mainWindow->deleteViewBar(this);
0260     m_bottomViewBar = nullptr;
0261 
0262     delete m_renderer;
0263 
0264     delete m_config;
0265 }
0266 
0267 void KTextEditor::ViewPrivate::toggleStatusBar()
0268 {
0269     // if there, delete it
0270     if (m_statusBar) {
0271         bottomViewBar()->removePermanentBarWidget(m_statusBar);
0272         delete m_statusBar;
0273         m_statusBar = nullptr;
0274         Q_EMIT statusBarEnabledChanged(this, false);
0275         return;
0276     }
0277 
0278     // else: create it
0279     m_statusBar = new KateStatusBar(this);
0280     bottomViewBar()->addPermanentBarWidget(m_statusBar);
0281     Q_EMIT statusBarEnabledChanged(this, true);
0282 }
0283 
0284 void KTextEditor::ViewPrivate::setupLayout()
0285 {
0286     // delete old layout if any
0287     if (layout()) {
0288         delete layout();
0289 
0290         //  need to recreate spacers because they are deleted with
0291         //  their belonging layout
0292         m_topSpacer = new QSpacerItem(0, 0);
0293         m_leftSpacer = new QSpacerItem(0, 0);
0294         m_rightSpacer = new QSpacerItem(0, 0);
0295         m_bottomSpacer = new QSpacerItem(0, 0);
0296     }
0297 
0298     // set margins
0299     QStyleOptionFrame opt;
0300     opt.initFrom(this);
0301     opt.frameShape = QFrame::StyledPanel;
0302     opt.state |= QStyle::State_Sunken;
0303     const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this);
0304     m_topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed);
0305     m_leftSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum);
0306     m_rightSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum);
0307     m_bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed);
0308 
0309     // define layout
0310     QGridLayout *layout = new QGridLayout(this);
0311     layout->setContentsMargins(0, 0, 0, 0);
0312     layout->setSpacing(0);
0313 
0314     const bool frameAroundContents = style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, this);
0315     if (frameAroundContents) {
0316         // top message widget
0317         layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5);
0318 
0319         // top spacer
0320         layout->addItem(m_topSpacer, 1, 0, 1, 4);
0321 
0322         // left spacer
0323         layout->addItem(m_leftSpacer, 2, 0, 1, 1);
0324 
0325         // left border
0326         layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1);
0327 
0328         // view
0329         layout->addWidget(m_viewInternal, 2, 2, 1, 1);
0330 
0331         // right spacer
0332         layout->addItem(m_rightSpacer, 2, 3, 1, 1);
0333 
0334         // bottom spacer
0335         layout->addItem(m_bottomSpacer, 3, 0, 1, 4);
0336 
0337         // vertical scrollbar
0338         layout->addWidget(m_viewInternal->m_lineScroll, 1, 4, 3, 1);
0339 
0340         // horizontal scrollbar
0341         layout->addWidget(m_viewInternal->m_columnScroll, 4, 0, 1, 4);
0342 
0343         // dummy
0344         layout->addWidget(m_viewInternal->m_dummy, 4, 4, 1, 1);
0345 
0346         // bottom message
0347         layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5);
0348 
0349         // bottom viewbar
0350         if (m_bottomViewBar->parentWidget() == this) {
0351             layout->addWidget(m_bottomViewBar, 6, 0, 1, 5);
0352         }
0353 
0354         // stretch
0355         layout->setColumnStretch(2, 1);
0356         layout->setRowStretch(2, 1);
0357 
0358         // adjust scrollbar background
0359         m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Window);
0360         m_viewInternal->m_lineScroll->setAutoFillBackground(false);
0361 
0362         m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Window);
0363         m_viewInternal->m_columnScroll->setAutoFillBackground(false);
0364 
0365     } else {
0366         // top message widget
0367         layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5);
0368 
0369         // top spacer
0370         layout->addItem(m_topSpacer, 1, 0, 1, 5);
0371 
0372         // left spacer
0373         layout->addItem(m_leftSpacer, 2, 0, 1, 1);
0374 
0375         // left border
0376         layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1);
0377 
0378         // view
0379         layout->addWidget(m_viewInternal, 2, 2, 1, 1);
0380 
0381         // vertical scrollbar
0382         layout->addWidget(m_viewInternal->m_lineScroll, 2, 3, 1, 1);
0383 
0384         // right spacer
0385         layout->addItem(m_rightSpacer, 2, 4, 1, 1);
0386 
0387         // horizontal scrollbar
0388         layout->addWidget(m_viewInternal->m_columnScroll, 3, 1, 1, 2);
0389 
0390         // dummy
0391         layout->addWidget(m_viewInternal->m_dummy, 3, 3, 1, 1);
0392 
0393         // bottom spacer
0394         layout->addItem(m_bottomSpacer, 4, 0, 1, 5);
0395 
0396         // bottom message
0397         layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5);
0398 
0399         // bottom viewbar
0400         if (m_bottomViewBar->parentWidget() == this) {
0401             layout->addWidget(m_bottomViewBar, 6, 0, 1, 5);
0402         }
0403 
0404         // stretch
0405         layout->setColumnStretch(2, 1);
0406         layout->setRowStretch(2, 1);
0407 
0408         // adjust scrollbar background
0409         m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Base);
0410         m_viewInternal->m_lineScroll->setAutoFillBackground(true);
0411 
0412         m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Base);
0413         m_viewInternal->m_columnScroll->setAutoFillBackground(true);
0414     }
0415 }
0416 
0417 void KTextEditor::ViewPrivate::setupConnections()
0418 {
0419     connect(m_doc, &KTextEditor::DocumentPrivate::undoChanged, this, &KTextEditor::ViewPrivate::slotUpdateUndo);
0420     connect(m_doc, &KTextEditor::DocumentPrivate::highlightingModeChanged, this, &KTextEditor::ViewPrivate::slotHlChanged);
0421     connect(m_doc, &KTextEditor::DocumentPrivate::canceled, this, &KTextEditor::ViewPrivate::slotSaveCanceled);
0422     connect(m_viewInternal, &KateViewInternal::dropEventPass, this, &KTextEditor::ViewPrivate::dropEventPass);
0423 
0424     connect(m_doc, &KTextEditor::DocumentPrivate::annotationModelChanged, m_viewInternal->m_leftBorder, &KateIconBorder::annotationModelChanged);
0425 }
0426 
0427 void KTextEditor::ViewPrivate::goToPreviousEditingPosition()
0428 {
0429     auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Previous, cursorPosition());
0430     if (c.isValid()) {
0431         setCursorPosition(c);
0432     }
0433 }
0434 
0435 void KTextEditor::ViewPrivate::goToNextEditingPosition()
0436 {
0437     auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Next, cursorPosition());
0438     if (c.isValid()) {
0439         setCursorPosition(c);
0440     }
0441 }
0442 void KTextEditor::ViewPrivate::setupActions()
0443 {
0444     KActionCollection *ac = actionCollection();
0445     QAction *a;
0446 
0447     m_toggleWriteLock = nullptr;
0448 
0449     m_cut = a = ac->addAction(KStandardAction::Cut, this, SLOT(cut()));
0450     a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard"));
0451 
0452     m_paste = a = ac->addAction(KStandardAction::Paste, this, SLOT(paste()));
0453     a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents"));
0454 
0455     m_copy = a = ac->addAction(KStandardAction::Copy, this, SLOT(copy()));
0456     a->setWhatsThis(i18n("Use this command to copy the currently selected text to the system clipboard."));
0457 
0458     m_clipboardHistory = a = ac->addAction(QStringLiteral("clipboard_history_paste"), this, [this] {
0459         ClipboardHistoryDialog chd(mainWindow()->window(), this);
0460         chd.openDialog(KTextEditor::EditorPrivate::self()->clipboardHistory());
0461     });
0462     a->setText(i18n("Clipboard &History Paste"));
0463     ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::ALT | Qt::Key_V));
0464 
0465     if (QApplication::clipboard()->supportsSelection()) {
0466         m_pasteSelection = a = ac->addAction(QStringLiteral("edit_paste_selection"), this, SLOT(pasteSelection()));
0467         a->setText(i18n("Paste Selection"));
0468         ac->setDefaultShortcuts(a, KStandardShortcut::pasteSelection());
0469         a->setWhatsThis(i18n("Paste previously mouse selection contents"));
0470     }
0471 
0472     m_swapWithClipboard = a = ac->addAction(QStringLiteral("edit_swap_with_clipboard"), this, SLOT(swapWithClipboard()));
0473     a->setText(i18n("Swap with Clipboard Contents"));
0474     a->setWhatsThis(i18n("Swap the selected text with the clipboard contents"));
0475 
0476     if (!doc()->readOnly()) {
0477         a = ac->addAction(KStandardAction::Save, m_doc, SLOT(documentSave()));
0478         a->setWhatsThis(i18n("Save the current document"));
0479 
0480         a = m_editUndo = ac->addAction(KStandardAction::Undo, m_doc, SLOT(undo()));
0481         a->setWhatsThis(i18n("Revert the most recent editing actions"));
0482 
0483         a = m_editRedo = ac->addAction(KStandardAction::Redo, m_doc, SLOT(redo()));
0484         a->setWhatsThis(i18n("Revert the most recent undo operation"));
0485 
0486         // Tools > Scripts
0487         // stored inside scoped pointer to ensure we destruct it early enough as it does internal cleanups of other child objects
0488         m_scriptActionMenu.reset(new KateScriptActionMenu(this, i18n("&Scripts")));
0489         ac->addAction(QStringLiteral("tools_scripts"), m_scriptActionMenu.data());
0490 
0491         a = ac->addAction(QStringLiteral("tools_apply_wordwrap"));
0492         a->setText(i18n("Apply &Word Wrap"));
0493         a->setWhatsThis(
0494             i18n("Use this to wrap the current line, or to reformat the selected lines as paragraph, "
0495                  "to fit the 'Wrap words at' setting in the configuration dialog.<br /><br />"
0496                  "This is a static word wrap, meaning the document is changed."));
0497         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::applyWordWrap);
0498 
0499         a = ac->addAction(QStringLiteral("tools_cleanIndent"));
0500         a->setText(i18n("&Clean Indentation"));
0501         a->setWhatsThis(
0502             i18n("Use this to clean the indentation of a selected block of text (only tabs/only spaces).<br /><br />"
0503                  "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog."));
0504         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::cleanIndent);
0505 
0506         a = ac->addAction(QStringLiteral("tools_formatIndent"));
0507         a->setText(i18n("&Format Indentation"));
0508         a->setWhatsThis(i18n("Use this to auto indent the current line or block of text to its proper indent level."));
0509         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::formatIndent);
0510 
0511         a = ac->addAction(QStringLiteral("tools_alignOn"));
0512         a->setText(i18n("&Align On..."));
0513         a->setWhatsThis(
0514             i18n("This command aligns lines in the selected block or whole document on the column given by a regular expression "
0515                  "that you will be prompted for.<br /><br />"
0516                  "If you give an empty pattern it will align on the first non-blank character by default.<br />"
0517                  "If the pattern has a capture it will indent on the captured match.<br /><br />"
0518                  "<i>Examples</i>:<br />"
0519                  "With '-' it will insert spaces before the first '-' of each lines to align them all on the same column.<br />"
0520                  "With ':\\s+(.)' it will insert spaces before the first non-blank character that occurs after a colon to align "
0521                  "them all on the same column."));
0522         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::alignOn);
0523 
0524         a = ac->addAction(QStringLiteral("tools_comment"));
0525         a->setText(i18n("C&omment"));
0526         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_D));
0527         a->setWhatsThis(
0528             i18n("This command comments out the current line or a selected block of text.<br /><br />"
0529                  "The characters for single/multiple line comments are defined within the language's highlighting."));
0530         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::comment);
0531 
0532         a = ac->addAction(QStringLiteral("Previous Editing Line"));
0533         a->setText(i18n("Go to Previous Editing Location"));
0534         a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left")));
0535         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_E));
0536         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::goToPreviousEditingPosition);
0537 
0538         a = ac->addAction(QStringLiteral("Next Editing Line"));
0539         a->setText(i18n("Go to Next Editing Location"));
0540         a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
0541         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_E));
0542         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::goToNextEditingPosition);
0543 
0544         a = ac->addAction(QStringLiteral("tools_uncomment"));
0545         a->setText(i18n("Unco&mment"));
0546         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_D));
0547         a->setWhatsThis(
0548             i18n("This command removes comments from the current line or a selected block of text.<br /><br />"
0549                  "The characters for single/multiple line comments are defined within the language's highlighting."));
0550         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::uncomment);
0551 
0552         a = ac->addAction(QStringLiteral("tools_toggle_comment"));
0553         a->setText(i18n("Toggle Comment"));
0554         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_Slash));
0555         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleComment);
0556 
0557         a = m_toggleWriteLock = new KToggleAction(i18n("&Read Only Mode"), this);
0558         a->setWhatsThis(i18n("Lock/unlock the document for writing"));
0559         a->setChecked(!doc()->isReadWrite());
0560         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleWriteLock);
0561         ac->addAction(QStringLiteral("tools_toggle_write_lock"), a);
0562 
0563         a = ac->addAction(QStringLiteral("tools_uppercase"));
0564         a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-uppercase")));
0565         a->setText(i18n("Uppercase"));
0566         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_U));
0567         a->setWhatsThis(
0568             i18n("Convert the selection to uppercase, or the character to the "
0569                  "right of the cursor if no text is selected."));
0570         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::uppercase);
0571 
0572         a = ac->addAction(QStringLiteral("tools_lowercase"));
0573         a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-lowercase")));
0574         a->setText(i18n("Lowercase"));
0575         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_U));
0576         a->setWhatsThis(
0577             i18n("Convert the selection to lowercase, or the character to the "
0578                  "right of the cursor if no text is selected."));
0579         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::lowercase);
0580 
0581         a = ac->addAction(QStringLiteral("tools_capitalize"));
0582         a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-capitalize")));
0583         a->setText(i18n("Capitalize"));
0584         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_U));
0585         a->setWhatsThis(
0586             i18n("Capitalize the selection, or the word under the "
0587                  "cursor if no text is selected."));
0588         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::capitalize);
0589 
0590         a = ac->addAction(QStringLiteral("tools_join_lines"));
0591         a->setText(i18n("Join Lines"));
0592         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_J));
0593         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::joinLines);
0594 
0595         a = ac->addAction(QStringLiteral("tools_invoke_code_completion"));
0596         a->setText(i18n("Invoke Code Completion"));
0597         a->setWhatsThis(i18n("Manually invoke command completion, usually by using a shortcut bound to this action."));
0598         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_Space));
0599         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::userInvokedCompletion);
0600     } else {
0601         for (auto *action : {m_cut, m_paste, m_clipboardHistory, m_swapWithClipboard}) {
0602             action->setEnabled(false);
0603         }
0604 
0605         if (m_pasteSelection) {
0606             m_pasteSelection->setEnabled(false);
0607         }
0608 
0609         m_editUndo = nullptr;
0610         m_editRedo = nullptr;
0611     }
0612 
0613     a = ac->addAction(KStandardAction::Print, this, SLOT(print()));
0614     a->setWhatsThis(i18n("Print the current document."));
0615 
0616     a = ac->addAction(KStandardAction::PrintPreview, this, SLOT(printPreview()));
0617     a->setWhatsThis(i18n("Show print preview of current document"));
0618 
0619     a = ac->addAction(QStringLiteral("file_reload"));
0620     a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0621     a->setText(i18n("Reloa&d"));
0622     ac->setDefaultShortcuts(a, KStandardShortcut::reload());
0623     a->setWhatsThis(i18n("Reload the current document from disk."));
0624     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::reloadFile);
0625 
0626     a = ac->addAction(KStandardAction::SaveAs, m_doc, SLOT(documentSaveAs()));
0627     a->setWhatsThis(i18n("Save the current document to disk, with a name of your choice."));
0628 
0629     a = new KateViewEncodingAction(m_doc, this, i18n("Save As with Encoding..."), this, true /* special mode for save as */);
0630     a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as")));
0631     ac->addAction(QStringLiteral("file_save_as_with_encoding"), a);
0632 
0633     a = ac->addAction(QStringLiteral("file_save_copy_as"));
0634     a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as")));
0635     a->setText(i18n("Save &Copy As..."));
0636     a->setWhatsThis(i18n("Save a copy of the current document to disk."));
0637     connect(a, &QAction::triggered, m_doc, &KTextEditor::DocumentPrivate::documentSaveCopyAs);
0638 
0639     a = ac->addAction(KStandardAction::GotoLine, this, SLOT(gotoLine()));
0640     a->setWhatsThis(i18n("This command opens a dialog and lets you choose a line that you want the cursor to move to."));
0641 
0642     a = ac->addAction(QStringLiteral("modified_line_up"));
0643     a->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
0644     a->setText(i18n("Go to Previous Modified Line"));
0645     a->setWhatsThis(i18n("Move upwards to the previous modified line."));
0646     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toPrevModifiedLine);
0647 
0648     a = ac->addAction(QStringLiteral("modified_line_down"));
0649     a->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
0650     a->setText(i18n("Go to Next Modified Line"));
0651     a->setWhatsThis(i18n("Move downwards to the next modified line."));
0652     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toNextModifiedLine);
0653 
0654     a = ac->addAction(QStringLiteral("set_confdlg"));
0655     a->setText(i18n("&Configure Editor..."));
0656     a->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other")));
0657     a->setWhatsThis(i18n("Configure various aspects of this editor."));
0658     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotConfigDialog);
0659 
0660     m_modeAction = new KateModeMenu(i18n("&Mode"), this);
0661     ac->addAction(QStringLiteral("tools_mode"), m_modeAction);
0662     m_modeAction->setWhatsThis(i18n(
0663         "Here you can choose which mode should be used for the current document. This will influence the highlighting and folding being used, for example."));
0664     m_modeAction->updateMenu(m_doc);
0665 
0666     KateHighlightingMenu *menu = new KateHighlightingMenu(i18n("&Highlighting"), this);
0667     ac->addAction(QStringLiteral("tools_highlighting"), menu);
0668     menu->setWhatsThis(i18n("Here you can choose how the current document should be highlighted."));
0669     menu->updateMenu(m_doc);
0670 
0671     KateViewSchemaAction *schemaMenu = new KateViewSchemaAction(i18n("&Editor Color Theme"), this);
0672     schemaMenu->setIcon(QIcon::fromTheme(QStringLiteral("kcolorchooser")));
0673     ac->addAction(QStringLiteral("view_schemas"), schemaMenu);
0674     schemaMenu->updateMenu(this);
0675 
0676     // indentation menu
0677     KateViewIndentationAction *indentMenu = new KateViewIndentationAction(m_doc, i18n("&Indentation"), this);
0678     ac->addAction(QStringLiteral("tools_indentation"), indentMenu);
0679 
0680     m_selectAll = ac->addAction(KStandardAction::SelectAll);
0681     connect(m_selectAll, &QAction::triggered, this, [this] {
0682         selectAll();
0683         qApp->clipboard()->setText(selectionText(), QClipboard::Selection);
0684     });
0685     a->setWhatsThis(i18n("Select the entire text of the current document."));
0686 
0687     m_deSelect = a = ac->addAction(KStandardAction::Deselect, this, SLOT(clearSelection()));
0688     a->setWhatsThis(i18n("If you have selected something within the current document, this will no longer be selected."));
0689 
0690     a = ac->addAction(QStringLiteral("view_inc_font_sizes"));
0691     a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in")));
0692     a->setText(i18n("Enlarge Font"));
0693     ac->setDefaultShortcuts(a, KStandardShortcut::zoomIn());
0694     a->setWhatsThis(i18n("This increases the display font size."));
0695     connect(a, &QAction::triggered, m_viewInternal, [this]() {
0696         m_viewInternal->slotIncFontSizes();
0697     });
0698 
0699     a = ac->addAction(QStringLiteral("view_dec_font_sizes"));
0700     a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out")));
0701     a->setText(i18n("Shrink Font"));
0702     ac->setDefaultShortcuts(a, KStandardShortcut::zoomOut());
0703     a->setWhatsThis(i18n("This decreases the display font size."));
0704     connect(a, &QAction::triggered, m_viewInternal, [this]() {
0705         m_viewInternal->slotDecFontSizes();
0706     });
0707 
0708     a = ac->addAction(QStringLiteral("view_reset_font_sizes"));
0709     a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original")));
0710     a->setText(i18n("Reset Font Size"));
0711     ac->setDefaultShortcuts(a, KStandardShortcut::shortcut(KStandardShortcut::ActualSize));
0712     a->setWhatsThis(i18n("This resets the display font size."));
0713     connect(a, &QAction::triggered, m_viewInternal, &KateViewInternal::slotResetFontSizes);
0714 
0715     a = m_toggleBlockSelection = new KToggleAction(i18n("Bl&ock Selection Mode"), this);
0716     ac->addAction(QStringLiteral("set_verticalSelect"), a);
0717     ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_B));
0718     a->setWhatsThis(i18n("This command allows switching between the normal (line based) selection mode and the block selection mode."));
0719     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleBlockSelection);
0720 
0721     a = ac->addAction(QStringLiteral("switch_next_input_mode"));
0722     a->setText(i18n("Switch to Next Input Mode"));
0723     ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_V));
0724     a->setWhatsThis(i18n("Switch to the next input mode."));
0725     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::cycleInputMode);
0726 
0727     a = m_toggleInsert = new KToggleAction(i18n("Overwr&ite Mode"), this);
0728     ac->addAction(QStringLiteral("set_insert"), a);
0729     ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Insert));
0730     a->setWhatsThis(i18n("Choose whether you want the text you type to be inserted or to overwrite existing text."));
0731     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleInsert);
0732 
0733     KToggleAction *toggleAction;
0734     a = m_toggleDynWrap = toggleAction = new KToggleAction(i18n("&Dynamic Word Wrap"), this);
0735     a->setIcon(QIcon::fromTheme(QStringLiteral("text-wrap")));
0736     ac->addAction(QStringLiteral("view_dynamic_word_wrap"), a);
0737     a->setWhatsThis(
0738         i18n("If this option is checked, the text lines will be wrapped at the view border on the screen.<br /><br />"
0739              "This is only a view option, meaning the document will not changed."));
0740     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleDynWordWrap);
0741 
0742     a = m_setDynWrapIndicators = new KSelectAction(i18n("Dynamic Word Wrap Indicators"), this);
0743     ac->addAction(QStringLiteral("dynamic_word_wrap_indicators"), a);
0744     a->setWhatsThis(i18n("Choose when the Dynamic Word Wrap Indicators should be displayed"));
0745     connect(m_setDynWrapIndicators, &KSelectAction::indexTriggered, this, &KTextEditor::ViewPrivate::setDynWrapIndicators);
0746     const QStringList list2{i18n("&Off"), i18n("Follow &Line Numbers"), i18n("&Always On")};
0747     m_setDynWrapIndicators->setItems(list2);
0748     m_setDynWrapIndicators->setEnabled(m_toggleDynWrap->isChecked()); // only synced on real change, later
0749 
0750     a = toggleAction = new KToggleAction(i18n("Static Word Wrap"), this);
0751     ac->addAction(QStringLiteral("view_static_word_wrap"), a);
0752     a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the column defined in the editing properties."));
0753     connect(a, &KToggleAction::triggered, [=] {
0754         if (m_doc) {
0755             m_doc->setWordWrap(!m_doc->wordWrap());
0756         }
0757     });
0758 
0759     a = toggleAction = m_toggleWWMarker = new KToggleAction(i18n("Show Static &Word Wrap Marker"), this);
0760     ac->addAction(QStringLiteral("view_word_wrap_marker"), a);
0761     a->setWhatsThis(
0762         i18n("Show/hide the Word Wrap Marker, a vertical line drawn at the word "
0763              "wrap column as defined in the editing properties"));
0764     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleWWMarker);
0765 
0766     a = toggleAction = m_toggleFoldingMarkers = new KToggleAction(i18n("Show Folding &Markers"), this);
0767     ac->addAction(QStringLiteral("view_folding_markers"), a);
0768     a->setWhatsThis(i18n("You can choose if the codefolding marks should be shown, if codefolding is possible."));
0769     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleFoldingMarkers);
0770 
0771     a = m_toggleIconBar = toggleAction = new KToggleAction(i18n("Show &Icon Border"), this);
0772     ac->addAction(QStringLiteral("view_border"), a);
0773     a->setWhatsThis(i18n("Show/hide the icon border.<br /><br />The icon border shows bookmark symbols, for instance."));
0774     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleIconBorder);
0775 
0776     a = toggleAction = m_toggleLineNumbers = new KToggleAction(i18n("Show &Line Numbers"), this);
0777     ac->addAction(QStringLiteral("view_line_numbers"), a);
0778     a->setWhatsThis(i18n("Show/hide the line numbers on the left hand side of the view."));
0779     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleLineNumbersOn);
0780 
0781     a = m_toggleScrollBarMarks = toggleAction = new KToggleAction(i18n("Show Scroll&bar Marks"), this);
0782     ac->addAction(QStringLiteral("view_scrollbar_marks"), a);
0783     a->setWhatsThis(i18n("Show/hide the marks on the vertical scrollbar.<br /><br />The marks show bookmarks, for instance."));
0784     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleScrollBarMarks);
0785 
0786     a = m_toggleScrollBarMiniMap = toggleAction = new KToggleAction(i18n("Show Scrollbar Mini-Map"), this);
0787     ac->addAction(QStringLiteral("view_scrollbar_minimap"), a);
0788     a->setWhatsThis(i18n("Show/hide the mini-map on the vertical scrollbar.<br /><br />The mini-map shows an overview of the whole document."));
0789     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleScrollBarMiniMap);
0790 
0791     a = m_doc->autoReloadToggleAction();
0792     ac->addAction(QStringLiteral("view_auto_reload"), a);
0793 
0794     //   a = m_toggleScrollBarMiniMapAll = toggleAction = new KToggleAction(i18n("Show the whole document in the Mini-Map"), this);
0795     //   ac->addAction(QLatin1String("view_scrollbar_minimap_all"), a);
0796     //   a->setWhatsThis(i18n("Display the whole document in the mini-map.<br /><br />With this option set the whole document will be visible in the
0797     //   mini-map.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMapAll())); connect(m_toggleScrollBarMiniMap, SIGNAL(triggered(bool)),
0798     //   m_toggleScrollBarMiniMapAll, SLOT(setEnabled(bool)));
0799 
0800     a = m_toggleNPSpaces = new KToggleAction(i18n("Show Non-Printable Spaces"), this);
0801     ac->addAction(QStringLiteral("view_non_printable_spaces"), a);
0802     a->setWhatsThis(i18n("Show/hide bounding box around non-printable spaces"));
0803     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleNPSpaces);
0804 
0805     a = m_switchCmdLine = ac->addAction(QStringLiteral("switch_to_cmd_line"));
0806     a->setText(i18n("Switch to Command Line"));
0807     ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F7));
0808     a->setWhatsThis(i18n("Show/hide the command line on the bottom of the view."));
0809     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::switchToCmdLine);
0810 
0811     KActionMenu *am = new KActionMenu(i18n("Input Modes"), this);
0812     m_inputModeActions = new QActionGroup(am);
0813     ac->addAction(QStringLiteral("view_input_modes"), am);
0814     auto switchInputModeAction = ac->action(QStringLiteral("switch_next_input_mode"));
0815     am->addAction(switchInputModeAction);
0816     am->addSeparator();
0817     for (const auto &mode : m_viewInternal->m_inputModes) {
0818         a = new QAction(mode->viewInputModeHuman(), m_inputModeActions);
0819         am->addAction(a);
0820         a->setWhatsThis(i18n("Activate/deactivate %1", mode->viewInputModeHuman()));
0821         const InputMode im = mode->viewInputMode();
0822         a->setData(static_cast<int>(im));
0823         a->setCheckable(true);
0824         if (im == m_config->inputMode()) {
0825             a->setChecked(true);
0826         }
0827         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleInputMode);
0828     }
0829 
0830     a = m_setEndOfLine = new KSelectAction(i18n("&End of Line"), this);
0831     ac->addAction(QStringLiteral("set_eol"), a);
0832     a->setWhatsThis(i18n("Choose which line endings should be used, when you save the document"));
0833     const QStringList list{i18nc("@item:inmenu End of Line", "&UNIX"),
0834                            i18nc("@item:inmenu End of Line", "&Windows/DOS"),
0835                            i18nc("@item:inmenu End of Line", "&Macintosh")};
0836     m_setEndOfLine->setItems(list);
0837     m_setEndOfLine->setCurrentItem(doc()->config()->eol());
0838     connect(m_setEndOfLine, &KSelectAction::indexTriggered, this, &KTextEditor::ViewPrivate::setEol);
0839 
0840     a = m_addBom = new KToggleAction(i18n("Add &Byte Order Mark (BOM)"), this);
0841     m_addBom->setChecked(doc()->config()->bom());
0842     ac->addAction(QStringLiteral("add_bom"), a);
0843     a->setWhatsThis(i18n("Enable/disable adding of byte order marks for UTF-8/UTF-16 encoded files while saving"));
0844     connect(m_addBom, &KToggleAction::triggered, this, &KTextEditor::ViewPrivate::setAddBom);
0845 
0846     // encoding menu
0847     m_encodingAction = new KateViewEncodingAction(m_doc, this, i18n("E&ncoding"), this);
0848     ac->addAction(QStringLiteral("set_encoding"), m_encodingAction);
0849 
0850     a = ac->addAction(KStandardAction::Find, this, SLOT(find()));
0851     a->setWhatsThis(i18n("Look up the first occurrence of a piece of text or regular expression."));
0852     addAction(a);
0853 
0854     a = ac->addAction(QStringLiteral("edit_find_selected"));
0855     a->setText(i18n("Find Selected"));
0856     ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_H));
0857     a->setWhatsThis(i18n("Finds next occurrence of selected text."));
0858     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findSelectedForwards);
0859 
0860     a = ac->addAction(QStringLiteral("edit_find_selected_backwards"));
0861     a->setText(i18n("Find Selected Backwards"));
0862     ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_H));
0863     a->setWhatsThis(i18n("Finds previous occurrence of selected text."));
0864     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findSelectedBackwards);
0865 
0866     a = ac->addAction(QStringLiteral("edit_find_multicursor_next_occurrence"));
0867     a->setText(i18n("Find and Select Next Occurrence"));
0868     ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_J));
0869     a->setWhatsThis(i18n("Finds next occurrence of the word under cursor and add it to selection."));
0870     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findNextOccurunceAndSelect);
0871 
0872     a = ac->addAction(QStringLiteral("edit_skip_multicursor_current_occurrence"));
0873     a->setText(i18n("Mark Currently Selected Occurrence as Skipped"));
0874     ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_K));
0875     a->setWhatsThis(i18n("Marks the currently selected word as skipped."));
0876     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::skipCurrentOccurunceSelection);
0877 
0878     a = ac->addAction(QStringLiteral("edit_find_multicursor_all_occurrences"));
0879     a->setText(i18n("Find and Select All Occurrences"));
0880     ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::CTRL | Qt::Key_J));
0881     a->setWhatsThis(i18n("Finds all occurrences of the word under cursor and select them."));
0882     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findAllOccuruncesAndSelect);
0883 
0884     a = ac->addAction(KStandardAction::FindNext, this, SLOT(findNext()));
0885     a->setWhatsThis(i18n("Look up the next occurrence of the search phrase."));
0886     addAction(a);
0887 
0888     a = ac->addAction(KStandardAction::FindPrev, QStringLiteral("edit_find_prev"), this, SLOT(findPrevious()));
0889     a->setWhatsThis(i18n("Look up the previous occurrence of the search phrase."));
0890     addAction(a);
0891 
0892     a = ac->addAction(KStandardAction::Replace, this, SLOT(replace()));
0893     a->setWhatsThis(i18n("Look up a piece of text or regular expression and replace the result with some given text."));
0894 
0895     a = ac->addAction(QStringLiteral("edit_create_multi_cursor_from_sel"));
0896     a->setText(i18n("Add Cursors to Line Ends"));
0897     ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_I));
0898     a->setWhatsThis(i18n("Creates a cursor at the end of every line in selection."));
0899     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::createMultiCursorsFromSelection);
0900 
0901     a = ac->addAction(QStringLiteral("edit_create_multi_cursor_down"));
0902     a->setText(i18n("Add Caret below Cursor"));
0903     ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Down));
0904     a->setWhatsThis(i18n("Adds a caret in the line below the current caret."));
0905     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::addSecondaryCursorDown);
0906 
0907     a = ac->addAction(QStringLiteral("edit_create_multi_cursor_up"));
0908     a->setText(i18n("Add Caret above Cursor"));
0909     ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Up));
0910     a->setWhatsThis(i18n("Adds a caret in the line above the current caret."));
0911     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::addSecondaryCursorUp);
0912 
0913     a = ac->addAction(QStringLiteral("edit_toggle_camel_case_cursor"));
0914     a->setText(i18n("Toggle Camel Case Cursor Movement"));
0915     a->setWhatsThis(i18n("Toggle between normal word movement and camel case cursor movement."));
0916     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleCamelCaseCursor);
0917 
0918     a = ac->addAction(QStringLiteral("edit_remove_cursors_from_empty_lines"));
0919     a->setText(i18n("Remove Cursors from Empty Lines"));
0920     a->setWhatsThis(i18n("Remove cursors from empty lines"));
0921     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::removeCursorsFromEmptyLines);
0922 
0923     m_spell->createActions(ac);
0924     m_toggleOnTheFlySpellCheck = new KToggleAction(i18n("Automatic Spell Checking"), this);
0925     m_toggleOnTheFlySpellCheck->setWhatsThis(i18n("Enable/disable automatic spell checking"));
0926     connect(m_toggleOnTheFlySpellCheck, &KToggleAction::triggered, this, &KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck);
0927     ac->addAction(QStringLiteral("tools_toggle_automatic_spell_checking"), m_toggleOnTheFlySpellCheck);
0928     ac->setDefaultShortcut(m_toggleOnTheFlySpellCheck, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_O));
0929 
0930     a = ac->addAction(QStringLiteral("tools_change_dictionary"));
0931     a->setText(i18n("Change Dictionary..."));
0932     a->setWhatsThis(i18n("Change the dictionary that is used for spell checking."));
0933     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::changeDictionary);
0934 
0935     a = ac->addAction(QStringLiteral("tools_clear_dictionary_ranges"));
0936     a->setText(i18n("Clear Dictionary Ranges"));
0937     a->setEnabled(false);
0938     a->setWhatsThis(i18n("Remove all the separate dictionary ranges that were set for spell checking."));
0939     connect(a, &QAction::triggered, m_doc, &KTextEditor::DocumentPrivate::clearDictionaryRanges);
0940     connect(m_doc, &KTextEditor::DocumentPrivate::dictionaryRangesPresent, a, &QAction::setEnabled);
0941 
0942     m_copyHtmlAction = ac->addAction(QStringLiteral("edit_copy_html"), this, SLOT(exportHtmlToClipboard()));
0943     m_copyHtmlAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
0944     m_copyHtmlAction->setText(i18n("Copy as &HTML"));
0945     m_copyHtmlAction->setWhatsThis(i18n("Use this command to copy the currently selected text as HTML to the system clipboard."));
0946 
0947     a = ac->addAction(QStringLiteral("file_export_html"), this, SLOT(exportHtmlToFile()));
0948     a->setIcon(QIcon::fromTheme(QStringLiteral("document-export")));
0949     a->setText(i18n("E&xport as HTML..."));
0950     a->setWhatsThis(
0951         i18n("This command allows you to export the current document"
0952              " with all highlighting information into a HTML document."));
0953 
0954     m_spellingMenu->createActions(ac);
0955 
0956     m_bookmarks->createActions(ac);
0957 
0958     slotSelectionChanged();
0959 
0960     // Now setup the editing actions before adding the associated
0961     // widget and setting the shortcut context
0962     setupEditActions();
0963     setupCodeFolding();
0964 
0965     ac->addAssociatedWidget(m_viewInternal);
0966 
0967     const auto actions = ac->actions();
0968     for (QAction *action : actions) {
0969         action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
0970     }
0971 
0972     connect(this, &KTextEditor::ViewPrivate::selectionChanged, this, &KTextEditor::ViewPrivate::slotSelectionChanged);
0973 }
0974 
0975 void KTextEditor::ViewPrivate::slotConfigDialog()
0976 {
0977     // invoke config dialog, will auto-save configuration to katepartrc
0978     KTextEditor::EditorPrivate::self()->configDialog(this);
0979 }
0980 
0981 void KTextEditor::ViewPrivate::setupEditActions()
0982 {
0983     // If you add an editing action to this
0984     // function make sure to include the line
0985     // m_editActions << a after creating the action
0986     KActionCollection *ac = actionCollection();
0987 
0988     QAction *a = ac->addAction(QStringLiteral("word_left"));
0989     a->setText(i18n("Move Word Left"));
0990     ac->setDefaultShortcuts(a, KStandardShortcut::backwardWord());
0991     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::wordLeft);
0992     m_editActions.push_back(a);
0993 
0994     a = ac->addAction(QStringLiteral("select_char_left"));
0995     a->setText(i18n("Select Character Left"));
0996     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Left));
0997     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftCursorLeft);
0998     m_editActions.push_back(a);
0999 
1000     a = ac->addAction(QStringLiteral("select_word_left"));
1001     a->setText(i18n("Select Word Left"));
1002     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Left));
1003     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftWordLeft);
1004     m_editActions.push_back(a);
1005 
1006     a = ac->addAction(QStringLiteral("word_right"));
1007     a->setText(i18n("Move Word Right"));
1008     ac->setDefaultShortcuts(a, KStandardShortcut::forwardWord());
1009     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::wordRight);
1010     m_editActions.push_back(a);
1011 
1012     a = ac->addAction(QStringLiteral("select_char_right"));
1013     a->setText(i18n("Select Character Right"));
1014     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Right));
1015     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftCursorRight);
1016     m_editActions.push_back(a);
1017 
1018     a = ac->addAction(QStringLiteral("select_word_right"));
1019     a->setText(i18n("Select Word Right"));
1020     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Right));
1021     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftWordRight);
1022     m_editActions.push_back(a);
1023 
1024     a = ac->addAction(QStringLiteral("beginning_of_line"));
1025     a->setText(i18n("Move to Beginning of Line"));
1026     ac->setDefaultShortcuts(a, KStandardShortcut::beginningOfLine());
1027     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::home);
1028     m_editActions.push_back(a);
1029 
1030     a = ac->addAction(QStringLiteral("beginning_of_document"));
1031     a->setText(i18n("Move to Beginning of Document"));
1032     ac->setDefaultShortcuts(a, KStandardShortcut::begin());
1033     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::top);
1034     m_editActions.push_back(a);
1035 
1036     a = ac->addAction(QStringLiteral("select_beginning_of_line"));
1037     a->setText(i18n("Select to Beginning of Line"));
1038     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Home));
1039     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftHome);
1040     m_editActions.push_back(a);
1041 
1042     a = ac->addAction(QStringLiteral("select_beginning_of_document"));
1043     a->setText(i18n("Select to Beginning of Document"));
1044     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Home));
1045     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftTop);
1046     m_editActions.push_back(a);
1047 
1048     a = ac->addAction(QStringLiteral("end_of_line"));
1049     a->setText(i18n("Move to End of Line"));
1050     ac->setDefaultShortcuts(a, KStandardShortcut::endOfLine());
1051     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::end);
1052     m_editActions.push_back(a);
1053 
1054     a = ac->addAction(QStringLiteral("end_of_document"));
1055     a->setText(i18n("Move to End of Document"));
1056     ac->setDefaultShortcuts(a, KStandardShortcut::end());
1057     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::bottom);
1058     m_editActions.push_back(a);
1059 
1060     a = ac->addAction(QStringLiteral("select_end_of_line"));
1061     a->setText(i18n("Select to End of Line"));
1062     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_End));
1063     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftEnd);
1064     m_editActions.push_back(a);
1065 
1066     a = ac->addAction(QStringLiteral("select_end_of_document"));
1067     a->setText(i18n("Select to End of Document"));
1068     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_End));
1069     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftBottom);
1070     m_editActions.push_back(a);
1071 
1072     a = ac->addAction(QStringLiteral("select_line_up"));
1073     a->setText(i18n("Select to Previous Line"));
1074     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Up));
1075     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftUp);
1076     m_editActions.push_back(a);
1077 
1078     a = ac->addAction(QStringLiteral("scroll_line_up"));
1079     a->setText(i18n("Scroll Line Up"));
1080     ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_Up));
1081     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::scrollUp);
1082     m_editActions.push_back(a);
1083 
1084     a = ac->addAction(QStringLiteral("move_line_down"));
1085     a->setText(i18n("Move to Next Line"));
1086     ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Down));
1087     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::down);
1088     m_editActions.push_back(a);
1089 
1090     a = ac->addAction(QStringLiteral("move_line_up"));
1091     a->setText(i18n("Move to Previous Line"));
1092     ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Up));
1093     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::up);
1094     m_editActions.push_back(a);
1095 
1096     a = ac->addAction(QStringLiteral("move_cursor_right"));
1097     a->setText(i18n("Move Cursor Right"));
1098     ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Right));
1099     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::cursorRight);
1100     m_editActions.push_back(a);
1101 
1102     a = ac->addAction(QStringLiteral("move_cursor_left"));
1103     a->setText(i18n("Move Cursor Left"));
1104     ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Left));
1105     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::cursorLeft);
1106     m_editActions.push_back(a);
1107 
1108     a = ac->addAction(QStringLiteral("select_line_down"));
1109     a->setText(i18n("Select to Next Line"));
1110     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Down));
1111     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftDown);
1112     m_editActions.push_back(a);
1113 
1114     a = ac->addAction(QStringLiteral("scroll_line_down"));
1115     a->setText(i18n("Scroll Line Down"));
1116     ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_Down));
1117     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::scrollDown);
1118     m_editActions.push_back(a);
1119 
1120     a = ac->addAction(QStringLiteral("scroll_page_up"));
1121     a->setText(i18n("Scroll Page Up"));
1122     ac->setDefaultShortcuts(a, KStandardShortcut::prior());
1123     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::pageUp);
1124     m_editActions.push_back(a);
1125 
1126     a = ac->addAction(QStringLiteral("select_page_up"));
1127     a->setText(i18n("Select Page Up"));
1128     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_PageUp));
1129     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftPageUp);
1130     m_editActions.push_back(a);
1131 
1132     a = ac->addAction(QStringLiteral("move_top_of_view"));
1133     a->setText(i18n("Move to Top of View"));
1134     ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_Home));
1135     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::topOfView);
1136     m_editActions.push_back(a);
1137 
1138     a = ac->addAction(QStringLiteral("select_top_of_view"));
1139     a->setText(i18n("Select to Top of View"));
1140     ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_Home));
1141     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftTopOfView);
1142     m_editActions.push_back(a);
1143 
1144     a = ac->addAction(QStringLiteral("scroll_page_down"));
1145     a->setText(i18n("Scroll Page Down"));
1146     ac->setDefaultShortcuts(a, KStandardShortcut::next());
1147     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::pageDown);
1148     m_editActions.push_back(a);
1149 
1150     a = ac->addAction(QStringLiteral("select_page_down"));
1151     a->setText(i18n("Select Page Down"));
1152     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_PageDown));
1153     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftPageDown);
1154     m_editActions.push_back(a);
1155 
1156     a = ac->addAction(QStringLiteral("move_bottom_of_view"));
1157     a->setText(i18n("Move to Bottom of View"));
1158     ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_End));
1159     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::bottomOfView);
1160     m_editActions.push_back(a);
1161 
1162     a = ac->addAction(QStringLiteral("select_bottom_of_view"));
1163     a->setText(i18n("Select to Bottom of View"));
1164     ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_End));
1165     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftBottomOfView);
1166     m_editActions.push_back(a);
1167 
1168     a = ac->addAction(QStringLiteral("to_matching_bracket"));
1169     a->setText(i18n("Go to Matching Bracket"));
1170     ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_6));
1171     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toMatchingBracket);
1172     // m_editActions << a;
1173 
1174     a = ac->addAction(QStringLiteral("select_matching_bracket"));
1175     a->setText(i18n("Select to Matching Bracket"));
1176     ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_6));
1177     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::shiftToMatchingBracket);
1178     // m_editActions << a;
1179 
1180     // anders: shortcuts doing any changes should not be created in read-only mode
1181     if (!doc()->readOnly()) {
1182         a = ac->addAction(QStringLiteral("transpose_char"));
1183         a->setText(i18n("Transpose Characters"));
1184         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::transpose);
1185         m_editActions.push_back(a);
1186 
1187         a = ac->addAction(QStringLiteral("transpose_word"));
1188         a->setText(i18n("Transpose Words"));
1189         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::transposeWord);
1190         m_editActions.push_back(a);
1191 
1192         a = ac->addAction(QStringLiteral("delete_line"));
1193         a->setText(i18n("Delete Line"));
1194         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_K));
1195         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::killLine);
1196         m_editActions.push_back(a);
1197 
1198         a = ac->addAction(QStringLiteral("delete_word_left"));
1199         a->setText(i18n("Delete Word Left"));
1200         ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordBack());
1201         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::deleteWordLeft);
1202         m_editActions.push_back(a);
1203 
1204         a = ac->addAction(QStringLiteral("delete_word_right"));
1205         a->setText(i18n("Delete Word Right"));
1206         ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordForward());
1207         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::deleteWordRight);
1208         m_editActions.push_back(a);
1209 
1210         a = ac->addAction(QStringLiteral("delete_next_character"));
1211         a->setText(i18n("Delete Next Character"));
1212         ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Delete));
1213         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::keyDelete);
1214         m_editActions.push_back(a);
1215 
1216         a = ac->addAction(QStringLiteral("backspace"));
1217         a->setText(i18n("Backspace"));
1218         QList<QKeySequence> scuts;
1219         scuts << QKeySequence(Qt::Key_Backspace) << QKeySequence(Qt::SHIFT | Qt::Key_Backspace);
1220         ac->setDefaultShortcuts(a, scuts);
1221         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::backspace);
1222         m_editActions.push_back(a);
1223 
1224         a = ac->addAction(QStringLiteral("insert_tabulator"));
1225         a->setText(i18n("Insert Tab Character"));
1226         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::insertTab);
1227         m_editActions.push_back(a);
1228 
1229         a = ac->addAction(QStringLiteral("smart_newline"));
1230         a->setText(i18n("Insert Smart Newline"));
1231         a->setWhatsThis(i18n("Insert newline including leading characters of the current line which are not letters or numbers."));
1232         scuts.clear();
1233         scuts << QKeySequence(Qt::SHIFT | Qt::Key_Return) << QKeySequence(Qt::SHIFT | Qt::Key_Enter);
1234         ac->setDefaultShortcuts(a, scuts);
1235         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::smartNewline);
1236         m_editActions.push_back(a);
1237 
1238         a = ac->addAction(QStringLiteral("no_indent_newline"));
1239         a->setText(i18n("Insert a Non-Indented Newline"));
1240         a->setWhatsThis(i18n("Insert a new line without indentation, regardless of indentation settings."));
1241         scuts.clear();
1242         scuts << QKeySequence(Qt::CTRL | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::Key_Enter);
1243         ac->setDefaultShortcuts(a, scuts);
1244         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::noIndentNewline);
1245         m_editActions.push_back(a);
1246 
1247         a = ac->addAction(QStringLiteral("newline_above"));
1248         a->setText(i18n("Insert a Newline Above Current Line"));
1249         a->setWhatsThis(i18n("Insert a new line above current line without modifying the current line."));
1250         scuts.clear();
1251         scuts << QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Enter);
1252         ac->setDefaultShortcuts(a, scuts);
1253         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::newLineAbove);
1254         m_editActions.push_back(a);
1255 
1256         a = ac->addAction(QStringLiteral("newline_below"));
1257         a->setText(i18n("Insert a Newline Below Current Line"));
1258         a->setWhatsThis(i18n("Insert a new line below current line without modifying the current line."));
1259         scuts.clear();
1260         scuts << QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Enter);
1261         ac->setDefaultShortcuts(a, scuts);
1262         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::newLineBelow);
1263         m_editActions.push_back(a);
1264 
1265         a = ac->addAction(QStringLiteral("tools_indent"));
1266         a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-more")));
1267         a->setText(i18n("&Indent"));
1268         a->setWhatsThis(
1269             i18n("Use this to indent a selected block of text.<br /><br />"
1270                  "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog."));
1271         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_I));
1272         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::indent);
1273 
1274         a = ac->addAction(QStringLiteral("tools_unindent"));
1275         a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-less")));
1276         a->setText(i18n("&Unindent"));
1277         a->setWhatsThis(i18n("Use this to unindent a selected block of text."));
1278         ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_I));
1279         connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::unIndent);
1280     }
1281 
1282     if (hasFocus()) {
1283         slotGotFocus();
1284     } else {
1285         slotLostFocus();
1286     }
1287 }
1288 
1289 void KTextEditor::ViewPrivate::setupCodeFolding()
1290 {
1291     KActionCollection *ac = this->actionCollection();
1292     QAction *a;
1293 
1294     a = ac->addAction(QStringLiteral("folding_toplevel"));
1295     a->setText(i18n("Fold Toplevel Nodes"));
1296     ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus));
1297     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotFoldToplevelNodes);
1298 
1299     a = ac->addAction(QStringLiteral("folding_expandtoplevel"));
1300     a->setText(i18n("Unfold Toplevel Nodes"));
1301     ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus));
1302     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotExpandToplevelNodes);
1303 
1304     /*a = ac->addAction(QLatin1String("folding_expandall"));
1305     a->setText(i18n("Unfold All Nodes"));
1306     connect(a, SIGNAL(triggered(bool)), doc()->foldingTree(), SLOT(expandAll()));
1307 
1308     a = ac->addAction(QLatin1String("folding_collapse_dsComment"));
1309     a->setText(i18n("Fold Multiline Comments"));
1310     connect(a, SIGNAL(triggered(bool)), doc()->foldingTree(), SLOT(collapseAll_dsComments()));
1311     */
1312     a = ac->addAction(QStringLiteral("folding_toggle_current"));
1313     a->setText(i18n("Toggle Current Node"));
1314     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotToggleFolding);
1315 
1316     a = ac->addAction(QStringLiteral("folding_toggle_in_current"));
1317     a->setText(i18n("Toggle Contained Nodes"));
1318     connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotToggleFoldingsInRange);
1319 }
1320 
1321 void KTextEditor::ViewPrivate::slotFoldToplevelNodes()
1322 {
1323     for (int line = 0; line < doc()->lines(); ++line) {
1324         if (textFolding().isLineVisible(line)) {
1325             foldLine(line);
1326         }
1327     }
1328 }
1329 
1330 void KTextEditor::ViewPrivate::slotExpandToplevelNodes()
1331 {
1332     const auto topLevelRanges(textFolding().foldingRangesForParentRange());
1333     for (const auto &range : topLevelRanges) {
1334         textFolding().unfoldRange(range.first);
1335     }
1336 }
1337 
1338 void KTextEditor::ViewPrivate::slotToggleFolding()
1339 {
1340     int line = cursorPosition().line();
1341     bool actionDone = false;
1342     while (!actionDone && (line > -1)) {
1343         actionDone = unfoldLine(line);
1344         if (!actionDone) {
1345             actionDone = foldLine(line--).isValid();
1346         }
1347     }
1348 }
1349 
1350 void KTextEditor::ViewPrivate::slotToggleFoldingsInRange()
1351 {
1352     int line = cursorPosition().line();
1353     while (!toggleFoldingsInRange(line) && (line > -1)) {
1354         --line;
1355     }
1356 }
1357 
1358 KTextEditor::Range KTextEditor::ViewPrivate::foldLine(int line)
1359 {
1360     KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line);
1361     if (!foldingRange.isValid()) {
1362         return foldingRange;
1363     }
1364 
1365     // Ensure not to fold the end marker to avoid a deceptive look, but only on token based folding
1366     // ensure we don't compute an invalid line by moving outside of the foldingRange range by checking onSingleLine(), see bug 417890
1367     if (!m_doc->buffer().isFoldingStartingOnLine(line).second && !foldingRange.onSingleLine()) {
1368         const int adjustedLine = foldingRange.end().line() - 1;
1369         foldingRange.setEnd(KTextEditor::Cursor(adjustedLine, doc()->buffer().plainLine(adjustedLine)->length()));
1370     }
1371 
1372     // Don't try to fold a single line, which can happens due to adjustment above
1373     // FIXME Avoid to offer such a folding marker
1374     if (!foldingRange.onSingleLine()) {
1375         textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded);
1376     }
1377 
1378     return foldingRange;
1379 }
1380 
1381 bool KTextEditor::ViewPrivate::unfoldLine(int line)
1382 {
1383     bool actionDone = false;
1384     const KTextEditor::Cursor currentCursor = cursorPosition();
1385 
1386     // ask the folding info for this line, if any folds are around!
1387     // auto = QVector<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>>
1388     auto startingRanges = textFolding().foldingRangesStartingOnLine(line);
1389     for (int i = 0; i < startingRanges.size() && !actionDone; ++i) {
1390         // Avoid jumping view in case of a big unfold and ensure nice highlight of folding marker
1391         setCursorPosition(textFolding().foldingRange(startingRanges[i].first).start());
1392 
1393         actionDone |= textFolding().unfoldRange(startingRanges[i].first);
1394     }
1395 
1396     if (!actionDone) {
1397         // Nothing unfolded? Restore old cursor position!
1398         setCursorPosition(currentCursor);
1399     }
1400 
1401     return actionDone;
1402 }
1403 
1404 bool KTextEditor::ViewPrivate::toggleFoldingOfLine(int line)
1405 {
1406     bool actionDone = unfoldLine(line);
1407     if (!actionDone) {
1408         actionDone = foldLine(line).isValid();
1409     }
1410 
1411     return actionDone;
1412 }
1413 
1414 bool KTextEditor::ViewPrivate::toggleFoldingsInRange(int line)
1415 {
1416     KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line);
1417     if (!foldingRange.isValid()) {
1418         // Either line is not valid or there is no start range
1419         return false;
1420     }
1421 
1422     bool actionDone = false; // Track success
1423     const KTextEditor::Cursor currentCursor = cursorPosition();
1424 
1425     // Don't be too eager but obliging! Only toggle containing ranges which are
1426     // visible -> Be done when the range is folded
1427     actionDone |= unfoldLine(line);
1428 
1429     if (!actionDone) {
1430         // Unfold all in range, but not the range itself
1431         for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) {
1432             actionDone |= unfoldLine(ln);
1433         }
1434 
1435         if (actionDone) {
1436             // In most cases we want now a not moved cursor
1437             setCursorPosition(currentCursor);
1438         }
1439     }
1440 
1441     if (!actionDone) {
1442         // Fold all in range, but not the range itself
1443         for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) {
1444             KTextEditor::Range fr = foldLine(ln);
1445             if (fr.isValid()) {
1446                 // qMax to avoid infinite loop in case of range without content
1447                 ln = qMax(ln, fr.end().line() - 1);
1448                 actionDone = true;
1449             }
1450         }
1451     }
1452 
1453     if (!actionDone) {
1454         // At this point was an unfolded range clicked which contains no "childs"
1455         // We assume the user want to fold it by the wrong button, be obliging!
1456         actionDone |= foldLine(line).isValid();
1457     }
1458 
1459     // At this point we should be always true
1460     return actionDone;
1461 }
1462 
1463 KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const
1464 {
1465     return currentInputMode()->viewMode();
1466 }
1467 
1468 QString KTextEditor::ViewPrivate::viewModeHuman() const
1469 {
1470     QString currentMode = currentInputMode()->viewModeHuman();
1471 
1472     // append read-only if needed
1473     if (!doc()->isReadWrite()) {
1474         currentMode = i18n("(R/O) %1", currentMode);
1475     }
1476 
1477     // return full mode
1478     return currentMode;
1479 }
1480 
1481 KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const
1482 {
1483     return currentInputMode()->viewInputMode();
1484 }
1485 
1486 QString KTextEditor::ViewPrivate::viewInputModeHuman() const
1487 {
1488     return currentInputMode()->viewInputModeHuman();
1489 }
1490 
1491 void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode, const bool rememberInConfig)
1492 {
1493     if (currentInputMode()->viewInputMode() == mode) {
1494         return;
1495     }
1496 
1497     // No multi cursors for vi
1498     if (mode == KTextEditor::View::InputMode::ViInputMode) {
1499         clearSecondaryCursors();
1500     }
1501 
1502     m_viewInternal->m_currentInputMode->deactivate();
1503     m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode].get();
1504     m_viewInternal->m_currentInputMode->activate();
1505 
1506     // remember in local config if requested, we skip this for the calls in updateConfig
1507     if (rememberInConfig) {
1508         config()->setValue(KateViewConfig::InputMode, mode);
1509     }
1510 
1511     /* small duplication, but need to do this if not toggled by action */
1512     const auto inputModeActions = m_inputModeActions->actions();
1513     for (QAction *action : inputModeActions) {
1514         if (static_cast<InputMode>(action->data().toInt()) == mode) {
1515             action->setChecked(true);
1516             break;
1517         }
1518     }
1519 
1520     /* inform the rest of the system about the change */
1521     Q_EMIT viewInputModeChanged(this, mode);
1522     Q_EMIT viewModeChanged(this, viewMode());
1523 }
1524 
1525 void KTextEditor::ViewPrivate::slotDocumentAboutToReload()
1526 {
1527     if (doc()->isAutoReload()) {
1528         const int lastVisibleLine = m_viewInternal->endLine();
1529         const int currentLine = cursorPosition().line();
1530         m_gotoBottomAfterReload = (lastVisibleLine == currentLine) && (currentLine == doc()->lastLine());
1531         if (!m_gotoBottomAfterReload) {
1532             // Ensure the view jumps not back when user scrolls around
1533             const int firstVisibleLine = 1 + lastVisibleLine - m_viewInternal->linesDisplayed();
1534             const int newLine = qBound(firstVisibleLine, currentLine, lastVisibleLine);
1535             setCursorPositionVisual(KTextEditor::Cursor(newLine, cursorPosition().column()));
1536         }
1537     } else {
1538         m_gotoBottomAfterReload = false;
1539     }
1540 }
1541 
1542 void KTextEditor::ViewPrivate::slotDocumentReloaded()
1543 {
1544     if (m_gotoBottomAfterReload) {
1545         bottom();
1546     }
1547 }
1548 
1549 void KTextEditor::ViewPrivate::slotGotFocus()
1550 {
1551     // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus";
1552     currentInputMode()->gotFocus();
1553 
1554     //  update current view and scrollbars
1555     //  it is needed for styles that implement different frame and scrollbar
1556     // rendering when focused
1557     update();
1558     if (m_viewInternal->m_lineScroll->isVisible()) {
1559         m_viewInternal->m_lineScroll->update();
1560     }
1561 
1562     if (m_viewInternal->m_columnScroll->isVisible()) {
1563         m_viewInternal->m_columnScroll->update();
1564     }
1565 
1566     Q_EMIT focusIn(this);
1567 }
1568 
1569 void KTextEditor::ViewPrivate::slotLostFocus()
1570 {
1571     // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus";
1572     currentInputMode()->lostFocus();
1573 
1574     //  update current view and scrollbars
1575     //  it is needed for styles that implement different frame and scrollbar
1576     // rendering when focused
1577     update();
1578     if (m_viewInternal->m_lineScroll->isVisible()) {
1579         m_viewInternal->m_lineScroll->update();
1580     }
1581 
1582     if (m_viewInternal->m_columnScroll->isVisible()) {
1583         m_viewInternal->m_columnScroll->update();
1584     }
1585 
1586     if (doc()->config()->autoSave() && doc()->config()->autoSaveOnFocusOut() && doc()->isModified() && doc()->url().isLocalFile()) {
1587         doc()->documentSave();
1588     }
1589 
1590     Q_EMIT focusOut(this);
1591 }
1592 
1593 void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode)
1594 {
1595     config()->setValue(KateViewConfig::DynWordWrapIndicators, mode);
1596 }
1597 
1598 bool KTextEditor::ViewPrivate::isOverwriteMode() const
1599 {
1600     return doc()->config()->ovr();
1601 }
1602 
1603 void KTextEditor::ViewPrivate::reloadFile()
1604 {
1605     // bookmarks and cursor positions are temporarily saved by the document
1606     doc()->documentReload();
1607 }
1608 
1609 void KTextEditor::ViewPrivate::slotReadWriteChanged()
1610 {
1611     if (m_toggleWriteLock) {
1612         m_toggleWriteLock->setChecked(!doc()->isReadWrite());
1613     }
1614 
1615     m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut()));
1616     m_paste->setEnabled(doc()->isReadWrite());
1617     if (m_pasteSelection) {
1618         m_pasteSelection->setEnabled(doc()->isReadWrite());
1619     }
1620     m_swapWithClipboard->setEnabled(doc()->isReadWrite());
1621     m_setEndOfLine->setEnabled(doc()->isReadWrite());
1622 
1623     static const auto l = {QStringLiteral("edit_replace"),
1624                            QStringLiteral("tools_spelling"),
1625                            QStringLiteral("tools_indent"),
1626                            QStringLiteral("tools_unindent"),
1627                            QStringLiteral("tools_cleanIndent"),
1628                            QStringLiteral("tools_formatIndet"),
1629                            QStringLiteral("tools_alignOn"),
1630                            QStringLiteral("tools_comment"),
1631                            QStringLiteral("tools_uncomment"),
1632                            QStringLiteral("tools_toggle_comment"),
1633                            QStringLiteral("tools_uppercase"),
1634                            QStringLiteral("tools_lowercase"),
1635                            QStringLiteral("tools_capitalize"),
1636                            QStringLiteral("tools_join_lines"),
1637                            QStringLiteral("tools_apply_wordwrap"),
1638                            QStringLiteral("tools_spelling_from_cursor"),
1639                            QStringLiteral("tools_spelling_selection")};
1640 
1641     for (const auto &action : l) {
1642         QAction *a = actionCollection()->action(action);
1643         if (a) {
1644             a->setEnabled(doc()->isReadWrite());
1645         }
1646     }
1647     slotUpdateUndo();
1648 
1649     currentInputMode()->readWriteChanged(doc()->isReadWrite());
1650 
1651     // => view mode changed
1652     Q_EMIT viewModeChanged(this, viewMode());
1653     Q_EMIT viewInputModeChanged(this, viewInputMode());
1654 }
1655 
1656 void KTextEditor::ViewPrivate::toggleCamelCaseCursor()
1657 {
1658     const auto enabled = doc()->config()->camelCursor();
1659     doc()->config()->setCamelCursor(!enabled);
1660     KTextEditor::Message *m;
1661     if (enabled) {
1662         m = new KTextEditor::Message(i18n("Camel case movement disabled"));
1663     } else {
1664         m = new KTextEditor::Message(i18n("Camel case movement enabled"));
1665     }
1666     m->setPosition(KTextEditor::Message::TopInView);
1667     m->setAutoHide(1000);
1668     m->setAutoHideMode(KTextEditor::Message::Immediate);
1669     doc()->postMessage(m);
1670 }
1671 
1672 void KTextEditor::ViewPrivate::slotUpdateUndo()
1673 {
1674     if (doc()->readOnly()) {
1675         return;
1676     }
1677 
1678     m_editUndo->setEnabled(doc()->isReadWrite() && doc()->undoCount() > 0);
1679     m_editRedo->setEnabled(doc()->isReadWrite() && doc()->redoCount() > 0);
1680 }
1681 
1682 bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor position, uint tabwidth, bool calledExternally)
1683 {
1684     Kate::TextLine l = doc()->kateTextLine(position.line());
1685 
1686     if (!l) {
1687         return false;
1688     }
1689 
1690     const QString line_str = l->text();
1691 
1692     int x = 0;
1693     int z = 0;
1694     for (; z < line_str.length() && z < position.column(); z++) {
1695         if (line_str[z] == QLatin1Char('\t')) {
1696             x += tabwidth - (x % tabwidth);
1697         } else {
1698             x++;
1699         }
1700     }
1701 
1702     if (blockSelection()) {
1703         if (z < position.column()) {
1704             x += position.column() - z;
1705         }
1706     }
1707 
1708     m_viewInternal->updateCursor(KTextEditor::Cursor(position.line(), x),
1709                                  false,
1710                                  calledExternally /* force center for external calls, see bug 408418 */,
1711                                  calledExternally);
1712 
1713     return true;
1714 }
1715 
1716 void KTextEditor::ViewPrivate::toggleInsert()
1717 {
1718     doc()->config()->setOvr(!doc()->config()->ovr());
1719     m_toggleInsert->setChecked(isOverwriteMode());
1720 
1721     // No multi cursors for overwrite mode
1722     if (isOverwriteMode()) {
1723         clearSecondaryCursors();
1724     }
1725 
1726     Q_EMIT viewModeChanged(this, viewMode());
1727     Q_EMIT viewInputModeChanged(this, viewInputMode());
1728 }
1729 
1730 void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error)
1731 {
1732     if (!error.isEmpty()) { // happens when canceling a job
1733         KMessageBox::error(this, error);
1734     }
1735 }
1736 
1737 void KTextEditor::ViewPrivate::gotoLine()
1738 {
1739     gotoBar()->updateData();
1740     bottomViewBar()->showBarWidget(gotoBar());
1741 }
1742 
1743 void KTextEditor::ViewPrivate::changeDictionary()
1744 {
1745     dictionaryBar()->updateData();
1746     bottomViewBar()->showBarWidget(dictionaryBar());
1747 }
1748 
1749 void KTextEditor::ViewPrivate::joinLines()
1750 {
1751     int first = selectionRange().start().line();
1752     int last = selectionRange().end().line();
1753     // int left = doc()->line( last ).length() - doc()->selEndCol();
1754     if (first == last) {
1755         first = cursorPosition().line();
1756         last = first + 1;
1757     }
1758     doc()->joinLines(first, last);
1759 }
1760 
1761 void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet<QString> &flags)
1762 {
1763     Q_UNUSED(flags)
1764 
1765     // cursor position
1766     setCursorPositionInternal(KTextEditor::Cursor(config.readEntry("CursorLine", 0), config.readEntry("CursorColumn", 0)));
1767 
1768     m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false));
1769 
1770     // restore text folding
1771     m_savedFoldingState = QJsonDocument::fromJson(config.readEntry("TextFolding", QByteArray()));
1772     applyFoldingState();
1773 
1774     for (const auto &mode : m_viewInternal->m_inputModes) {
1775         mode->readSessionConfig(config);
1776     }
1777 }
1778 
1779 void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet<QString> &flags)
1780 {
1781     Q_UNUSED(flags)
1782 
1783     // cursor position
1784     config.writeEntry("CursorLine", cursorPosition().line());
1785     config.writeEntry("CursorColumn", cursorPosition().column());
1786 
1787     config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap());
1788 
1789     // save text folding state
1790     saveFoldingState();
1791     config.writeEntry("TextFolding", m_savedFoldingState.toJson(QJsonDocument::Compact));
1792     m_savedFoldingState = QJsonDocument();
1793 
1794     for (const auto &mode : m_viewInternal->m_inputModes) {
1795         mode->writeSessionConfig(config);
1796     }
1797 }
1798 
1799 int KTextEditor::ViewPrivate::getEol() const
1800 {
1801     return doc()->config()->eol();
1802 }
1803 
1804 QMenu *KTextEditor::ViewPrivate::getEolMenu()
1805 {
1806     return m_setEndOfLine->menu();
1807 }
1808 
1809 void KTextEditor::ViewPrivate::setEol(int eol)
1810 {
1811     if (!doc()->isReadWrite()) {
1812         return;
1813     }
1814 
1815     if (m_updatingDocumentConfig) {
1816         return;
1817     }
1818 
1819     if (eol != doc()->config()->eol()) {
1820         doc()->setModified(true); // mark modified (bug #143120)
1821         doc()->config()->setEol(eol);
1822     }
1823 }
1824 
1825 void KTextEditor::ViewPrivate::setAddBom(bool enabled)
1826 {
1827     if (!doc()->isReadWrite()) {
1828         return;
1829     }
1830 
1831     if (m_updatingDocumentConfig) {
1832         return;
1833     }
1834 
1835     doc()->config()->setBom(enabled);
1836     doc()->bomSetByUser();
1837 }
1838 
1839 void KTextEditor::ViewPrivate::setIconBorder(bool enable)
1840 {
1841     config()->setValue(KateViewConfig::ShowIconBar, enable);
1842 }
1843 
1844 void KTextEditor::ViewPrivate::toggleIconBorder()
1845 {
1846     config()->setValue(KateViewConfig::ShowIconBar, !config()->iconBar());
1847 }
1848 
1849 void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable)
1850 {
1851     config()->setValue(KateViewConfig::ShowLineNumbers, enable);
1852 }
1853 
1854 void KTextEditor::ViewPrivate::toggleLineNumbersOn()
1855 {
1856     config()->setValue(KateViewConfig::ShowLineNumbers, !config()->lineNumbers());
1857 }
1858 
1859 void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable)
1860 {
1861     config()->setValue(KateViewConfig::ShowScrollBarMarks, enable);
1862 }
1863 
1864 void KTextEditor::ViewPrivate::toggleScrollBarMarks()
1865 {
1866     config()->setValue(KateViewConfig::ShowScrollBarMarks, !config()->scrollBarMarks());
1867 }
1868 
1869 void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable)
1870 {
1871     config()->setValue(KateViewConfig::ShowScrollBarMiniMap, enable);
1872 }
1873 
1874 void KTextEditor::ViewPrivate::toggleScrollBarMiniMap()
1875 {
1876     config()->setValue(KateViewConfig::ShowScrollBarMiniMap, !config()->scrollBarMiniMap());
1877 }
1878 
1879 void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable)
1880 {
1881     config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, enable);
1882 }
1883 
1884 void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll()
1885 {
1886     config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, !config()->scrollBarMiniMapAll());
1887 }
1888 
1889 void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width)
1890 {
1891     config()->setValue(KateViewConfig::ScrollBarMiniMapWidth, width);
1892 }
1893 
1894 void KTextEditor::ViewPrivate::toggleDynWordWrap()
1895 {
1896     config()->setDynWordWrap(!config()->dynWordWrap());
1897 }
1898 
1899 void KTextEditor::ViewPrivate::toggleWWMarker()
1900 {
1901     m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker());
1902 }
1903 
1904 void KTextEditor::ViewPrivate::toggleNPSpaces()
1905 {
1906     m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces());
1907     m_viewInternal->update(); // force redraw
1908 }
1909 
1910 void KTextEditor::ViewPrivate::toggleWordCount(bool on)
1911 {
1912     config()->setShowWordCount(on);
1913 }
1914 
1915 void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable)
1916 {
1917     config()->setValue(KateViewConfig::ShowFoldingBar, enable);
1918 }
1919 
1920 void KTextEditor::ViewPrivate::toggleFoldingMarkers()
1921 {
1922     config()->setValue(KateViewConfig::ShowFoldingBar, !config()->foldingBar());
1923 }
1924 
1925 bool KTextEditor::ViewPrivate::iconBorder()
1926 {
1927     return m_viewInternal->m_leftBorder->iconBorderOn();
1928 }
1929 
1930 bool KTextEditor::ViewPrivate::lineNumbersOn()
1931 {
1932     return m_viewInternal->m_leftBorder->lineNumbersOn();
1933 }
1934 
1935 bool KTextEditor::ViewPrivate::scrollBarMarks()
1936 {
1937     return m_viewInternal->m_lineScroll->showMarks();
1938 }
1939 
1940 bool KTextEditor::ViewPrivate::scrollBarMiniMap()
1941 {
1942     return m_viewInternal->m_lineScroll->showMiniMap();
1943 }
1944 
1945 int KTextEditor::ViewPrivate::dynWrapIndicators()
1946 {
1947     return m_viewInternal->m_leftBorder->dynWrapIndicators();
1948 }
1949 
1950 bool KTextEditor::ViewPrivate::foldingMarkersOn()
1951 {
1952     return m_viewInternal->m_leftBorder->foldingMarkersOn();
1953 }
1954 
1955 void KTextEditor::ViewPrivate::toggleWriteLock()
1956 {
1957     doc()->setReadWrite(!doc()->isReadWrite());
1958 }
1959 
1960 void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider)
1961 {
1962     m_viewInternal->registerTextHintProvider(provider);
1963 }
1964 
1965 void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider)
1966 {
1967     m_viewInternal->unregisterTextHintProvider(provider);
1968 }
1969 
1970 void KTextEditor::ViewPrivate::setTextHintDelay(int delay)
1971 {
1972     m_viewInternal->setTextHintDelay(delay);
1973 }
1974 
1975 int KTextEditor::ViewPrivate::textHintDelay() const
1976 {
1977     return m_viewInternal->textHintDelay();
1978 }
1979 
1980 void KTextEditor::ViewPrivate::find()
1981 {
1982     currentInputMode()->find();
1983 }
1984 
1985 void KTextEditor::ViewPrivate::findSelectedForwards()
1986 {
1987     currentInputMode()->findSelectedForwards();
1988 }
1989 
1990 void KTextEditor::ViewPrivate::findSelectedBackwards()
1991 {
1992     currentInputMode()->findSelectedBackwards();
1993 }
1994 
1995 void KTextEditor::ViewPrivate::skipCurrentOccurunceSelection()
1996 {
1997     if (isMulticursorNotAllowed()) {
1998         return;
1999     }
2000     m_skipCurrentSelection = true;
2001 }
2002 
2003 void KTextEditor::ViewPrivate::findNextOccurunceAndSelect()
2004 {
2005     if (isMulticursorNotAllowed()) {
2006         return;
2007     }
2008 
2009     const auto text = selection() ? doc()->text(selectionRange()) : QString();
2010     if (text.isEmpty()) {
2011         const auto selection = doc()->wordRangeAt(cursorPosition());
2012         // We don't want matching word highlights
2013         setSelection(selection);
2014         setCursorPosition(selection.end());
2015         clearHighlights();
2016 
2017         for (auto &c : m_secondaryCursors) {
2018             const auto range = doc()->wordRangeAt(c.cursor());
2019             if (!c.range && !c.anchor.isValid()) {
2020                 c.anchor = range.start();
2021                 c.range.reset(newSecondarySelectionRange(range));
2022                 c.pos->setPosition(range.end());
2023             }
2024             tagLines(range);
2025         }
2026         return;
2027     } else if (!m_rangesForHighlights.empty()) {
2028         clearHighlights();
2029     }
2030 
2031     // Use selection range end as starting point
2032     const auto lastSelectionRange = selectionRange();
2033 
2034     KTextEditor::Range searchRange(lastSelectionRange.end(), doc()->documentRange().end());
2035     QVector<KTextEditor::Range> matches = doc()->searchText(searchRange, text, KTextEditor::Default);
2036     if (!matches.isEmpty() && !matches.constFirst().isValid()) {
2037         searchRange.setRange(doc()->documentRange().start(), lastSelectionRange.end());
2038         matches = doc()->searchText(searchRange, text, KTextEditor::Default);
2039     }
2040 
2041     // No match found or only one possible match
2042     if (matches.empty() || !matches.constFirst().isValid() || matches.constFirst() == selectionRange()) {
2043         return;
2044     }
2045 
2046     auto it = std::find_if(m_secondaryCursors.begin(), m_secondaryCursors.end(), [&](const SecondaryCursor &c) {
2047         return c.range && c.range->toRange() == matches.constFirst();
2048     });
2049 
2050     if (it != m_secondaryCursors.end()) {
2051         m_secondaryCursors.erase(it);
2052     }
2053 
2054     // Move our primary to cursor to this match and select it
2055     // Ensure we don't create occurence highlights
2056     setSelection(matches.constFirst());
2057     setCursorPosition(matches.constFirst().end());
2058     clearHighlights();
2059 
2060     // If we are skipping this selection, then we don't have to do anything
2061     if (!m_skipCurrentSelection) {
2062         PlainSecondaryCursor c;
2063         c.pos = lastSelectionRange.end();
2064         c.range = lastSelectionRange;
2065         // make our previous primary selection a secondary
2066         addSecondaryCursorsWithSelection({c});
2067     }
2068     // reset value
2069     m_skipCurrentSelection = false;
2070 }
2071 
2072 void KTextEditor::ViewPrivate::findAllOccuruncesAndSelect()
2073 {
2074     if (isMulticursorNotAllowed()) {
2075         return;
2076     }
2077 
2078     QString text = selection() ? doc()->text(selectionRange()) : QString();
2079     if (text.isEmpty()) {
2080         const auto selection = doc()->wordRangeAt(cursorPosition());
2081         setSelection(selection);
2082         setCursorPosition(selection.end());
2083         clearHighlights();
2084         text = doc()->text(selection);
2085 
2086         for (auto &c : m_secondaryCursors) {
2087             const auto range = doc()->wordRangeAt(c.cursor());
2088             if (!c.range && !c.anchor.isValid()) {
2089                 c.anchor = range.start();
2090                 c.range.reset(newSecondarySelectionRange(range));
2091                 c.pos->setPosition(range.end());
2092             }
2093             tagLines(range);
2094         }
2095     }
2096 
2097     KTextEditor::Range searchRange(doc()->documentRange());
2098     QVector<KTextEditor::Range> matches;
2099     QVector<PlainSecondaryCursor> resultRanges;
2100     do {
2101         matches = doc()->searchText(searchRange, text, KTextEditor::Default);
2102 
2103         if (matches.constFirst().isValid()) {
2104             // Dont add if matches primary selection
2105             if (matches.constFirst() != selectionRange()) {
2106                 PlainSecondaryCursor c;
2107                 c.pos = matches.constFirst().end();
2108                 c.range = matches.constFirst();
2109                 resultRanges.push_back(c);
2110             }
2111             searchRange.setStart(matches.constFirst().end());
2112         }
2113     } while (matches.first().isValid());
2114 
2115     // ensure to clear occurence highlights
2116     if (!resultRanges.empty()) {
2117         clearHighlights();
2118     }
2119 
2120     clearSecondaryCursors();
2121     addSecondaryCursorsWithSelection(resultRanges);
2122 }
2123 
2124 void KTextEditor::ViewPrivate::replace()
2125 {
2126     currentInputMode()->findReplace();
2127 }
2128 
2129 void KTextEditor::ViewPrivate::findNext()
2130 {
2131     currentInputMode()->findNext();
2132 }
2133 
2134 void KTextEditor::ViewPrivate::findPrevious()
2135 {
2136     currentInputMode()->findPrevious();
2137 }
2138 
2139 void KTextEditor::ViewPrivate::showSearchWrappedHint(bool isReverseSearch)
2140 {
2141     // show message widget when wrapping
2142     const QIcon icon = isReverseSearch ? QIcon::fromTheme(QStringLiteral("go-up-search")) : QIcon::fromTheme(QStringLiteral("go-down-search"));
2143 
2144     if (!m_wrappedMessage || m_isLastSearchReversed != isReverseSearch) {
2145         m_isLastSearchReversed = isReverseSearch;
2146         m_wrappedMessage = new KTextEditor::Message(i18n("Search wrapped"), KTextEditor::Message::Information);
2147         m_wrappedMessage->setIcon(icon);
2148         m_wrappedMessage->setPosition(KTextEditor::Message::BottomInView);
2149         m_wrappedMessage->setAutoHide(2000);
2150         m_wrappedMessage->setAutoHideMode(KTextEditor::Message::Immediate);
2151         m_wrappedMessage->setView(this);
2152         this->doc()->postMessage(m_wrappedMessage);
2153     }
2154 }
2155 
2156 void KTextEditor::ViewPrivate::createMultiCursorsFromSelection()
2157 {
2158     if (!selection() || selectionRange().isEmpty()) {
2159         return;
2160     }
2161     // Is this really needed?
2162     // Lets just clear them now for simplicity
2163     clearSecondaryCursors();
2164 
2165     const auto range = selectionRange();
2166     QVector<KTextEditor::Cursor> cursorsToAdd;
2167     const auto start = range.start().line() < 0 ? 0 : range.start().line();
2168     const auto end = range.end().line() > doc()->lines() ? doc()->lines() : range.end().line();
2169     const auto currentLine = cursorPosition().line();
2170     setCursorPosition({currentLine, doc()->lineLength(currentLine)});
2171     for (int line = start; line <= end; ++line) {
2172         if (line != currentLine) {
2173             cursorsToAdd.push_back({line, doc()->lineLength(line)});
2174         }
2175     }
2176     // clear selection
2177     setSelection({});
2178     setSecondaryCursors(cursorsToAdd);
2179 }
2180 
2181 void KTextEditor::ViewPrivate::removeCursorsFromEmptyLines()
2182 {
2183     if (!m_secondaryCursors.empty()) {
2184         std::vector<KTextEditor::Cursor> cursorsToRemove;
2185         for (const auto &c : m_secondaryCursors) {
2186             auto cursor = c.cursor();
2187             if (doc()->lineLength(cursor.line()) == 0) {
2188                 cursorsToRemove.push_back(cursor);
2189             }
2190         }
2191         removeSecondaryCursors(cursorsToRemove);
2192     }
2193 }
2194 
2195 void KTextEditor::ViewPrivate::slotSelectionChanged()
2196 {
2197     m_copy->setEnabled(selection() || m_config->smartCopyCut());
2198     m_deSelect->setEnabled(selection());
2199     m_copyHtmlAction->setEnabled(selection());
2200 
2201     // update highlighting of current selected word
2202     selectionChangedForHighlights();
2203 
2204     if (doc()->readOnly()) {
2205         return;
2206     }
2207 
2208     m_cut->setEnabled(selection() || m_config->smartCopyCut());
2209 }
2210 
2211 void KTextEditor::ViewPrivate::switchToCmdLine()
2212 {
2213     currentInputMode()->activateCommandLine();
2214 }
2215 
2216 KateRenderer *KTextEditor::ViewPrivate::renderer()
2217 {
2218     return m_renderer;
2219 }
2220 
2221 void KTextEditor::ViewPrivate::updateConfig()
2222 {
2223     if (m_startingUp) {
2224         return;
2225     }
2226 
2227     // dyn. word wrap & markers
2228     if (m_hasWrap != config()->dynWordWrap()) {
2229         m_hasWrap = config()->dynWordWrap();
2230 
2231         m_viewInternal->dynWrapChanged();
2232 
2233         m_setDynWrapIndicators->setEnabled(config()->dynWordWrap());
2234         m_toggleDynWrap->setChecked(config()->dynWordWrap());
2235     }
2236 
2237     m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators());
2238     m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators());
2239 
2240     // line numbers
2241     m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers());
2242     m_toggleLineNumbers->setChecked(config()->lineNumbers());
2243 
2244     // icon bar
2245     m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar());
2246     m_toggleIconBar->setChecked(config()->iconBar());
2247 
2248     // scrollbar marks
2249     m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks());
2250     m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks());
2251 
2252     // scrollbar mini-map
2253     m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap());
2254     m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap());
2255 
2256     // scrollbar mini-map - (whole document)
2257     m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll());
2258     // m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() );
2259 
2260     // scrollbar mini-map.width
2261     m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth());
2262 
2263     // misc edit
2264     m_toggleBlockSelection->setChecked(blockSelection());
2265     m_toggleInsert->setChecked(isOverwriteMode());
2266 
2267     updateFoldingConfig();
2268 
2269     // bookmark
2270     m_bookmarks->setSorting((KateBookmarks::Sorting)config()->bookmarkSort());
2271 
2272     m_viewInternal->setAutoCenterLines(config()->autoCenterLines());
2273 
2274     for (const auto &input : m_viewInternal->m_inputModes) {
2275         input->updateConfig();
2276     }
2277 
2278     setInputMode(config()->inputMode(), false /* don't remember in config for these calls */);
2279 
2280     reflectOnTheFlySpellCheckStatus(doc()->isOnTheFlySpellCheckingEnabled());
2281 
2282     // register/unregister word completion...
2283     bool wc = config()->wordCompletion();
2284     if (wc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->wordCompletionModel())) {
2285         if (wc) {
2286             registerCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel());
2287         } else {
2288             unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel());
2289         }
2290     }
2291 
2292     bool kc = config()->keywordCompletion();
2293     if (kc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->keywordCompletionModel())) {
2294         if (kc) {
2295             registerCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel());
2296         } else {
2297             unregisterCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel());
2298         }
2299     }
2300 
2301     m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut()));
2302     m_copy->setEnabled(selection() || m_config->smartCopyCut());
2303 
2304     // if not disabled, update status bar
2305     if (m_statusBar) {
2306         m_statusBar->updateStatus();
2307     }
2308 
2309     // now redraw...
2310     m_viewInternal->cache()->clear();
2311     tagAll();
2312     updateView(true);
2313 
2314     Q_EMIT configChanged(this);
2315 }
2316 
2317 void KTextEditor::ViewPrivate::updateDocumentConfig()
2318 {
2319     if (m_startingUp) {
2320         return;
2321     }
2322 
2323     m_updatingDocumentConfig = true;
2324 
2325     m_setEndOfLine->setCurrentItem(doc()->config()->eol());
2326 
2327     m_addBom->setChecked(doc()->config()->bom());
2328 
2329     m_updatingDocumentConfig = false;
2330 
2331     // maybe block selection or wrap-cursor mode changed
2332     ensureCursorColumnValid();
2333 
2334     // first change this
2335     m_renderer->setTabWidth(doc()->config()->tabWidth());
2336     m_renderer->setIndentWidth(doc()->config()->indentationWidth());
2337 
2338     // now redraw...
2339     m_viewInternal->cache()->clear();
2340     tagAll();
2341     updateView(true);
2342 }
2343 
2344 void KTextEditor::ViewPrivate::updateRendererConfig()
2345 {
2346     if (m_startingUp) {
2347         return;
2348     }
2349 
2350     m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker());
2351 
2352     m_viewInternal->updateBracketMarkAttributes();
2353     m_viewInternal->updateBracketMarks();
2354 
2355     // now redraw...
2356     m_viewInternal->cache()->clear();
2357     tagAll();
2358     m_viewInternal->updateView(true);
2359 
2360     // update the left border right, for example linenumbers
2361     m_viewInternal->m_leftBorder->updateFont();
2362     m_viewInternal->m_leftBorder->repaint();
2363 
2364     m_viewInternal->m_lineScroll->queuePixmapUpdate();
2365 
2366     currentInputMode()->updateRendererConfig();
2367 
2368     // @@ showIndentLines is not cached anymore.
2369     //  m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines());
2370     Q_EMIT configChanged(this);
2371 }
2372 
2373 void KTextEditor::ViewPrivate::updateFoldingConfig()
2374 {
2375     // folding bar
2376     m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar());
2377     m_toggleFoldingMarkers->setChecked(config()->foldingBar());
2378 
2379     if (hasCommentInFirstLine(m_doc)) {
2380         if (config()->foldFirstLine() && !m_autoFoldedFirstLine) {
2381             foldLine(0);
2382             m_autoFoldedFirstLine = true;
2383         } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) {
2384             unfoldLine(0);
2385             m_autoFoldedFirstLine = false;
2386         }
2387     } else {
2388         m_autoFoldedFirstLine = false;
2389     }
2390 
2391 #if 0
2392     // FIXME: FOLDING
2393     const QStringList l = {
2394           QStringLiteral("folding_toplevel")
2395         , QStringLiteral("folding_expandtoplevel")
2396         , QStringLiteral("folding_toggle_current")
2397         , QStringLiteral("folding_toggle_in_current")
2398     };
2399 
2400     QAction *a = 0;
2401     for (int z = 0; z < l.size(); z++)
2402         if ((a = actionCollection()->action(l[z].toAscii().constData()))) {
2403             a->setEnabled(doc()->highlight() && doc()->highlight()->allowsFolding());
2404         }
2405 #endif
2406 }
2407 
2408 void KTextEditor::ViewPrivate::ensureCursorColumnValid()
2409 {
2410     KTextEditor::Cursor c = m_viewInternal->cursorPosition();
2411 
2412     // make sure the cursor is valid:
2413     // - in block selection mode or if wrap cursor is off, the column is arbitrary
2414     // - otherwise: it's bounded by the line length
2415     if (!blockSelection() && wrapCursor() && (!c.isValid() || c.column() > doc()->lineLength(c.line()))) {
2416         c.setColumn(doc()->lineLength(cursorPosition().line()));
2417         setCursorPosition(c);
2418     }
2419 }
2420 
2421 // BEGIN EDIT STUFF
2422 void KTextEditor::ViewPrivate::editStart()
2423 {
2424     m_viewInternal->editStart();
2425 }
2426 
2427 void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom)
2428 {
2429     m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom);
2430     textFolding().editEnd(editTagLineStart, editTagLineEnd, [this](int line) {
2431         return m_doc->buffer().isFoldingStartingOnLine(line).first;
2432     });
2433 }
2434 
2435 void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor cursor)
2436 {
2437     m_viewInternal->editSetCursor(cursor);
2438 }
2439 // END
2440 
2441 // BEGIN TAG & CLEAR
2442 bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor virtualCursor)
2443 {
2444     return m_viewInternal->tagLine(virtualCursor);
2445 }
2446 
2447 bool KTextEditor::ViewPrivate::tagRange(KTextEditor::Range range, bool realLines)
2448 {
2449     return m_viewInternal->tagRange(range, realLines);
2450 }
2451 
2452 bool KTextEditor::ViewPrivate::tagLines(KTextEditor::LineRange lineRange, bool realLines)
2453 {
2454     return m_viewInternal->tagLines(lineRange.start(), lineRange.end(), realLines);
2455 }
2456 
2457 bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors)
2458 {
2459     return m_viewInternal->tagLines(start, end, realCursors);
2460 }
2461 
2462 void KTextEditor::ViewPrivate::tagAll()
2463 {
2464     m_viewInternal->tagAll();
2465 }
2466 
2467 void KTextEditor::ViewPrivate::clear()
2468 {
2469     m_viewInternal->clear();
2470 }
2471 
2472 void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty)
2473 {
2474     if (paintOnlyDirty) {
2475         m_viewInternal->updateDirty();
2476     } else {
2477         m_viewInternal->update();
2478     }
2479 }
2480 
2481 void KTextEditor::ViewPrivate::updateView(bool changed)
2482 {
2483     // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView";
2484 
2485     m_viewInternal->updateView(changed);
2486     m_viewInternal->m_leftBorder->update();
2487 }
2488 
2489 // END
2490 
2491 void KTextEditor::ViewPrivate::slotHlChanged()
2492 {
2493     KateHighlighting *hl = doc()->highlight();
2494     bool ok(!hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty());
2495 
2496     if (actionCollection()->action(QStringLiteral("tools_comment"))) {
2497         actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok);
2498     }
2499 
2500     if (actionCollection()->action(QStringLiteral("tools_uncomment"))) {
2501         actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok);
2502     }
2503 
2504     if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) {
2505         actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok);
2506     }
2507 
2508     // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry
2509     updateFoldingConfig();
2510 }
2511 
2512 int KTextEditor::ViewPrivate::virtualCursorColumn() const
2513 {
2514     return doc()->toVirtualColumn(m_viewInternal->cursorPosition());
2515 }
2516 
2517 void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor newPosition)
2518 {
2519     Q_EMIT mousePositionChanged(this, newPosition);
2520 }
2521 
2522 // BEGIN KTextEditor::SelectionInterface stuff
2523 
2524 bool KTextEditor::ViewPrivate::setSelection(const KTextEditor::Range &selection)
2525 {
2526     // anything to do?
2527     if (selection == m_selection) {
2528         return true;
2529     }
2530 
2531     // backup old range
2532     KTextEditor::Range oldSelection = m_selection;
2533 
2534     // set new range
2535     m_selection.setRange(selection.isEmpty() ? KTextEditor::Range::invalid() : selection);
2536 
2537     // trigger update of correct area
2538     tagSelection(oldSelection);
2539     repaintText(true);
2540 
2541     // emit holy signal
2542     Q_EMIT selectionChanged(this);
2543 
2544     // be done
2545     return true;
2546 }
2547 
2548 bool KTextEditor::ViewPrivate::clearSelection()
2549 {
2550     return clearSelection(true);
2551 }
2552 
2553 bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection)
2554 {
2555     // no selection, nothing to do...
2556     if (!selection()) {
2557         return false;
2558     }
2559 
2560     // backup old range
2561     KTextEditor::Range oldSelection = m_selection;
2562 
2563     // invalidate current selection
2564     m_selection.setRange(KTextEditor::Range::invalid());
2565 
2566     // trigger update of correct area
2567     tagSelection(oldSelection);
2568     if (redraw) {
2569         repaintText(true);
2570     }
2571 
2572     // emit holy signal
2573     if (finishedChangingSelection) {
2574         Q_EMIT selectionChanged(this);
2575     }
2576 
2577     m_viewInternal->m_selChangedByUser = false;
2578     // be done
2579     return true;
2580 }
2581 
2582 bool KTextEditor::ViewPrivate::selection() const
2583 {
2584     if (!wrapCursor()) {
2585         return m_selection != KTextEditor::Range::invalid();
2586     } else {
2587         return m_selection.toRange().isValid();
2588     }
2589 }
2590 
2591 QString KTextEditor::ViewPrivate::selectionText() const
2592 {
2593     if (blockSelect) {
2594         return doc()->text(m_selection, blockSelect);
2595     }
2596 
2597     QVarLengthArray<KTextEditor::Range, 16> ranges;
2598     for (const auto &c : m_secondaryCursors) {
2599         if (c.range) {
2600             ranges.push_back(c.range->toRange());
2601         }
2602     }
2603     ranges.push_back(m_selection.toRange());
2604     std::sort(ranges.begin(), ranges.end());
2605 
2606     QString text;
2607     text.reserve(ranges.size() * m_selection.toRange().columnWidth());
2608     for (int i = 0; i < ranges.size() - 1; ++i) {
2609         text += doc()->text(ranges[i]) + QStringLiteral("\n");
2610     }
2611     text += doc()->text(ranges.last());
2612 
2613     return text;
2614 }
2615 
2616 bool KTextEditor::ViewPrivate::removeSelectedText()
2617 {
2618     if (!hasSelections()) {
2619         return false;
2620     }
2621 
2622     KTextEditor::Document::EditingTransaction t(doc());
2623 
2624     bool removed = false;
2625     // Handle multicursors selection removal
2626     if (!blockSelect) {
2627         completionWidget()->setIgnoreBufferSignals(true);
2628         for (auto &c : m_secondaryCursors) {
2629             if (c.range) {
2630                 removed = true;
2631                 doc()->removeText(c.range->toRange());
2632                 c.clearSelection();
2633             }
2634         }
2635         completionWidget()->setIgnoreBufferSignals(false);
2636     }
2637 
2638     // Optimization: clear selection before removing text
2639     KTextEditor::Range selection = m_selection;
2640     if (!selection.isValid()) {
2641         return removed;
2642     }
2643     doc()->removeText(selection, blockSelect);
2644     removed = true;
2645 
2646     // don't redraw the cleared selection - that's done in editEnd().
2647     if (blockSelect) {
2648         int selectionColumn = qMin(doc()->toVirtualColumn(selection.start()), doc()->toVirtualColumn(selection.end()));
2649         KTextEditor::Range newSelection = selection;
2650         newSelection.setStart(KTextEditor::Cursor(newSelection.start().line(), doc()->fromVirtualColumn(newSelection.start().line(), selectionColumn)));
2651         newSelection.setEnd(KTextEditor::Cursor(newSelection.end().line(), doc()->fromVirtualColumn(newSelection.end().line(), selectionColumn)));
2652         setSelection(newSelection);
2653         setCursorPositionInternal(newSelection.start());
2654     } else {
2655         clearSecondarySelections();
2656         clearSelection(false);
2657     }
2658 
2659     return removed;
2660 }
2661 
2662 bool KTextEditor::ViewPrivate::selectAll()
2663 {
2664     clearSecondaryCursors();
2665     setBlockSelection(false);
2666     // We use setSelection here to ensure we don't scroll anywhere
2667     // The cursor stays in place i.e., it doesn't move to end of selection
2668     // that is okay and expected.
2669     // The idea here is to maintain scroll position in case select all was
2670     // mistakenly triggered, and also to if you just want to copy text,
2671     // there is no need to scroll anywhere.
2672     setSelection(doc()->documentRange());
2673     m_viewInternal->moveCursorToSelectionEdge(/*scroll=*/false);
2674     m_viewInternal->updateMicroFocus();
2675     return true;
2676 }
2677 
2678 bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor cursor)
2679 {
2680     KTextEditor::Cursor ret = cursor;
2681     if ((!blockSelect) && (ret.column() < 0)) {
2682         ret.setColumn(0);
2683     }
2684 
2685     if (blockSelect) {
2686         return cursor.line() >= m_selection.start().line() && ret.line() <= m_selection.end().line() && ret.column() >= m_selection.start().column()
2687             && ret.column() <= m_selection.end().column();
2688     } else {
2689         return m_selection.toRange().contains(cursor) || m_selection.end() == cursor;
2690     }
2691 }
2692 
2693 bool KTextEditor::ViewPrivate::lineSelected(int line)
2694 {
2695     return !blockSelect && m_selection.toRange().containsLine(line);
2696 }
2697 
2698 bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor lineEndPos)
2699 {
2700     return (!blockSelect)
2701         && (lineEndPos.line() > m_selection.start().line()
2702             || (lineEndPos.line() == m_selection.start().line() && (m_selection.start().column() < lineEndPos.column() || lineEndPos.column() == -1)))
2703         && (lineEndPos.line() < m_selection.end().line()
2704             || (lineEndPos.line() == m_selection.end().line() && (lineEndPos.column() <= m_selection.end().column() && lineEndPos.column() != -1)));
2705 }
2706 
2707 bool KTextEditor::ViewPrivate::lineHasSelected(int line)
2708 {
2709     return selection() && m_selection.toRange().containsLine(line);
2710 }
2711 
2712 bool KTextEditor::ViewPrivate::lineIsSelection(int line)
2713 {
2714     return (line == m_selection.start().line() && line == m_selection.end().line());
2715 }
2716 
2717 void KTextEditor::ViewPrivate::tagSelection(KTextEditor::Range oldSelection)
2718 {
2719     if (selection()) {
2720         if (oldSelection.start().line() == -1) {
2721             // We have to tag the whole lot if
2722             // 1) we have a selection, and:
2723             //  a) it's new; or
2724             tagLines(m_selection, true);
2725 
2726         } else if (blockSelection()
2727                    && (oldSelection.start().column() != m_selection.start().column() || oldSelection.end().column() != m_selection.end().column())) {
2728             //  b) we're in block selection mode and the columns have changed
2729             tagLines(m_selection, true);
2730             tagLines(oldSelection, true);
2731 
2732         } else {
2733             if (oldSelection.start() != m_selection.start()) {
2734                 tagLines(KTextEditor::LineRange(oldSelection.start().line(), m_selection.start().line()), true);
2735             }
2736 
2737             if (oldSelection.end() != m_selection.end()) {
2738                 tagLines(KTextEditor::LineRange(oldSelection.end().line(), m_selection.end().line()), true);
2739             }
2740         }
2741 
2742     } else {
2743         // No more selection, clean up
2744         tagLines(oldSelection, true);
2745     }
2746 }
2747 
2748 void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor cursor)
2749 {
2750     setSelection(doc()->wordRangeAt(cursor));
2751 }
2752 
2753 void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor cursor)
2754 {
2755     int line = cursor.line();
2756     if (line + 1 >= doc()->lines()) {
2757         setSelection(KTextEditor::Range(line, 0, line, doc()->lineLength(line)));
2758     } else {
2759         setSelection(KTextEditor::Range(line, 0, line + 1, 0));
2760     }
2761 }
2762 
2763 void KTextEditor::ViewPrivate::cut()
2764 {
2765     if (!selection() && !m_config->smartCopyCut()) {
2766         return;
2767     }
2768 
2769     copy();
2770     if (!selection()) {
2771         selectLine(cursorPosition());
2772     }
2773     removeSelectedText();
2774 }
2775 
2776 void KTextEditor::ViewPrivate::copy() const
2777 {
2778     QString text;
2779     KTextEditor::EditorPrivate::self()->copyToMulticursorClipboard({});
2780 
2781     if (!selection()) {
2782         if (!m_config->smartCopyCut()) {
2783             return;
2784         }
2785         text = doc()->line(cursorPosition().line()) + QLatin1Char('\n');
2786         m_viewInternal->moveEdge(KateViewInternal::left, false);
2787     } else {
2788         text = selectionText();
2789 
2790         // Multicursor copy
2791         if (!m_secondaryCursors.empty()) {
2792             QVarLengthArray<KTextEditor::Range, 16> ranges;
2793             for (const auto &c : m_secondaryCursors) {
2794                 if (c.range) {
2795                     ranges.push_back(c.range->toRange());
2796                 }
2797             }
2798             ranges.push_back(m_selection.toRange());
2799             std::sort(ranges.begin(), ranges.end());
2800             QStringList texts;
2801             for (auto range : ranges) {
2802                 texts.append(doc()->text(range));
2803             }
2804             KTextEditor::EditorPrivate::self()->copyToMulticursorClipboard(texts);
2805         }
2806     }
2807 
2808     // copy to clipboard and our history!
2809     KTextEditor::EditorPrivate::self()->copyToClipboard(text, m_doc->url().fileName());
2810 }
2811 
2812 void KTextEditor::ViewPrivate::pasteSelection()
2813 {
2814     m_temporaryAutomaticInvocationDisabled = true;
2815     doc()->paste(this, QApplication::clipboard()->text(QClipboard::Selection));
2816     m_temporaryAutomaticInvocationDisabled = false;
2817 }
2818 
2819 void KTextEditor::ViewPrivate::swapWithClipboard()
2820 {
2821     m_temporaryAutomaticInvocationDisabled = true;
2822 
2823     // get text to paste
2824     const auto text = QApplication::clipboard()->text(QClipboard::Clipboard);
2825 
2826     // do copy
2827     copy();
2828 
2829     // do paste of "previous" clipboard content we saved
2830     doc()->paste(this, text);
2831 
2832     m_temporaryAutomaticInvocationDisabled = false;
2833 }
2834 
2835 void KTextEditor::ViewPrivate::applyWordWrap()
2836 {
2837     int first = selectionRange().start().line();
2838     int last = selectionRange().end().line();
2839 
2840     if (first == last) {
2841         // Either no selection or only one line selected, wrap only the current line
2842         first = cursorPosition().line();
2843         last = first;
2844     }
2845 
2846     doc()->wrapParagraph(first, last);
2847 }
2848 
2849 // END
2850 
2851 // BEGIN KTextEditor::BlockSelectionInterface stuff
2852 
2853 bool KTextEditor::ViewPrivate::blockSelection() const
2854 {
2855     return blockSelect;
2856 }
2857 
2858 bool KTextEditor::ViewPrivate::setBlockSelection(bool on)
2859 {
2860     if (on != blockSelect) {
2861         blockSelect = on;
2862 
2863         KTextEditor::Range oldSelection = m_selection;
2864 
2865         const bool hadSelection = clearSelection(false, false);
2866 
2867         setSelection(oldSelection);
2868 
2869         m_toggleBlockSelection->setChecked(blockSelection());
2870 
2871         // when leaving block selection mode, if cursor is at an invalid position or past the end of the
2872         // line, move the cursor to the last column of the current line unless cursor wrapping is off
2873         ensureCursorColumnValid();
2874 
2875         if (!hadSelection) {
2876             // emit selectionChanged() according to the KTextEditor::View api
2877             // documentation also if there is no selection around. This is needed,
2878             // as e.g. the Kate App status bar uses this signal to update the state
2879             // of the selection mode (block selection, line based selection)
2880             Q_EMIT selectionChanged(this);
2881         }
2882     }
2883 
2884     return true;
2885 }
2886 
2887 bool KTextEditor::ViewPrivate::toggleBlockSelection()
2888 {
2889     // no multicursors for blockselect
2890     clearSecondaryCursors();
2891 
2892     m_toggleBlockSelection->setChecked(!blockSelect);
2893     return setBlockSelection(!blockSelect);
2894 }
2895 
2896 bool KTextEditor::ViewPrivate::wrapCursor() const
2897 {
2898     return !blockSelection();
2899 }
2900 
2901 // END
2902 
2903 void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor position, const QString &text)
2904 {
2905     Q_EMIT textInserted(view, position, text);
2906 }
2907 
2908 bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor c, const QString &templateString, const QString &script)
2909 {
2910     // no empty templates
2911     if (templateString.isEmpty()) {
2912         return false;
2913     }
2914 
2915     // not for read-only docs
2916     if (!doc()->isReadWrite()) {
2917         return false;
2918     }
2919 
2920     // only one handler maybe active at a time; store it in the document.
2921     // Clear it first to make sure at no time two handlers are active at once
2922     doc()->setActiveTemplateHandler(nullptr);
2923     doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, doc()->undoManager()));
2924     return true;
2925 }
2926 
2927 bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange)
2928 {
2929     return tagLines(range.start(), range.end(), realRange);
2930 }
2931 
2932 void KTextEditor::ViewPrivate::deactivateEditActions()
2933 {
2934     for (QAction *action : std::as_const(m_editActions)) {
2935         action->setEnabled(false);
2936     }
2937 }
2938 
2939 void KTextEditor::ViewPrivate::activateEditActions()
2940 {
2941     for (QAction *action : std::as_const(m_editActions)) {
2942         action->setEnabled(true);
2943     }
2944 }
2945 
2946 bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const
2947 {
2948     // FIXME support
2949     return true;
2950 }
2951 
2952 bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool)
2953 {
2954     // FIXME support
2955     return true;
2956 }
2957 
2958 bool KTextEditor::ViewPrivate::isMulticursorNotAllowed() const
2959 {
2960     return blockSelection() || isOverwriteMode() || currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode;
2961 }
2962 
2963 void KTextEditor::ViewPrivate::addSecondaryCursor(const KTextEditor::Cursor &pos)
2964 {
2965     auto primaryCursor = cursorPosition();
2966     const bool overlapsOrOnPrimary = pos == primaryCursor || (selection() && selectionRange().contains(pos));
2967     if (overlapsOrOnPrimary && m_secondaryCursors.empty()) {
2968         // Clicking on primary cursor while it is the only cursor,
2969         // we do nothing
2970         return;
2971     } else if (overlapsOrOnPrimary) {
2972         // Clicking on primary cursor, we have secondaries
2973         // so just make the last secondary cursor primary
2974         // and remove caret at current primary cursor position
2975         auto &last = m_secondaryCursors.back();
2976         setCursorPosition(last.cursor());
2977         if (last.range) {
2978             setSelection(last.range->toRange());
2979             Q_ASSERT(last.anchor.isValid());
2980             m_viewInternal->m_selectAnchor = last.anchor;
2981         }
2982         m_secondaryCursors.pop_back();
2983         return;
2984     }
2985 
2986     // If there are any existing cursors at this position
2987     // remove them and be done i.e., if you click on an
2988     // existing cursor it is removed.
2989     if (removeSecondaryCursors({pos}, /*removeIfSelectionOverlap=*/true)) {
2990         return;
2991     }
2992 
2993     // We are adding a new cursor!
2994     // - Move primary cursor to the position where the click happened
2995     // - Old primary cursor becomes a secondary cursor
2996     // Doing it like this makes multi mouse selections very easy
2997     setCursorPosition(pos);
2998     KTextEditor::ViewPrivate::PlainSecondaryCursor p;
2999     p.pos = primaryCursor;
3000     p.range = selection() ? selectionRange() : KTextEditor::Range::invalid();
3001     clearSelection();
3002     addSecondaryCursorsWithSelection({p});
3003 }
3004 
3005 void KTextEditor::ViewPrivate::setSecondaryCursors(const QVector<KTextEditor::Cursor> &positions)
3006 {
3007     clearSecondaryCursors();
3008 
3009     if (positions.isEmpty() || isMulticursorNotAllowed()) {
3010         return;
3011     }
3012 
3013     const auto totalLines = doc()->lines();
3014     for (auto p : positions) {
3015         if (p != cursorPosition() && p.line() < totalLines) {
3016             SecondaryCursor c;
3017             c.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(p)));
3018             m_secondaryCursors.push_back(std::move(c));
3019             tagLine(p);
3020         }
3021     }
3022     sortCursors();
3023     paintCursors();
3024 }
3025 
3026 void KTextEditor::ViewPrivate::clearSecondarySelections()
3027 {
3028     for (auto &c : m_secondaryCursors) {
3029         c.range.reset();
3030         c.anchor = KTextEditor::Cursor::invalid();
3031     }
3032 }
3033 
3034 void KTextEditor::ViewPrivate::clearSecondaryCursors()
3035 {
3036     if (m_secondaryCursors.empty()) {
3037         return;
3038     }
3039     for (const auto &c : m_secondaryCursors) {
3040         tagLine(c.cursor());
3041     }
3042     m_secondaryCursors.clear();
3043     m_viewInternal->updateDirty();
3044 }
3045 
3046 const std::vector<KTextEditor::ViewPrivate::SecondaryCursor> &KTextEditor::ViewPrivate::secondaryCursors() const
3047 {
3048     return m_secondaryCursors;
3049 }
3050 
3051 QVector<KTextEditor::ViewPrivate::PlainSecondaryCursor> KTextEditor::ViewPrivate::plainSecondaryCursors() const
3052 {
3053     QVector<PlainSecondaryCursor> cursors;
3054     cursors.reserve(m_secondaryCursors.size());
3055     std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(cursors), [](const SecondaryCursor &c) {
3056         if (c.range) {
3057             return PlainSecondaryCursor{c.cursor(), c.range->toRange()};
3058         }
3059         return PlainSecondaryCursor{c.cursor(), KTextEditor::Range::invalid()};
3060     });
3061     return cursors;
3062 }
3063 
3064 bool KTextEditor::ViewPrivate::removeSecondaryCursors(const std::vector<KTextEditor::Cursor> &cursorsToRemove, bool removeIfOverlapsSelection)
3065 {
3066     Q_ASSERT(std::is_sorted(cursorsToRemove.begin(), cursorsToRemove.end()));
3067 
3068     QVarLengthArray<KTextEditor::Cursor, 8> linesToTag;
3069 
3070     if (removeIfOverlapsSelection) {
3071         m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(),
3072                                                 m_secondaryCursors.end(),
3073                                                 [&](const SecondaryCursor &c) {
3074                                                     auto it =
3075                                                         std::find_if(cursorsToRemove.begin(), cursorsToRemove.end(), [&c](const KTextEditor::Cursor &pos) {
3076                                                             return c.cursor() == pos || (c.range && c.range->contains(pos));
3077                                                         });
3078                                                     const bool match = it != cursorsToRemove.end();
3079                                                     if (match) {
3080                                                         linesToTag.push_back(c.cursor());
3081                                                     }
3082                                                     return match;
3083                                                 }),
3084                                  m_secondaryCursors.end());
3085     } else {
3086         m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(),
3087                                                 m_secondaryCursors.end(),
3088                                                 [&](const SecondaryCursor &c) {
3089                                                     auto it =
3090                                                         std::find_if(cursorsToRemove.begin(), cursorsToRemove.end(), [&c](const KTextEditor::Cursor &pos) {
3091                                                             return c.cursor() == pos;
3092                                                         });
3093                                                     const bool match = it != cursorsToRemove.end();
3094                                                     if (match) {
3095                                                         linesToTag.push_back(c.cursor());
3096                                                     }
3097                                                     return match;
3098                                                 }),
3099                                  m_secondaryCursors.end());
3100     }
3101 
3102     for (const auto &c : linesToTag) {
3103         tagLine(m_viewInternal->toVirtualCursor(c));
3104     }
3105     return !linesToTag.empty();
3106 
3107     for (auto cur : cursorsToRemove) {
3108         auto &sec = m_secondaryCursors;
3109         auto it = std::find_if(sec.begin(), sec.end(), [cur](const SecondaryCursor &c) {
3110             return c.cursor() == cur;
3111         });
3112         if (it != sec.end()) {
3113             //             removedAny = true;
3114             m_secondaryCursors.erase(it);
3115             tagLine(m_viewInternal->toVirtualCursor(cur));
3116         }
3117     }
3118 
3119     //     if (removedAny) {
3120     m_viewInternal->updateDirty();
3121     if (cursorPosition() == KTextEditor::Cursor(0, 0)) {
3122         m_viewInternal->paintCursor();
3123     }
3124     return !linesToTag.empty();
3125     //     }
3126     //     return removedAny;
3127 }
3128 
3129 void KTextEditor::ViewPrivate::ensureUniqueCursors(bool matchLine)
3130 {
3131     if (m_secondaryCursors.empty()) {
3132         return;
3133     }
3134 
3135     std::vector<SecondaryCursor>::iterator it;
3136     if (matchLine) {
3137         auto matchLine = [](const SecondaryCursor &l, const SecondaryCursor &r) {
3138             return l.cursor().line() == r.cursor().line();
3139         };
3140         it = std::unique(m_secondaryCursors.begin(), m_secondaryCursors.end(), matchLine);
3141     } else {
3142         it = std::unique(m_secondaryCursors.begin(), m_secondaryCursors.end());
3143     }
3144     if (it != m_secondaryCursors.end()) {
3145         m_secondaryCursors.erase(it, m_secondaryCursors.end());
3146     }
3147 
3148     if (matchLine) {
3149         const int ln = cursorPosition().line();
3150         m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(),
3151                                                 m_secondaryCursors.end(),
3152                                                 [ln](const SecondaryCursor &c) {
3153                                                     return c.cursor().line() == ln;
3154                                                 }),
3155                                  m_secondaryCursors.end());
3156     } else {
3157         const auto cp = cursorPosition();
3158         m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(),
3159                                                 m_secondaryCursors.end(),
3160                                                 [cp](const SecondaryCursor &c) {
3161                                                     return c.cursor() == cp;
3162                                                 }),
3163                                  m_secondaryCursors.end());
3164     }
3165 }
3166 
3167 void KTextEditor::ViewPrivate::addSecondaryCursorsWithSelection(const QVector<PlainSecondaryCursor> &cursorsWithSelection)
3168 {
3169     if (isMulticursorNotAllowed() || cursorsWithSelection.isEmpty()) {
3170         return;
3171     }
3172 
3173     for (const auto &c : cursorsWithSelection) {
3174         // We don't want to add on top of primary cursor
3175         if (c.pos == cursorPosition()) {
3176             continue;
3177         }
3178         SecondaryCursor n;
3179         n.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(c.pos)));
3180         if (c.range.isValid()) {
3181             n.range.reset(newSecondarySelectionRange(c.range));
3182             n.anchor = c.range.start() == c.pos ? c.range.end() : c.range.start();
3183         }
3184         m_secondaryCursors.push_back(std::move(n));
3185     }
3186     sortCursors();
3187     paintCursors();
3188 }
3189 
3190 Kate::TextRange *KTextEditor::ViewPrivate::newSecondarySelectionRange(KTextEditor::Range selRange)
3191 {
3192     constexpr auto expandBehaviour = KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight;
3193     auto range = new Kate::TextRange(doc()->buffer(), selRange, expandBehaviour);
3194     static KTextEditor::Attribute::Ptr selAttr;
3195     if (!selAttr) {
3196         selAttr = new KTextEditor::Attribute;
3197         auto color = QColor::fromRgba(theme().editorColor(KSyntaxHighlighting::Theme::TextSelection));
3198         selAttr->setBackground(color);
3199     }
3200     range->setZDepth(-999999.);
3201     range->setAttribute(selAttr);
3202     return range;
3203 }
3204 
3205 bool KTextEditor::ViewPrivate::hasSelections() const
3206 {
3207     if (selection())
3208         return true;
3209     return std::any_of(m_secondaryCursors.cbegin(), m_secondaryCursors.cend(), [](const SecondaryCursor &c) {
3210         return c.range && !c.range->isEmpty();
3211     });
3212 }
3213 
3214 void KTextEditor::ViewPrivate::addSecondaryCursorDown()
3215 {
3216     KTextEditor::Cursor last = cursorPosition();
3217     const auto &secondary = secondaryCursors();
3218     if (!secondary.empty()) {
3219         last = secondary.back().cursor();
3220         last = std::max(cursorPosition(), last);
3221     }
3222     if (last.line() >= doc()->lastLine()) {
3223         return;
3224     }
3225 
3226     auto nextRange = m_viewInternal->nextLayout(last);
3227     if (!nextRange.isValid()) {
3228         return;
3229     }
3230     auto primaryCursorLineLayout = m_viewInternal->currentLayout(cursorPosition());
3231     if (!primaryCursorLineLayout.isValid()) {
3232         return;
3233     }
3234 
3235     int x = renderer()->cursorToX(primaryCursorLineLayout, cursorPosition().column(), !wrapCursor());
3236     auto next = renderer()->xToCursor(nextRange, x, !wrapCursor());
3237     addSecondaryCursor(next);
3238 }
3239 
3240 void KTextEditor::ViewPrivate::addSecondaryCursorUp()
3241 {
3242     KTextEditor::Cursor last = cursorPosition();
3243     const auto &secondary = secondaryCursors();
3244     if (!secondary.empty()) {
3245         last = secondary.front().cursor();
3246         last = std::min(cursorPosition(), last);
3247     }
3248     if (last.line() == 0) {
3249         return;
3250     }
3251     auto nextRange = m_viewInternal->previousLayout(last);
3252     if (!nextRange.isValid()) {
3253         return;
3254     }
3255 
3256     auto primaryCursorLineLayout = m_viewInternal->currentLayout(cursorPosition());
3257     if (!primaryCursorLineLayout.isValid()) {
3258         return;
3259     }
3260 
3261     int x = renderer()->cursorToX(primaryCursorLineLayout, cursorPosition().column(), !wrapCursor());
3262     auto next = renderer()->xToCursor(nextRange, x, !wrapCursor());
3263     addSecondaryCursor(next);
3264 }
3265 
3266 QVector<KTextEditor::Cursor> KTextEditor::ViewPrivate::cursors() const
3267 {
3268     QVector<KTextEditor::Cursor> ret;
3269     ret.reserve(m_secondaryCursors.size() + 1);
3270     ret << cursorPosition();
3271     std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(ret), [](const SecondaryCursor &c) {
3272         return c.cursor();
3273     });
3274     return ret;
3275 }
3276 
3277 QVector<KTextEditor::Range> KTextEditor::ViewPrivate::selectionRanges() const
3278 {
3279     if (!selection()) {
3280         return {};
3281     }
3282 
3283     QVector<KTextEditor::Range> ret;
3284     ret.reserve(m_secondaryCursors.size() + 1);
3285     ret << selectionRange();
3286     std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(ret), [](const SecondaryCursor &c) {
3287         if (!c.range) {
3288             qWarning() << "selectionRanges(): Unexpected null selection range, please fix";
3289             return KTextEditor::Range::invalid();
3290         }
3291         return c.range->toRange();
3292     });
3293     return ret;
3294 }
3295 
3296 void KTextEditor::ViewPrivate::setCursors(const QVector<KTextEditor::Cursor> &cursorPositions)
3297 {
3298     if (isMulticursorNotAllowed()) {
3299         qWarning() << "setCursors failed: Multicursors not allowed because one of the following is true"
3300                    << ", blockSelection: " << blockSelection() << ", overwriteMode: " << isOverwriteMode()
3301                    << ", viMode: " << (currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode);
3302         return;
3303     }
3304 
3305     clearSecondaryCursors();
3306     if (cursorPositions.empty()) {
3307         return;
3308     }
3309 
3310     const auto primary = cursorPositions.front();
3311     // We clear primary selection because primary and secondary
3312     // cursors should always have same selection state
3313     setSelection({});
3314     setCursorPosition(primary);
3315     // First will be auto ignored because it equals cursorPosition()
3316     setSecondaryCursors(cursorPositions);
3317 }
3318 
3319 void KTextEditor::ViewPrivate::setSelections(const QVector<KTextEditor::Range> &selectionRanges)
3320 {
3321     if (isMulticursorNotAllowed()) {
3322         qWarning() << "setSelections failed: Multicursors not allowed because one of the following is true"
3323                    << ", blockSelection: " << blockSelection() << ", overwriteMode: " << isOverwriteMode()
3324                    << ", viMode: " << (currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode);
3325         return;
3326     }
3327 
3328     clearSecondaryCursors();
3329     setSelection({});
3330     if (selectionRanges.isEmpty()) {
3331         return;
3332     }
3333 
3334     auto first = selectionRanges.front();
3335     setCursorPosition(first.end());
3336     setSelection(first);
3337 
3338     if (selectionRanges.size() == 1) {
3339         return;
3340     }
3341 
3342     const auto docRange = doc()->documentRange();
3343     for (auto it = selectionRanges.begin() + 1; it != selectionRanges.end(); ++it) {
3344         KTextEditor::Range r = *it;
3345         KTextEditor::Cursor c = r.end();
3346         if (c == cursorPosition() || !r.isValid() || r.isEmpty() || !docRange.contains(r)) {
3347             continue;
3348         }
3349 
3350         SecondaryCursor n;
3351         n.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(c)));
3352         n.range.reset(newSecondarySelectionRange(r));
3353         n.anchor = r.start();
3354         m_secondaryCursors.push_back(std::move(n));
3355     }
3356     m_viewInternal->mergeSelections();
3357 
3358     sortCursors();
3359     paintCursors();
3360 }
3361 
3362 void KTextEditor::ViewPrivate::sortCursors()
3363 {
3364     std::sort(m_secondaryCursors.begin(), m_secondaryCursors.end());
3365     ensureUniqueCursors();
3366 }
3367 
3368 void KTextEditor::ViewPrivate::paintCursors()
3369 {
3370     if (m_viewInternal->m_cursorTimer.isActive()) {
3371         if (QApplication::cursorFlashTime() > 0) {
3372             m_viewInternal->m_cursorTimer.start(QApplication::cursorFlashTime() / 2);
3373         }
3374         renderer()->setDrawCaret(true);
3375     }
3376     m_viewInternal->paintCursor();
3377 }
3378 
3379 bool KTextEditor::ViewPrivate::isCompletionActive() const
3380 {
3381     return completionWidget()->isCompletionActive();
3382 }
3383 
3384 KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const
3385 {
3386     if (!m_completionWidget) {
3387         m_completionWidget = new KateCompletionWidget(const_cast<KTextEditor::ViewPrivate *>(this));
3388     }
3389 
3390     return m_completionWidget;
3391 }
3392 
3393 void KTextEditor::ViewPrivate::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model)
3394 {
3395     completionWidget()->startCompletion(word, model);
3396 }
3397 
3398 void KTextEditor::ViewPrivate::startCompletion(const Range &word,
3399                                                const QList<KTextEditor::CodeCompletionModel *> &models,
3400                                                KTextEditor::CodeCompletionModel::InvocationType invocationType)
3401 {
3402     completionWidget()->startCompletion(word, models, invocationType);
3403 }
3404 
3405 void KTextEditor::ViewPrivate::abortCompletion()
3406 {
3407     completionWidget()->abortCompletion();
3408 }
3409 
3410 void KTextEditor::ViewPrivate::forceCompletion()
3411 {
3412     completionWidget()->execute();
3413 }
3414 
3415 void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model)
3416 {
3417     completionWidget()->registerCompletionModel(model);
3418 }
3419 
3420 void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model)
3421 {
3422     completionWidget()->unregisterCompletionModel(model);
3423 }
3424 
3425 bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const
3426 {
3427     return completionWidget()->isCompletionModelRegistered(model);
3428 }
3429 
3430 QList<KTextEditor::CodeCompletionModel *> KTextEditor::ViewPrivate::codeCompletionModels() const
3431 {
3432     return completionWidget()->codeCompletionModels();
3433 }
3434 
3435 bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const
3436 {
3437     return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation();
3438 }
3439 
3440 void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled)
3441 {
3442     config()->setValue(KateViewConfig::AutomaticCompletionInvocation, enabled);
3443 }
3444 
3445 void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index)
3446 {
3447     Q_EMIT completionExecuted(this, position, model, index);
3448 }
3449 
3450 void KTextEditor::ViewPrivate::sendCompletionAborted()
3451 {
3452     Q_EMIT completionAborted(this);
3453 }
3454 
3455 void KTextEditor::ViewPrivate::paste(const QString *textToPaste)
3456 {
3457     const int cursorCount = m_secondaryCursors.size() + 1; // 1 primary cursor
3458     const auto multicursorClipboard = KTextEditor::EditorPrivate::self()->multicursorClipboard();
3459     if (cursorCount == multicursorClipboard.size() && !textToPaste) {
3460         if (doc()->multiPaste(this, multicursorClipboard)) {
3461             return;
3462         }
3463     } else if (!textToPaste && cursorCount > 1) {
3464         // We still have multiple cursors, but the amount
3465         // of multicursors doesn't match the entry count in clipboard
3466         QStringList texts;
3467         texts.reserve(cursorCount);
3468         QString clipboard = QApplication::clipboard()->text(QClipboard::Clipboard);
3469         for (int i = 0; i < cursorCount; ++i) {
3470             texts << clipboard;
3471         }
3472         // It might still fail for e.g., if we are in block mode,
3473         // in that case we will fallback to normal pasting below
3474         if (doc()->multiPaste(this, texts)) {
3475             return;
3476         }
3477     }
3478 
3479     m_temporaryAutomaticInvocationDisabled = true;
3480     doc()->paste(this, textToPaste ? *textToPaste : QApplication::clipboard()->text(QClipboard::Clipboard));
3481     m_temporaryAutomaticInvocationDisabled = false;
3482 }
3483 
3484 bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position)
3485 {
3486     return setCursorPositionInternal(position, 1, true);
3487 }
3488 
3489 KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const
3490 {
3491     return m_viewInternal->cursorPosition();
3492 }
3493 
3494 KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const
3495 {
3496     return KTextEditor::Cursor(m_viewInternal->cursorPosition().line(), virtualCursorColumn());
3497 }
3498 
3499 QPoint KTextEditor::ViewPrivate::cursorToCoordinate(const KTextEditor::Cursor &cursor) const
3500 {
3501     // map from ViewInternal to View coordinates
3502     const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, true, false);
3503     return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt);
3504 }
3505 
3506 KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const
3507 {
3508     // map from View to ViewInternal coordinates
3509     return m_viewInternal->coordinatesToCursor(m_viewInternal->mapFromParent(coords), false);
3510 }
3511 
3512 QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const
3513 {
3514     // map from ViewInternal to View coordinates
3515     const QPoint pt = m_viewInternal->cursorCoordinates(false);
3516     return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt);
3517 }
3518 
3519 void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor cursor)
3520 {
3521     m_viewInternal->scrollPos(cursor, false, true, false);
3522 }
3523 
3524 void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x)
3525 {
3526     m_viewInternal->scrollColumns(x);
3527 }
3528 
3529 KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const
3530 {
3531     return m_viewInternal->maxStartPos(true);
3532 }
3533 
3534 int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const
3535 {
3536     if (lineType == RealLine) {
3537         return m_textFolding.visibleLineToLine(m_viewInternal->startLine());
3538     } else {
3539         return m_viewInternal->startLine();
3540     }
3541 }
3542 
3543 int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const
3544 {
3545     if (lineType == RealLine) {
3546         return m_textFolding.visibleLineToLine(m_viewInternal->endLine());
3547     } else {
3548         return m_viewInternal->endLine();
3549     }
3550 }
3551 
3552 QRect KTextEditor::ViewPrivate::textAreaRectInternal() const
3553 {
3554     const auto sourceRect = m_viewInternal->rect();
3555     const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft());
3556     const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight());
3557     return {topLeft, bottomRight};
3558 }
3559 
3560 bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor position)
3561 {
3562     return setCursorPositionInternal(position, doc()->config()->tabWidth(), true);
3563 }
3564 
3565 bool KTextEditor::ViewPrivate::isLineRTL(int line) const
3566 {
3567     const QString s = doc()->line(line);
3568     if (s.isEmpty()) {
3569         int line = cursorPosition().line();
3570         if (line == 0) {
3571             const int count = doc()->lines();
3572             for (int i = 1; i < count; ++i) {
3573                 const QString ln = doc()->line(i);
3574                 if (ln.isEmpty()) {
3575                     continue;
3576                 }
3577                 return ln.isRightToLeft();
3578             }
3579         } else {
3580             int line = cursorPosition().line();
3581             for (; line >= 0; --line) {
3582                 const QString s = doc()->line(line);
3583                 if (s.isEmpty()) {
3584                     continue;
3585                 }
3586                 return s.isRightToLeft();
3587             }
3588         }
3589         return false;
3590     } else {
3591         return s.isRightToLeft();
3592     }
3593 }
3594 
3595 QTextLayout *KTextEditor::ViewPrivate::textLayout(int line) const
3596 {
3597     KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(line);
3598 
3599     return thisLine->isValid() ? thisLine->layout() : nullptr;
3600 }
3601 
3602 QTextLayout *KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor pos) const
3603 {
3604     KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(pos);
3605 
3606     return thisLine->isValid() ? thisLine->layout() : nullptr;
3607 }
3608 
3609 void KTextEditor::ViewPrivate::indent()
3610 {
3611     KTextEditor::Cursor c(cursorPosition().line(), 0);
3612     KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c);
3613     doc()->indent(r, 1);
3614 }
3615 
3616 void KTextEditor::ViewPrivate::unIndent()
3617 {
3618     KTextEditor::Cursor c(cursorPosition().line(), 0);
3619     KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c);
3620     doc()->indent(r, -1);
3621 }
3622 
3623 void KTextEditor::ViewPrivate::cleanIndent()
3624 {
3625     KTextEditor::Cursor c(cursorPosition().line(), 0);
3626     KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c);
3627     doc()->indent(r, 0);
3628 }
3629 
3630 void KTextEditor::ViewPrivate::formatIndent()
3631 {
3632     // no selection: align current line; selection: use selection range
3633     const int line = cursorPosition().line();
3634     KTextEditor::Range formatRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0));
3635     if (selection()) {
3636         formatRange = selectionRange();
3637     }
3638 
3639     doc()->align(this, formatRange);
3640 }
3641 
3642 // alias of formatIndent, for backward compatibility
3643 void KTextEditor::ViewPrivate::align()
3644 {
3645     formatIndent();
3646 }
3647 
3648 void KTextEditor::ViewPrivate::alignOn()
3649 {
3650     static QString pattern;
3651     KTextEditor::Range range;
3652     if (!selection()) {
3653         range = doc()->documentRange();
3654     } else {
3655         range = selectionRange();
3656     }
3657     bool ok;
3658     pattern = QInputDialog::getText(window(), i18n("Align On"), i18n("Alignment pattern:"), QLineEdit::Normal, pattern, &ok);
3659     if (!ok) {
3660         return;
3661     }
3662     doc()->alignOn(range, pattern, this->blockSelection());
3663 }
3664 
3665 void KTextEditor::ViewPrivate::comment()
3666 {
3667     m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight);
3668     doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::Comment);
3669     m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight);
3670 }
3671 
3672 void KTextEditor::ViewPrivate::uncomment()
3673 {
3674     doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::UnComment);
3675 }
3676 
3677 void KTextEditor::ViewPrivate::toggleComment()
3678 {
3679     m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight);
3680     doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::ToggleComment);
3681     m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight);
3682 }
3683 
3684 void KTextEditor::ViewPrivate::uppercase()
3685 {
3686     doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Uppercase);
3687 }
3688 
3689 void KTextEditor::ViewPrivate::killLine()
3690 {
3691     std::vector<int> linesToRemove;
3692     if (m_selection.isEmpty()) {
3693         // collect lines of all cursors
3694         linesToRemove.reserve(m_secondaryCursors.size() + 1);
3695         for (const auto &c : m_secondaryCursors) {
3696             linesToRemove.push_back(c.pos->line());
3697         }
3698         // add primary cursor line
3699         linesToRemove.push_back(cursorPosition().line());
3700     } else {
3701         linesToRemove.reserve(m_secondaryCursors.size() + 1);
3702         for (const auto &c : m_secondaryCursors) {
3703             const auto &range = c.range;
3704             if (!range) {
3705                 continue;
3706             }
3707             for (int line = range->end().line(); line >= range->start().line(); line--) {
3708                 linesToRemove.push_back(line);
3709             }
3710         }
3711 
3712         // cache endline, else that moves and we might delete complete document if last line is selected!
3713         for (int line = m_selection.end().line(), endLine = m_selection.start().line(); line >= endLine; line--) {
3714             linesToRemove.push_back(line);
3715         }
3716     }
3717 
3718     std::sort(linesToRemove.begin(), linesToRemove.end(), std::greater{});
3719     linesToRemove.erase(std::unique(linesToRemove.begin(), linesToRemove.end()), linesToRemove.end());
3720 
3721     doc()->editStart();
3722     std::for_each(linesToRemove.begin(), linesToRemove.end(), [this](int line) {
3723         doc()->removeLine(line);
3724     });
3725     doc()->editEnd();
3726 
3727     ensureUniqueCursors();
3728 }
3729 
3730 void KTextEditor::ViewPrivate::lowercase()
3731 {
3732     doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase);
3733 }
3734 
3735 void KTextEditor::ViewPrivate::capitalize()
3736 {
3737     doc()->editStart();
3738     doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase);
3739     doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Capitalize);
3740     doc()->editEnd();
3741 }
3742 
3743 void KTextEditor::ViewPrivate::keyReturn()
3744 {
3745     doc()->newLine(this);
3746     m_viewInternal->iconBorder()->updateForCursorLineChange();
3747     m_viewInternal->updateView();
3748 }
3749 
3750 void KTextEditor::ViewPrivate::smartNewline()
3751 {
3752     const KTextEditor::Cursor cursor = cursorPosition();
3753     const int ln = cursor.line();
3754     Kate::TextLine line = doc()->kateTextLine(ln);
3755     int col = qMin(cursor.column(), line->firstChar());
3756     if (col != -1) {
3757         while (line->length() > col && !(line->at(col).isLetterOrNumber() || line->at(col) == QLatin1Char('_')) && col < cursor.column()) {
3758             ++col;
3759         }
3760     } else {
3761         col = line->length(); // stay indented
3762     }
3763     doc()->editStart();
3764     doc()->editWrapLine(ln, cursor.column());
3765     doc()->insertText(KTextEditor::Cursor(ln + 1, 0), line->string(0, col));
3766     doc()->editEnd();
3767 
3768     m_viewInternal->updateView();
3769 }
3770 
3771 void KTextEditor::ViewPrivate::noIndentNewline()
3772 {
3773     doc()->newLine(this, KTextEditor::DocumentPrivate::NoIndent);
3774     m_viewInternal->iconBorder()->updateForCursorLineChange();
3775     m_viewInternal->updateView();
3776 }
3777 
3778 void KTextEditor::ViewPrivate::newLineAbove()
3779 {
3780     doc()->newLine(this, KTextEditor::DocumentPrivate::Indent, KTextEditor::DocumentPrivate::Above);
3781     m_viewInternal->iconBorder()->updateForCursorLineChange();
3782     m_viewInternal->updateView();
3783 }
3784 
3785 void KTextEditor::ViewPrivate::newLineBelow()
3786 {
3787     doc()->newLine(this, KTextEditor::DocumentPrivate::Indent, KTextEditor::DocumentPrivate::Below);
3788     m_viewInternal->iconBorder()->updateForCursorLineChange();
3789     m_viewInternal->updateView();
3790 }
3791 
3792 void KTextEditor::ViewPrivate::backspace()
3793 {
3794     // Will take care of both multi and primary cursors
3795     doc()->backspace(this);
3796 }
3797 
3798 void KTextEditor::ViewPrivate::insertTab()
3799 {
3800     doc()->insertTab(this, cursorPosition());
3801 }
3802 
3803 void KTextEditor::ViewPrivate::deleteWordLeft()
3804 {
3805     doc()->editStart();
3806     m_viewInternal->wordPrev(true);
3807     KTextEditor::Range selection = selectionRange();
3808     removeSelectedText();
3809     doc()->editEnd();
3810 
3811     ensureUniqueCursors();
3812 
3813     m_viewInternal->tagRange(selection, true);
3814     m_viewInternal->updateDirty();
3815 }
3816 
3817 void KTextEditor::ViewPrivate::keyDelete()
3818 {
3819     KTextEditor::Document::EditingTransaction t(doc());
3820     // multi cursor
3821 
3822     if (removeSelectedText()) {
3823         return;
3824     }
3825 
3826     for (const auto &c : m_secondaryCursors) {
3827         if (c.range) {
3828             doc()->removeText(c.range->toRange());
3829         } else {
3830             doc()->del(this, c.cursor());
3831         }
3832     }
3833 
3834     // primary cursor
3835     doc()->del(this, cursorPosition());
3836 
3837     ensureUniqueCursors();
3838 }
3839 
3840 void KTextEditor::ViewPrivate::deleteWordRight()
3841 {
3842     doc()->editStart();
3843     m_viewInternal->wordNext(true);
3844     KTextEditor::Range selection = selectionRange();
3845     removeSelectedText();
3846     doc()->editEnd();
3847 
3848     ensureUniqueCursors();
3849 
3850     m_viewInternal->tagRange(selection, true);
3851     m_viewInternal->updateDirty();
3852 }
3853 
3854 void KTextEditor::ViewPrivate::transpose()
3855 {
3856     doc()->editBegin();
3857     for (const auto &c : m_secondaryCursors) {
3858         doc()->transpose(c.cursor());
3859     }
3860     doc()->transpose(cursorPosition());
3861     doc()->editEnd();
3862 }
3863 
3864 void KTextEditor::ViewPrivate::transposeWord()
3865 {
3866     const KTextEditor::Cursor originalCurPos = cursorPosition();
3867     const KTextEditor::Range firstWord = doc()->wordRangeAt(originalCurPos);
3868     if (!firstWord.isValid()) {
3869         return;
3870     }
3871 
3872     auto wordIsInvalid = [](QStringView word) {
3873         for (const QChar &character : word) {
3874             if (character.isLetterOrNumber()) {
3875                 return false;
3876             }
3877         }
3878         return true;
3879     };
3880 
3881     if (wordIsInvalid(doc()->text(firstWord))) {
3882         return;
3883     }
3884 
3885     setCursorPosition(firstWord.end());
3886     wordRight();
3887     KTextEditor::Cursor curPos = cursorPosition();
3888     // swap with the word to the right if it exists, otherwise try to swap with word to the left
3889     if (curPos.line() != firstWord.end().line() || curPos.column() == firstWord.end().column()) {
3890         setCursorPosition(firstWord.start());
3891         wordLeft();
3892         curPos = cursorPosition();
3893         // if there is still no next word in this line, no swapping will be done
3894         if (curPos.line() != firstWord.start().line() || curPos.column() == firstWord.start().column() || wordIsInvalid(doc()->wordAt(curPos))) {
3895             setCursorPosition(originalCurPos);
3896             return;
3897         }
3898     }
3899 
3900     if (wordIsInvalid(doc()->wordAt(curPos))) {
3901         setCursorPosition(originalCurPos);
3902         return;
3903     }
3904 
3905     const KTextEditor::Range secondWord = doc()->wordRangeAt(curPos);
3906     doc()->swapTextRanges(firstWord, secondWord);
3907 
3908     // return cursor to its original position inside the word before swap
3909     // after the swap, the cursor will be at the end of the word, so we compute the position relative to the end of the word
3910     const int offsetFromWordEnd = firstWord.end().column() - originalCurPos.column();
3911     setCursorPosition(cursorPosition() - KTextEditor::Cursor(0, offsetFromWordEnd));
3912 }
3913 
3914 void KTextEditor::ViewPrivate::cursorLeft()
3915 {
3916     if (selection() && !config()->persistentSelection()) {
3917         if (isLineRTL(cursorPosition().line())) {
3918             m_viewInternal->updateCursor(selectionRange().end());
3919             setSelection(KTextEditor::Range::invalid());
3920         } else {
3921             m_viewInternal->updateCursor(selectionRange().start());
3922             setSelection(KTextEditor::Range::invalid());
3923         }
3924 
3925         for (const auto &c : m_secondaryCursors) {
3926             if (!c.range) {
3927                 continue;
3928             }
3929             const bool rtl = isLineRTL(c.cursor().line());
3930             c.pos->setPosition(rtl ? c.range->end() : c.range->start());
3931         }
3932         clearSecondarySelections();
3933     } else {
3934         if (isLineRTL(cursorPosition().line())) {
3935             m_viewInternal->cursorNextChar();
3936         } else {
3937             m_viewInternal->cursorPrevChar();
3938         }
3939     }
3940 }
3941 
3942 void KTextEditor::ViewPrivate::shiftCursorLeft()
3943 {
3944     if (isLineRTL(cursorPosition().line())) {
3945         m_viewInternal->cursorNextChar(true);
3946     } else {
3947         m_viewInternal->cursorPrevChar(true);
3948     }
3949 }
3950 
3951 void KTextEditor::ViewPrivate::cursorRight()
3952 {
3953     if (selection() && !config()->persistentSelection()) {
3954         if (isLineRTL(cursorPosition().line())) {
3955             m_viewInternal->updateCursor(selectionRange().start());
3956             setSelection(KTextEditor::Range::invalid());
3957         } else {
3958             m_viewInternal->updateCursor(selectionRange().end());
3959             setSelection(KTextEditor::Range::invalid());
3960         }
3961 
3962         for (const auto &c : m_secondaryCursors) {
3963             if (!c.range) {
3964                 continue;
3965             }
3966             const bool rtl = doc()->line(c.cursor().line()).isRightToLeft();
3967             c.pos->setPosition(rtl ? c.range->start() : c.range->end());
3968         }
3969         clearSecondarySelections();
3970     } else {
3971         if (isLineRTL(cursorPosition().line())) {
3972             m_viewInternal->cursorPrevChar();
3973         } else {
3974             m_viewInternal->cursorNextChar();
3975         }
3976     }
3977 }
3978 
3979 void KTextEditor::ViewPrivate::shiftCursorRight()
3980 {
3981     if (isLineRTL(cursorPosition().line())) {
3982         m_viewInternal->cursorPrevChar(true);
3983     } else {
3984         m_viewInternal->cursorNextChar(true);
3985     }
3986 }
3987 
3988 void KTextEditor::ViewPrivate::wordLeft()
3989 {
3990     if (isLineRTL(cursorPosition().line())) {
3991         m_viewInternal->wordNext();
3992     } else {
3993         m_viewInternal->wordPrev();
3994     }
3995 }
3996 
3997 void KTextEditor::ViewPrivate::shiftWordLeft()
3998 {
3999     if (isLineRTL(cursorPosition().line())) {
4000         m_viewInternal->wordNext(true);
4001     } else {
4002         m_viewInternal->wordPrev(true);
4003     }
4004 }
4005 
4006 void KTextEditor::ViewPrivate::wordRight()
4007 {
4008     if (isLineRTL(cursorPosition().line())) {
4009         m_viewInternal->wordPrev();
4010     } else {
4011         m_viewInternal->wordNext();
4012     }
4013 }
4014 
4015 void KTextEditor::ViewPrivate::shiftWordRight()
4016 {
4017     if (isLineRTL(cursorPosition().line())) {
4018         m_viewInternal->wordPrev(true);
4019     } else {
4020         m_viewInternal->wordNext(true);
4021     }
4022 }
4023 
4024 void KTextEditor::ViewPrivate::home()
4025 {
4026     m_viewInternal->home();
4027 }
4028 
4029 void KTextEditor::ViewPrivate::shiftHome()
4030 {
4031     m_viewInternal->home(true);
4032 }
4033 
4034 void KTextEditor::ViewPrivate::end()
4035 {
4036     m_viewInternal->end();
4037 }
4038 
4039 void KTextEditor::ViewPrivate::shiftEnd()
4040 {
4041     m_viewInternal->end(true);
4042 }
4043 
4044 void KTextEditor::ViewPrivate::up()
4045 {
4046     m_viewInternal->cursorUp();
4047 }
4048 
4049 void KTextEditor::ViewPrivate::shiftUp()
4050 {
4051     m_viewInternal->cursorUp(true);
4052 }
4053 
4054 void KTextEditor::ViewPrivate::down()
4055 {
4056     m_viewInternal->cursorDown();
4057 }
4058 
4059 void KTextEditor::ViewPrivate::shiftDown()
4060 {
4061     m_viewInternal->cursorDown(true);
4062 }
4063 
4064 void KTextEditor::ViewPrivate::scrollUp()
4065 {
4066     m_viewInternal->scrollUp();
4067 }
4068 
4069 void KTextEditor::ViewPrivate::scrollDown()
4070 {
4071     m_viewInternal->scrollDown();
4072 }
4073 
4074 void KTextEditor::ViewPrivate::topOfView()
4075 {
4076     m_viewInternal->topOfView();
4077 }
4078 
4079 void KTextEditor::ViewPrivate::shiftTopOfView()
4080 {
4081     m_viewInternal->topOfView(true);
4082 }
4083 
4084 void KTextEditor::ViewPrivate::bottomOfView()
4085 {
4086     m_viewInternal->bottomOfView();
4087 }
4088 
4089 void KTextEditor::ViewPrivate::shiftBottomOfView()
4090 {
4091     m_viewInternal->bottomOfView(true);
4092 }
4093 
4094 void KTextEditor::ViewPrivate::pageUp()
4095 {
4096     m_viewInternal->pageUp();
4097 }
4098 
4099 void KTextEditor::ViewPrivate::shiftPageUp()
4100 {
4101     m_viewInternal->pageUp(true);
4102 }
4103 
4104 void KTextEditor::ViewPrivate::pageDown()
4105 {
4106     m_viewInternal->pageDown();
4107 }
4108 
4109 void KTextEditor::ViewPrivate::shiftPageDown()
4110 {
4111     m_viewInternal->pageDown(true);
4112 }
4113 
4114 void KTextEditor::ViewPrivate::top()
4115 {
4116     m_viewInternal->top_home();
4117 }
4118 
4119 void KTextEditor::ViewPrivate::shiftTop()
4120 {
4121     m_viewInternal->top_home(true);
4122 }
4123 
4124 void KTextEditor::ViewPrivate::bottom()
4125 {
4126     m_viewInternal->bottom_end();
4127 }
4128 
4129 void KTextEditor::ViewPrivate::shiftBottom()
4130 {
4131     m_viewInternal->bottom_end(true);
4132 }
4133 
4134 void KTextEditor::ViewPrivate::toMatchingBracket()
4135 {
4136     m_viewInternal->cursorToMatchingBracket();
4137 }
4138 
4139 void KTextEditor::ViewPrivate::shiftToMatchingBracket()
4140 {
4141     m_viewInternal->cursorToMatchingBracket(true);
4142 }
4143 
4144 void KTextEditor::ViewPrivate::toPrevModifiedLine()
4145 {
4146     const int startLine = cursorPosition().line() - 1;
4147     const int line = doc()->findTouchedLine(startLine, false);
4148     if (line >= 0) {
4149         KTextEditor::Cursor c(line, 0);
4150         m_viewInternal->updateSelection(c, false);
4151         m_viewInternal->updateCursor(c);
4152     }
4153 }
4154 
4155 void KTextEditor::ViewPrivate::toNextModifiedLine()
4156 {
4157     const int startLine = cursorPosition().line() + 1;
4158     const int line = doc()->findTouchedLine(startLine, true);
4159     if (line >= 0) {
4160         KTextEditor::Cursor c(line, 0);
4161         m_viewInternal->updateSelection(c, false);
4162         m_viewInternal->updateCursor(c);
4163     }
4164 }
4165 
4166 KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const
4167 {
4168     return m_selection;
4169 }
4170 
4171 KTextEditor::Document *KTextEditor::ViewPrivate::document() const
4172 {
4173     return m_doc;
4174 }
4175 
4176 void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu)
4177 {
4178     if (m_contextMenu) {
4179         disconnect(m_contextMenu.data(), &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu);
4180         disconnect(m_contextMenu.data(), &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu);
4181     }
4182     m_contextMenu = menu;
4183     m_userContextMenuSet = true;
4184 
4185     if (m_contextMenu) {
4186         connect(m_contextMenu.data(), &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu);
4187         connect(m_contextMenu.data(), &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu);
4188     }
4189 }
4190 
4191 QMenu *KTextEditor::ViewPrivate::contextMenu() const
4192 {
4193     if (m_userContextMenuSet) {
4194         return m_contextMenu;
4195     } else {
4196         KXMLGUIClient *client = const_cast<KTextEditor::ViewPrivate *>(this);
4197         while (client->parentClient()) {
4198             client = client->parentClient();
4199         }
4200 
4201         // qCDebug(LOG_KTE) << "looking up all menu containers";
4202         if (client->factory()) {
4203             const QList<QWidget *> menuContainers = client->factory()->containers(QStringLiteral("menu"));
4204             for (QWidget *w : menuContainers) {
4205                 if (w->objectName() == QLatin1String("ktexteditor_popup")) {
4206                     // perhaps optimize this block
4207                     QMenu *menu = (QMenu *)w;
4208                     // menu is a reusable instance shared among all views. Therefore,
4209                     // disconnect the current receiver(s) from the menu show/hide signals
4210                     // before connecting `this` view. This ensures that only the current
4211                     // view gets a signal when the menu is about to be shown or hidden,
4212                     // and not also the view(s) that previously had the menu open.
4213                     disconnect(menu, &QMenu::aboutToShow, nullptr, nullptr);
4214                     disconnect(menu, &QMenu::aboutToHide, nullptr, nullptr);
4215                     connect(menu, &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu);
4216                     connect(menu, &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu);
4217                     return menu;
4218                 }
4219             }
4220         }
4221     }
4222     return nullptr;
4223 }
4224 
4225 QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const
4226 {
4227     if (!menu) {
4228         menu = new QMenu(const_cast<KTextEditor::ViewPrivate *>(this));
4229     }
4230 
4231     if (m_editUndo) {
4232         menu->addAction(m_editUndo);
4233     }
4234     if (m_editRedo) {
4235         menu->addAction(m_editRedo);
4236     }
4237 
4238     menu->addSeparator();
4239     menu->addAction(m_cut);
4240     menu->addAction(m_copy);
4241     menu->addAction(m_paste);
4242     if (m_pasteSelection) {
4243         menu->addAction(m_pasteSelection);
4244     }
4245     menu->addAction(m_swapWithClipboard);
4246     menu->addSeparator();
4247     menu->addAction(m_selectAll);
4248     menu->addAction(m_deSelect);
4249     QAction *editing = actionCollection()->action(QStringLiteral("tools_scripts_Editing"));
4250     if (editing) {
4251         menu->addAction(editing);
4252     }
4253     if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) {
4254         menu->addSeparator();
4255         menu->addAction(spellingSuggestions);
4256     }
4257     if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) {
4258         menu->addSeparator();
4259         menu->addAction(bookmark);
4260     }
4261 
4262     return menu;
4263 }
4264 
4265 void KTextEditor::ViewPrivate::aboutToShowContextMenu()
4266 {
4267     QMenu *menu = qobject_cast<QMenu *>(sender());
4268 
4269     if (menu) {
4270         Q_EMIT contextMenuAboutToShow(this, menu);
4271     }
4272 }
4273 
4274 void KTextEditor::ViewPrivate::aboutToHideContextMenu()
4275 {
4276     m_spellingMenu->cleanUpAfterShown();
4277 }
4278 
4279 // BEGIN ConfigInterface stff
4280 QStringList KTextEditor::ViewPrivate::configKeys() const
4281 {
4282     static const QStringList keys = {QStringLiteral("icon-bar"),
4283                                      QStringLiteral("line-numbers"),
4284                                      QStringLiteral("dynamic-word-wrap"),
4285                                      QStringLiteral("background-color"),
4286                                      QStringLiteral("selection-color"),
4287                                      QStringLiteral("search-highlight-color"),
4288                                      QStringLiteral("replace-highlight-color"),
4289                                      QStringLiteral("default-mark-type"),
4290                                      QStringLiteral("allow-mark-menu"),
4291                                      QStringLiteral("folding-bar"),
4292                                      QStringLiteral("folding-preview"),
4293                                      QStringLiteral("icon-border-color"),
4294                                      QStringLiteral("folding-marker-color"),
4295                                      QStringLiteral("line-number-color"),
4296                                      QStringLiteral("current-line-number-color"),
4297                                      QStringLiteral("modification-markers"),
4298                                      QStringLiteral("keyword-completion"),
4299                                      QStringLiteral("word-count"),
4300                                      QStringLiteral("line-count"),
4301                                      QStringLiteral("scrollbar-minimap"),
4302                                      QStringLiteral("scrollbar-preview"),
4303                                      QStringLiteral("font"),
4304                                      QStringLiteral("theme")};
4305     return keys;
4306 }
4307 
4308 QVariant KTextEditor::ViewPrivate::configValue(const QString &key)
4309 {
4310     if (key == QLatin1String("icon-bar")) {
4311         return config()->iconBar();
4312     } else if (key == QLatin1String("line-numbers")) {
4313         return config()->lineNumbers();
4314     } else if (key == QLatin1String("dynamic-word-wrap")) {
4315         return config()->dynWordWrap();
4316     } else if (key == QLatin1String("background-color")) {
4317         return renderer()->config()->backgroundColor();
4318     } else if (key == QLatin1String("selection-color")) {
4319         return renderer()->config()->selectionColor();
4320     } else if (key == QLatin1String("search-highlight-color")) {
4321         return renderer()->config()->searchHighlightColor();
4322     } else if (key == QLatin1String("replace-highlight-color")) {
4323         return renderer()->config()->replaceHighlightColor();
4324     } else if (key == QLatin1String("default-mark-type")) {
4325         return config()->defaultMarkType();
4326     } else if (key == QLatin1String("allow-mark-menu")) {
4327         return config()->allowMarkMenu();
4328     } else if (key == QLatin1String("folding-bar")) {
4329         return config()->foldingBar();
4330     } else if (key == QLatin1String("folding-preview")) {
4331         return config()->foldingPreview();
4332     } else if (key == QLatin1String("icon-border-color")) {
4333         return renderer()->config()->iconBarColor();
4334     } else if (key == QLatin1String("folding-marker-color")) {
4335         return renderer()->config()->foldingColor();
4336     } else if (key == QLatin1String("line-number-color")) {
4337         return renderer()->config()->lineNumberColor();
4338     } else if (key == QLatin1String("current-line-number-color")) {
4339         return renderer()->config()->currentLineNumberColor();
4340     } else if (key == QLatin1String("modification-markers")) {
4341         return config()->lineModification();
4342     } else if (key == QLatin1String("keyword-completion")) {
4343         return config()->keywordCompletion();
4344     } else if (key == QLatin1String("word-count")) {
4345         return config()->showWordCount();
4346     } else if (key == QLatin1String("line-count")) {
4347         return config()->showLineCount();
4348     } else if (key == QLatin1String("scrollbar-minimap")) {
4349         return config()->scrollBarMiniMap();
4350     } else if (key == QLatin1String("scrollbar-preview")) {
4351         return config()->scrollBarPreview();
4352     } else if (key == QLatin1String("font")) {
4353         return renderer()->config()->baseFont();
4354     } else if (key == QLatin1String("theme")) {
4355         return renderer()->config()->schema();
4356     }
4357 
4358     // return invalid variant
4359     return QVariant();
4360 }
4361 
4362 void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value)
4363 {
4364     // First, try the new config interface
4365     if (config()->setValue(key, value)) {
4366         return;
4367 
4368     } else if (renderer()->config()->setValue(key, value)) {
4369         return;
4370     }
4371 
4372     // No success? Go the old way
4373     if (value.canConvert<QColor>()) {
4374         if (key == QLatin1String("background-color")) {
4375             renderer()->config()->setBackgroundColor(value.value<QColor>());
4376         } else if (key == QLatin1String("selection-color")) {
4377             renderer()->config()->setSelectionColor(value.value<QColor>());
4378         } else if (key == QLatin1String("search-highlight-color")) {
4379             renderer()->config()->setSearchHighlightColor(value.value<QColor>());
4380         } else if (key == QLatin1String("replace-highlight-color")) {
4381             renderer()->config()->setReplaceHighlightColor(value.value<QColor>());
4382         } else if (key == QLatin1String("icon-border-color")) {
4383             renderer()->config()->setIconBarColor(value.value<QColor>());
4384         } else if (key == QLatin1String("folding-marker-color")) {
4385             renderer()->config()->setFoldingColor(value.value<QColor>());
4386         } else if (key == QLatin1String("line-number-color")) {
4387             renderer()->config()->setLineNumberColor(value.value<QColor>());
4388         } else if (key == QLatin1String("current-line-number-color")) {
4389             renderer()->config()->setCurrentLineNumberColor(value.value<QColor>());
4390         }
4391     }
4392     if (value.type() == QVariant::Bool) {
4393         // Note explicit type check above. If we used canConvert, then
4394         // values of type UInt will be trapped here.
4395         if (key == QLatin1String("dynamic-word-wrap")) {
4396             config()->setDynWordWrap(value.toBool());
4397         } else if (key == QLatin1String("word-count")) {
4398             config()->setShowWordCount(value.toBool());
4399         } else if (key == QLatin1String("line-count")) {
4400             config()->setShowLineCount(value.toBool());
4401         }
4402     } else if (key == QLatin1String("font") && value.canConvert<QFont>()) {
4403         renderer()->config()->setFont(value.value<QFont>());
4404     } else if (key == QLatin1String("theme") && value.type() == QVariant::String) {
4405         renderer()->config()->setSchema(value.toString());
4406     }
4407 }
4408 
4409 // END ConfigInterface
4410 
4411 void KTextEditor::ViewPrivate::userInvokedCompletion()
4412 {
4413     completionWidget()->userInvokedCompletion();
4414 }
4415 
4416 KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const
4417 {
4418     return m_bottomViewBar;
4419 }
4420 
4421 KateGotoBar *KTextEditor::ViewPrivate::gotoBar()
4422 {
4423     if (!m_gotoBar) {
4424         m_gotoBar = new KateGotoBar(this);
4425         bottomViewBar()->addBarWidget(m_gotoBar);
4426     }
4427 
4428     return m_gotoBar;
4429 }
4430 
4431 KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar()
4432 {
4433     if (!m_dictionaryBar) {
4434         m_dictionaryBar = new KateDictionaryBar(this);
4435         bottomViewBar()->addBarWidget(m_dictionaryBar);
4436     }
4437 
4438     return m_dictionaryBar;
4439 }
4440 
4441 void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model)
4442 {
4443     KTextEditor::AnnotationModel *oldmodel = m_annotationModel;
4444     m_annotationModel = model;
4445     m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, m_annotationModel);
4446 }
4447 
4448 KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const
4449 {
4450     return m_annotationModel;
4451 }
4452 
4453 void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible)
4454 {
4455     m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible);
4456 }
4457 
4458 bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const
4459 {
4460     return m_viewInternal->m_leftBorder->annotationBorderOn();
4461 }
4462 
4463 KTextEditor::AbstractAnnotationItemDelegate *KTextEditor::ViewPrivate::annotationItemDelegate() const
4464 {
4465     return m_viewInternal->m_leftBorder->annotationItemDelegate();
4466 }
4467 
4468 void KTextEditor::ViewPrivate::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate)
4469 {
4470     m_viewInternal->m_leftBorder->setAnnotationItemDelegate(delegate);
4471 }
4472 
4473 bool KTextEditor::ViewPrivate::uniformAnnotationItemSizes() const
4474 {
4475     return m_viewInternal->m_leftBorder->uniformAnnotationItemSizes();
4476 }
4477 
4478 void KTextEditor::ViewPrivate::setAnnotationUniformItemSizes(bool enable)
4479 {
4480     m_viewInternal->m_leftBorder->setAnnotationUniformItemSizes(enable);
4481 }
4482 
4483 KTextEditor::Range KTextEditor::ViewPrivate::visibleRange()
4484 {
4485     // ensure that the view is up-to-date, otherwise 'endPos()' might fail!
4486     if (!m_viewInternal->endPos().isValid()) {
4487         m_viewInternal->updateView();
4488     }
4489     return KTextEditor::Range(m_viewInternal->toRealCursor(m_viewInternal->startPos()), m_viewInternal->toRealCursor(m_viewInternal->endPos()));
4490 }
4491 
4492 bool KTextEditor::ViewPrivate::event(QEvent *e)
4493 {
4494     switch (e->type()) {
4495     case QEvent::StyleChange:
4496         setupLayout();
4497         return true;
4498     default:
4499         return KTextEditor::View::event(e);
4500     }
4501 }
4502 
4503 void KTextEditor::ViewPrivate::paintEvent(QPaintEvent *e)
4504 {
4505     // base class
4506     KTextEditor::View::paintEvent(e);
4507 
4508     if (!config()->showFocusFrame()) {
4509         return;
4510     }
4511 
4512     const QRect contentsRect = m_topSpacer->geometry() | m_bottomSpacer->geometry() | m_leftSpacer->geometry() | m_rightSpacer->geometry();
4513 
4514     if (contentsRect.isValid()) {
4515         QStyleOptionFrame opt;
4516         opt.initFrom(this);
4517         opt.frameShape = QFrame::StyledPanel;
4518         opt.state |= QStyle::State_Sunken;
4519 
4520         // clear mouseOver and focus state
4521         // update from relevant widgets
4522         opt.state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver);
4523         const QList<QWidget *> widgets = QList<QWidget *>()
4524             << m_viewInternal << m_viewInternal->m_leftBorder << m_viewInternal->m_lineScroll << m_viewInternal->m_columnScroll;
4525         for (const QWidget *w : widgets) {
4526             if (w->hasFocus()) {
4527                 opt.state |= QStyle::State_HasFocus;
4528             }
4529             if (w->underMouse()) {
4530                 opt.state |= QStyle::State_MouseOver;
4531             }
4532         }
4533 
4534         // update rect
4535         opt.rect = contentsRect;
4536 
4537         // render
4538         QPainter paint(this);
4539         paint.setClipRegion(e->region());
4540         paint.setRenderHints(QPainter::Antialiasing);
4541         style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this);
4542     }
4543 }
4544 
4545 void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b)
4546 {
4547     doc()->onTheFlySpellCheckingEnabled(b);
4548 }
4549 
4550 void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled)
4551 {
4552     m_spellingMenu->setVisible(enabled);
4553     m_toggleOnTheFlySpellCheck->setChecked(enabled);
4554 }
4555 
4556 KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu()
4557 {
4558     return m_spellingMenu;
4559 }
4560 
4561 void KTextEditor::ViewPrivate::notifyAboutRangeChange(KTextEditor::LineRange lineRange, bool needsRepaint)
4562 {
4563 #ifdef VIEW_RANGE_DEBUG
4564     // output args
4565     qCDebug(LOG_KTE) << "trigger attribute changed in line range " << lineRange << "needsRepaint" << needsRepaint;
4566 #endif
4567 
4568     // if we need repaint, we will need to collect the line ranges we will update
4569     if (needsRepaint && lineRange.isValid()) {
4570         if (m_lineToUpdateRange.isValid()) {
4571             m_lineToUpdateRange.expandToRange(lineRange);
4572         } else {
4573             m_lineToUpdateRange = lineRange;
4574         }
4575     }
4576 
4577     // first call => trigger later update of view via delayed signal to group updates
4578     if (!m_delayedUpdateTimer.isActive()) {
4579         m_delayedUpdateTimer.start();
4580     }
4581 }
4582 
4583 void KTextEditor::ViewPrivate::slotDelayedUpdateOfView()
4584 {
4585 #ifdef VIEW_RANGE_DEBUG
4586     // output args
4587     qCDebug(LOG_KTE) << "delayed attribute changed in line range" << m_lineToUpdateRange;
4588 #endif
4589     // update ranges in
4590     updateRangesIn(KTextEditor::Attribute::ActivateMouseIn);
4591     updateRangesIn(KTextEditor::Attribute::ActivateCaretIn);
4592 
4593     // update view, if valid line range, else only feedback update wanted anyway
4594     if (m_lineToUpdateRange.isValid()) {
4595         tagLines(m_lineToUpdateRange, true);
4596         updateView(true);
4597     }
4598 
4599     // reset flags
4600     m_lineToUpdateRange = KTextEditor::LineRange::invalid();
4601 }
4602 
4603 void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType)
4604 {
4605     // new ranges with cursor in, default none
4606     QSet<Kate::TextRange *> newRangesIn;
4607 
4608     // on which range set we work?
4609     QSet<Kate::TextRange *> &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn;
4610 
4611     // which cursor position to honor?
4612     KTextEditor::Cursor currentCursor =
4613         (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->mousePosition() : m_viewInternal->cursorPosition();
4614 
4615     // first: validate the remembered ranges
4616     QSet<Kate::TextRange *> validRanges;
4617     for (Kate::TextRange *range : std::as_const(oldSet)) {
4618         if (doc()->buffer().rangePointerValid(range)) {
4619             validRanges.insert(range);
4620         }
4621     }
4622 
4623     // cursor valid? else no new ranges can be found
4624     if (currentCursor.isValid() && currentCursor.line() < doc()->buffer().lines()) {
4625         // now: get current ranges for the line of cursor with an attribute
4626         const QVector<Kate::TextRange *> rangesForCurrentCursor = doc()->buffer().rangesForLine(currentCursor.line(), this, false);
4627 
4628         // match which ranges really fit the given cursor
4629         for (Kate::TextRange *range : rangesForCurrentCursor) {
4630             // range has no dynamic attribute of right type and no feedback object
4631             auto attribute = range->attribute();
4632             if ((!attribute || !attribute->dynamicAttribute(activationType)) && !range->feedback()) {
4633                 continue;
4634             }
4635 
4636             // range doesn't contain cursor, not interesting
4637             if ((range->startInternal().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->toRange().start())
4638                                                                                                      : (currentCursor <= range->toRange().start())) {
4639                 continue;
4640             }
4641 
4642             if ((range->endInternal().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->toRange().end() <= currentCursor)
4643                                                                                                    : (range->toRange().end() < currentCursor)) {
4644                 continue;
4645             }
4646 
4647             // range contains cursor, was it already in old set?
4648             auto it = validRanges.find(range);
4649             if (it != validRanges.end()) {
4650                 // insert in new, remove from old, be done with it
4651                 newRangesIn.insert(range);
4652                 validRanges.erase(it);
4653                 continue;
4654             }
4655 
4656             // oh, new range, trigger update and insert into new set
4657             newRangesIn.insert(range);
4658 
4659             if (attribute && attribute->dynamicAttribute(activationType)) {
4660                 notifyAboutRangeChange(range->toLineRange(), true);
4661             }
4662 
4663             // feedback
4664             if (range->feedback()) {
4665                 if (activationType == KTextEditor::Attribute::ActivateMouseIn) {
4666                     range->feedback()->mouseEnteredRange(range, this);
4667                 } else {
4668                     range->feedback()->caretEnteredRange(range, this);
4669                     Q_EMIT caretChangedRange(this);
4670                 }
4671             }
4672 
4673 #ifdef VIEW_RANGE_DEBUG
4674             // found new range for activation
4675             qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType;
4676 #endif
4677         }
4678     }
4679 
4680     // now: notify for left ranges!
4681     for (Kate::TextRange *range : std::as_const(validRanges)) {
4682         // range valid + right dynamic attribute, trigger update
4683         if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(activationType)) {
4684             notifyAboutRangeChange(range->toLineRange(), true);
4685         }
4686 
4687         // feedback
4688         if (range->feedback()) {
4689             if (activationType == KTextEditor::Attribute::ActivateMouseIn) {
4690                 range->feedback()->mouseExitedRange(range, this);
4691             } else {
4692                 range->feedback()->caretExitedRange(range, this);
4693                 Q_EMIT caretChangedRange(this);
4694             }
4695         }
4696     }
4697 
4698     // set new ranges
4699     oldSet = newRangesIn;
4700 }
4701 
4702 void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList<QSharedPointer<QAction>> actions)
4703 {
4704     // just forward to KateMessageWidget :-)
4705     auto messageWidget = m_messageWidgets[message->position()];
4706     if (!messageWidget) {
4707         // this branch is used for: TopInView, CenterInView, and BottomInView
4708         messageWidget = new KateMessageWidget(m_viewInternal, true);
4709         m_messageWidgets[message->position()] = messageWidget;
4710         m_notificationLayout->addWidget(messageWidget, message->position());
4711         connect(this, &KTextEditor::ViewPrivate::displayRangeChanged, messageWidget, &KateMessageWidget::startAutoHideTimer);
4712         connect(this, &KTextEditor::ViewPrivate::cursorPositionChanged, messageWidget, &KateMessageWidget::startAutoHideTimer);
4713     }
4714     messageWidget->postMessage(message, std::move(actions));
4715 }
4716 
4717 KateMessageWidget *KTextEditor::ViewPrivate::messageWidget()
4718 {
4719     return m_messageWidgets[KTextEditor::Message::TopInView];
4720 }
4721 
4722 void KTextEditor::ViewPrivate::saveFoldingState()
4723 {
4724     m_savedFoldingState = m_textFolding.exportFoldingRanges();
4725 }
4726 
4727 void KTextEditor::ViewPrivate::clearFoldingState()
4728 {
4729     m_savedFoldingState = {};
4730 }
4731 
4732 void KTextEditor::ViewPrivate::applyFoldingState()
4733 {
4734     m_textFolding.importFoldingRanges(m_savedFoldingState);
4735     m_savedFoldingState = QJsonDocument();
4736 }
4737 
4738 void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file)
4739 {
4740     KateExporter(this).exportToFile(file);
4741 }
4742 
4743 void KTextEditor::ViewPrivate::exportHtmlToClipboard()
4744 {
4745     KateExporter(this).exportToClipboard();
4746 }
4747 
4748 void KTextEditor::ViewPrivate::exportHtmlToFile()
4749 {
4750     const QString file = QFileDialog::getSaveFileName(this, i18n("Export File as HTML"), doc()->documentName());
4751     if (!file.isEmpty()) {
4752         KateExporter(this).exportToFile(file);
4753     }
4754 }
4755 
4756 void KTextEditor::ViewPrivate::clearHighlights()
4757 {
4758     m_rangesForHighlights.clear();
4759     m_currentTextForHighlights.clear();
4760 }
4761 
4762 void KTextEditor::ViewPrivate::selectionChangedForHighlights()
4763 {
4764     QString text;
4765     // if text of selection is still the same, abort
4766     if (selection() && selectionRange().onSingleLine()) {
4767         text = selectionText();
4768         if (text == m_currentTextForHighlights) {
4769             return;
4770         }
4771     }
4772 
4773     // text changed: remove all highlights + create new ones
4774     // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights
4775     m_rangesForHighlights.clear();
4776 
4777     // do not highlight strings with leading and trailing spaces
4778     if (!text.isEmpty() && (text.at(0).isSpace() || text.at(text.length() - 1).isSpace())) {
4779         return;
4780     }
4781 
4782     // trigger creation of ranges for current view range
4783     m_currentTextForHighlights = text;
4784     createHighlights();
4785 }
4786 
4787 void KTextEditor::ViewPrivate::createHighlights()
4788 {
4789     // do nothing if no text to highlight
4790     if (m_currentTextForHighlights.isEmpty()) {
4791         return;
4792     }
4793 
4794     // clear existing highlighting ranges, otherwise we stack over and over the same ones eventually
4795     m_rangesForHighlights.clear();
4796 
4797     KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute());
4798     attr->setBackground(Qt::yellow);
4799 
4800     // set correct highlight color from Kate's color schema
4801     QColor fgColor = defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color();
4802     QColor bgColor = renderer()->config()->searchHighlightColor();
4803     attr->setForeground(fgColor);
4804     attr->setBackground(bgColor);
4805 
4806     KTextEditor::Cursor start(visibleRange().start());
4807     KTextEditor::Range searchRange;
4808 
4809     // only add word boundary if we can find the text then
4810     // fixes $lala hl
4811     QString pattern = QRegularExpression::escape(m_currentTextForHighlights);
4812     if (m_currentTextForHighlights.contains(QRegularExpression(QLatin1String("\\b") + pattern, QRegularExpression::UseUnicodePropertiesOption))) {
4813         pattern.prepend(QLatin1String("\\b"));
4814     }
4815 
4816     if (m_currentTextForHighlights.contains(QRegularExpression(pattern + QLatin1String("\\b"), QRegularExpression::UseUnicodePropertiesOption))) {
4817         pattern += QLatin1String("\\b");
4818     }
4819 
4820     QVector<KTextEditor::Range> matches;
4821     do {
4822         searchRange.setRange(start, visibleRange().end());
4823 
4824         matches = doc()->searchText(searchRange, pattern, KTextEditor::Regex);
4825 
4826         if (matches.first().isValid()) {
4827             if (matches.first() != selectionRange()) {
4828                 std::unique_ptr<KTextEditor::MovingRange> mr(doc()->newMovingRange(matches.first()));
4829                 mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection
4830                 mr->setAttribute(attr);
4831                 mr->setView(this);
4832                 mr->setAttributeOnlyForViews(true);
4833                 m_rangesForHighlights.push_back(std::move(mr));
4834             }
4835             start = matches.first().end();
4836         }
4837     } while (matches.first().isValid());
4838 }
4839 
4840 KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const
4841 {
4842     return m_viewInternal->m_currentInputMode;
4843 }
4844 
4845 void KTextEditor::ViewPrivate::toggleInputMode()
4846 {
4847     if (QAction *a = qobject_cast<QAction *>(sender())) {
4848         setInputMode(static_cast<KTextEditor::View::InputMode>(a->data().toInt()));
4849     }
4850 }
4851 
4852 void KTextEditor::ViewPrivate::cycleInputMode()
4853 {
4854     InputMode current = currentInputMode()->viewInputMode();
4855     InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode;
4856     setInputMode(to);
4857 }
4858 
4859 // BEGIN KTextEditor::PrintInterface stuff
4860 bool KTextEditor::ViewPrivate::print()
4861 {
4862     return KatePrinter::print(this);
4863 }
4864 
4865 void KTextEditor::ViewPrivate::printPreview()
4866 {
4867     KatePrinter::printPreview(this);
4868 }
4869 
4870 // END
4871 
4872 // BEGIN KTextEditor::InlineNoteInterface
4873 void KTextEditor::ViewPrivate::registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider)
4874 {
4875     if (std::find(m_inlineNoteProviders.cbegin(), m_inlineNoteProviders.cend(), provider) == m_inlineNoteProviders.cend()) {
4876         m_inlineNoteProviders.push_back(provider);
4877 
4878         connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesReset, this, &KTextEditor::ViewPrivate::inlineNotesReset);
4879         connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesChanged, this, &KTextEditor::ViewPrivate::inlineNotesLineChanged);
4880 
4881         inlineNotesReset();
4882     }
4883 }
4884 
4885 void KTextEditor::ViewPrivate::unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider)
4886 {
4887     auto it = std::find(m_inlineNoteProviders.cbegin(), m_inlineNoteProviders.cend(), provider);
4888     if (it != m_inlineNoteProviders.cend()) {
4889         m_inlineNoteProviders.erase(it);
4890         provider->disconnect(this);
4891 
4892         inlineNotesReset();
4893     }
4894 }
4895 
4896 QVarLengthArray<KateInlineNoteData, 8> KTextEditor::ViewPrivate::inlineNotes(int line) const
4897 {
4898     QVarLengthArray<KateInlineNoteData, 8> allInlineNotes;
4899     for (KTextEditor::InlineNoteProvider *provider : m_inlineNoteProviders) {
4900         int index = 0;
4901         const auto columns = provider->inlineNotes(line);
4902         for (int column : columns) {
4903             const bool underMouse = Cursor(line, column) == m_viewInternal->m_activeInlineNote.m_position;
4904             KateInlineNoteData note =
4905                 {provider, this, {line, column}, index, underMouse, m_viewInternal->renderer()->currentFont(), m_viewInternal->renderer()->lineHeight()};
4906             allInlineNotes.append(note);
4907             index++;
4908         }
4909     }
4910     return allInlineNotes;
4911 }
4912 
4913 QRect KTextEditor::ViewPrivate::inlineNoteRect(const KateInlineNoteData &note) const
4914 {
4915     return m_viewInternal->inlineNoteRect(note);
4916 }
4917 
4918 void KTextEditor::ViewPrivate::inlineNotesReset()
4919 {
4920     m_viewInternal->m_activeInlineNote = {};
4921     tagLines(KTextEditor::LineRange(0, doc()->lastLine()), true);
4922 }
4923 
4924 void KTextEditor::ViewPrivate::inlineNotesLineChanged(int line)
4925 {
4926     if (line == m_viewInternal->m_activeInlineNote.m_position.line()) {
4927         m_viewInternal->m_activeInlineNote = {};
4928     }
4929     tagLines({line, line}, true);
4930 }
4931 
4932 // END KTextEditor::InlineNoteInterface
4933 
4934 KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const
4935 {
4936     KateRendererConfig *renderConfig = const_cast<KTextEditor::ViewPrivate *>(this)->renderer()->config();
4937 
4938     KTextEditor::Attribute::Ptr style = doc()->highlight()->attributes(renderConfig->schema()).at(defaultStyle);
4939     if (!style->hasProperty(QTextFormat::BackgroundBrush)) {
4940         // make sure the returned style has the default background color set
4941         style = new KTextEditor::Attribute(*style);
4942         style->setBackground(QBrush(renderConfig->backgroundColor()));
4943     }
4944     return style;
4945 }
4946 
4947 QList<KTextEditor::AttributeBlock> KTextEditor::ViewPrivate::lineAttributes(int line)
4948 {
4949     QList<KTextEditor::AttributeBlock> attribs;
4950 
4951     if (line < 0 || line >= doc()->lines()) {
4952         return attribs;
4953     }
4954 
4955     Kate::TextLine kateLine = doc()->kateTextLine(line);
4956     if (!kateLine) {
4957         return attribs;
4958     }
4959 
4960     const QVector<Kate::TextLineData::Attribute> &intAttrs = kateLine->attributesList();
4961     for (int i = 0; i < intAttrs.size(); ++i) {
4962         if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) {
4963             attribs << KTextEditor::AttributeBlock(intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(intAttrs.at(i).attributeValue));
4964         }
4965     }
4966 
4967     return attribs;
4968 }
4969 
4970 #include "moc_kateview.cpp"