1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2009 Michel Ludwig <michel.ludwig@kdemail.net>
4 SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch>
5 SPDX-FileCopyrightText: 2003 Hamish Rodda <rodda@kde.org>
6 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
7 SPDX-FileCopyrightText: 2001-2004 Christoph Cullmann <cullmann@kde.org>
8 SPDX-FileCopyrightText: 2001-2010 Joseph Wenninger <jowenn@kde.org>
9 SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
10
11 SPDX-License-Identifier: LGPL-2.0-only
12*/
13
14// BEGIN includes
15#include "kateview.h"
16
17#include "clipboardhistorydialog.h"
18#include "export/exporter.h"
19#include "inlinenotedata.h"
20#include "kateabstractinputmode.h"
21#include "kateautoindent.h"
22#include "katebookmarks.h"
23#include "katebuffer.h"
24#include "katecompletionwidget.h"
25#include "kateconfig.h"
26#include "katedialogs.h"
27#include "katedocument.h"
28#include "kateglobal.h"
29#include "katehighlight.h"
30#include "katehighlightmenu.h"
31#include "katekeywordcompletion.h"
32#include "katelayoutcache.h"
33#include "katemessagewidget.h"
34#include "katemodemenu.h"
35#include "katepartdebug.h"
36#include "katerenderer.h"
37#include "katestatusbar.h"
38#include "katetemplatehandler.h"
39#include "katetextline.h"
40#include "kateundomanager.h"
41#include "kateviewhelpers.h"
42#include "kateviewinternal.h"
43#include "katewordcompletion.h"
44#include "printing/kateprinter.h"
45#include "screenshotdialog.h"
46#include "script/katescriptaction.h"
47#include "script/katescriptmanager.h"
48#include "spellcheck/spellcheck.h"
49#include "spellcheck/spellcheckdialog.h"
50#include "spellcheck/spellingmenu.h"
51
52#include <KTextEditor/Message>
53#include <ktexteditor/annotationinterface.h>
54#include <ktexteditor/inlinenoteprovider.h>
55#include <ktexteditor/texthintinterface.h>
56
57#include <KActionCollection>
58#include <KConfig>
59#include <KConfigGroup>
60#include <KCursor>
61#include <KMessageBox>
62#include <KSelectAction>
63#include <KStandardActions>
64#include <KStandardShortcut>
65#include <KToggleAction>
66#include <KXMLGUIFactory>
67
68#include <QActionGroup>
69#include <QApplication>
70#include <QClipboard>
71#include <QFileDialog>
72#include <QFileInfo>
73#include <QFont>
74#include <QInputDialog>
75#include <QKeyEvent>
76#include <QLayout>
77#include <QMimeData>
78#include <QPainter>
79#include <QRegularExpression>
80#include <QTextToSpeech>
81#include <QToolTip>
82
83// #define VIEW_RANGE_DEBUG
84
85// END includes
86
87namespace
88{
89bool hasCommentInFirstLine(KTextEditor::DocumentPrivate *doc)
90{
91 const Kate::TextLine line = doc->kateTextLine(i: 0);
92 return doc->isComment(line: 0, column: line.firstChar());
93}
94
95}
96
97void KTextEditor::ViewPrivate::blockFix(KTextEditor::Range &range)
98{
99 if (range.start().column() > range.end().column()) {
100 int tmp = range.start().column();
101 range.setStart(KTextEditor::Cursor(range.start().line(), range.end().column()));
102 range.setEnd(KTextEditor::Cursor(range.end().line(), tmp));
103 }
104}
105
106KTextEditor::ViewPrivate::ViewPrivate(KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow)
107 : KTextEditor::View(this, parent)
108 , m_completionWidget(nullptr)
109 , m_annotationModel(nullptr)
110 , m_markedSelection(false)
111 , m_hasWrap(false)
112 , m_doc(doc)
113 , m_textFolding(doc->buffer())
114 , m_config(new KateViewConfig(this))
115 , m_renderer(new KateRenderer(doc, m_textFolding, this))
116 , m_viewInternal(new KateViewInternal(this))
117 , m_spell(new KateSpellCheckDialog(this))
118 , m_bookmarks(new KateBookmarks(this))
119 , m_topSpacer(new QSpacerItem(0, 0))
120 , m_leftSpacer(new QSpacerItem(0, 0))
121 , m_rightSpacer(new QSpacerItem(0, 0))
122 , m_bottomSpacer(new QSpacerItem(0, 0))
123 , m_startingUp(true)
124 , m_updatingDocumentConfig(false)
125 , m_selection(&m_doc->buffer(), KTextEditor::Range::invalid(), Kate::TextRange::ExpandLeft, Kate::TextRange::AllowEmpty)
126 , blockSelect(false)
127 , m_bottomViewBar(nullptr)
128 , m_gotoBar(nullptr)
129 , m_dictionaryBar(nullptr)
130 , m_spellingMenu(new KateSpellingMenu(this))
131 , m_userContextMenuSet(false)
132 , m_lineToUpdateRange(KTextEditor::LineRange::invalid())
133 , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there!
134 , m_statusBar(nullptr)
135 , m_temporaryAutomaticInvocationDisabled(false)
136 , m_autoFoldedFirstLine(false)
137{
138 // queued connect to collapse view updates for range changes, INIT THIS EARLY ENOUGH!
139 connect(sender: this, signal: &KTextEditor::ViewPrivate::delayedUpdateOfView, context: this, slot: &KTextEditor::ViewPrivate::slotDelayedUpdateOfView, type: Qt::QueuedConnection);
140
141 m_delayedUpdateTimer.setSingleShot(true);
142 m_delayedUpdateTimer.setInterval(0);
143 connect(sender: &m_delayedUpdateTimer, signal: &QTimer::timeout, context: this, slot: &KTextEditor::ViewPrivate::delayedUpdateOfView);
144
145 KXMLGUIClient::setComponentName(componentName: KTextEditor::EditorPrivate::self()->aboutData().componentName(),
146 componentDisplayName: KTextEditor::EditorPrivate::self()->aboutData().displayName());
147
148 // selection if for this view only and will invalidate if becoming empty
149 m_selection.setView(this);
150
151 // use z depth defined in moving ranges interface
152 m_selection.setZDepth(-100000.0);
153
154 KTextEditor::EditorPrivate::self()->registerView(view: this);
155
156 // try to let the main window, if any, create a view bar for this view
157 QWidget *bottomBarParent = m_mainWindow->createViewBar(view: this);
158
159 m_bottomViewBar = new KateViewBar(bottomBarParent != nullptr, bottomBarParent ? bottomBarParent : this, this);
160
161 // ugly workaround:
162 // Force the layout to be left-to-right even on RTL desktop, as discussed
163 // on the mailing list. This will cause the lines and icons panel to be on
164 // the left, even for Arabic/Hebrew/Farsi/whatever users.
165 setLayoutDirection(Qt::LeftToRight);
166
167 m_bottomViewBar->installEventFilter(filterObj: m_viewInternal);
168
169 // add KateMessageWidget for KTE::MessageInterface immediately above view
170 m_messageWidgets[KTextEditor::Message::AboveView] = new KateMessageWidget(this);
171 m_messageWidgets[KTextEditor::Message::AboveView]->setPosition(KateMessageWidget::Position::Header);
172 m_messageWidgets[KTextEditor::Message::AboveView]->hide();
173
174 // add KateMessageWidget for KTE::MessageInterface immediately below view
175 m_messageWidgets[KTextEditor::Message::BelowView] = new KateMessageWidget(this);
176 m_messageWidgets[KTextEditor::Message::BelowView]->setPosition(KateMessageWidget::Position::Footer);
177 m_messageWidgets[KTextEditor::Message::BelowView]->hide();
178
179 // add bottom viewbar...
180 if (bottomBarParent) {
181 m_mainWindow->addWidgetToViewBar(view: this, bar: m_bottomViewBar);
182 }
183
184 // add layout for floating message widgets to KateViewInternal
185 m_notificationLayout = new KateMessageLayout(m_viewInternal);
186 m_notificationLayout->setContentsMargins(left: 20, top: 20, right: 20, bottom: 20);
187 m_viewInternal->setLayout(m_notificationLayout);
188
189 // this really is needed :)
190 m_viewInternal->updateView();
191
192 doc->addView(this);
193
194 setFocusProxy(m_viewInternal);
195 setFocusPolicy(Qt::StrongFocus);
196
197 setXMLFile(QStringLiteral("katepart5ui.rc"));
198
199 setupConnections();
200 setupActions();
201
202 // auto word completion
203 new KateWordCompletionView(this, actionCollection());
204
205 // update the enabled state of the undo/redo actions...
206 slotUpdateUndo();
207
208 // create the status bar of this view
209 // do this after action creation, we use some of them!
210 toggleStatusBar();
211
212 m_startingUp = false;
213 updateConfig();
214
215 slotHlChanged();
216 KCursor::setAutoHideCursor(w: m_viewInternal, enable: true);
217
218 for (auto messageWidget : m_messageWidgets) {
219 if (messageWidget) {
220 // user interaction (scrolling) starts notification auto-hide timer
221 connect(sender: this, signal: &KTextEditor::View::displayRangeChanged, context: messageWidget, slot: &KateMessageWidget::startAutoHideTimer);
222
223 // user interaction (cursor navigation) starts notification auto-hide timer
224 connect(sender: this, signal: &KTextEditor::ViewPrivate::cursorPositionChanged, context: messageWidget, slot: &KateMessageWidget::startAutoHideTimer);
225 }
226 }
227
228 // folding restoration on reload
229 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::aboutToReload, context: this, slot: &KTextEditor::ViewPrivate::saveFoldingState);
230 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::reloaded, context: this, slot: &KTextEditor::ViewPrivate::applyFoldingState);
231
232 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::reloaded, context: this, slot: &KTextEditor::ViewPrivate::slotDocumentReloaded);
233 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::aboutToReload, context: this, slot: &KTextEditor::ViewPrivate::slotDocumentAboutToReload);
234
235 // update highlights on scrolling and co
236 connect(sender: this, signal: &KTextEditor::View::displayRangeChanged, context: this, slot: &KTextEditor::ViewPrivate::createHighlights);
237
238 // clear highlights on reload
239 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::aboutToReload, context: this, slot: &KTextEditor::ViewPrivate::clearHighlights);
240
241 // setup layout
242 setupLayout();
243}
244
245KTextEditor::ViewPrivate::~ViewPrivate()
246{
247 // de-register views early from global collections
248 // otherwise we might "use" them again during destruction in a half-valid state
249 // see e.g. bug 422546
250 // Kate::TextBuffer::notifyAboutRangeChange will access views() in a chain during
251 // deletion of m_viewInternal
252 doc()->removeView(this);
253 KTextEditor::EditorPrivate::self()->deregisterView(view: this);
254
255 delete m_completionWidget;
256
257 // remove from xmlgui factory, to be safe
258 if (factory()) {
259 factory()->removeClient(client: this);
260 }
261
262 // delete internal view before view bar!
263 delete m_viewInternal;
264
265 // remove view bar again, if needed
266 m_mainWindow->deleteViewBar(view: this);
267 m_bottomViewBar = nullptr;
268
269 delete m_renderer;
270
271 delete m_config;
272}
273
274void KTextEditor::ViewPrivate::toggleStatusBar()
275{
276 // if there, delete it
277 if (m_statusBar) {
278 bottomViewBar()->removePermanentBarWidget(barWidget: m_statusBar);
279 delete m_statusBar;
280 m_statusBar = nullptr;
281 Q_EMIT statusBarEnabledChanged(view: this, enabled: false);
282 return;
283 }
284
285 // else: create it
286 m_statusBar = new KateStatusBar(this);
287 bottomViewBar()->addPermanentBarWidget(barWidget: m_statusBar);
288 Q_EMIT statusBarEnabledChanged(view: this, enabled: true);
289}
290
291void KTextEditor::ViewPrivate::setupLayout()
292{
293 // delete old layout if any
294 if (layout()) {
295 delete layout();
296
297 // need to recreate spacers because they are deleted with
298 // their belonging layout
299 m_topSpacer = new QSpacerItem(0, 0);
300 m_leftSpacer = new QSpacerItem(0, 0);
301 m_rightSpacer = new QSpacerItem(0, 0);
302 m_bottomSpacer = new QSpacerItem(0, 0);
303 }
304
305 // set margins
306 QStyleOptionFrame opt;
307 opt.initFrom(w: this);
308 opt.frameShape = QFrame::StyledPanel;
309 opt.state |= QStyle::State_Sunken;
310 const int margin = style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth, option: &opt, widget: this);
311 m_topSpacer->changeSize(w: 0, h: margin, hData: QSizePolicy::Minimum, vData: QSizePolicy::Fixed);
312 m_leftSpacer->changeSize(w: margin, h: 0, hData: QSizePolicy::Fixed, vData: QSizePolicy::Minimum);
313 m_rightSpacer->changeSize(w: margin, h: 0, hData: QSizePolicy::Fixed, vData: QSizePolicy::Minimum);
314 m_bottomSpacer->changeSize(w: 0, h: margin, hData: QSizePolicy::Minimum, vData: QSizePolicy::Fixed);
315
316 // define layout
317 QGridLayout *layout = new QGridLayout(this);
318 layout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
319 layout->setSpacing(0);
320
321 const bool frameAroundContents = style()->styleHint(stylehint: QStyle::SH_ScrollView_FrameOnlyAroundContents, opt: &opt, widget: this);
322 if (frameAroundContents) {
323 // top message widget
324 layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], row: 0, column: 0, rowSpan: 1, columnSpan: 5);
325
326 // top spacer
327 layout->addItem(item: m_topSpacer, row: 1, column: 0, rowSpan: 1, columnSpan: 4);
328
329 // left spacer
330 layout->addItem(item: m_leftSpacer, row: 2, column: 0, rowSpan: 1, columnSpan: 1);
331
332 // left border
333 layout->addWidget(m_viewInternal->m_leftBorder, row: 2, column: 1, rowSpan: 1, columnSpan: 1);
334
335 // view
336 layout->addWidget(m_viewInternal, row: 2, column: 2, rowSpan: 1, columnSpan: 1);
337
338 // right spacer
339 layout->addItem(item: m_rightSpacer, row: 2, column: 3, rowSpan: 1, columnSpan: 1);
340
341 // bottom spacer
342 layout->addItem(item: m_bottomSpacer, row: 3, column: 0, rowSpan: 1, columnSpan: 4);
343
344 // vertical scrollbar
345 layout->addWidget(m_viewInternal->m_lineScroll, row: 1, column: 4, rowSpan: 3, columnSpan: 1);
346
347 // horizontal scrollbar
348 layout->addWidget(m_viewInternal->m_columnScroll, row: 4, column: 0, rowSpan: 1, columnSpan: 4);
349
350 // dummy
351 layout->addWidget(m_viewInternal->m_dummy, row: 4, column: 4, rowSpan: 1, columnSpan: 1);
352
353 // bottom message
354 layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], row: 5, column: 0, rowSpan: 1, columnSpan: 5);
355
356 // bottom viewbar
357 if (m_bottomViewBar->parentWidget() == this) {
358 layout->addWidget(m_bottomViewBar, row: 6, column: 0, rowSpan: 1, columnSpan: 5);
359 }
360
361 // stretch
362 layout->setColumnStretch(column: 2, stretch: 1);
363 layout->setRowStretch(row: 2, stretch: 1);
364
365 // adjust scrollbar background
366 m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Window);
367 m_viewInternal->m_lineScroll->setAutoFillBackground(false);
368
369 m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Window);
370 m_viewInternal->m_columnScroll->setAutoFillBackground(false);
371
372 } else {
373 // top message widget
374 layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], row: 0, column: 0, rowSpan: 1, columnSpan: 5);
375
376 // top spacer
377 layout->addItem(item: m_topSpacer, row: 1, column: 0, rowSpan: 1, columnSpan: 5);
378
379 // left spacer
380 layout->addItem(item: m_leftSpacer, row: 2, column: 0, rowSpan: 1, columnSpan: 1);
381
382 // left border
383 layout->addWidget(m_viewInternal->m_leftBorder, row: 2, column: 1, rowSpan: 1, columnSpan: 1);
384
385 // view
386 layout->addWidget(m_viewInternal, row: 2, column: 2, rowSpan: 1, columnSpan: 1);
387
388 // vertical scrollbar
389 layout->addWidget(m_viewInternal->m_lineScroll, row: 2, column: 3, rowSpan: 1, columnSpan: 1);
390
391 // right spacer
392 layout->addItem(item: m_rightSpacer, row: 2, column: 4, rowSpan: 1, columnSpan: 1);
393
394 // horizontal scrollbar
395 layout->addWidget(m_viewInternal->m_columnScroll, row: 3, column: 1, rowSpan: 1, columnSpan: 2);
396
397 // dummy
398 layout->addWidget(m_viewInternal->m_dummy, row: 3, column: 3, rowSpan: 1, columnSpan: 1);
399
400 // bottom spacer
401 layout->addItem(item: m_bottomSpacer, row: 4, column: 0, rowSpan: 1, columnSpan: 5);
402
403 // bottom message
404 layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], row: 5, column: 0, rowSpan: 1, columnSpan: 5);
405
406 // bottom viewbar
407 if (m_bottomViewBar->parentWidget() == this) {
408 layout->addWidget(m_bottomViewBar, row: 6, column: 0, rowSpan: 1, columnSpan: 5);
409 }
410
411 // stretch
412 layout->setColumnStretch(column: 2, stretch: 1);
413 layout->setRowStretch(row: 2, stretch: 1);
414
415 // adjust scrollbar background
416 m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Base);
417 m_viewInternal->m_lineScroll->setAutoFillBackground(true);
418
419 m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Base);
420 m_viewInternal->m_columnScroll->setAutoFillBackground(true);
421 }
422}
423
424void KTextEditor::ViewPrivate::setupConnections()
425{
426 connect(sender: m_doc->undoManager(), signal: &KateUndoManager::undoChanged, context: this, slot: &KTextEditor::ViewPrivate::slotUpdateUndo);
427 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::highlightingModeChanged, context: this, slot: &KTextEditor::ViewPrivate::slotHlChanged);
428 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::canceled, context: this, slot: &KTextEditor::ViewPrivate::slotSaveCanceled);
429 connect(sender: m_viewInternal, signal: &KateViewInternal::dropEventPass, context: this, slot: &KTextEditor::ViewPrivate::dropEventPass);
430
431 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::annotationModelChanged, context: m_viewInternal->m_leftBorder, slot: &KateIconBorder::annotationModelChanged);
432}
433
434void KTextEditor::ViewPrivate::goToPreviousEditingPosition()
435{
436 auto c = doc()->lastEditingPosition(nextOrPrevious: KTextEditor::DocumentPrivate::Previous, cursorPosition());
437 if (c.isValid()) {
438 setCursorPosition(c);
439 }
440}
441
442void KTextEditor::ViewPrivate::goToNextEditingPosition()
443{
444 auto c = doc()->lastEditingPosition(nextOrPrevious: KTextEditor::DocumentPrivate::Next, cursorPosition());
445 if (c.isValid()) {
446 setCursorPosition(c);
447 }
448}
449void KTextEditor::ViewPrivate::setupActions()
450{
451 KActionCollection *ac = actionCollection();
452 QAction *a;
453
454 m_toggleWriteLock = nullptr;
455
456 m_cut = a = ac->addAction(actionType: KStandardActions::Cut, receiver: this, slot: &KTextEditor::ViewPrivate::cut);
457 a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard"));
458
459 m_paste = a = ac->addAction(actionType: KStandardActions::Paste, receiver: this, slot: [this] {
460 paste();
461 });
462 a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents"));
463
464 m_copy = a = ac->addAction(actionType: KStandardActions::Copy, receiver: this, slot: &KTextEditor::ViewPrivate::copy);
465 a->setWhatsThis(i18n("Use this command to copy the currently selected text to the system clipboard."));
466
467 m_clipboardHistory = a = ac->addAction(QStringLiteral("clipboard_history_paste"), receiver: this, slot: [this] {
468 ClipboardHistoryDialog chd(mainWindow()->window(), this);
469 chd.openDialog(clipboardHistory: KTextEditor::EditorPrivate::self()->clipboardHistory());
470 });
471 a->setText(i18n("Clipboard &History Paste"));
472 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::ALT | Qt::Key_V));
473
474 if (QApplication::clipboard()->supportsSelection()) {
475 m_pasteSelection = a = ac->addAction(QStringLiteral("edit_paste_selection"), receiver: this, slot: &KTextEditor::ViewPrivate::pasteSelection);
476 a->setText(i18n("Paste Selection"));
477 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::pasteSelection());
478 a->setWhatsThis(i18n("Paste previously mouse selection contents"));
479 }
480
481 a = ac->addAction(QStringLiteral("edit_paste_from_file"), receiver: this, slot: &KTextEditor::ViewPrivate::pasteFromFile);
482 a->setText(i18n("Paste From File"));
483
484 a = ac->addAction(QStringLiteral("edit_copy_file_location"), receiver: this, slot: &KTextEditor::ViewPrivate::copyFileLocation);
485 a->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
486 a->setText(i18n("Copy File Location"));
487 a->setWhatsThis(i18n("Copy the current file name and line number"));
488
489 m_swapWithClipboard = a = ac->addAction(QStringLiteral("edit_swap_with_clipboard"), receiver: this, slot: &KTextEditor::ViewPrivate::swapWithClipboard);
490 a->setText(i18n("Swap with Clipboard Contents"));
491 a->setWhatsThis(i18n("Swap the selected text with the clipboard contents"));
492
493 m_screenshotSelection = a = ac->addAction(QStringLiteral("text_screenshot_selection"), receiver: this, slot: &KTextEditor::ViewPrivate::screenshot);
494 a->setText(i18n("Take Screenshot of Selection"));
495
496 if (!doc()->readOnly()) {
497 a = ac->addAction(actionType: KStandardActions::Save, receiver: m_doc, slot: &KTextEditor::DocumentPrivate::documentSave);
498 a->setWhatsThis(i18n("Save the current document"));
499
500 a = m_editUndo = ac->addAction(actionType: KStandardActions::Undo, receiver: m_doc, slot: &KTextEditor::DocumentPrivate::undo);
501 a->setWhatsThis(i18n("Revert the most recent editing actions"));
502
503 a = m_editRedo = ac->addAction(actionType: KStandardActions::Redo, receiver: m_doc, slot: &KTextEditor::DocumentPrivate::redo);
504 a->setWhatsThis(i18n("Revert the most recent undo operation"));
505
506 // Tools > Scripts
507 // stored inside scoped pointer to ensure we destruct it early enough as it does internal cleanups of other child objects
508 m_scriptActionMenu.reset(p: new KateScriptActionMenu(this, i18n("&Scripts")));
509 ac->addAction(QStringLiteral("tools_scripts"), action: m_scriptActionMenu.get());
510
511 a = ac->addAction(QStringLiteral("tools_apply_wordwrap"));
512 a->setText(i18n("Apply &Word Wrap"));
513 a->setWhatsThis(
514 i18n("Use this to wrap the current line, or to reformat the selected lines as paragraph, "
515 "to fit the 'Wrap words at' setting in the configuration dialog.<br /><br />"
516 "This is a static word wrap, meaning the document is changed."));
517 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::applyWordWrap);
518
519 a = ac->addAction(QStringLiteral("tools_cleanIndent"));
520 a->setText(i18n("&Clean Indentation"));
521 a->setWhatsThis(
522 i18n("Use this to clean the indentation of a selected block of text (only tabs/only spaces).<br /><br />"
523 "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog."));
524 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::cleanIndent);
525
526 a = ac->addAction(QStringLiteral("tools_convertTabsSpaces"));
527 a->setText(i18n("Convert Indentation to Spaces"));
528 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this] {
529 doc()->config()->setReplaceTabsDyn(true);
530 doc()->indent(range: doc()->documentRange(), change: 0);
531 });
532
533 a = ac->addAction(QStringLiteral("tools_convertSpacesTabs"));
534 a->setText(i18n("Convert Indentation to Tabs"));
535 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this] {
536 auto config = doc()->config();
537 if (config->replaceTabsDyn()) {
538 config->configStart();
539 config->setReplaceTabsDyn(false);
540 config->setTabWidth(config->indentationWidth());
541 config->configEnd();
542 } else {
543 config->setTabWidth(config->indentationWidth());
544 }
545 doc()->indent(range: doc()->documentRange(), change: 0);
546 });
547
548 a = ac->addAction(QStringLiteral("tools_formatIndent"));
549 a->setText(i18n("&Format Indentation"));
550 a->setWhatsThis(i18n("Use this to auto indent the current line or block of text to its proper indent level."));
551 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::formatIndent);
552
553 a = ac->addAction(QStringLiteral("tools_alignOn"));
554 a->setText(i18nc("@action", "&Align On…"));
555 a->setWhatsThis(
556 i18n("This command aligns lines in the selected block or whole document on the column given by a regular expression "
557 "that you will be prompted for.<br /><br />"
558 "If you give an empty pattern it will align on the first non-blank character by default.<br />"
559 "If the pattern has a capture it will indent on the captured match.<br /><br />"
560 "<i>Examples</i>:<br />"
561 "With '-' it will insert spaces before the first '-' of each lines to align them all on the same column.<br />"
562 "With ':\\s+(.)' it will insert spaces before the first non-blank character that occurs after a colon to align "
563 "them all on the same column."));
564 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::alignOn);
565
566 a = ac->addAction(QStringLiteral("tools_comment"));
567 a->setText(i18n("C&omment"));
568 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_D));
569 a->setWhatsThis(
570 i18n("This command comments out the current line or a selected block of text.<br /><br />"
571 "The characters for single/multiple line comments are defined within the language's highlighting."));
572 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::comment);
573
574 a = ac->addAction(QStringLiteral("Previous Editing Line"));
575 a->setText(i18n("Go to Previous Editing Location"));
576 a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left")));
577 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_E));
578 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::goToPreviousEditingPosition);
579
580 a = ac->addAction(QStringLiteral("Next Editing Line"));
581 a->setText(i18n("Go to Next Editing Location"));
582 a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
583 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_E));
584 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::goToNextEditingPosition);
585
586 a = ac->addAction(QStringLiteral("tools_uncomment"));
587 a->setText(i18n("Unco&mment"));
588 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_D));
589 a->setWhatsThis(
590 i18n("This command removes comments from the current line or a selected block of text.<br /><br />"
591 "The characters for single/multiple line comments are defined within the language's highlighting."));
592 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::uncomment);
593
594 a = ac->addAction(QStringLiteral("tools_toggle_comment"));
595 a->setText(i18n("Toggle Comment"));
596 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_Slash));
597 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleComment);
598
599 a = m_toggleWriteLock = new KToggleAction(i18n("&Read Only Mode"), this);
600 a->setWhatsThis(i18n("Lock/unlock the document for writing"));
601 a->setChecked(!doc()->isReadWrite());
602 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleWriteLock);
603 ac->addAction(QStringLiteral("tools_toggle_write_lock"), action: a);
604
605 a = m_forceRTLDirection = new KToggleAction(i18n("&Force RTL Direction"), this);
606 a->setWhatsThis(i18n("Force RTL Text Direction"));
607 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this](bool checked) {
608 m_forceRTL = checked;
609 tagAll();
610 updateView(changed: true);
611 });
612 ac->addAction(QStringLiteral("force_rtl_direction"), action: a);
613
614 a = ac->addAction(QStringLiteral("tools_uppercase"));
615 a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-uppercase")));
616 a->setText(i18n("Uppercase"));
617 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_U));
618 a->setWhatsThis(
619 i18n("Convert the selection to uppercase, or the character to the "
620 "right of the cursor if no text is selected."));
621 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::uppercase);
622
623 a = ac->addAction(QStringLiteral("tools_lowercase"));
624 a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-lowercase")));
625 a->setText(i18n("Lowercase"));
626 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_U));
627 a->setWhatsThis(
628 i18n("Convert the selection to lowercase, or the character to the "
629 "right of the cursor if no text is selected."));
630 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::lowercase);
631
632 a = ac->addAction(QStringLiteral("tools_capitalize"));
633 a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-capitalize")));
634 a->setText(i18n("Capitalize"));
635 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_U));
636 a->setWhatsThis(
637 i18n("Capitalize the selection, or the word under the "
638 "cursor if no text is selected."));
639 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::capitalize);
640
641 a = ac->addAction(QStringLiteral("tools_join_lines"));
642 a->setText(i18n("Join Lines"));
643 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_J));
644 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::joinLines);
645
646 a = ac->addAction(QStringLiteral("tools_invoke_code_completion"));
647 a->setText(i18n("Invoke Code Completion"));
648 a->setWhatsThis(i18n("Manually invoke command completion, usually by using a shortcut bound to this action."));
649 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_Space));
650 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::userInvokedCompletion);
651
652 a = ac->addAction(QStringLiteral("remove_trailing_spaces"));
653 a->setText(i18n("Remove Trailing Spaces"));
654 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this] {
655 doc()->removeAllTrailingSpaces();
656 });
657 } else {
658 for (auto *action : {m_cut, m_paste, m_clipboardHistory, m_swapWithClipboard}) {
659 action->setEnabled(false);
660 }
661
662 if (m_pasteSelection) {
663 m_pasteSelection->setEnabled(false);
664 }
665
666 m_editUndo = nullptr;
667 m_editRedo = nullptr;
668 }
669
670 a = ac->addAction(actionType: KStandardActions::Print, receiver: this, slot: &KTextEditor::ViewPrivate::print);
671 a->setWhatsThis(i18n("Print the current document."));
672
673 a = ac->addAction(actionType: KStandardActions::PrintPreview, receiver: this, slot: &KTextEditor::ViewPrivate::printPreview);
674 a->setWhatsThis(i18n("Show print preview of current document"));
675
676 a = ac->addAction(QStringLiteral("file_reload"));
677 a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
678 a->setText(i18n("Reloa&d"));
679 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::reload());
680 a->setWhatsThis(i18n("Reload the current document from disk."));
681 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::reloadFile);
682
683 a = ac->addAction(actionType: KStandardActions::SaveAs, receiver: m_doc, slot: &KTextEditor::DocumentPrivate::documentSaveAs);
684 a->setWhatsThis(i18n("Save the current document to disk, with a name of your choice."));
685
686 a = new KateViewEncodingAction(m_doc, this, i18nc("@action", "Save As with Encodin&g…"), this, true /* special mode for save as */);
687 a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as")));
688 ac->addAction(QStringLiteral("file_save_as_with_encoding"), action: a);
689
690 a = ac->addAction(QStringLiteral("file_save_copy_as"));
691 a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as")));
692 a->setText(i18nc("@action", "Save Cop&y As…"));
693 a->setWhatsThis(i18n("Save a copy of the current document to disk."));
694 connect(sender: a, signal: &QAction::triggered, context: m_doc, slot: &KTextEditor::DocumentPrivate::documentSaveCopyAs);
695
696 a = ac->addAction(actionType: KStandardActions::GotoLine, receiver: this, slot: &KTextEditor::ViewPrivate::gotoLine);
697 a->setWhatsThis(i18n("This command opens a dialog and lets you choose a line that you want the cursor to move to."));
698
699 a = ac->addAction(QStringLiteral("modified_line_up"));
700 a->setIcon(QIcon::fromTheme(QStringLiteral("go-previous")));
701 a->setText(i18n("Go to Previous Modified Line"));
702 a->setWhatsThis(i18n("Move upwards to the previous modified line."));
703 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toPrevModifiedLine);
704
705 a = ac->addAction(QStringLiteral("modified_line_down"));
706 a->setIcon(QIcon::fromTheme(QStringLiteral("go-next")));
707 a->setText(i18n("Go to Next Modified Line"));
708 a->setWhatsThis(i18n("Move downwards to the next modified line."));
709 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toNextModifiedLine);
710
711 a = ac->addAction(QStringLiteral("set_confdlg"));
712 a->setText(i18nc("@action", "&Configure Editor…"));
713 a->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other")));
714 a->setWhatsThis(i18n("Configure various aspects of this editor."));
715 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::slotConfigDialog);
716
717 m_modeAction = new KateModeMenu(i18n("&Mode"), this);
718 ac->addAction(QStringLiteral("tools_mode"), action: m_modeAction);
719 m_modeAction->setWhatsThis(i18n(
720 "Here you can choose which mode should be used for the current document. This will influence the highlighting and folding being used, for example."));
721 m_modeAction->updateMenu(doc: m_doc);
722
723 KateHighlightingMenu *menu = new KateHighlightingMenu(i18n("&Highlighting"), this);
724 ac->addAction(QStringLiteral("tools_highlighting"), action: menu);
725 menu->setWhatsThis(i18n("Here you can choose how the current document should be highlighted."));
726 menu->updateMenu(doc: m_doc);
727
728 KateViewSchemaAction *schemaMenu = new KateViewSchemaAction(i18n("&Editor Color Theme"), this);
729 schemaMenu->setIcon(QIcon::fromTheme(QStringLiteral("kcolorchooser")));
730 ac->addAction(QStringLiteral("view_schemas"), action: schemaMenu);
731 schemaMenu->updateMenu(view: this);
732
733 // indentation menu
734 KateViewIndentationAction *indentMenu = new KateViewIndentationAction(m_doc, i18n("&Indentation"), this);
735 ac->addAction(QStringLiteral("tools_indentation"), action: indentMenu);
736
737 m_selectAll = ac->addAction(actionType: KStandardActions::SelectAll, receiver: this, slot: [this] {
738 selectAll();
739 qApp->clipboard()->setText(selectionText(), mode: QClipboard::Selection);
740 });
741 a->setWhatsThis(i18n("Select the entire text of the current document."));
742
743 m_deSelect = a = ac->addAction(actionType: KStandardActions::Deselect, receiver: this, slot: qOverload<>(&KTextEditor::ViewPrivate::clearSelection));
744 a->setWhatsThis(i18n("If you have selected something within the current document, this will no longer be selected."));
745
746 a = ac->addAction(QStringLiteral("view_inc_font_sizes"));
747 a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in")));
748 a->setText(i18n("Enlarge Font"));
749 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::zoomIn());
750 a->setWhatsThis(i18n("This increases the display font size."));
751 connect(sender: a, signal: &QAction::triggered, context: m_viewInternal, slot: [this]() {
752 m_viewInternal->slotIncFontSizes();
753 });
754
755 a = ac->addAction(QStringLiteral("view_dec_font_sizes"));
756 a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out")));
757 a->setText(i18n("Shrink Font"));
758 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::zoomOut());
759 a->setWhatsThis(i18n("This decreases the display font size."));
760 connect(sender: a, signal: &QAction::triggered, context: m_viewInternal, slot: [this]() {
761 m_viewInternal->slotDecFontSizes();
762 });
763
764 a = ac->addAction(QStringLiteral("view_reset_font_sizes"));
765 a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original")));
766 a->setText(i18n("Reset Font Size"));
767 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::shortcut(id: KStandardShortcut::ActualSize));
768 a->setWhatsThis(i18n("This resets the display font size."));
769 connect(sender: a, signal: &QAction::triggered, context: m_viewInternal, slot: &KateViewInternal::slotResetFontSizes);
770
771 a = m_toggleBlockSelection = new KToggleAction(i18n("Bl&ock Selection Mode"), this);
772 ac->addAction(QStringLiteral("set_verticalSelect"), action: a);
773 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_B));
774 a->setWhatsThis(i18n("This command allows switching between the normal (line based) selection mode and the block selection mode."));
775 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleBlockSelection);
776
777 a = ac->addAction(QStringLiteral("switch_next_input_mode"));
778 a->setText(i18n("Switch to Next Input Mode"));
779 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_V));
780 a->setWhatsThis(i18n("Switch to the next input mode."));
781 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::cycleInputMode);
782
783 a = m_toggleInsert = new KToggleAction(i18n("Overwr&ite Mode"), this);
784 ac->addAction(QStringLiteral("set_insert"), action: a);
785 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::Key_Insert));
786 a->setWhatsThis(i18n("Choose whether you want the text you type to be inserted or to overwrite existing text."));
787 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleInsert);
788
789 KToggleAction *toggleAction;
790 a = m_toggleShowSpace = toggleAction = new KToggleAction(i18n("Show Whitespace"), this);
791 ac->addAction(QStringLiteral("view_show_whitespaces"), action: a);
792 a->setWhatsThis(
793 i18n("If this option is checked, whitespaces in this document will be visible.<br /><br />"
794 "This is only a view option, meaning the document will not be changed."));
795 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleShowSpaces);
796
797 a = m_toggleDynWrap = toggleAction = new KToggleAction(i18n("&Dynamic Word Wrap"), this);
798 a->setIcon(QIcon::fromTheme(QStringLiteral("text-wrap")));
799 ac->addAction(QStringLiteral("view_dynamic_word_wrap"), action: a);
800 a->setWhatsThis(
801 i18n("If this option is checked, the text lines will be wrapped at the view border on the screen.<br /><br />"
802 "This is only a view option, meaning the document will not be changed."));
803 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleDynWordWrap);
804
805 a = m_setDynWrapIndicators = new KSelectAction(i18n("Dynamic Word Wrap Indicators"), this);
806 ac->addAction(QStringLiteral("dynamic_word_wrap_indicators"), action: a);
807 a->setWhatsThis(i18n("Choose when the Dynamic Word Wrap Indicators should be displayed"));
808 connect(sender: m_setDynWrapIndicators, signal: &KSelectAction::indexTriggered, context: this, slot: &KTextEditor::ViewPrivate::setDynWrapIndicators);
809 const QStringList list2{i18n("&Off"), i18n("Follow &Line Numbers"), i18n("&Always On")};
810 m_setDynWrapIndicators->setItems(list2);
811 m_setDynWrapIndicators->setEnabled(m_toggleDynWrap->isChecked()); // only synced on real change, later
812
813 a = toggleAction = new KToggleAction(i18n("Static Word Wrap"), this);
814 ac->addAction(QStringLiteral("view_static_word_wrap"), action: a);
815 a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the column defined in the editing properties."));
816 connect(sender: a, signal: &KToggleAction::triggered, slot: [this] {
817 if (m_doc) {
818 m_doc->setWordWrap(!m_doc->wordWrap());
819 }
820 });
821
822 a = toggleAction = m_toggleWWMarker = new KToggleAction(i18n("Show Static &Word Wrap Marker"), this);
823 ac->addAction(QStringLiteral("view_word_wrap_marker"), action: a);
824 a->setWhatsThis(
825 i18n("Show/hide the Word Wrap Marker, a vertical line drawn at the word "
826 "wrap column as defined in the editing properties"));
827 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleWWMarker);
828
829 a = toggleAction = m_toggleFoldingMarkers = new KToggleAction(i18n("Show Folding &Markers"), this);
830 ac->addAction(QStringLiteral("view_folding_markers"), action: a);
831 a->setWhatsThis(i18n("You can choose if the codefolding marks should be shown, if codefolding is possible."));
832 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleFoldingMarkers);
833
834 a = m_toggleIconBar = toggleAction = new KToggleAction(i18n("Show &Icon Border"), this);
835 ac->addAction(QStringLiteral("view_border"), action: a);
836 a->setWhatsThis(i18n("Show/hide the icon border.<br /><br />The icon border shows bookmark symbols, for instance."));
837 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleIconBorder);
838
839 a = toggleAction = m_toggleLineNumbers = new KToggleAction(i18n("Show &Line Numbers"), this);
840 ac->addAction(QStringLiteral("view_line_numbers"), action: a);
841 a->setWhatsThis(i18n("Show/hide the line numbers on the left hand side of the view."));
842 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleLineNumbersOn);
843
844 a = m_toggleScrollBarMarks = toggleAction = new KToggleAction(i18n("Show Scroll&bar Marks"), this);
845 ac->addAction(QStringLiteral("view_scrollbar_marks"), action: a);
846 a->setWhatsThis(i18n("Show/hide the marks on the vertical scrollbar.<br /><br />The marks show bookmarks, for instance."));
847 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleScrollBarMarks);
848
849 a = m_toggleScrollBarMiniMap = toggleAction = new KToggleAction(i18n("Show Scrollbar Mini-Map"), this);
850 ac->addAction(QStringLiteral("view_scrollbar_minimap"), action: a);
851 a->setWhatsThis(i18n("Show/hide the mini-map on the vertical scrollbar.<br /><br />The mini-map shows an overview of the whole document."));
852 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleScrollBarMiniMap);
853
854 a = m_doc->autoReloadToggleAction();
855 ac->addAction(QStringLiteral("view_auto_reload"), action: a);
856
857 // a = m_toggleScrollBarMiniMapAll = toggleAction = new KToggleAction(i18n("Show the whole document in the Mini-Map"), this);
858 // ac->addAction(QLatin1String("view_scrollbar_minimap_all"), a);
859 // 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
860 // mini-map.")); connect(a, SIGNAL(triggered(bool)), SlOT(toggleScrollBarMiniMapAll())); connect(m_toggleScrollBarMiniMap, SIGNAL(triggered(bool)),
861 // m_toggleScrollBarMiniMapAll, SLOT(setEnabled(bool)));
862
863 a = m_toggleNPSpaces = new KToggleAction(i18n("Show Non-Printable Spaces"), this);
864 ac->addAction(QStringLiteral("view_non_printable_spaces"), action: a);
865 a->setWhatsThis(i18n("Show/hide bounding box around non-printable spaces"));
866 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleNPSpaces);
867
868 a = m_switchCmdLine = ac->addAction(QStringLiteral("switch_to_cmd_line"));
869 a->setText(i18n("Switch to Command Line"));
870 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::Key_F7));
871 a->setWhatsThis(i18n("Show/hide the command line on the bottom of the view."));
872 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::switchToCmdLine);
873
874 KActionMenu *am = new KActionMenu(i18n("Input Modes"), this);
875 m_inputModeActions = new QActionGroup(am);
876 ac->addAction(QStringLiteral("view_input_modes"), action: am);
877 auto switchInputModeAction = ac->action(QStringLiteral("switch_next_input_mode"));
878 am->addAction(action: switchInputModeAction);
879 am->addSeparator();
880 for (const auto &mode : m_viewInternal->m_inputModes) {
881 a = new QAction(mode->viewInputModeHuman(), m_inputModeActions);
882 am->addAction(action: a);
883 a->setWhatsThis(i18n("Activate/deactivate %1", mode->viewInputModeHuman()));
884 const InputMode im = mode->viewInputMode();
885 a->setData(static_cast<int>(im));
886 a->setCheckable(true);
887 if (im == m_config->inputMode()) {
888 a->setChecked(true);
889 }
890 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleInputMode);
891 }
892
893 a = m_setEndOfLine = new KSelectAction(i18n("&End of Line"), this);
894 ac->addAction(QStringLiteral("set_eol"), action: a);
895 a->setWhatsThis(i18n("Choose which line endings should be used, when you save the document"));
896 const QStringList list{i18nc("@item:inmenu End of Line", "&UNIX"),
897 i18nc("@item:inmenu End of Line", "&Windows/DOS"),
898 i18nc("@item:inmenu End of Line", "&Macintosh")};
899 m_setEndOfLine->setItems(list);
900 m_setEndOfLine->setCurrentItem(doc()->config()->eol());
901 connect(sender: m_setEndOfLine, signal: &KSelectAction::indexTriggered, context: this, slot: &KTextEditor::ViewPrivate::setEol);
902
903 a = m_addBom = new KToggleAction(i18n("Add &Byte Order Mark (BOM)"), this);
904 m_addBom->setChecked(doc()->config()->bom());
905 ac->addAction(QStringLiteral("add_bom"), action: a);
906 a->setWhatsThis(i18n("Enable/disable adding of byte order marks for UTF-8/UTF-16 encoded files while saving"));
907 connect(sender: m_addBom, signal: &KToggleAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::setAddBom);
908
909 // encoding menu
910 m_encodingAction = new KateViewEncodingAction(m_doc, this, i18n("E&ncoding"), this);
911 ac->addAction(QStringLiteral("set_encoding"), action: m_encodingAction);
912
913 a = ac->addAction(actionType: KStandardActions::Find, receiver: this, slot: &KTextEditor::ViewPrivate::find);
914 a->setWhatsThis(i18n("Look up the first occurrence of a piece of text or regular expression."));
915 addAction(action: a);
916
917 a = ac->addAction(QStringLiteral("edit_find_selected"));
918 a->setText(i18n("Find Selected"));
919 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_H));
920 a->setWhatsThis(i18n("Finds next occurrence of selected text."));
921 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::findSelectedForwards);
922
923 a = ac->addAction(QStringLiteral("edit_find_selected_backwards"));
924 a->setText(i18n("Find Selected Backwards"));
925 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_H));
926 a->setWhatsThis(i18n("Finds previous occurrence of selected text."));
927 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::findSelectedBackwards);
928
929 a = ac->addAction(QStringLiteral("edit_find_multicursor_next_occurrence"));
930 a->setText(i18n("Find and Select Next Occurrence"));
931 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::ALT | Qt::Key_J));
932 a->setWhatsThis(i18n("Finds next occurrence of the word under cursor and add it to selection."));
933 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::findNextOccurunceAndSelect);
934
935 a = ac->addAction(QStringLiteral("edit_skip_multicursor_current_occurrence"));
936 a->setText(i18n("Mark Currently Selected Occurrence as Skipped"));
937 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::ALT | Qt::Key_K));
938 a->setWhatsThis(i18n("Marks the currently selected word as skipped."));
939 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::skipCurrentOccurunceSelection);
940
941 a = ac->addAction(QStringLiteral("edit_find_multicursor_all_occurrences"));
942 a->setText(i18n("Find and Select All Occurrences"));
943 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::ALT | Qt::SHIFT | Qt::CTRL | Qt::Key_J));
944 a->setWhatsThis(i18n("Finds all occurrences of the word under cursor and selects them."));
945 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::findAllOccuruncesAndSelect);
946
947 a = ac->addAction(actionType: KStandardActions::FindNext, receiver: this, slot: &KTextEditor::ViewPrivate::findNext);
948 a->setWhatsThis(i18n("Look up the next occurrence of the search phrase."));
949 addAction(action: a);
950
951 a = ac->addAction(actionType: KStandardActions::FindPrev, QStringLiteral("edit_find_prev"), receiver: this, slot: &KTextEditor::ViewPrivate::findPrevious);
952 a->setWhatsThis(i18n("Look up the previous occurrence of the search phrase."));
953 addAction(action: a);
954
955 a = ac->addAction(actionType: KStandardActions::Replace, receiver: this, slot: &KTextEditor::ViewPrivate::replace);
956 a->setWhatsThis(i18n("Look up a piece of text or regular expression and replace the result with some given text."));
957
958 a = ac->addAction(QStringLiteral("edit_create_multi_cursor_from_sel"));
959 a->setText(i18n("Add Cursors to Line Ends"));
960 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_I));
961 a->setWhatsThis(i18n("Creates a cursor at the end of every line in selection."));
962 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::createMultiCursorsFromSelection);
963
964 a = ac->addAction(QStringLiteral("edit_create_multi_cursor_down"));
965 a->setText(i18n("Add Caret below Cursor"));
966 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Down));
967 a->setWhatsThis(i18n("Adds a caret in the line below the current caret."));
968 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::addSecondaryCursorDown);
969
970 a = ac->addAction(QStringLiteral("edit_create_multi_cursor_up"));
971 a->setText(i18n("Add Caret above Cursor"));
972 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Up));
973 a->setWhatsThis(i18n("Adds a caret in the line above the current caret."));
974 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::addSecondaryCursorUp);
975
976 a = ac->addAction(QStringLiteral("edit_toggle_camel_case_cursor"));
977 a->setText(i18n("Toggle Camel Case Cursor Movement"));
978 a->setWhatsThis(i18n("Toggle between normal word movement and camel case cursor movement."));
979 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleCamelCaseCursor);
980
981 a = ac->addAction(QStringLiteral("edit_remove_cursors_from_empty_lines"));
982 a->setText(i18n("Remove Cursors from Empty Lines"));
983 a->setWhatsThis(i18n("Remove cursors from empty lines"));
984 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::removeCursorsFromEmptyLines);
985
986 m_spell->createActions(ac);
987 m_toggleOnTheFlySpellCheck = new KToggleAction(i18n("Automatic Spell Checking"), this);
988 m_toggleOnTheFlySpellCheck->setWhatsThis(i18n("Enable/disable automatic spell checking"));
989 connect(sender: m_toggleOnTheFlySpellCheck, signal: &KToggleAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck);
990 ac->addAction(QStringLiteral("tools_toggle_automatic_spell_checking"), action: m_toggleOnTheFlySpellCheck);
991 ac->setDefaultShortcut(action: m_toggleOnTheFlySpellCheck, shortcut: QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_O));
992
993 a = ac->addAction(QStringLiteral("tools_change_dictionary"));
994 a->setText(i18nc("@action", "Change Dictionary…"));
995 a->setWhatsThis(i18n("Change the dictionary that is used for spell checking."));
996 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::changeDictionary);
997
998 a = ac->addAction(QStringLiteral("tools_clear_dictionary_ranges"));
999 a->setText(i18n("Clear Dictionary Ranges"));
1000 a->setEnabled(false);
1001 a->setWhatsThis(i18n("Remove all the separate dictionary ranges that were set for spell checking."));
1002 connect(sender: a, signal: &QAction::triggered, context: m_doc, slot: &KTextEditor::DocumentPrivate::clearDictionaryRanges);
1003 connect(sender: m_doc, signal: &KTextEditor::DocumentPrivate::dictionaryRangesPresent, context: a, slot: &QAction::setEnabled);
1004
1005 m_copyHtmlAction = ac->addAction(QStringLiteral("edit_copy_html"), receiver: this, SLOT(exportHtmlToClipboard()));
1006 m_copyHtmlAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
1007 m_copyHtmlAction->setText(i18n("Copy as &HTML"));
1008 m_copyHtmlAction->setWhatsThis(i18n("Copy the selection as HTML, formatted using the current syntax highlighting and color scheme settings."));
1009
1010 a = ac->addAction(QStringLiteral("file_export_html"), receiver: this, SLOT(exportHtmlToFile()));
1011 a->setIcon(QIcon::fromTheme(QStringLiteral("document-export")));
1012 a->setText(i18nc("@action", "E&xport as HTML…"));
1013 a->setWhatsThis(
1014 i18n("This command allows you to export the current document"
1015 " with all highlighting information into a HTML document."));
1016
1017 m_spellingMenu->createActions(ac);
1018
1019 m_bookmarks->createActions(ac);
1020
1021 slotSelectionChanged();
1022
1023 // Now setup the editing actions before adding the associated
1024 // widget and setting the shortcut context
1025 setupEditActions();
1026 setupCodeFolding();
1027 setupSpeechActions();
1028
1029 ac->addAssociatedWidget(widget: m_viewInternal);
1030
1031 const auto actions = ac->actions();
1032 for (QAction *action : actions) {
1033 action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
1034 }
1035
1036 connect(sender: this, signal: &KTextEditor::ViewPrivate::selectionChanged, context: this, slot: &KTextEditor::ViewPrivate::slotSelectionChanged);
1037}
1038
1039void KTextEditor::ViewPrivate::slotConfigDialog()
1040{
1041 // invoke config dialog, will auto-save configuration to katepartrc
1042 KTextEditor::EditorPrivate::self()->configDialog(parent: this);
1043}
1044
1045void KTextEditor::ViewPrivate::setupEditActions()
1046{
1047 // If you add an editing action to this
1048 // function make sure to include the line
1049 // m_editActions << a after creating the action
1050 KActionCollection *ac = actionCollection();
1051
1052 QAction *a = ac->addAction(QStringLiteral("word_left"));
1053 a->setText(i18n("Move Word Left"));
1054 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::backwardWord());
1055 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::wordLeft);
1056 m_editActions.push_back(x: a);
1057
1058 a = ac->addAction(QStringLiteral("select_char_left"));
1059 a->setText(i18n("Select Character Left"));
1060 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::Key_Left));
1061 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftCursorLeft);
1062 m_editActions.push_back(x: a);
1063
1064 a = ac->addAction(QStringLiteral("select_word_left"));
1065 a->setText(i18n("Select Word Left"));
1066#ifdef Q_OS_MACOS
1067 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::ALT | Qt::Key_Left));
1068#else
1069 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Left));
1070#endif
1071 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftWordLeft);
1072 m_editActions.push_back(x: a);
1073
1074 a = ac->addAction(QStringLiteral("word_right"));
1075 a->setText(i18n("Move Word Right"));
1076 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::forwardWord());
1077 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::wordRight);
1078 m_editActions.push_back(x: a);
1079
1080 a = ac->addAction(QStringLiteral("select_char_right"));
1081 a->setText(i18n("Select Character Right"));
1082 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::Key_Right));
1083 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftCursorRight);
1084 m_editActions.push_back(x: a);
1085
1086 a = ac->addAction(QStringLiteral("select_word_right"));
1087 a->setText(i18n("Select Word Right"));
1088#ifdef Q_OS_MACOS
1089 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::ALT | Qt::Key_Right));
1090#else
1091 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Right));
1092#endif
1093 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftWordRight);
1094 m_editActions.push_back(x: a);
1095
1096 a = ac->addAction(QStringLiteral("mark_selection"));
1097 a->setText(i18n("Start the Marked Selection"));
1098 a->setWhatsThis(i18n("Emulate the Emacs-like selection mode, where the beginning is marked and then the selection is continuously updated."));
1099 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::markSelection);
1100 m_editActions.push_back(x: a);
1101
1102 a = ac->addAction(QStringLiteral("beginning_of_line"));
1103 a->setText(i18n("Move to Beginning of Line"));
1104 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::beginningOfLine());
1105 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::home);
1106 m_editActions.push_back(x: a);
1107
1108 a = ac->addAction(QStringLiteral("beginning_of_document"));
1109 a->setText(i18n("Move to Beginning of Document"));
1110 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::begin());
1111 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::top);
1112 m_editActions.push_back(x: a);
1113
1114 a = ac->addAction(QStringLiteral("select_beginning_of_line"));
1115 a->setText(i18n("Select to Beginning of Line"));
1116#ifdef Q_OS_MACOS
1117 ac->setDefaultShortcuts(a, {QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Left), QKeySequence(Qt::SHIFT | Qt::Key_Home)});
1118#else
1119 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::Key_Home));
1120#endif
1121 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftHome);
1122 m_editActions.push_back(x: a);
1123
1124 a = ac->addAction(QStringLiteral("select_beginning_of_document"));
1125 a->setText(i18n("Select to Beginning of Document"));
1126#ifdef Q_OS_MACOS
1127 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Up));
1128#else
1129 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Home));
1130#endif
1131 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftTop);
1132 m_editActions.push_back(x: a);
1133
1134 a = ac->addAction(QStringLiteral("end_of_line"));
1135 a->setText(i18n("Move to End of Line"));
1136 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::endOfLine());
1137 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::end);
1138 m_editActions.push_back(x: a);
1139
1140 a = ac->addAction(QStringLiteral("end_of_document"));
1141 a->setText(i18n("Move to End of Document"));
1142 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::end());
1143 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::bottom);
1144 m_editActions.push_back(x: a);
1145
1146 a = ac->addAction(QStringLiteral("select_end_of_line"));
1147 a->setText(i18n("Select to End of Line"));
1148#ifdef Q_OS_MACOS
1149 ac->setDefaultShortcuts(a, {QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Right), QKeySequence(Qt::SHIFT | Qt::Key_End)});
1150#else
1151 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::Key_End));
1152#endif
1153 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftEnd);
1154 m_editActions.push_back(x: a);
1155
1156 a = ac->addAction(QStringLiteral("select_end_of_document"));
1157 a->setText(i18n("Select to End of Document"));
1158#ifdef Q_OS_MACOS
1159 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Down));
1160#else
1161 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_End));
1162#endif
1163 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftBottom);
1164 m_editActions.push_back(x: a);
1165
1166 a = ac->addAction(QStringLiteral("select_line_up"));
1167 a->setText(i18n("Select to Previous Line"));
1168 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::Key_Up));
1169 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftUp);
1170 m_editActions.push_back(x: a);
1171
1172 a = ac->addAction(QStringLiteral("scroll_line_up"));
1173 a->setText(i18n("Scroll Line Up"));
1174#ifndef Q_OS_MACOS
1175 // this shortcut is used for moving to beginning of doc on mac
1176 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_Up));
1177#endif
1178 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::scrollUp);
1179 m_editActions.push_back(x: a);
1180
1181 a = ac->addAction(QStringLiteral("move_line_down"));
1182 a->setText(i18n("Move to Next Line"));
1183 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::Key_Down));
1184 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::down);
1185 m_editActions.push_back(x: a);
1186
1187 a = ac->addAction(QStringLiteral("move_line_up"));
1188 a->setText(i18n("Move to Previous Line"));
1189 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::Key_Up));
1190 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::up);
1191 m_editActions.push_back(x: a);
1192
1193 a = ac->addAction(QStringLiteral("move_cursor_right"));
1194 a->setText(i18n("Move Cursor Right"));
1195 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::Key_Right));
1196 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::cursorRight);
1197 m_editActions.push_back(x: a);
1198
1199 a = ac->addAction(QStringLiteral("move_cursor_left"));
1200 a->setText(i18n("Move Cursor Left"));
1201 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::Key_Left));
1202 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::cursorLeft);
1203 m_editActions.push_back(x: a);
1204
1205 a = ac->addAction(QStringLiteral("select_line_down"));
1206 a->setText(i18n("Select to Next Line"));
1207 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::Key_Down));
1208 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftDown);
1209 m_editActions.push_back(x: a);
1210
1211 a = ac->addAction(QStringLiteral("scroll_line_down"));
1212 a->setText(i18n("Scroll Line Down"));
1213#ifndef Q_OS_MACOS
1214 // this shortcut is used for moving to end of doc
1215 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_Down));
1216#endif
1217 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::scrollDown);
1218 m_editActions.push_back(x: a);
1219
1220 a = ac->addAction(QStringLiteral("scroll_page_up"));
1221 a->setText(i18n("Scroll Page Up"));
1222 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::prior());
1223 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::pageUp);
1224 m_editActions.push_back(x: a);
1225
1226 a = ac->addAction(QStringLiteral("select_page_up"));
1227 a->setText(i18n("Select Page Up"));
1228 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::Key_PageUp));
1229 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftPageUp);
1230 m_editActions.push_back(x: a);
1231
1232 a = ac->addAction(QStringLiteral("move_top_of_view"));
1233 a->setText(i18n("Move to Top of View"));
1234 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::ALT | Qt::Key_Home));
1235 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::topOfView);
1236 m_editActions.push_back(x: a);
1237
1238 a = ac->addAction(QStringLiteral("select_top_of_view"));
1239 a->setText(i18n("Select to Top of View"));
1240 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_Home));
1241 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftTopOfView);
1242 m_editActions.push_back(x: a);
1243
1244 a = ac->addAction(QStringLiteral("scroll_page_down"));
1245 a->setText(i18n("Scroll Page Down"));
1246 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::next());
1247 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::pageDown);
1248 m_editActions.push_back(x: a);
1249
1250 a = ac->addAction(QStringLiteral("select_page_down"));
1251 a->setText(i18n("Select Page Down"));
1252 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::Key_PageDown));
1253 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftPageDown);
1254 m_editActions.push_back(x: a);
1255
1256 a = ac->addAction(QStringLiteral("move_bottom_of_view"));
1257 a->setText(i18n("Move to Bottom of View"));
1258 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::ALT | Qt::Key_End));
1259 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::bottomOfView);
1260 m_editActions.push_back(x: a);
1261
1262 a = ac->addAction(QStringLiteral("select_bottom_of_view"));
1263 a->setText(i18n("Select to Bottom of View"));
1264 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_End));
1265 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftBottomOfView);
1266 m_editActions.push_back(x: a);
1267
1268 a = ac->addAction(QStringLiteral("to_matching_bracket"));
1269 a->setText(i18n("Go to Matching Bracket"));
1270 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_6));
1271 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::toMatchingBracket);
1272 // m_editActions << a;
1273
1274 a = ac->addAction(QStringLiteral("select_matching_bracket"));
1275 a->setText(i18n("Select to Matching Bracket"));
1276 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_6));
1277 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::shiftToMatchingBracket);
1278 // m_editActions << a;
1279
1280 // BEGIN subword actions
1281 a = ac->addAction(QStringLiteral("subword_right"));
1282 a->setText(i18n("Move Sub-Word Right"));
1283 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this] {
1284 wordRight(/*subword=*/true);
1285 });
1286 m_editActions.push_back(x: a);
1287
1288 a = ac->addAction(QStringLiteral("select_subword_right"));
1289 a->setText(i18n("Select Sub-Word Right"));
1290 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this] {
1291 shiftWordRight(/*subword=*/true);
1292 });
1293 m_editActions.push_back(x: a);
1294
1295 a = ac->addAction(QStringLiteral("subword_left"));
1296 a->setText(i18n("Move Sub-Word Left"));
1297 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this] {
1298 wordLeft(/*subword=*/true);
1299 });
1300 m_editActions.push_back(x: a);
1301
1302 a = ac->addAction(QStringLiteral("select_subword_left"));
1303 a->setText(i18n("Select Sub-Word Left"));
1304 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this] {
1305 shiftWordLeft(/*subword=*/true);
1306 });
1307 m_editActions.push_back(x: a);
1308 // END subword actions
1309
1310 // anders: shortcuts doing any changes should not be created in read-only mode
1311 if (!doc()->readOnly()) {
1312 a = ac->addAction(QStringLiteral("transpose_char"));
1313 a->setText(i18n("Transpose Characters"));
1314 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::transpose);
1315 m_editActions.push_back(x: a);
1316
1317 a = ac->addAction(QStringLiteral("transpose_word"));
1318 a->setText(i18n("Transpose Words"));
1319 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::transposeWord);
1320 m_editActions.push_back(x: a);
1321
1322 a = ac->addAction(QStringLiteral("delete_line"));
1323 a->setText(i18n("Delete Line"));
1324 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_K));
1325 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::killLine);
1326 m_editActions.push_back(x: a);
1327
1328 a = ac->addAction(QStringLiteral("delete_word_left"));
1329 a->setText(i18n("Delete Word Left"));
1330 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::deleteWordBack());
1331 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::deleteWordLeft);
1332 m_editActions.push_back(x: a);
1333
1334 a = ac->addAction(QStringLiteral("delete_word_right"));
1335 a->setText(i18n("Delete Word Right"));
1336 ac->setDefaultShortcuts(action: a, shortcuts: KStandardShortcut::deleteWordForward());
1337 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::deleteWordRight);
1338 m_editActions.push_back(x: a);
1339
1340 a = ac->addAction(QStringLiteral("delete_next_character"));
1341 a->setText(i18n("Delete Next Character"));
1342 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::Key_Delete));
1343 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::keyDelete);
1344 m_editActions.push_back(x: a);
1345
1346 a = ac->addAction(QStringLiteral("backspace"));
1347 a->setText(i18n("Backspace"));
1348 QList<QKeySequence> scuts;
1349 scuts << QKeySequence(Qt::Key_Backspace) << QKeySequence(Qt::SHIFT | Qt::Key_Backspace);
1350 ac->setDefaultShortcuts(action: a, shortcuts: scuts);
1351 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::backspace);
1352 m_editActions.push_back(x: a);
1353
1354 a = ac->addAction(QStringLiteral("insert_tabulator"));
1355 a->setText(i18n("Insert Tab Character"));
1356 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::insertTab);
1357 m_editActions.push_back(x: a);
1358
1359 a = ac->addAction(QStringLiteral("smart_newline"));
1360 a->setText(i18n("Insert Smart Newline"));
1361 a->setWhatsThis(i18n("Insert newline including leading characters of the current line which are not letters or numbers."));
1362 scuts.clear();
1363 scuts << QKeySequence(Qt::SHIFT | Qt::Key_Return) << QKeySequence(Qt::SHIFT | Qt::Key_Enter);
1364 ac->setDefaultShortcuts(action: a, shortcuts: scuts);
1365 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::smartNewline);
1366 m_editActions.push_back(x: a);
1367
1368 a = ac->addAction(QStringLiteral("no_indent_newline"));
1369 a->setText(i18n("Insert a Non-Indented Newline"));
1370 a->setWhatsThis(i18n("Insert a new line without indentation, regardless of indentation settings."));
1371 scuts.clear();
1372 scuts << QKeySequence(Qt::CTRL | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::Key_Enter);
1373 ac->setDefaultShortcuts(action: a, shortcuts: scuts);
1374 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::noIndentNewline);
1375 m_editActions.push_back(x: a);
1376
1377 a = ac->addAction(QStringLiteral("newline_above"));
1378 a->setText(i18n("Insert a Newline Above Current Line"));
1379 a->setWhatsThis(i18n("Insert a new line above current line without modifying the current line."));
1380 scuts.clear();
1381 scuts << QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Enter);
1382 ac->setDefaultShortcuts(action: a, shortcuts: scuts);
1383 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::newLineAbove);
1384 m_editActions.push_back(x: a);
1385
1386 a = ac->addAction(QStringLiteral("newline_below"));
1387 a->setText(i18n("Insert a Newline Below Current Line"));
1388 a->setWhatsThis(i18n("Insert a new line below current line without modifying the current line."));
1389 scuts.clear();
1390 scuts << QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Enter);
1391 ac->setDefaultShortcuts(action: a, shortcuts: scuts);
1392 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::newLineBelow);
1393 m_editActions.push_back(x: a);
1394
1395 a = ac->addAction(QStringLiteral("tools_indent"));
1396 a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-more")));
1397 a->setText(i18n("&Indent"));
1398 a->setWhatsThis(
1399 i18n("Use this to indent a selected block of text.<br /><br />"
1400 "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog."));
1401 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::Key_I));
1402 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::indent);
1403
1404 a = ac->addAction(QStringLiteral("tools_unindent"));
1405 a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-less")));
1406 a->setText(i18n("&Unindent"));
1407 a->setWhatsThis(i18n("Use this to unindent a selected block of text."));
1408 ac->setDefaultShortcut(action: a, shortcut: QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_I));
1409 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::unIndent);
1410
1411 // BEGIN subword actions
1412 a = ac->addAction(QStringLiteral("delete_subword_left"));
1413 a->setText(i18n("Delete Sub-Word Left"));
1414 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this] {
1415 deleteWordLeft(subword: true);
1416 });
1417 m_editActions.push_back(x: a);
1418
1419 a = ac->addAction(QStringLiteral("delete_subword_right"));
1420 a->setText(i18n("Delete Sub-Word Right"));
1421 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this] {
1422 deleteWordRight(subword: true);
1423 });
1424 m_editActions.push_back(x: a);
1425 // END
1426 }
1427
1428 if (hasFocus()) {
1429 slotGotFocus();
1430 } else {
1431 slotLostFocus();
1432 }
1433}
1434
1435void KTextEditor::ViewPrivate::setupCodeFolding()
1436{
1437 KActionCollection *ac = this->actionCollection();
1438 QAction *a;
1439
1440 a = ac->addAction(QStringLiteral("folding_toplevel"));
1441 a->setText(i18n("Fold Toplevel Nodes"));
1442 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::slotFoldToplevelNodes);
1443
1444 a = ac->addAction(QStringLiteral("folding_expandtoplevel"));
1445 a->setText(i18n("Unfold Toplevel Nodes"));
1446 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::slotExpandToplevelNodes);
1447
1448 a = ac->addAction(QStringLiteral("folding_toggle_current"));
1449 a->setText(i18n("Toggle Current Node"));
1450 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::slotToggleFolding);
1451
1452 a = ac->addAction(QStringLiteral("folding_toggle_in_current"));
1453 a->setText(i18n("Toggle Contained Nodes"));
1454 connect(sender: a, signal: &QAction::triggered, context: this, slot: &KTextEditor::ViewPrivate::slotToggleFoldingsInRange);
1455}
1456
1457void KTextEditor::ViewPrivate::setupSpeechActions()
1458{
1459 KActionCollection *ac = actionCollection();
1460
1461 QAction *a = ac->addAction(QStringLiteral("tools_speech_say"));
1462 a->setText(i18n("Say current selection or document"));
1463 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this]() {
1464 if (selection()) {
1465 KTextEditor::EditorPrivate::self()->speechEngine(view: this)->say(text: selectionText());
1466 } else {
1467 KTextEditor::EditorPrivate::self()->speechEngine(view: this)->say(text: document()->text());
1468 }
1469 });
1470
1471 a = ac->addAction(QStringLiteral("tools_speech_stop"));
1472 a->setText(i18n("Stop current output"));
1473 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this]() {
1474 KTextEditor::EditorPrivate::self()->speechEngine(view: this)->stop();
1475 });
1476
1477 a = ac->addAction(QStringLiteral("tools_speech_pause"));
1478 a->setText(i18n("Pause current output"));
1479 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this]() {
1480 KTextEditor::EditorPrivate::self()->speechEngine(view: this)->pause();
1481 });
1482
1483 a = ac->addAction(QStringLiteral("tools_speech_resume"));
1484 a->setText(i18n("Resume current output"));
1485 connect(sender: a, signal: &QAction::triggered, context: this, slot: [this]() {
1486 KTextEditor::EditorPrivate::self()->speechEngine(view: this)->resume();
1487 });
1488}
1489
1490void KTextEditor::ViewPrivate::slotFoldToplevelNodes()
1491{
1492 for (int line = 0; line < doc()->lines(); ++line) {
1493 if (textFolding().isLineVisible(line)) {
1494 foldLine(line);
1495 }
1496 }
1497}
1498
1499void KTextEditor::ViewPrivate::slotExpandToplevelNodes()
1500{
1501 const auto topLevelRanges(textFolding().foldingRangesForParentRange());
1502 for (const auto &range : topLevelRanges) {
1503 textFolding().unfoldRange(id: range.first);
1504 }
1505}
1506
1507void KTextEditor::ViewPrivate::slotToggleFolding()
1508{
1509 int line = cursorPosition().line();
1510 bool actionDone = false;
1511 while (!actionDone && (line > -1)) {
1512 actionDone = unfoldLine(line);
1513 if (!actionDone) {
1514 actionDone = foldLine(line: line--).isValid();
1515 }
1516 }
1517}
1518
1519void KTextEditor::ViewPrivate::slotToggleFoldingsInRange()
1520{
1521 int line = cursorPosition().line();
1522 while (!toggleFoldingsInRange(line) && (line > -1)) {
1523 --line;
1524 }
1525}
1526
1527KTextEditor::Range KTextEditor::ViewPrivate::foldLine(int line)
1528{
1529 KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(startLine: line);
1530 if (!foldingRange.isValid()) {
1531 return foldingRange;
1532 }
1533
1534 // Ensure not to fold the end marker to avoid a deceptive look, but only on token based folding
1535 // ensure we don't compute an invalid line by moving outside of the foldingRange range by checking onSingleLine(), see bug 417890
1536 if (!m_doc->buffer().isFoldingStartingOnLine(startLine: line).second && !foldingRange.onSingleLine()) {
1537 const int adjustedLine = foldingRange.end().line() - 1;
1538 foldingRange.setEnd(KTextEditor::Cursor(adjustedLine, doc()->buffer().plainLine(lineno: adjustedLine).length()));
1539 }
1540
1541 // Check if the discovered fold is already folded up.
1542 // If so, we should not fold the same range again!
1543 // This can lead to issues where we need to open the fold multiple times
1544 // in order to actually open it.
1545 auto folds = textFolding().foldingRangesStartingOnLine(line);
1546 for (int i = 0; i < folds.size(); ++i) {
1547 KTextEditor::Range fold = textFolding().foldingRange(id: folds[i].first);
1548 if (fold == foldingRange) {
1549 return foldingRange;
1550 }
1551 }
1552
1553 // Don't try to fold a single line, which can happens due to adjustment above
1554 // FIXME Avoid to offer such a folding marker
1555 if (!foldingRange.onSingleLine()) {
1556 textFolding().newFoldingRange(range: foldingRange, flags: Kate::TextFolding::Folded);
1557 }
1558
1559 return foldingRange;
1560}
1561
1562bool KTextEditor::ViewPrivate::unfoldLine(int line)
1563{
1564 bool actionDone = false;
1565 const KTextEditor::Cursor currentCursor = cursorPosition();
1566
1567 // ask the folding info for this line, if any folds are around!
1568 // auto = QList<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>>
1569 auto startingRanges = textFolding().foldingRangesStartingOnLine(line);
1570 for (int i = 0; i < startingRanges.size() && !actionDone; ++i) {
1571 // Avoid jumping view in case of a big unfold and ensure nice highlight of folding marker
1572 setCursorPosition(textFolding().foldingRange(id: startingRanges[i].first).start());
1573
1574 actionDone |= textFolding().unfoldRange(id: startingRanges[i].first);
1575 }
1576
1577 if (!actionDone) {
1578 // Nothing unfolded? Restore old cursor position!
1579 setCursorPosition(currentCursor);
1580 }
1581
1582 return actionDone;
1583}
1584
1585bool KTextEditor::ViewPrivate::toggleFoldingOfLine(int line)
1586{
1587 bool actionDone = unfoldLine(line);
1588 if (!actionDone) {
1589 actionDone = foldLine(line).isValid();
1590 }
1591
1592 return actionDone;
1593}
1594
1595bool KTextEditor::ViewPrivate::toggleFoldingsInRange(int line)
1596{
1597 KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(startLine: line);
1598 if (!foldingRange.isValid()) {
1599 // Either line is not valid or there is no start range
1600 return false;
1601 }
1602
1603 bool actionDone = false; // Track success
1604 const KTextEditor::Cursor currentCursor = cursorPosition();
1605
1606 // Don't be too eager but obliging! Only toggle containing ranges which are
1607 // visible -> Be done when the range is folded
1608 actionDone |= unfoldLine(line);
1609
1610 if (!actionDone) {
1611 // Unfold all in range, but not the range itself
1612 for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) {
1613 actionDone |= unfoldLine(line: ln);
1614 }
1615
1616 if (actionDone) {
1617 // In most cases we want now a not moved cursor
1618 setCursorPosition(currentCursor);
1619 }
1620 }
1621
1622 if (!actionDone) {
1623 // Fold all in range, but not the range itself
1624 for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) {
1625 KTextEditor::Range fr = foldLine(line: ln);
1626 if (fr.isValid()) {
1627 // qMax to avoid infinite loop in case of range without content
1628 ln = qMax(a: ln, b: fr.end().line() - 1);
1629 actionDone = true;
1630 }
1631 }
1632 }
1633
1634 if (!actionDone) {
1635 // At this point was an unfolded range clicked which contains no "childs"
1636 // We assume the user want to fold it by the wrong button, be obliging!
1637 actionDone |= foldLine(line).isValid();
1638 }
1639
1640 // At this point we should be always true
1641 return actionDone;
1642}
1643
1644KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const
1645{
1646 return currentInputMode()->viewMode();
1647}
1648
1649QString KTextEditor::ViewPrivate::viewModeHuman() const
1650{
1651 QString currentMode = currentInputMode()->viewModeHuman();
1652
1653 // append read-only if needed
1654 if (!doc()->isReadWrite()) {
1655 currentMode = i18n("(R/O) %1", currentMode);
1656 }
1657
1658 // return full mode
1659 return currentMode;
1660}
1661
1662KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const
1663{
1664 return currentInputMode()->viewInputMode();
1665}
1666
1667QString KTextEditor::ViewPrivate::viewInputModeHuman() const
1668{
1669 return currentInputMode()->viewInputModeHuman();
1670}
1671
1672void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode, const bool rememberInConfig)
1673{
1674 if (currentInputMode()->viewInputMode() == mode) {
1675 return;
1676 }
1677
1678 // No multi cursors for vi
1679 if (mode == KTextEditor::View::InputMode::ViInputMode) {
1680 clearSecondaryCursors();
1681 }
1682
1683 m_viewInternal->m_currentInputMode->deactivate();
1684 m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode].get();
1685 m_viewInternal->m_currentInputMode->activate();
1686
1687 // remember in local config if requested, we skip this for the calls in updateConfig
1688 if (rememberInConfig) {
1689 config()->setValue(key: KateViewConfig::InputMode, value: mode);
1690 }
1691
1692 /* small duplication, but need to do this if not toggled by action */
1693 const auto inputModeActions = m_inputModeActions->actions();
1694 for (QAction *action : inputModeActions) {
1695 if (static_cast<InputMode>(action->data().toInt()) == mode) {
1696 action->setChecked(true);
1697 break;
1698 }
1699 }
1700
1701 /* inform the rest of the system about the change */
1702 Q_EMIT viewInputModeChanged(view: this, mode);
1703 Q_EMIT viewModeChanged(view: this, mode: viewMode());
1704}
1705
1706void KTextEditor::ViewPrivate::slotDocumentAboutToReload()
1707{
1708 if (doc()->isAutoReload()) {
1709 const int lastVisibleLine = m_viewInternal->endLine();
1710 const int currentLine = cursorPosition().line();
1711 m_gotoBottomAfterReload = (lastVisibleLine == currentLine) && (currentLine == doc()->lastLine());
1712 if (!m_gotoBottomAfterReload) {
1713 // Ensure the view jumps not back when user scrolls around
1714 const int firstVisibleLine = 1 + lastVisibleLine - m_viewInternal->linesDisplayed();
1715 const int newLine = qBound(min: firstVisibleLine, val: currentLine, max: lastVisibleLine);
1716 setCursorPositionVisual(KTextEditor::Cursor(newLine, cursorPosition().column()));
1717 }
1718 } else {
1719 m_gotoBottomAfterReload = false;
1720 }
1721}
1722
1723void KTextEditor::ViewPrivate::slotDocumentReloaded()
1724{
1725 if (m_gotoBottomAfterReload) {
1726 bottom();
1727 }
1728}
1729
1730void KTextEditor::ViewPrivate::slotGotFocus()
1731{
1732 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus";
1733 currentInputMode()->gotFocus();
1734
1735 // update current view and scrollbars
1736 // it is needed for styles that implement different frame and scrollbar
1737 // rendering when focused
1738 update();
1739 if (m_viewInternal->m_lineScroll->isVisible()) {
1740 m_viewInternal->m_lineScroll->update();
1741 }
1742
1743 if (m_viewInternal->m_columnScroll->isVisible()) {
1744 m_viewInternal->m_columnScroll->update();
1745 }
1746
1747 Q_EMIT focusIn(view: this);
1748}
1749
1750void KTextEditor::ViewPrivate::slotLostFocus()
1751{
1752 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus";
1753 currentInputMode()->lostFocus();
1754
1755 // update current view and scrollbars
1756 // it is needed for styles that implement different frame and scrollbar
1757 // rendering when focused
1758 update();
1759 if (m_viewInternal->m_lineScroll->isVisible()) {
1760 m_viewInternal->m_lineScroll->update();
1761 }
1762
1763 if (m_viewInternal->m_columnScroll->isVisible()) {
1764 m_viewInternal->m_columnScroll->update();
1765 }
1766
1767 if (doc()->config()->autoSave() && doc()->config()->autoSaveOnFocusOut() && doc()->isModified() && doc()->url().isLocalFile()) {
1768 doc()->documentSave();
1769 }
1770
1771 Q_EMIT focusOut(view: this);
1772}
1773
1774void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode)
1775{
1776 config()->setValue(key: KateViewConfig::DynWordWrapIndicators, value: mode);
1777}
1778
1779bool KTextEditor::ViewPrivate::isOverwriteMode() const
1780{
1781 return doc()->config()->ovr();
1782}
1783
1784void KTextEditor::ViewPrivate::reloadFile()
1785{
1786 // bookmarks and cursor positions are temporarily saved by the document
1787 doc()->documentReload();
1788}
1789
1790void KTextEditor::ViewPrivate::slotReadWriteChanged()
1791{
1792 if (m_toggleWriteLock) {
1793 m_toggleWriteLock->setChecked(!doc()->isReadWrite());
1794 }
1795
1796 m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut()));
1797 m_paste->setEnabled(doc()->isReadWrite());
1798 if (m_pasteSelection) {
1799 m_pasteSelection->setEnabled(doc()->isReadWrite());
1800 }
1801 m_swapWithClipboard->setEnabled(doc()->isReadWrite());
1802 m_setEndOfLine->setEnabled(doc()->isReadWrite());
1803
1804 static const auto l = {QStringLiteral("edit_replace"),
1805 QStringLiteral("tools_spelling"),
1806 QStringLiteral("tools_indent"),
1807 QStringLiteral("tools_unindent"),
1808 QStringLiteral("tools_cleanIndent"),
1809 QStringLiteral("tools_formatIndet"),
1810 QStringLiteral("tools_alignOn"),
1811 QStringLiteral("tools_comment"),
1812 QStringLiteral("tools_uncomment"),
1813 QStringLiteral("tools_toggle_comment"),
1814 QStringLiteral("tools_uppercase"),
1815 QStringLiteral("tools_lowercase"),
1816 QStringLiteral("tools_capitalize"),
1817 QStringLiteral("tools_join_lines"),
1818 QStringLiteral("tools_apply_wordwrap"),
1819 QStringLiteral("tools_spelling_from_cursor"),
1820 QStringLiteral("tools_spelling_selection")};
1821
1822 for (const auto &action : l) {
1823 QAction *a = actionCollection()->action(name: action);
1824 if (a) {
1825 a->setEnabled(doc()->isReadWrite());
1826 }
1827 }
1828 slotUpdateUndo();
1829
1830 currentInputMode()->readWriteChanged(rw: doc()->isReadWrite());
1831
1832 // => view mode changed
1833 Q_EMIT viewModeChanged(view: this, mode: viewMode());
1834 Q_EMIT viewInputModeChanged(view: this, mode: viewInputMode());
1835}
1836
1837void KTextEditor::ViewPrivate::toggleCamelCaseCursor()
1838{
1839 const auto enabled = doc()->config()->camelCursor();
1840 doc()->config()->setCamelCursor(!enabled);
1841 KTextEditor::Message *m;
1842 if (enabled) {
1843 m = new KTextEditor::Message(i18n("Camel case movement disabled"));
1844 } else {
1845 m = new KTextEditor::Message(i18n("Camel case movement enabled"));
1846 }
1847 m->setPosition(KTextEditor::Message::TopInView);
1848 m->setAutoHide(1000);
1849 m->setAutoHideMode(KTextEditor::Message::Immediate);
1850 doc()->postMessage(message: m);
1851}
1852
1853void KTextEditor::ViewPrivate::slotUpdateUndo()
1854{
1855 if (doc()->readOnly()) {
1856 return;
1857 }
1858
1859 m_editUndo->setEnabled(doc()->isReadWrite() && doc()->undoCount() > 0);
1860 m_editRedo->setEnabled(doc()->isReadWrite() && doc()->redoCount() > 0);
1861}
1862
1863bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor position, uint tabwidth, bool calledExternally)
1864{
1865 if (position.line() < 0 || position.line() >= doc()->lines()) {
1866 return false;
1867 }
1868
1869 Kate::TextLine l = doc()->kateTextLine(i: position.line());
1870 const QString line_str = l.text();
1871
1872 int x = 0;
1873 int z = 0;
1874 for (; z < line_str.length() && z < position.column(); z++) {
1875 if (line_str[z] == QLatin1Char('\t')) {
1876 x += tabwidth - (x % tabwidth);
1877 } else {
1878 x++;
1879 }
1880 }
1881
1882 if (blockSelection()) {
1883 if (z < position.column()) {
1884 x += position.column() - z;
1885 }
1886 }
1887
1888 m_viewInternal->updateCursor(newCursor: KTextEditor::Cursor(position.line(), x),
1889 force: false,
1890 center: calledExternally /* force center for external calls, see bug 408418 */,
1891 calledExternally);
1892
1893 return true;
1894}
1895
1896void KTextEditor::ViewPrivate::toggleInsert()
1897{
1898 doc()->config()->setOvr(!doc()->config()->ovr());
1899 m_toggleInsert->setChecked(isOverwriteMode());
1900
1901 // No multi cursors for overwrite mode
1902 if (isOverwriteMode()) {
1903 clearSecondaryCursors();
1904 }
1905
1906 Q_EMIT viewModeChanged(view: this, mode: viewMode());
1907 Q_EMIT viewInputModeChanged(view: this, mode: viewInputMode());
1908}
1909
1910void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error)
1911{
1912 if (!error.isEmpty()) { // happens when canceling a job
1913 KMessageBox::error(parent: this, text: error);
1914 }
1915}
1916
1917void KTextEditor::ViewPrivate::gotoLine()
1918{
1919 gotoBar()->updateData();
1920 bottomViewBar()->showBarWidget(barWidget: gotoBar());
1921}
1922
1923void KTextEditor::ViewPrivate::changeDictionary()
1924{
1925 dictionaryBar()->updateData();
1926 bottomViewBar()->showBarWidget(barWidget: dictionaryBar());
1927}
1928
1929void KTextEditor::ViewPrivate::joinLines()
1930{
1931 int first = selectionRange().start().line();
1932 int last = selectionRange().end().line();
1933 // int left = doc()->line( last ).length() - doc()->selEndCol();
1934 if (first == last) {
1935 first = cursorPosition().line();
1936 last = first + 1;
1937 }
1938 doc()->joinLines(first, last);
1939}
1940
1941void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet<QString> &flags)
1942{
1943 Q_UNUSED(flags)
1944
1945 // cursor position
1946 KTextEditor::Cursor savedPosition(config.readEntry(key: "CursorLine", defaultValue: 0), config.readEntry(key: "CursorColumn", defaultValue: 0));
1947 setCursorPositionInternal(position: savedPosition);
1948
1949 // scroll position
1950 const int scroll = config.readEntry(key: "ScrollLine", defaultValue: -1);
1951 if (scroll >= 0 && scroll < doc()->lines() && savedPosition.line() < doc()->lines()) {
1952 setScrollPositionInternal(KTextEditor::Cursor(scroll, 0));
1953 }
1954
1955 // only touch that if we did write it out in writeSessionConfig, bug 487216
1956 if (config.hasKey(key: "Dynamic Word Wrap")) {
1957 // in any case, use the current global setting as default
1958 m_config->setDynWordWrap(config.readEntry(key: "Dynamic Word Wrap", defaultValue: m_config->global()->dynWordWrap()));
1959 }
1960
1961 // restore text folding
1962 m_savedFoldingState = QJsonDocument::fromJson(json: config.readEntry(key: "TextFolding", defaultValue: QByteArray()));
1963 applyFoldingState();
1964
1965 m_forceRTL = config.readEntry(key: "Force RTL Direction", defaultValue: false);
1966 m_forceRTLDirection->setChecked(m_forceRTL);
1967
1968 for (const auto &mode : m_viewInternal->m_inputModes) {
1969 mode->readSessionConfig(config);
1970 }
1971}
1972
1973void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet<QString> &)
1974{
1975 // ensure we don't amass stuff
1976 config.deleteGroup();
1977
1978 // cursor position
1979 const auto cursor = cursorPosition();
1980 if (cursor.isValid() && cursor != KTextEditor::Cursor(0, 0)) {
1981 config.writeEntry(key: "CursorLine", value: cursor.line());
1982 config.writeEntry(key: "CursorColumn", value: cursor.column());
1983 }
1984
1985 // save scroll position if its different from cursorPosition
1986 const int scrollLine = firstDisplayedLineInternal(lineType: LineType::RealLine);
1987 if (scrollLine > 0 && scrollLine != cursor.line()) {
1988 config.writeEntry(key: "ScrollLine", value: scrollLine);
1989 }
1990
1991 // only write if set in this view
1992 if (m_config->isSet(key: KateViewConfig::DynamicWordWrap)) {
1993 config.writeEntry(key: "Dynamic Word Wrap", value: m_config->dynWordWrap());
1994 }
1995
1996 // save text folding state
1997 saveFoldingState();
1998 if (!m_savedFoldingState.object().value(key: QLatin1String("ranges")).toArray().isEmpty()) {
1999 config.writeEntry(key: "TextFolding", value: m_savedFoldingState.toJson(format: QJsonDocument::Compact));
2000 m_savedFoldingState = QJsonDocument();
2001 }
2002
2003 if (m_forceRTL) {
2004 config.writeEntry(key: "Force RTL Direction", value: m_forceRTL);
2005 }
2006
2007 for (const auto &mode : m_viewInternal->m_inputModes) {
2008 mode->writeSessionConfig(config);
2009 }
2010}
2011
2012int KTextEditor::ViewPrivate::getEol() const
2013{
2014 return doc()->config()->eol();
2015}
2016
2017QMenu *KTextEditor::ViewPrivate::getEolMenu()
2018{
2019 return m_setEndOfLine->menu();
2020}
2021
2022void KTextEditor::ViewPrivate::setEol(int eol)
2023{
2024 if (!doc()->isReadWrite()) {
2025 return;
2026 }
2027
2028 if (m_updatingDocumentConfig) {
2029 return;
2030 }
2031
2032 if (eol != doc()->config()->eol()) {
2033 doc()->setModified(true); // mark modified (bug #143120)
2034 doc()->config()->setEol(eol);
2035 }
2036}
2037
2038void KTextEditor::ViewPrivate::setAddBom(bool enabled)
2039{
2040 if (!doc()->isReadWrite()) {
2041 return;
2042 }
2043
2044 if (m_updatingDocumentConfig) {
2045 return;
2046 }
2047
2048 doc()->config()->setBom(enabled);
2049 doc()->bomSetByUser();
2050}
2051
2052void KTextEditor::ViewPrivate::setIconBorder(bool enable)
2053{
2054 config()->setValue(key: KateViewConfig::ShowIconBar, value: enable);
2055}
2056
2057void KTextEditor::ViewPrivate::toggleIconBorder()
2058{
2059 config()->setValue(key: KateViewConfig::ShowIconBar, value: !config()->iconBar());
2060}
2061
2062void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable)
2063{
2064 config()->setValue(key: KateViewConfig::ShowLineNumbers, value: enable);
2065}
2066
2067void KTextEditor::ViewPrivate::toggleLineNumbersOn()
2068{
2069 config()->setValue(key: KateViewConfig::ShowLineNumbers, value: !config()->lineNumbers());
2070}
2071
2072void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable)
2073{
2074 config()->setValue(key: KateViewConfig::ShowScrollBarMarks, value: enable);
2075}
2076
2077void KTextEditor::ViewPrivate::toggleScrollBarMarks()
2078{
2079 config()->setValue(key: KateViewConfig::ShowScrollBarMarks, value: !config()->scrollBarMarks());
2080}
2081
2082void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable)
2083{
2084 config()->setValue(key: KateViewConfig::ShowScrollBarMiniMap, value: enable);
2085}
2086
2087void KTextEditor::ViewPrivate::toggleScrollBarMiniMap()
2088{
2089 config()->setValue(key: KateViewConfig::ShowScrollBarMiniMap, value: !config()->scrollBarMiniMap());
2090}
2091
2092void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable)
2093{
2094 config()->setValue(key: KateViewConfig::ShowScrollBarMiniMapAll, value: enable);
2095}
2096
2097void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll()
2098{
2099 config()->setValue(key: KateViewConfig::ShowScrollBarMiniMapAll, value: !config()->scrollBarMiniMapAll());
2100}
2101
2102void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width)
2103{
2104 config()->setValue(key: KateViewConfig::ScrollBarMiniMapWidth, value: width);
2105}
2106
2107void KTextEditor::ViewPrivate::toggleShowSpaces()
2108{
2109 if (m_updatingDocumentConfig) {
2110 return;
2111 }
2112
2113 using WhitespaceRendering = KateDocumentConfig::WhitespaceRendering;
2114 doc()->config()->setShowSpaces(doc()->config()->showSpaces() != WhitespaceRendering::None ? WhitespaceRendering::None : WhitespaceRendering::All);
2115}
2116
2117void KTextEditor::ViewPrivate::toggleDynWordWrap()
2118{
2119 config()->setDynWordWrap(!config()->dynWordWrap());
2120}
2121
2122void KTextEditor::ViewPrivate::toggleWWMarker()
2123{
2124 m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker());
2125}
2126
2127void KTextEditor::ViewPrivate::toggleNPSpaces()
2128{
2129 m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces());
2130 m_viewInternal->update(); // force redraw
2131}
2132
2133void KTextEditor::ViewPrivate::toggleWordCount(bool on)
2134{
2135 config()->setShowWordCount(on);
2136}
2137
2138void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable)
2139{
2140 config()->setValue(key: KateViewConfig::ShowFoldingBar, value: enable);
2141}
2142
2143void KTextEditor::ViewPrivate::toggleFoldingMarkers()
2144{
2145 config()->setValue(key: KateViewConfig::ShowFoldingBar, value: !config()->foldingBar());
2146}
2147
2148bool KTextEditor::ViewPrivate::iconBorder()
2149{
2150 return m_viewInternal->m_leftBorder->iconBorderOn();
2151}
2152
2153bool KTextEditor::ViewPrivate::lineNumbersOn()
2154{
2155 return m_viewInternal->m_leftBorder->lineNumbersOn();
2156}
2157
2158bool KTextEditor::ViewPrivate::scrollBarMarks()
2159{
2160 return m_viewInternal->m_lineScroll->showMarks();
2161}
2162
2163bool KTextEditor::ViewPrivate::scrollBarMiniMap()
2164{
2165 return m_viewInternal->m_lineScroll->showMiniMap();
2166}
2167
2168int KTextEditor::ViewPrivate::dynWrapIndicators()
2169{
2170 return m_viewInternal->m_leftBorder->dynWrapIndicators();
2171}
2172
2173bool KTextEditor::ViewPrivate::foldingMarkersOn()
2174{
2175 return m_viewInternal->m_leftBorder->foldingMarkersOn();
2176}
2177
2178bool KTextEditor::ViewPrivate::forceRTLDirection() const
2179{
2180 return m_forceRTL;
2181}
2182
2183void KTextEditor::ViewPrivate::toggleWriteLock()
2184{
2185 doc()->setReadWrite(!doc()->isReadWrite());
2186}
2187
2188void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider)
2189{
2190 m_viewInternal->registerTextHintProvider(provider);
2191}
2192
2193void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider)
2194{
2195 m_viewInternal->unregisterTextHintProvider(provider);
2196}
2197
2198void KTextEditor::ViewPrivate::setTextHintDelay(int delay)
2199{
2200 m_viewInternal->setTextHintDelay(delay);
2201}
2202
2203int KTextEditor::ViewPrivate::textHintDelay() const
2204{
2205 return m_viewInternal->textHintDelay();
2206}
2207
2208// NOLINTNEXTLINE(readability-make-member-function-const)
2209void KTextEditor::ViewPrivate::find()
2210{
2211 currentInputMode()->find();
2212}
2213
2214// NOLINTNEXTLINE(readability-make-member-function-const)
2215void KTextEditor::ViewPrivate::findSelectedForwards()
2216{
2217 currentInputMode()->findSelectedForwards();
2218}
2219
2220// NOLINTNEXTLINE(readability-make-member-function-const)
2221void KTextEditor::ViewPrivate::findSelectedBackwards()
2222{
2223 currentInputMode()->findSelectedBackwards();
2224}
2225
2226void KTextEditor::ViewPrivate::skipCurrentOccurunceSelection()
2227{
2228 if (isMulticursorNotAllowed()) {
2229 return;
2230 }
2231 m_skipCurrentSelection = true;
2232}
2233
2234void KTextEditor::ViewPrivate::findNextOccurunceAndSelect()
2235{
2236 if (isMulticursorNotAllowed()) {
2237 return;
2238 }
2239
2240 const auto text = selection() ? doc()->text(range: selectionRange()) : QString();
2241 if (text.isEmpty()) {
2242 const auto selection = doc()->wordRangeAt(cursor: cursorPosition());
2243 // We don't want matching word highlights
2244 setSelection(selection);
2245 setCursorPosition(selection.end());
2246 clearHighlights();
2247
2248 for (auto &c : m_secondaryCursors) {
2249 const auto range = doc()->wordRangeAt(cursor: c.cursor());
2250 if (!c.range && !c.anchor.isValid()) {
2251 c.anchor = range.start();
2252 c.range.reset(p: newSecondarySelectionRange(range));
2253 c.pos->setPosition(range.end());
2254 }
2255 tagLines(range);
2256 }
2257 return;
2258 } else if (!m_rangesForHighlights.empty()) {
2259 clearHighlights();
2260 }
2261
2262 // Use selection range end as starting point
2263 const auto lastSelectionRange = selectionRange();
2264
2265 KTextEditor::Range searchRange(lastSelectionRange.end(), doc()->documentRange().end());
2266 QList<KTextEditor::Range> matches = doc()->searchText(range: searchRange, pattern: text, options: KTextEditor::Default);
2267 if (!matches.isEmpty() && !matches.constFirst().isValid()) {
2268 searchRange.setRange(start: doc()->documentRange().start(), end: lastSelectionRange.end());
2269 matches = doc()->searchText(range: searchRange, pattern: text, options: KTextEditor::Default);
2270 }
2271
2272 // No match found or only one possible match
2273 if (matches.empty() || !matches.constFirst().isValid() || matches.constFirst() == selectionRange()) {
2274 return;
2275 }
2276
2277 auto it = std::find_if(first: m_secondaryCursors.begin(), last: m_secondaryCursors.end(), pred: [&](const SecondaryCursor &c) {
2278 return c.range && c.range->toRange() == matches.constFirst();
2279 });
2280
2281 if (it != m_secondaryCursors.end()) {
2282 m_secondaryCursors.erase(position: it);
2283 }
2284
2285 // Move our primary to cursor to this match and select it
2286 // Ensure we don't create occurence highlights
2287 setSelection(matches.constFirst());
2288 setCursorPosition(matches.constFirst().end());
2289 clearHighlights();
2290
2291 // If we are skipping this selection, then we don't have to do anything
2292 if (!m_skipCurrentSelection) {
2293 PlainSecondaryCursor c;
2294 c.pos = lastSelectionRange.end();
2295 c.range = lastSelectionRange;
2296 // make our previous primary selection a secondary
2297 addSecondaryCursorsWithSelection(cursorsWithSelection: {c});
2298 }
2299 // reset value
2300 m_skipCurrentSelection = false;
2301}
2302
2303void KTextEditor::ViewPrivate::findAllOccuruncesAndSelect()
2304{
2305 if (isMulticursorNotAllowed()) {
2306 return;
2307 }
2308
2309 QString text = selection() ? doc()->text(range: selectionRange()) : QString();
2310 if (text.isEmpty()) {
2311 const auto selection = doc()->wordRangeAt(cursor: cursorPosition());
2312 setSelection(selection);
2313 setCursorPosition(selection.end());
2314 clearHighlights();
2315 text = doc()->text(range: selection);
2316
2317 for (auto &c : m_secondaryCursors) {
2318 const auto range = doc()->wordRangeAt(cursor: c.cursor());
2319 if (!c.range && !c.anchor.isValid()) {
2320 c.anchor = range.start();
2321 c.range.reset(p: newSecondarySelectionRange(range));
2322 c.pos->setPosition(range.end());
2323 }
2324 tagLines(range);
2325 }
2326 }
2327
2328 KTextEditor::Range searchRange(doc()->documentRange());
2329 QList<KTextEditor::Range> matches;
2330 QList<PlainSecondaryCursor> resultRanges;
2331 do {
2332 matches = doc()->searchText(range: searchRange, pattern: text, options: KTextEditor::Default);
2333
2334 if (matches.constFirst().isValid()) {
2335 // Dont add if matches primary selection
2336 if (matches.constFirst() != selectionRange()) {
2337 PlainSecondaryCursor c;
2338 c.pos = matches.constFirst().end();
2339 c.range = matches.constFirst();
2340 resultRanges.push_back(t: c);
2341 }
2342 searchRange.setStart(matches.constFirst().end());
2343 }
2344 } while (matches.first().isValid());
2345
2346 // ensure to clear occurence highlights
2347 if (!resultRanges.empty()) {
2348 clearHighlights();
2349 }
2350
2351 clearSecondaryCursors();
2352 addSecondaryCursorsWithSelection(cursorsWithSelection: resultRanges);
2353}
2354
2355// NOLINTNEXTLINE(readability-make-member-function-const)
2356void KTextEditor::ViewPrivate::replace()
2357{
2358 currentInputMode()->findReplace();
2359}
2360
2361// NOLINTNEXTLINE(readability-make-member-function-const)
2362void KTextEditor::ViewPrivate::findNext()
2363{
2364 currentInputMode()->findNext();
2365}
2366
2367// NOLINTNEXTLINE(readability-make-member-function-const)
2368void KTextEditor::ViewPrivate::findPrevious()
2369{
2370 currentInputMode()->findPrevious();
2371}
2372
2373void KTextEditor::ViewPrivate::showSearchWrappedHint(bool isReverseSearch)
2374{
2375 // show message widget when wrapping
2376 const QIcon icon = isReverseSearch ? QIcon::fromTheme(QStringLiteral("go-up-search")) : QIcon::fromTheme(QStringLiteral("go-down-search"));
2377
2378 if (!m_wrappedMessage || m_isLastSearchReversed != isReverseSearch) {
2379 m_isLastSearchReversed = isReverseSearch;
2380 m_wrappedMessage = new KTextEditor::Message(i18n("Search wrapped"), KTextEditor::Message::Information);
2381 m_wrappedMessage->setIcon(icon);
2382 m_wrappedMessage->setPosition(KTextEditor::Message::BottomInView);
2383 m_wrappedMessage->setAutoHide(2000);
2384 m_wrappedMessage->setAutoHideMode(KTextEditor::Message::Immediate);
2385 m_wrappedMessage->setView(this);
2386 this->doc()->postMessage(message: m_wrappedMessage);
2387 }
2388}
2389
2390void KTextEditor::ViewPrivate::createMultiCursorsFromSelection()
2391{
2392 if (!selection() || selectionRange().isEmpty()) {
2393 return;
2394 }
2395 // Is this really needed?
2396 // Lets just clear them now for simplicity
2397 clearSecondaryCursors();
2398
2399 const auto range = selectionRange();
2400 QList<KTextEditor::Cursor> cursorsToAdd;
2401 const auto start = range.start().line() < 0 ? 0 : range.start().line();
2402 const auto end = range.end().line() > doc()->lines() ? doc()->lines() : range.end().line();
2403 const auto currentLine = cursorPosition().line();
2404 setCursorPosition({currentLine, doc()->lineLength(line: currentLine)});
2405 for (int line = start; line <= end; ++line) {
2406 if (line != currentLine) {
2407 cursorsToAdd.push_back(t: {line, doc()->lineLength(line)});
2408 }
2409 }
2410 // clear selection
2411 setSelection({});
2412 setSecondaryCursors(cursorsToAdd);
2413}
2414
2415void KTextEditor::ViewPrivate::removeCursorsFromEmptyLines()
2416{
2417 if (!m_secondaryCursors.empty()) {
2418 std::vector<KTextEditor::Cursor> cursorsToRemove;
2419 for (const auto &c : m_secondaryCursors) {
2420 auto cursor = c.cursor();
2421 if (doc()->lineLength(line: cursor.line()) == 0) {
2422 cursorsToRemove.push_back(x: cursor);
2423 }
2424 }
2425 removeSecondaryCursors(cursorToRemove: cursorsToRemove);
2426 }
2427}
2428
2429void KTextEditor::ViewPrivate::slotSelectionChanged()
2430{
2431 m_copy->setEnabled(selection() || m_config->smartCopyCut());
2432 m_deSelect->setEnabled(selection());
2433 m_copyHtmlAction->setEnabled(selection());
2434
2435 // update highlighting of current selected word
2436 selectionChangedForHighlights();
2437
2438 if (doc()->readOnly()) {
2439 return;
2440 }
2441
2442 m_cut->setEnabled(selection() || m_config->smartCopyCut());
2443 m_screenshotSelection->setVisible(selection());
2444 m_screenshotSelection->setEnabled(selection());
2445}
2446
2447// NOLINTNEXTLINE(readability-make-member-function-const)
2448void KTextEditor::ViewPrivate::switchToCmdLine()
2449{
2450 currentInputMode()->activateCommandLine();
2451}
2452
2453KateRenderer *KTextEditor::ViewPrivate::renderer()
2454{
2455 return m_renderer;
2456}
2457
2458KateRendererConfig *KTextEditor::ViewPrivate::rendererConfig()
2459{
2460 return m_renderer->config();
2461}
2462
2463void KTextEditor::ViewPrivate::updateConfig()
2464{
2465 if (m_startingUp) {
2466 return;
2467 }
2468
2469 m_toggleShowSpace->setChecked(doc()->config()->showSpaces() != KateDocumentConfig::WhitespaceRendering::None);
2470
2471 // dyn. word wrap & markers
2472 if (m_hasWrap != config()->dynWordWrap()) {
2473 m_hasWrap = config()->dynWordWrap();
2474
2475 m_viewInternal->dynWrapChanged();
2476
2477 m_setDynWrapIndicators->setEnabled(config()->dynWordWrap());
2478 m_toggleDynWrap->setChecked(config()->dynWordWrap());
2479 }
2480
2481 m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators());
2482 m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators());
2483
2484 // line numbers
2485 m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers());
2486 m_toggleLineNumbers->setChecked(config()->lineNumbers());
2487
2488 // icon bar
2489 m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar());
2490 m_toggleIconBar->setChecked(config()->iconBar());
2491
2492 // scrollbar marks
2493 m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks());
2494 m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks());
2495
2496 // scrollbar mini-map
2497 m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap());
2498 m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap());
2499
2500 // scrollbar mini-map - (whole document)
2501 m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll());
2502 // m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() );
2503
2504 // scrollbar mini-map.width
2505 m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth());
2506
2507 // misc edit
2508 m_toggleBlockSelection->setChecked(blockSelection());
2509 m_toggleInsert->setChecked(isOverwriteMode());
2510
2511 updateFoldingConfig();
2512
2513 // bookmark
2514 m_bookmarks->setSorting((KateBookmarks::Sorting)config()->bookmarkSort());
2515
2516 m_viewInternal->setAutoCenterLines(viewLines: config()->autoCenterLines());
2517
2518 for (const auto &input : m_viewInternal->m_inputModes) {
2519 input->updateConfig();
2520 }
2521
2522 setInputMode(mode: config()->inputMode(), rememberInConfig: false /* don't remember in config for these calls */);
2523
2524 reflectOnTheFlySpellCheckStatus(enabled: doc()->isOnTheFlySpellCheckingEnabled());
2525
2526 // register/unregister word completion...
2527 bool wc = config()->wordCompletion();
2528 if (wc != isCompletionModelRegistered(model: KTextEditor::EditorPrivate::self()->wordCompletionModel())) {
2529 if (wc) {
2530 registerCompletionModel(model: KTextEditor::EditorPrivate::self()->wordCompletionModel());
2531 } else {
2532 unregisterCompletionModel(model: KTextEditor::EditorPrivate::self()->wordCompletionModel());
2533 }
2534 }
2535
2536 bool kc = config()->keywordCompletion();
2537 if (kc != isCompletionModelRegistered(model: KTextEditor::EditorPrivate::self()->keywordCompletionModel())) {
2538 if (kc) {
2539 registerCompletionModel(model: KTextEditor::EditorPrivate::self()->keywordCompletionModel());
2540 } else {
2541 unregisterCompletionModel(model: KTextEditor::EditorPrivate::self()->keywordCompletionModel());
2542 }
2543 }
2544
2545 m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut()));
2546 m_copy->setEnabled(selection() || m_config->smartCopyCut());
2547
2548 m_accessibilityEnabled = m_config->value(key: KateViewConfig::EnableAccessibility).toBool();
2549 m_bookmarks->setCycleThroughBookmarks(m_config->value(key: KateViewConfig::CycleThroughBookmarks).toBool());
2550
2551 // if not disabled, update status bar
2552 if (m_statusBar) {
2553 m_statusBar->updateStatus();
2554 }
2555
2556 // now redraw...
2557 m_viewInternal->cache()->clear();
2558 tagAll();
2559 updateView(changed: true);
2560
2561 Q_EMIT configChanged(view: this);
2562}
2563
2564void KTextEditor::ViewPrivate::updateDocumentConfig()
2565{
2566 if (m_startingUp) {
2567 return;
2568 }
2569
2570 m_updatingDocumentConfig = true;
2571
2572 m_setEndOfLine->setCurrentItem(doc()->config()->eol());
2573
2574 m_addBom->setChecked(doc()->config()->bom());
2575
2576 m_updatingDocumentConfig = false;
2577
2578 // maybe block selection or wrap-cursor mode changed
2579 ensureCursorColumnValid();
2580
2581 // first change this
2582 m_renderer->setTabWidth(doc()->config()->tabWidth());
2583 m_renderer->setIndentWidth(doc()->config()->indentationWidth());
2584
2585 // now redraw...
2586 m_viewInternal->cache()->clear();
2587 tagAll();
2588 updateView(changed: true);
2589}
2590
2591void KTextEditor::ViewPrivate::updateRendererConfig()
2592{
2593 if (m_startingUp) {
2594 return;
2595 }
2596
2597 m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker());
2598
2599 m_viewInternal->updateBracketMarkAttributes();
2600 m_viewInternal->updateBracketMarks();
2601
2602 // now redraw...
2603 m_viewInternal->cache()->clear();
2604 tagAll();
2605 m_viewInternal->updateView(changed: true);
2606
2607 // update the left border right, for example linenumbers
2608 m_viewInternal->m_leftBorder->updateFont();
2609 m_viewInternal->m_leftBorder->repaint();
2610
2611 m_viewInternal->m_lineScroll->queuePixmapUpdate();
2612
2613 currentInputMode()->updateRendererConfig();
2614
2615 // @@ showIndentLines is not cached anymore.
2616 // m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines());
2617 Q_EMIT configChanged(view: this);
2618}
2619
2620void KTextEditor::ViewPrivate::updateFoldingConfig()
2621{
2622 // folding bar
2623 m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar());
2624 m_toggleFoldingMarkers->setChecked(config()->foldingBar());
2625
2626 if (hasCommentInFirstLine(doc: m_doc)) {
2627 if (config()->foldFirstLine() && !m_autoFoldedFirstLine) {
2628 foldLine(line: 0);
2629 m_autoFoldedFirstLine = true;
2630 } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) {
2631 unfoldLine(line: 0);
2632 m_autoFoldedFirstLine = false;
2633 }
2634 } else {
2635 m_autoFoldedFirstLine = false;
2636 }
2637
2638#if 0
2639 // FIXME: FOLDING
2640 const QStringList l = {
2641 QStringLiteral("folding_toplevel")
2642 , QStringLiteral("folding_expandtoplevel")
2643 , QStringLiteral("folding_toggle_current")
2644 , QStringLiteral("folding_toggle_in_current")
2645 };
2646
2647 QAction *a = 0;
2648 for (int z = 0; z < l.size(); z++)
2649 if ((a = actionCollection()->action(l[z].toAscii().constData()))) {
2650 a->setEnabled(doc()->highlight() && doc()->highlight()->allowsFolding());
2651 }
2652#endif
2653}
2654
2655void KTextEditor::ViewPrivate::ensureCursorColumnValid()
2656{
2657 KTextEditor::Cursor c = m_viewInternal->cursorPosition();
2658
2659 // make sure the cursor is valid:
2660 // - in block selection mode or if wrap cursor is off, the column is arbitrary
2661 // - otherwise: it's bounded by the line length
2662 if (!blockSelection() && wrapCursor() && (!c.isValid() || c.column() > doc()->lineLength(line: c.line()))) {
2663 c.setColumn(doc()->lineLength(line: cursorPosition().line()));
2664 setCursorPosition(c);
2665 }
2666}
2667
2668// BEGIN EDIT STUFF
2669void KTextEditor::ViewPrivate::editStart()
2670{
2671 m_viewInternal->editStart();
2672}
2673
2674void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom)
2675{
2676 m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom);
2677 textFolding().editEnd(startLine: editTagLineStart, endLine: editTagLineEnd, isLineFoldingStart: [this](int line) {
2678 return m_doc->buffer().isFoldingStartingOnLine(startLine: line).first;
2679 });
2680}
2681
2682void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor cursor)
2683{
2684 m_viewInternal->editSetCursor(cursor);
2685}
2686// END
2687
2688// BEGIN TAG & CLEAR
2689bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor virtualCursor)
2690{
2691 return m_viewInternal->tagLine(virtualCursor);
2692}
2693
2694bool KTextEditor::ViewPrivate::tagRange(KTextEditor::Range range, bool realLines)
2695{
2696 return m_viewInternal->tagRange(range, realCursors: realLines);
2697}
2698
2699bool KTextEditor::ViewPrivate::tagLines(KTextEditor::LineRange lineRange, bool realLines)
2700{
2701 return m_viewInternal->tagLines(start: lineRange.start(), end: lineRange.end(), realLines);
2702}
2703
2704bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors)
2705{
2706 return m_viewInternal->tagLines(start, end, realCursors);
2707}
2708
2709void KTextEditor::ViewPrivate::tagAll()
2710{
2711 m_viewInternal->tagAll();
2712}
2713
2714void KTextEditor::ViewPrivate::clear()
2715{
2716 m_viewInternal->clear();
2717}
2718
2719void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty)
2720{
2721 if (paintOnlyDirty) {
2722 m_viewInternal->updateDirty();
2723 } else {
2724 m_viewInternal->update();
2725 }
2726}
2727
2728void KTextEditor::ViewPrivate::updateView(bool changed)
2729{
2730 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView";
2731 m_viewInternal->updateView(changed);
2732 m_viewInternal->m_leftBorder->update();
2733}
2734
2735// END
2736
2737void KTextEditor::ViewPrivate::slotHlChanged()
2738{
2739 KateHighlighting *hl = doc()->highlight();
2740 bool ok(!hl->getCommentStart(attrib: 0).isEmpty() || !hl->getCommentSingleLineStart(attrib: 0).isEmpty());
2741
2742 if (actionCollection()->action(QStringLiteral("tools_comment"))) {
2743 actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok);
2744 }
2745
2746 if (actionCollection()->action(QStringLiteral("tools_uncomment"))) {
2747 actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok);
2748 }
2749
2750 if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) {
2751 actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok);
2752 }
2753
2754 // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry
2755 updateFoldingConfig();
2756}
2757
2758int KTextEditor::ViewPrivate::virtualCursorColumn() const
2759{
2760 return doc()->toVirtualColumn(m_viewInternal->cursorPosition());
2761}
2762
2763void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor newPosition)
2764{
2765 Q_EMIT mousePositionChanged(view: this, newPosition);
2766}
2767
2768// BEGIN KTextEditor::SelectionInterface stuff
2769
2770bool KTextEditor::ViewPrivate::setSelection(KTextEditor::Range selection)
2771{
2772 // anything to do?
2773 if (selection == m_selection) {
2774 return true;
2775 }
2776
2777 // backup old range
2778 KTextEditor::Range oldSelection = m_selection;
2779
2780 // set new range
2781 m_selection.setRange(selection.isEmpty() ? KTextEditor::Range::invalid() : selection);
2782
2783 // trigger update of correct area
2784 tagSelection(oldSelection);
2785 repaintText(paintOnlyDirty: true);
2786
2787 // emit holy signal
2788 Q_EMIT selectionChanged(view: this);
2789
2790 // be done
2791 return true;
2792}
2793
2794bool KTextEditor::ViewPrivate::clearSelection()
2795{
2796 return clearSelection(redraw: true);
2797}
2798
2799bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection)
2800{
2801 // no selection, nothing to do...
2802 if (!selection()) {
2803 return false;
2804 }
2805
2806 // backup old range
2807 KTextEditor::Range oldSelection = m_selection;
2808
2809 // invalidate current selection
2810 m_selection.setRange(KTextEditor::Range::invalid());
2811
2812 // trigger update of correct area
2813 tagSelection(oldSelection);
2814 if (redraw) {
2815 repaintText(paintOnlyDirty: true);
2816 }
2817
2818 // emit holy signal
2819 if (finishedChangingSelection) {
2820 Q_EMIT selectionChanged(view: this);
2821 }
2822
2823 m_viewInternal->m_selChangedByUser = false;
2824 // be done
2825 return true;
2826}
2827
2828bool KTextEditor::ViewPrivate::selection() const
2829{
2830 if (!wrapCursor()) {
2831 return m_selection != KTextEditor::Range::invalid();
2832 } else {
2833 return m_selection.toRange().isValid();
2834 }
2835}
2836
2837QString KTextEditor::ViewPrivate::selectionText() const
2838{
2839 if (!selection() && m_secondaryCursors.empty()) {
2840 return {};
2841 }
2842
2843 if (blockSelect) {
2844 return doc()->text(range: m_selection, blockwise: blockSelect);
2845 }
2846
2847 QVarLengthArray<KTextEditor::Range, 16> ranges;
2848 for (const auto &c : m_secondaryCursors) {
2849 if (c.range) {
2850 ranges.push_back(t: c.range->toRange());
2851 }
2852 }
2853 ranges.push_back(t: m_selection.toRange());
2854 std::sort(first: ranges.begin(), last: ranges.end());
2855
2856 QString text;
2857 text.reserve(asize: ranges.size() * m_selection.toRange().columnWidth());
2858 for (int i = 0; i < ranges.size() - 1; ++i) {
2859 text += doc()->text(range: ranges[i]) + QStringLiteral("\n");
2860 }
2861 text += doc()->text(range: ranges.last());
2862
2863 return text;
2864}
2865
2866bool KTextEditor::ViewPrivate::removeSelectedText()
2867{
2868 if (!hasSelections()) {
2869 return false;
2870 }
2871
2872 KTextEditor::Document::EditingTransaction t(doc());
2873
2874 bool removed = false;
2875 // Handle multicursors selection removal
2876 if (!blockSelect) {
2877 completionWidget()->setIgnoreBufferSignals(true);
2878 for (auto &c : m_secondaryCursors) {
2879 if (c.range) {
2880 removed = true;
2881 doc()->removeText(range: c.range->toRange());
2882 c.clearSelection();
2883 }
2884 }
2885 completionWidget()->setIgnoreBufferSignals(false);
2886 }
2887
2888 // Optimization: clear selection before removing text
2889 KTextEditor::Range selection = m_selection;
2890 if (!selection.isValid()) {
2891 return removed;
2892 }
2893 doc()->removeText(range: selection, block: blockSelect);
2894 removed = true;
2895
2896 // don't redraw the cleared selection - that's done in editEnd().
2897 if (blockSelect) {
2898 int selectionColumn = qMin(a: doc()->toVirtualColumn(selection.start()), b: doc()->toVirtualColumn(selection.end()));
2899 KTextEditor::Range newSelection = selection;
2900 newSelection.setStart(KTextEditor::Cursor(newSelection.start().line(), doc()->fromVirtualColumn(line: newSelection.start().line(), column: selectionColumn)));
2901 newSelection.setEnd(KTextEditor::Cursor(newSelection.end().line(), doc()->fromVirtualColumn(line: newSelection.end().line(), column: selectionColumn)));
2902 setSelection(newSelection);
2903 setCursorPositionInternal(position: newSelection.start());
2904 } else {
2905 clearSecondarySelections();
2906 clearSelection(redraw: false);
2907 }
2908
2909 return removed;
2910}
2911
2912bool KTextEditor::ViewPrivate::selectAll()
2913{
2914 clearSecondaryCursors();
2915 setBlockSelection(false);
2916 // We use setSelection here to ensure we don't scroll anywhere
2917 // The cursor stays in place i.e., it doesn't move to end of selection
2918 // that is okay and expected.
2919 // The idea here is to maintain scroll position in case select all was
2920 // mistakenly triggered, and also to if you just want to copy text,
2921 // there is no need to scroll anywhere.
2922 setSelection(doc()->documentRange());
2923 m_viewInternal->moveCursorToSelectionEdge(/*scroll=*/false);
2924 m_viewInternal->updateMicroFocus();
2925 return true;
2926}
2927
2928bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor cursor)
2929{
2930 KTextEditor::Cursor ret = cursor;
2931 if ((!blockSelect) && (ret.column() < 0)) {
2932 ret.setColumn(0);
2933 }
2934
2935 if (blockSelect) {
2936 return cursor.line() >= m_selection.start().line() && ret.line() <= m_selection.end().line() && ret.column() >= m_selection.start().column()
2937 && ret.column() <= m_selection.end().column();
2938 } else {
2939 return m_selection.toRange().contains(cursor) || m_selection.end() == cursor;
2940 }
2941}
2942
2943bool KTextEditor::ViewPrivate::lineSelected(int line)
2944{
2945 return !blockSelect && m_selection.toRange().containsLine(line);
2946}
2947
2948bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor lineEndPos)
2949{
2950 return (!blockSelect)
2951 && (lineEndPos.line() > m_selection.start().line()
2952 || (lineEndPos.line() == m_selection.start().line() && (m_selection.start().column() < lineEndPos.column() || lineEndPos.column() == -1)))
2953 && (lineEndPos.line() < m_selection.end().line()
2954 || (lineEndPos.line() == m_selection.end().line() && (lineEndPos.column() <= m_selection.end().column() && lineEndPos.column() != -1)));
2955}
2956
2957bool KTextEditor::ViewPrivate::lineHasSelected(int line)
2958{
2959 return selection() && m_selection.toRange().containsLine(line);
2960}
2961
2962bool KTextEditor::ViewPrivate::lineIsSelection(int line)
2963{
2964 return (line == m_selection.start().line() && line == m_selection.end().line());
2965}
2966
2967void KTextEditor::ViewPrivate::tagSelection(KTextEditor::Range oldSelection)
2968{
2969 if (selection()) {
2970 if (oldSelection.start().line() == -1) {
2971 // We have to tag the whole lot if
2972 // 1) we have a selection, and:
2973 // a) it's new; or
2974 tagLines(range: m_selection, realRange: true);
2975
2976 } else if (blockSelection()
2977 && (oldSelection.start().column() != m_selection.start().column() || oldSelection.end().column() != m_selection.end().column())) {
2978 // b) we're in block selection mode and the columns have changed
2979 tagLines(range: m_selection, realRange: true);
2980 tagLines(range: oldSelection, realRange: true);
2981
2982 } else {
2983 if (oldSelection.start() != m_selection.start()) {
2984 tagLines(lineRange: KTextEditor::LineRange(oldSelection.start().line(), m_selection.start().line()), realLines: true);
2985 }
2986
2987 if (oldSelection.end() != m_selection.end()) {
2988 tagLines(lineRange: KTextEditor::LineRange(oldSelection.end().line(), m_selection.end().line()), realLines: true);
2989 }
2990 }
2991
2992 } else {
2993 // No more selection, clean up
2994 tagLines(range: oldSelection, realRange: true);
2995 }
2996}
2997
2998void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor cursor)
2999{
3000 setSelection(doc()->wordRangeAt(cursor));
3001}
3002
3003void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor cursor)
3004{
3005 int line = cursor.line();
3006 if (line + 1 >= doc()->lines()) {
3007 setSelection(KTextEditor::Range(line, 0, line, doc()->lineLength(line)));
3008 } else {
3009 setSelection(KTextEditor::Range(line, 0, line + 1, 0));
3010 }
3011}
3012
3013void KTextEditor::ViewPrivate::cut()
3014{
3015 if (!selection() && !m_config->smartCopyCut()) {
3016 return;
3017 }
3018
3019 // If markedSelection is true, copy() invalidates the selection,
3020 // which would obviate the removeSelectedText() here below.
3021 m_markedSelection = false;
3022
3023 copy();
3024 if (!selection()) {
3025 selectLine(cursor: cursorPosition());
3026 }
3027 removeSelectedText();
3028}
3029
3030void KTextEditor::ViewPrivate::copy()
3031{
3032 QString text;
3033 if (!selection()) {
3034 if (!m_config->smartCopyCut()) {
3035 return;
3036 }
3037 text = doc()->line(line: cursorPosition().line()) + QLatin1Char('\n');
3038 m_viewInternal->moveEdge(bias: KateViewInternal::left, sel: false);
3039 } else {
3040 text = selectionText();
3041
3042 if (m_markedSelection) {
3043 setSelection(KTextEditor::Range::invalid());
3044 m_markedSelection = false;
3045 }
3046 }
3047
3048 // copy to clipboard and our history!
3049 KTextEditor::EditorPrivate::self()->copyToClipboard(text, fileName: m_doc->url().fileName());
3050}
3051
3052void KTextEditor::ViewPrivate::screenshot()
3053{
3054 if (!selection()) {
3055 return;
3056 }
3057
3058 ScreenshotDialog d(selectionRange(), this);
3059 d.renderScreenshot(renderer: m_renderer);
3060 d.exec();
3061}
3062
3063void KTextEditor::ViewPrivate::pasteSelection()
3064{
3065 m_temporaryAutomaticInvocationDisabled = true;
3066 doc()->paste(view: this, text: QApplication::clipboard()->text(mode: QClipboard::Selection));
3067 m_temporaryAutomaticInvocationDisabled = false;
3068}
3069
3070void KTextEditor::ViewPrivate::pasteFromFile()
3071{
3072 QUrl insertFromUrl = QFileDialog::getOpenFileUrl(parent: parentWidget(), i18n("Open File"), dir: doc()->startUrlForFileDialog());
3073
3074 // Open a new KTextEditor::Document of file to be inserted
3075 KTextEditor::DocumentPrivate insertDocument;
3076 insertDocument.openUrl(url: insertFromUrl);
3077
3078 // Insert document()->text() at the current cursor position
3079 document()->insertText(position: cursorPosition(), text: insertDocument.text());
3080}
3081
3082void KTextEditor::ViewPrivate::swapWithClipboard()
3083{
3084 m_temporaryAutomaticInvocationDisabled = true;
3085
3086 // get text to paste
3087 const auto text = QApplication::clipboard()->text(mode: QClipboard::Clipboard);
3088
3089 // do copy
3090 copy();
3091
3092 // do paste of "previous" clipboard content we saved
3093 doc()->paste(view: this, text);
3094
3095 m_temporaryAutomaticInvocationDisabled = false;
3096}
3097
3098void KTextEditor::ViewPrivate::applyWordWrap()
3099{
3100 int first = selectionRange().start().line();
3101 int last = selectionRange().end().line();
3102
3103 if (first == last) {
3104 // Either no selection or only one line selected, wrap only the current line
3105 first = cursorPosition().line();
3106 last = first;
3107 }
3108
3109 doc()->wrapParagraph(first, last);
3110}
3111
3112// END
3113
3114// BEGIN KTextEditor::BlockSelectionInterface stuff
3115
3116bool KTextEditor::ViewPrivate::blockSelection() const
3117{
3118 return blockSelect;
3119}
3120
3121bool KTextEditor::ViewPrivate::setBlockSelection(bool on)
3122{
3123 if (on != blockSelect) {
3124 blockSelect = on;
3125
3126 KTextEditor::Range oldSelection = m_selection;
3127
3128 const bool hadSelection = clearSelection(redraw: false, finishedChangingSelection: false);
3129
3130 setSelection(oldSelection);
3131
3132 m_toggleBlockSelection->setChecked(blockSelection());
3133
3134 // when leaving block selection mode, if cursor is at an invalid position or past the end of the
3135 // line, move the cursor to the last column of the current line unless cursor wrapping is off
3136 ensureCursorColumnValid();
3137
3138 if (!hadSelection) {
3139 // emit selectionChanged() according to the KTextEditor::View api
3140 // documentation also if there is no selection around. This is needed,
3141 // as e.g. the Kate App status bar uses this signal to update the state
3142 // of the selection mode (block selection, line based selection)
3143 Q_EMIT selectionChanged(view: this);
3144 }
3145 }
3146
3147 return true;
3148}
3149
3150bool KTextEditor::ViewPrivate::toggleBlockSelection()
3151{
3152 // no multicursors for blockselect
3153 clearSecondaryCursors();
3154
3155 m_toggleBlockSelection->setChecked(!blockSelect);
3156 return setBlockSelection(!blockSelect);
3157}
3158
3159bool KTextEditor::ViewPrivate::wrapCursor() const
3160{
3161 return !blockSelection();
3162}
3163
3164// END
3165
3166void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor position, const QString &text)
3167{
3168 Q_EMIT textInserted(view, position, text);
3169}
3170
3171bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor c, const QString &templateString, const QString &script)
3172{
3173 // no empty templates
3174 if (templateString.isEmpty()) {
3175 return false;
3176 }
3177
3178 // not for read-only docs
3179 if (!doc()->isReadWrite()) {
3180 return false;
3181 }
3182
3183 // only one handler maybe active at a time; store it in the document.
3184 // Clear it first to make sure at no time two handlers are active at once
3185 doc()->setActiveTemplateHandler(nullptr);
3186 doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, doc()->undoManager()));
3187 return true;
3188}
3189
3190bool KTextEditor::ViewPrivate::evaluateScriptInternal(const QString &script, QVariant *result)
3191{
3192 KTextEditor::Document::EditingTransaction transaction(document());
3193 // separate script into a "library" and a "program", in order to match logic in KateScript::evaluate
3194 KateScript kateScript(QStringLiteral("function _runKateScript() {\n") + script + QStringLiteral("\n}"), KateScript::InputSCRIPT);
3195 if (!kateScript.load()) {
3196 if (result) {
3197 *result = kateScript.errorMessage();
3198 }
3199 return false;
3200 }
3201 kateScript.setView(this);
3202 auto res = kateScript.evaluate(QStringLiteral("_runKateScript();"));
3203 if (result) {
3204 *result = res.isError() ? res.toString() : res.toVariant(behavior: QJSValue::ConvertJSObjects);
3205 }
3206 return (!res.isError());
3207}
3208
3209bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange)
3210{
3211 return tagLines(start: range.start(), end: range.end(), realCursors: realRange);
3212}
3213
3214void KTextEditor::ViewPrivate::deactivateEditActions()
3215{
3216 for (QAction *action : std::as_const(t&: m_editActions)) {
3217 action->setEnabled(false);
3218 }
3219}
3220
3221void KTextEditor::ViewPrivate::activateEditActions()
3222{
3223 for (QAction *action : std::as_const(t&: m_editActions)) {
3224 action->setEnabled(true);
3225 }
3226}
3227
3228bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const
3229{
3230 // FIXME support
3231 return true;
3232}
3233
3234bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool)
3235{
3236 // FIXME support
3237 return true;
3238}
3239
3240bool KTextEditor::ViewPrivate::isMulticursorNotAllowed() const
3241{
3242 return blockSelection() || isOverwriteMode() || currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode;
3243}
3244
3245void KTextEditor::ViewPrivate::addSecondaryCursor(KTextEditor::Cursor pos)
3246{
3247 auto primaryCursor = cursorPosition();
3248 const bool overlapsOrOnPrimary = pos == primaryCursor || (selection() && selectionRange().contains(cursor: pos));
3249 if (overlapsOrOnPrimary && m_secondaryCursors.empty()) {
3250 // Clicking on primary cursor while it is the only cursor,
3251 // we do nothing
3252 return;
3253 } else if (overlapsOrOnPrimary) {
3254 // Clicking on primary cursor, we have secondaries
3255 // so just make the last secondary cursor primary
3256 // and remove caret at current primary cursor position
3257 auto &last = m_secondaryCursors.back();
3258 setCursorPosition(last.cursor());
3259 if (last.range) {
3260 setSelection(last.range->toRange());
3261 Q_ASSERT(last.anchor.isValid());
3262 m_viewInternal->m_selectAnchor = last.anchor;
3263 }
3264 m_secondaryCursors.pop_back();
3265 return;
3266 }
3267
3268 // If there are any existing cursors at this position
3269 // remove them and be done i.e., if you click on an
3270 // existing cursor it is removed.
3271 if (removeSecondaryCursors(cursorToRemove: {pos}, /*removeIfSelectionOverlap=*/removeIfOverlapsSelection: true)) {
3272 return;
3273 }
3274
3275 // We are adding a new cursor!
3276 // - Move primary cursor to the position where the click happened
3277 // - Old primary cursor becomes a secondary cursor
3278 // Doing it like this makes multi mouse selections very easy
3279 setCursorPosition(pos);
3280 KTextEditor::ViewPrivate::PlainSecondaryCursor p;
3281 p.pos = primaryCursor;
3282 p.range = selection() ? selectionRange() : KTextEditor::Range::invalid();
3283 clearSelection();
3284 addSecondaryCursorsWithSelection(cursorsWithSelection: {p});
3285}
3286
3287void KTextEditor::ViewPrivate::setSecondaryCursors(const QList<KTextEditor::Cursor> &positions)
3288{
3289 clearSecondaryCursors();
3290
3291 if (positions.isEmpty() || isMulticursorNotAllowed()) {
3292 return;
3293 }
3294
3295 const auto totalLines = doc()->lines();
3296 for (auto p : positions) {
3297 if (p != cursorPosition() && p.line() < totalLines) {
3298 SecondaryCursor c;
3299 c.pos.reset(p: static_cast<Kate::TextCursor *>(doc()->newMovingCursor(position: p)));
3300 m_secondaryCursors.push_back(x: std::move(c));
3301 tagLine(virtualCursor: p);
3302 }
3303 }
3304 sortCursors();
3305 paintCursors();
3306}
3307
3308void KTextEditor::ViewPrivate::clearSecondarySelections()
3309{
3310 for (auto &c : m_secondaryCursors) {
3311 c.clearSelection();
3312 }
3313}
3314
3315void KTextEditor::ViewPrivate::clearSecondaryCursors()
3316{
3317 if (m_secondaryCursors.empty()) {
3318 return;
3319 }
3320 for (const auto &c : m_secondaryCursors) {
3321 tagLine(virtualCursor: c.cursor());
3322 }
3323 m_secondaryCursors.clear();
3324 m_viewInternal->updateDirty();
3325}
3326
3327const std::vector<KTextEditor::ViewPrivate::SecondaryCursor> &KTextEditor::ViewPrivate::secondaryCursors() const
3328{
3329 return m_secondaryCursors;
3330}
3331
3332QList<KTextEditor::ViewPrivate::PlainSecondaryCursor> KTextEditor::ViewPrivate::plainSecondaryCursors() const
3333{
3334 QList<PlainSecondaryCursor> cursors;
3335 cursors.reserve(asize: m_secondaryCursors.size());
3336 std::transform(first: m_secondaryCursors.begin(), last: m_secondaryCursors.end(), result: std::back_inserter(x&: cursors), unary_op: [](const SecondaryCursor &c) {
3337 if (c.range) {
3338 return PlainSecondaryCursor{.pos = c.cursor(), .range = c.range->toRange()};
3339 }
3340 return PlainSecondaryCursor{.pos = c.cursor(), .range = KTextEditor::Range::invalid()};
3341 });
3342 return cursors;
3343}
3344
3345bool KTextEditor::ViewPrivate::removeSecondaryCursors(const std::vector<KTextEditor::Cursor> &cursorsToRemove, bool removeIfOverlapsSelection)
3346{
3347 Q_ASSERT(std::is_sorted(cursorsToRemove.begin(), cursorsToRemove.end()));
3348
3349 QVarLengthArray<KTextEditor::Cursor, 8> linesToTag;
3350
3351 if (removeIfOverlapsSelection) {
3352 m_secondaryCursors.erase(first: std::remove_if(first: m_secondaryCursors.begin(),
3353 last: m_secondaryCursors.end(),
3354 pred: [&](const SecondaryCursor &c) {
3355 auto it = std::find_if(first: cursorsToRemove.begin(), last: cursorsToRemove.end(), pred: [&c](KTextEditor::Cursor pos) {
3356 return c.cursor() == pos || (c.range && c.range->contains(cursor: pos));
3357 });
3358 const bool match = it != cursorsToRemove.end();
3359 if (match) {
3360 linesToTag.push_back(t: c.cursor());
3361 }
3362 return match;
3363 }),
3364 last: m_secondaryCursors.end());
3365 } else {
3366 m_secondaryCursors.erase(first: std::remove_if(first: m_secondaryCursors.begin(),
3367 last: m_secondaryCursors.end(),
3368 pred: [&](const SecondaryCursor &c) {
3369 auto it = std::find_if(first: cursorsToRemove.begin(), last: cursorsToRemove.end(), pred: [&c](KTextEditor::Cursor pos) {
3370 return c.cursor() == pos;
3371 });
3372 const bool match = it != cursorsToRemove.end();
3373 if (match) {
3374 linesToTag.push_back(t: c.cursor());
3375 }
3376 return match;
3377 }),
3378 last: m_secondaryCursors.end());
3379 }
3380
3381 for (const auto &c : linesToTag) {
3382 tagLine(virtualCursor: m_viewInternal->toVirtualCursor(realCursor: c));
3383 }
3384 return !linesToTag.empty();
3385
3386 for (auto cur : cursorsToRemove) {
3387 auto &sec = m_secondaryCursors;
3388 auto it = std::find_if(first: sec.begin(), last: sec.end(), pred: [cur](const SecondaryCursor &c) {
3389 return c.cursor() == cur;
3390 });
3391 if (it != sec.end()) {
3392 // removedAny = true;
3393 m_secondaryCursors.erase(position: it);
3394 tagLine(virtualCursor: m_viewInternal->toVirtualCursor(realCursor: cur));
3395 }
3396 }
3397
3398 // if (removedAny) {
3399 m_viewInternal->updateDirty();
3400 if (cursorPosition() == KTextEditor::Cursor(0, 0)) {
3401 m_viewInternal->paintCursor();
3402 }
3403 return !linesToTag.empty();
3404 // }
3405 // return removedAny;
3406}
3407
3408void KTextEditor::ViewPrivate::ensureUniqueCursors(bool matchLine)
3409{
3410 if (m_secondaryCursors.empty()) {
3411 return;
3412 }
3413
3414 std::vector<SecondaryCursor>::iterator it;
3415 if (matchLine) {
3416 auto matchLine = [](const SecondaryCursor &l, const SecondaryCursor &r) {
3417 return l.cursor().line() == r.cursor().line();
3418 };
3419 it = std::unique(first: m_secondaryCursors.begin(), last: m_secondaryCursors.end(), binary_pred: matchLine);
3420 } else {
3421 it = std::unique(first: m_secondaryCursors.begin(), last: m_secondaryCursors.end());
3422 }
3423 if (it != m_secondaryCursors.end()) {
3424 m_secondaryCursors.erase(first: it, last: m_secondaryCursors.end());
3425 }
3426
3427 if (matchLine) {
3428 const int ln = cursorPosition().line();
3429 m_secondaryCursors.erase(first: std::remove_if(first: m_secondaryCursors.begin(),
3430 last: m_secondaryCursors.end(),
3431 pred: [ln](const SecondaryCursor &c) {
3432 return c.cursor().line() == ln;
3433 }),
3434 last: m_secondaryCursors.end());
3435 } else {
3436 const auto cp = cursorPosition();
3437 const auto sel = selectionRange();
3438 m_secondaryCursors.erase(first: std::remove_if(first: m_secondaryCursors.begin(),
3439 last: m_secondaryCursors.end(),
3440 pred: [cp, sel](const SecondaryCursor &c) {
3441 return c.cursor() == cp && c.selectionRange() == sel;
3442 }),
3443 last: m_secondaryCursors.end());
3444 }
3445}
3446
3447void KTextEditor::ViewPrivate::addSecondaryCursorsWithSelection(const QList<PlainSecondaryCursor> &cursorsWithSelection)
3448{
3449 if (isMulticursorNotAllowed() || cursorsWithSelection.isEmpty()) {
3450 return;
3451 }
3452
3453 for (const auto &c : cursorsWithSelection) {
3454 // We don't want to add on top of primary cursor
3455 if (c.pos == cursorPosition()) {
3456 continue;
3457 }
3458 SecondaryCursor n;
3459 n.pos.reset(p: static_cast<Kate::TextCursor *>(doc()->newMovingCursor(position: c.pos)));
3460 if (c.range.isValid()) {
3461 n.range.reset(p: newSecondarySelectionRange(c.range));
3462 n.anchor = c.range.start() == c.pos ? c.range.end() : c.range.start();
3463 }
3464 m_secondaryCursors.push_back(x: std::move(n));
3465 }
3466 sortCursors();
3467 paintCursors();
3468}
3469
3470Kate::TextRange *KTextEditor::ViewPrivate::newSecondarySelectionRange(KTextEditor::Range selRange)
3471{
3472 constexpr auto expandBehaviour = KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight;
3473 auto range = new Kate::TextRange(&doc()->buffer(), selRange, expandBehaviour);
3474 static KTextEditor::Attribute::Ptr selAttr;
3475 if (!selAttr) {
3476 selAttr = new KTextEditor::Attribute;
3477 auto color = QColor::fromRgba(rgba: theme().editorColor(role: KSyntaxHighlighting::Theme::TextSelection));
3478 selAttr->setBackground(color);
3479 }
3480 range->setZDepth(-999999.);
3481 range->setAttribute(selAttr);
3482 return range;
3483}
3484
3485bool KTextEditor::ViewPrivate::hasSelections() const
3486{
3487 if (selection())
3488 return true;
3489 return std::any_of(first: m_secondaryCursors.cbegin(), last: m_secondaryCursors.cend(), pred: [](const SecondaryCursor &c) {
3490 return c.range && !c.range->isEmpty();
3491 });
3492}
3493
3494void KTextEditor::ViewPrivate::addSecondaryCursorDown()
3495{
3496 KTextEditor::Cursor last = cursorPosition();
3497 const auto &secondary = secondaryCursors();
3498 if (!secondary.empty()) {
3499 last = secondary.back().cursor();
3500 last = std::max(a: cursorPosition(), b: last);
3501 }
3502 if (last.line() >= doc()->lastLine()) {
3503 return;
3504 }
3505
3506 auto nextRange = m_viewInternal->nextLayout(c: last);
3507 if (!nextRange.isValid()) {
3508 return;
3509 }
3510 auto primaryCursorLineLayout = m_viewInternal->currentLayout(c: cursorPosition());
3511 if (!primaryCursorLineLayout.isValid()) {
3512 return;
3513 }
3514
3515 int x = renderer()->cursorToX(range: primaryCursorLineLayout, col: cursorPosition().column(), returnPastLine: !wrapCursor());
3516 auto next = renderer()->xToCursor(range: nextRange, x, returnPastLine: !wrapCursor());
3517 addSecondaryCursor(pos: next);
3518}
3519
3520void KTextEditor::ViewPrivate::addSecondaryCursorUp()
3521{
3522 KTextEditor::Cursor last = cursorPosition();
3523 const auto &secondary = secondaryCursors();
3524 if (!secondary.empty()) {
3525 last = secondary.front().cursor();
3526 last = std::min(a: cursorPosition(), b: last);
3527 }
3528 if (last.line() == 0) {
3529 return;
3530 }
3531 auto nextRange = m_viewInternal->previousLayout(c: last);
3532 if (!nextRange.isValid()) {
3533 return;
3534 }
3535
3536 auto primaryCursorLineLayout = m_viewInternal->currentLayout(c: cursorPosition());
3537 if (!primaryCursorLineLayout.isValid()) {
3538 return;
3539 }
3540
3541 int x = renderer()->cursorToX(range: primaryCursorLineLayout, col: cursorPosition().column(), returnPastLine: !wrapCursor());
3542 auto next = renderer()->xToCursor(range: nextRange, x, returnPastLine: !wrapCursor());
3543 addSecondaryCursor(pos: next);
3544}
3545
3546QList<KTextEditor::Cursor> KTextEditor::ViewPrivate::cursors() const
3547{
3548 QList<KTextEditor::Cursor> ret;
3549 ret.reserve(asize: m_secondaryCursors.size() + 1);
3550 ret << cursorPosition();
3551 std::transform(first: m_secondaryCursors.begin(), last: m_secondaryCursors.end(), result: std::back_inserter(x&: ret), unary_op: [](const SecondaryCursor &c) {
3552 return c.cursor();
3553 });
3554 return ret;
3555}
3556
3557QList<KTextEditor::Range> KTextEditor::ViewPrivate::selectionRanges() const
3558{
3559 if (!selection()) {
3560 return {};
3561 }
3562
3563 QList<KTextEditor::Range> ret;
3564 ret.reserve(asize: m_secondaryCursors.size() + 1);
3565 ret << selectionRange();
3566 std::transform(first: m_secondaryCursors.begin(), last: m_secondaryCursors.end(), result: std::back_inserter(x&: ret), unary_op: [](const SecondaryCursor &c) {
3567 if (!c.range) {
3568 qWarning() << "selectionRanges(): Unexpected null selection range, please fix";
3569 return KTextEditor::Range::invalid();
3570 }
3571 return c.range->toRange();
3572 });
3573 return ret;
3574}
3575
3576void KTextEditor::ViewPrivate::setCursors(const QList<KTextEditor::Cursor> &cursorPositions)
3577{
3578 if (isMulticursorNotAllowed()) {
3579 qWarning() << "setCursors failed: Multicursors not allowed because one of the following is true"
3580 << ", blockSelection: " << blockSelection() << ", overwriteMode: " << isOverwriteMode()
3581 << ", viMode: " << (currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode);
3582 return;
3583 }
3584
3585 clearSecondaryCursors();
3586 if (cursorPositions.empty()) {
3587 return;
3588 }
3589
3590 const auto primary = cursorPositions.front();
3591 // We clear primary selection because primary and secondary
3592 // cursors should always have same selection state
3593 setSelection({});
3594 setCursorPosition(primary);
3595 // First will be auto ignored because it equals cursorPosition()
3596 setSecondaryCursors(cursorPositions);
3597}
3598
3599void KTextEditor::ViewPrivate::setSelections(const QList<KTextEditor::Range> &selectionRanges)
3600{
3601 if (isMulticursorNotAllowed()) {
3602 qWarning() << "setSelections failed: Multicursors not allowed because one of the following is true"
3603 << ", blockSelection: " << blockSelection() << ", overwriteMode: " << isOverwriteMode()
3604 << ", viMode: " << (currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode);
3605 return;
3606 }
3607
3608 clearSecondaryCursors();
3609 setSelection({});
3610 if (selectionRanges.isEmpty()) {
3611 return;
3612 }
3613
3614 auto first = selectionRanges.front();
3615 setCursorPosition(first.end());
3616 setSelection(first);
3617
3618 if (selectionRanges.size() == 1) {
3619 return;
3620 }
3621
3622 const auto docRange = doc()->documentRange();
3623 for (auto it = selectionRanges.begin() + 1; it != selectionRanges.end(); ++it) {
3624 KTextEditor::Range r = *it;
3625 KTextEditor::Cursor c = r.end();
3626 if (c == cursorPosition() || !r.isValid() || r.isEmpty() || !docRange.contains(range: r)) {
3627 continue;
3628 }
3629
3630 SecondaryCursor n;
3631 n.pos.reset(p: static_cast<Kate::TextCursor *>(doc()->newMovingCursor(position: c)));
3632 n.range.reset(p: newSecondarySelectionRange(selRange: r));
3633 n.anchor = r.start();
3634 m_secondaryCursors.push_back(x: std::move(n));
3635 }
3636 m_viewInternal->mergeSelections();
3637
3638 sortCursors();
3639 paintCursors();
3640}
3641
3642void KTextEditor::ViewPrivate::sortCursors()
3643{
3644 std::sort(first: m_secondaryCursors.begin(), last: m_secondaryCursors.end());
3645 ensureUniqueCursors();
3646}
3647
3648void KTextEditor::ViewPrivate::paintCursors()
3649{
3650 if (m_viewInternal->m_cursorTimer.isActive()) {
3651 if (QApplication::cursorFlashTime() > 0) {
3652 m_viewInternal->m_cursorTimer.start(msec: QApplication::cursorFlashTime() / 2);
3653 }
3654 renderer()->setDrawCaret(true);
3655 }
3656 m_viewInternal->paintCursor();
3657}
3658
3659bool KTextEditor::ViewPrivate::isCompletionActive() const
3660{
3661 return completionWidget()->isCompletionActive();
3662}
3663
3664KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const
3665{
3666 if (!m_completionWidget) {
3667 m_completionWidget = new KateCompletionWidget(const_cast<KTextEditor::ViewPrivate *>(this));
3668 }
3669
3670 return m_completionWidget;
3671}
3672
3673void KTextEditor::ViewPrivate::startCompletion(KTextEditor::Range word, KTextEditor::CodeCompletionModel *model)
3674{
3675 completionWidget()->startCompletion(word, model);
3676}
3677
3678void KTextEditor::ViewPrivate::startCompletion(const Range &word,
3679 const QList<KTextEditor::CodeCompletionModel *> &models,
3680 KTextEditor::CodeCompletionModel::InvocationType invocationType)
3681{
3682 completionWidget()->startCompletion(word, models, invocationType);
3683}
3684
3685void KTextEditor::ViewPrivate::abortCompletion()
3686{
3687 completionWidget()->abortCompletion();
3688}
3689
3690void KTextEditor::ViewPrivate::forceCompletion()
3691{
3692 completionWidget()->execute();
3693}
3694
3695void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model)
3696{
3697 completionWidget()->registerCompletionModel(model);
3698}
3699
3700void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model)
3701{
3702 completionWidget()->unregisterCompletionModel(model);
3703}
3704
3705bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const
3706{
3707 return completionWidget()->isCompletionModelRegistered(model);
3708}
3709
3710QList<KTextEditor::CodeCompletionModel *> KTextEditor::ViewPrivate::codeCompletionModels() const
3711{
3712 return completionWidget()->codeCompletionModels();
3713}
3714
3715bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const
3716{
3717 return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation();
3718}
3719
3720void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled)
3721{
3722 config()->setValue(key: KateViewConfig::AutomaticCompletionInvocation, value: enabled);
3723}
3724
3725void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index)
3726{
3727 Q_EMIT completionExecuted(view: this, position, model, index);
3728}
3729
3730void KTextEditor::ViewPrivate::sendCompletionAborted()
3731{
3732 Q_EMIT completionAborted(view: this);
3733}
3734
3735void KTextEditor::ViewPrivate::paste(const QString *textToPaste)
3736{
3737 const int cursorCount = m_secondaryCursors.size() + 1; // 1 primary cursor
3738 if (!textToPaste && cursorCount > 1) {
3739 // We still have multiple cursors, but the amount
3740 // of multicursors doesn't match the entry count in clipboard
3741 QString clipboard = QApplication::clipboard()->text(mode: QClipboard::Clipboard);
3742 static const QRegularExpression lineEndings(QStringLiteral("\r\n?"));
3743 clipboard.replace(re: lineEndings, QStringLiteral("\n"));
3744
3745 // 1. Try to see if the number of lines in clipboard text == number of cursors
3746 QStringList texts = clipboard.split(sep: u'\n', behavior: Qt::KeepEmptyParts);
3747
3748 if (texts.size() != cursorCount) {
3749 // otherwise paste the clipboard text at each cursor position
3750 texts.clear();
3751 for (int i = 0; i < cursorCount; ++i) {
3752 texts << clipboard;
3753 }
3754 }
3755 // It might still fail for e.g., if we are in block mode,
3756 // in that case we will fallback to normal pasting below
3757 if (doc()->multiPaste(view: this, texts)) {
3758 return;
3759 }
3760 }
3761
3762 m_temporaryAutomaticInvocationDisabled = true;
3763 doc()->paste(view: this, text: textToPaste ? *textToPaste : QApplication::clipboard()->text(mode: QClipboard::Clipboard));
3764 m_temporaryAutomaticInvocationDisabled = false;
3765}
3766
3767bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position)
3768{
3769 return setCursorPositionInternal(position, tabwidth: 1, calledExternally: true);
3770}
3771
3772KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const
3773{
3774 return m_viewInternal->cursorPosition();
3775}
3776
3777KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const
3778{
3779 return KTextEditor::Cursor(m_viewInternal->cursorPosition().line(), virtualCursorColumn());
3780}
3781
3782QPoint KTextEditor::ViewPrivate::cursorToCoordinate(KTextEditor::Cursor cursor) const
3783{
3784 // map from ViewInternal to View coordinates
3785 const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, realCursor: true, includeBorder: false);
3786 return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt);
3787}
3788
3789KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const
3790{
3791 // map from View to ViewInternal coordinates
3792 return m_viewInternal->coordinatesToCursor(coord: m_viewInternal->mapFromParent(coords), includeBorder: false);
3793}
3794
3795QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const
3796{
3797 // map from ViewInternal to View coordinates
3798 const QPoint pt = m_viewInternal->cursorCoordinates(includeBorder: false);
3799 return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt);
3800}
3801
3802void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor cursor)
3803{
3804 m_viewInternal->scrollPos(c&: cursor, force: false, calledExternally: true, emitSignals: false);
3805}
3806
3807void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x)
3808{
3809 m_viewInternal->scrollColumns(x);
3810}
3811
3812KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const
3813{
3814 return m_viewInternal->maxStartPos(changed: true);
3815}
3816
3817int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const
3818{
3819 if (lineType == RealLine) {
3820 return m_textFolding.visibleLineToLine(visibleLine: m_viewInternal->startLine());
3821 } else {
3822 return m_viewInternal->startLine();
3823 }
3824}
3825
3826int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const
3827{
3828 if (lineType == RealLine) {
3829 return m_textFolding.visibleLineToLine(visibleLine: m_viewInternal->endLine());
3830 } else {
3831 return m_viewInternal->endLine();
3832 }
3833}
3834
3835QRect KTextEditor::ViewPrivate::textAreaRectInternal() const
3836{
3837 const auto sourceRect = m_viewInternal->rect();
3838 const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft());
3839 const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight());
3840 return {topLeft, bottomRight};
3841}
3842
3843bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor position)
3844{
3845 return setCursorPositionInternal(position, tabwidth: doc()->config()->tabWidth(), calledExternally: true);
3846}
3847
3848QScrollBar *KTextEditor::ViewPrivate::verticalScrollBar() const
3849{
3850 return m_viewInternal->m_lineScroll;
3851}
3852
3853QScrollBar *KTextEditor::ViewPrivate::horizontalScrollBar() const
3854{
3855 return m_viewInternal->m_columnScroll;
3856}
3857
3858bool KTextEditor::ViewPrivate::isLineRTL(int line) const
3859{
3860 const QString s = doc()->line(line);
3861 if (s.isEmpty()) {
3862 int line = cursorPosition().line();
3863 if (line == 0) {
3864 const int count = doc()->lines();
3865 for (int i = 1; i < count; ++i) {
3866 const QString ln = doc()->line(line: i);
3867 if (ln.isEmpty()) {
3868 continue;
3869 }
3870 return ln.isRightToLeft();
3871 }
3872 } else {
3873 int line = cursorPosition().line();
3874 for (; line >= 0; --line) {
3875 const QString s = doc()->line(line);
3876 if (s.isEmpty()) {
3877 continue;
3878 }
3879 return s.isRightToLeft();
3880 }
3881 }
3882 return false;
3883 } else {
3884 return s.isRightToLeft();
3885 }
3886}
3887
3888const QTextLayout *KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor pos) const
3889{
3890 KateLineLayout *thisLine = m_viewInternal->cache()->line(realLine: pos.line());
3891 return thisLine && thisLine->isValid() ? &thisLine->layout() : nullptr;
3892}
3893
3894void KTextEditor::ViewPrivate::indent()
3895{
3896 if (blockSelect && selection()) {
3897 for (int line = selectionRange().start().line(); line <= selectionRange().end().line(); line++) {
3898 KTextEditor::Cursor c(line, 0);
3899 KTextEditor::Range r = KTextEditor::Range(c, c);
3900 doc()->indent(range: r, change: 1);
3901 }
3902 } else {
3903 KTextEditor::Cursor c(cursorPosition().line(), 0);
3904 QSet<int> indentedLines = {c.line()};
3905 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c);
3906 doc()->indent(range: r, change: 1);
3907 // indent secondary cursors
3908 for (const auto &cursor : secondaryCursors()) {
3909 int line = cursor.cursor().line();
3910 if (indentedLines.contains(value: line)) {
3911 continue;
3912 }
3913 indentedLines.insert(value: line);
3914 KTextEditor::Cursor c(line, 0);
3915 KTextEditor::Range r = cursor.range ? cursor.range->toRange() : KTextEditor::Range(c, c);
3916 doc()->indent(range: r, change: 1);
3917 }
3918 }
3919}
3920
3921void KTextEditor::ViewPrivate::unIndent()
3922{
3923 if (blockSelect && selection()) {
3924 for (int line = selectionRange().start().line(); line <= selectionRange().end().line(); line++) {
3925 KTextEditor::Cursor c(line, 0);
3926 KTextEditor::Range r = KTextEditor::Range(c, c);
3927 doc()->indent(range: r, change: -1);
3928 }
3929 } else {
3930 KTextEditor::Cursor c(cursorPosition().line(), 0);
3931 QSet<int> indentedLines = {c.line()};
3932 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c);
3933 doc()->indent(range: r, change: -1);
3934 // indent secondary cursors
3935 for (const auto &cursor : secondaryCursors()) {
3936 int line = cursor.cursor().line();
3937 if (indentedLines.contains(value: line)) {
3938 continue;
3939 }
3940 indentedLines.insert(value: line);
3941 KTextEditor::Cursor c(line, 0);
3942 KTextEditor::Range r = cursor.range ? cursor.range->toRange() : KTextEditor::Range(c, c);
3943 doc()->indent(range: r, change: -1);
3944 }
3945 }
3946}
3947
3948void KTextEditor::ViewPrivate::cleanIndent()
3949{
3950 KTextEditor::Cursor c(cursorPosition().line(), 0);
3951 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c);
3952 doc()->indent(range: r, change: 0);
3953}
3954
3955void KTextEditor::ViewPrivate::formatIndent()
3956{
3957 // no selection: align current line; selection: use selection range
3958 const int line = cursorPosition().line();
3959 KTextEditor::Range formatRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0));
3960 if (selection()) {
3961 formatRange = selectionRange();
3962 }
3963
3964 doc()->align(view: this, range: formatRange);
3965}
3966
3967// alias of formatIndent, for backward compatibility
3968void KTextEditor::ViewPrivate::align()
3969{
3970 formatIndent();
3971}
3972
3973void KTextEditor::ViewPrivate::alignOn()
3974{
3975 static QString pattern;
3976 KTextEditor::Range range;
3977 if (!selection()) {
3978 range = doc()->documentRange();
3979 } else {
3980 range = selectionRange();
3981 }
3982 bool ok;
3983 pattern = QInputDialog::getText(parent: window(), i18n("Align On"), i18n("Alignment pattern:"), echo: QLineEdit::Normal, text: pattern, ok: &ok);
3984 if (!ok) {
3985 return;
3986 }
3987 doc()->alignOn(range, pattern, blockwise: this->blockSelection());
3988}
3989
3990void KTextEditor::ViewPrivate::comment()
3991{
3992 m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight);
3993 doc()->comment(view: this, line: cursorPosition().line(), column: cursorPosition().column(), change: DocumentPrivate::Comment);
3994 m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight);
3995}
3996
3997void KTextEditor::ViewPrivate::uncomment()
3998{
3999 doc()->comment(view: this, line: cursorPosition().line(), column: cursorPosition().column(), change: DocumentPrivate::UnComment);
4000}
4001
4002void KTextEditor::ViewPrivate::toggleComment()
4003{
4004 m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight);
4005 doc()->comment(view: this, line: cursorPosition().line(), column: cursorPosition().column(), change: DocumentPrivate::ToggleComment);
4006 m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight);
4007}
4008
4009void KTextEditor::ViewPrivate::uppercase()
4010{
4011 doc()->transform(view: this, cursorPosition(), KTextEditor::DocumentPrivate::Uppercase);
4012}
4013
4014void KTextEditor::ViewPrivate::killLine()
4015{
4016 std::vector<int> linesToRemove;
4017 if (m_selection.isEmpty()) {
4018 // collect lines of all cursors
4019 linesToRemove.reserve(n: m_secondaryCursors.size() + 1);
4020 for (const auto &c : m_secondaryCursors) {
4021 linesToRemove.push_back(x: c.pos->line());
4022 }
4023 // add primary cursor line
4024 linesToRemove.push_back(x: cursorPosition().line());
4025 } else {
4026 linesToRemove.reserve(n: m_secondaryCursors.size() + 1);
4027 for (const auto &c : m_secondaryCursors) {
4028 const auto &range = c.range;
4029 if (!range) {
4030 continue;
4031 }
4032 for (int line = range->end().line(); line >= range->start().line(); line--) {
4033 linesToRemove.push_back(x: line);
4034 }
4035 }
4036
4037 // cache endline, else that moves and we might delete complete document if last line is selected!
4038 for (int line = m_selection.end().line(), endLine = m_selection.start().line(); line >= endLine; line--) {
4039 linesToRemove.push_back(x: line);
4040 }
4041 }
4042
4043 std::sort(first: linesToRemove.begin(), last: linesToRemove.end(), comp: std::greater{});
4044 linesToRemove.erase(first: std::unique(first: linesToRemove.begin(), last: linesToRemove.end()), last: linesToRemove.end());
4045
4046 doc()->editStart();
4047 // clear selections after editStart so that they are saved in undo.
4048 // We might have a lot of moving range selections which can make killLine very slow
4049 clearSecondarySelections();
4050 int removeCount = 0;
4051 for (int line : linesToRemove) {
4052 doc()->removeLine(line);
4053 // every 1000 lines, uniquify the cursors to ensure we dont end up accumulating
4054 // too many cursors in the buffer and slowing down too much
4055 if (removeCount++ > 1000) {
4056 ensureUniqueCursors();
4057 removeCount = 0;
4058 }
4059 }
4060 doc()->editEnd();
4061
4062 ensureUniqueCursors();
4063}
4064
4065void KTextEditor::ViewPrivate::lowercase()
4066{
4067 doc()->transform(view: this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase);
4068}
4069
4070void KTextEditor::ViewPrivate::capitalize()
4071{
4072 doc()->editStart();
4073 doc()->transform(view: this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase);
4074 doc()->transform(view: this, cursorPosition(), KTextEditor::DocumentPrivate::Capitalize);
4075 doc()->editEnd();
4076}
4077
4078void KTextEditor::ViewPrivate::keyReturn()
4079{
4080 doc()->newLine(view: this);
4081 m_viewInternal->iconBorder()->updateForCursorLineChange();
4082 m_viewInternal->updateView();
4083}
4084
4085void KTextEditor::ViewPrivate::smartNewline()
4086{
4087 const KTextEditor::Cursor cursor = cursorPosition();
4088 const int ln = cursor.line();
4089 Kate::TextLine line = doc()->kateTextLine(i: ln);
4090 int col = qMin(a: cursor.column(), b: line.firstChar());
4091 if (col != -1) {
4092 while (line.length() > col && !(line.at(column: col).isLetterOrNumber() || line.at(column: col) == QLatin1Char('_')) && col < cursor.column()) {
4093 ++col;
4094 }
4095 } else {
4096 col = line.length(); // stay indented
4097 }
4098 doc()->editStart();
4099 doc()->editWrapLine(line: ln, col: cursor.column());
4100 doc()->insertText(position: KTextEditor::Cursor(ln + 1, 0), s: line.string(column: 0, length: col));
4101 doc()->editEnd();
4102
4103 m_viewInternal->updateView();
4104}
4105
4106void KTextEditor::ViewPrivate::noIndentNewline()
4107{
4108 doc()->newLine(view: this, indent: KTextEditor::DocumentPrivate::NoIndent);
4109 m_viewInternal->iconBorder()->updateForCursorLineChange();
4110 m_viewInternal->updateView();
4111}
4112
4113void KTextEditor::ViewPrivate::newLineAbove()
4114{
4115 doc()->newLine(view: this, indent: KTextEditor::DocumentPrivate::Indent, newLinePos: KTextEditor::DocumentPrivate::Above);
4116 m_viewInternal->iconBorder()->updateForCursorLineChange();
4117 m_viewInternal->updateView();
4118}
4119
4120void KTextEditor::ViewPrivate::newLineBelow()
4121{
4122 doc()->newLine(view: this, indent: KTextEditor::DocumentPrivate::Indent, newLinePos: KTextEditor::DocumentPrivate::Below);
4123 m_viewInternal->iconBorder()->updateForCursorLineChange();
4124 m_viewInternal->updateView();
4125}
4126
4127void KTextEditor::ViewPrivate::backspace()
4128{
4129 // Will take care of both multi and primary cursors
4130 doc()->backspace(view: this);
4131}
4132
4133void KTextEditor::ViewPrivate::insertTab()
4134{
4135 doc()->insertTab(view: this, cursorPosition());
4136}
4137
4138void KTextEditor::ViewPrivate::deleteWordLeft(bool subword)
4139{
4140 doc()->editStart();
4141 m_viewInternal->wordPrev(sel: true, subword);
4142 KTextEditor::Range selection = selectionRange();
4143 removeSelectedText();
4144 doc()->editEnd();
4145
4146 ensureUniqueCursors();
4147
4148 m_viewInternal->tagRange(range: selection, realCursors: true);
4149 m_viewInternal->updateDirty();
4150}
4151
4152void KTextEditor::ViewPrivate::keyDelete()
4153{
4154 KTextEditor::Document::EditingTransaction t(doc());
4155
4156 if (blockSelect) {
4157 KTextEditor::Range selection = m_selection;
4158 if (selection.isValid() && selection.start().column() == selection.end().column()) {
4159 KTextEditor::Cursor end = {selection.end().line(), selection.end().column() + 1};
4160 selection = {selection.start(), end};
4161 doc()->removeText(range: selection, block: blockSelect);
4162 return;
4163 }
4164 }
4165
4166 // multi cursor
4167
4168 if (removeSelectedText()) {
4169 return;
4170 }
4171
4172 for (auto it = m_secondaryCursors.rbegin(); it != m_secondaryCursors.rend(); ++it) {
4173 if (it->range) {
4174 doc()->removeText(range: it->range->toRange());
4175 } else {
4176 doc()->del(view: this, it->cursor());
4177 }
4178 }
4179
4180 // primary cursor
4181 doc()->del(view: this, cursorPosition());
4182
4183 ensureUniqueCursors();
4184}
4185
4186void KTextEditor::ViewPrivate::deleteWordRight(bool subword)
4187{
4188 doc()->editStart();
4189 m_viewInternal->wordNext(sel: true, subword);
4190 KTextEditor::Range selection = selectionRange();
4191 removeSelectedText();
4192 doc()->editEnd();
4193
4194 ensureUniqueCursors();
4195
4196 m_viewInternal->tagRange(range: selection, realCursors: true);
4197 m_viewInternal->updateDirty();
4198}
4199
4200void KTextEditor::ViewPrivate::transpose()
4201{
4202 doc()->editStart();
4203 for (const auto &c : m_secondaryCursors) {
4204 doc()->transpose(c.cursor());
4205 }
4206 doc()->transpose(cursorPosition());
4207 doc()->editEnd();
4208}
4209
4210void KTextEditor::ViewPrivate::transposeWord()
4211{
4212 const KTextEditor::Cursor originalCurPos = cursorPosition();
4213 const KTextEditor::Range firstWord = doc()->wordRangeAt(cursor: originalCurPos);
4214 if (!firstWord.isValid()) {
4215 return;
4216 }
4217
4218 auto wordIsInvalid = [](QStringView word) {
4219 for (const QChar &character : word) {
4220 if (character.isLetterOrNumber()) {
4221 return false;
4222 }
4223 }
4224 return true;
4225 };
4226
4227 if (wordIsInvalid(doc()->text(range: firstWord))) {
4228 return;
4229 }
4230
4231 setCursorPosition(firstWord.end());
4232 wordRight();
4233 KTextEditor::Cursor curPos = cursorPosition();
4234 // swap with the word to the right if it exists, otherwise try to swap with word to the left
4235 if (curPos.line() != firstWord.end().line() || curPos.column() == firstWord.end().column()) {
4236 setCursorPosition(firstWord.start());
4237 wordLeft();
4238 curPos = cursorPosition();
4239 // if there is still no next word in this line, no swapping will be done
4240 if (curPos.line() != firstWord.start().line() || curPos.column() == firstWord.start().column() || wordIsInvalid(doc()->wordAt(cursor: curPos))) {
4241 setCursorPosition(originalCurPos);
4242 return;
4243 }
4244 }
4245
4246 if (wordIsInvalid(doc()->wordAt(cursor: curPos))) {
4247 setCursorPosition(originalCurPos);
4248 return;
4249 }
4250
4251 const KTextEditor::Range secondWord = doc()->wordRangeAt(cursor: curPos);
4252 doc()->swapTextRanges(firstWord, secondWord);
4253
4254 // return cursor to its original position inside the word before swap
4255 // 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
4256 const int offsetFromWordEnd = firstWord.end().column() - originalCurPos.column();
4257 setCursorPosition(cursorPosition() - KTextEditor::Cursor(0, offsetFromWordEnd));
4258}
4259
4260void KTextEditor::ViewPrivate::cursorLeft()
4261{
4262 if (selection() && !config()->persistentSelection() && !m_markedSelection) {
4263 if (isLineRTL(line: cursorPosition().line())) {
4264 m_viewInternal->updateCursor(newCursor: selectionRange().end());
4265 setSelection(KTextEditor::Range::invalid());
4266 } else {
4267 m_viewInternal->updateCursor(newCursor: selectionRange().start());
4268 setSelection(KTextEditor::Range::invalid());
4269 }
4270
4271 for (const auto &c : m_secondaryCursors) {
4272 if (!c.range) {
4273 continue;
4274 }
4275 const bool rtl = isLineRTL(line: c.cursor().line());
4276 c.pos->setPosition(rtl ? c.range->end() : c.range->start());
4277 }
4278 clearSecondarySelections();
4279 } else {
4280 if (isLineRTL(line: cursorPosition().line())) {
4281 m_viewInternal->cursorNextChar(sel: m_markedSelection);
4282 } else {
4283 m_viewInternal->cursorPrevChar(sel: m_markedSelection);
4284 }
4285 }
4286}
4287
4288void KTextEditor::ViewPrivate::shiftCursorLeft()
4289{
4290 if (isLineRTL(line: cursorPosition().line())) {
4291 m_viewInternal->cursorNextChar(sel: true);
4292 } else {
4293 m_viewInternal->cursorPrevChar(sel: true);
4294 }
4295}
4296
4297void KTextEditor::ViewPrivate::cursorRight()
4298{
4299 if (selection() && !config()->persistentSelection() && !m_markedSelection) {
4300 if (isLineRTL(line: cursorPosition().line())) {
4301 m_viewInternal->updateCursor(newCursor: selectionRange().start());
4302 setSelection(KTextEditor::Range::invalid());
4303 } else {
4304 m_viewInternal->updateCursor(newCursor: selectionRange().end());
4305 setSelection(KTextEditor::Range::invalid());
4306 }
4307
4308 for (const auto &c : m_secondaryCursors) {
4309 if (!c.range) {
4310 continue;
4311 }
4312 const bool rtl = doc()->line(line: c.cursor().line()).isRightToLeft();
4313 c.pos->setPosition(rtl ? c.range->start() : c.range->end());
4314 }
4315 clearSecondarySelections();
4316 } else {
4317 if (isLineRTL(line: cursorPosition().line())) {
4318 m_viewInternal->cursorPrevChar(sel: m_markedSelection);
4319 } else {
4320 m_viewInternal->cursorNextChar(sel: m_markedSelection);
4321 }
4322 }
4323}
4324
4325void KTextEditor::ViewPrivate::shiftCursorRight()
4326{
4327 if (isLineRTL(line: cursorPosition().line())) {
4328 m_viewInternal->cursorPrevChar(sel: true);
4329 } else {
4330 m_viewInternal->cursorNextChar(sel: true);
4331 }
4332}
4333
4334void KTextEditor::ViewPrivate::wordLeft(bool subword)
4335{
4336 if (isLineRTL(line: cursorPosition().line())) {
4337 m_viewInternal->wordNext(sel: m_markedSelection, subword);
4338 } else {
4339 m_viewInternal->wordPrev(sel: m_markedSelection, subword);
4340 }
4341}
4342
4343void KTextEditor::ViewPrivate::shiftWordLeft(bool subword)
4344{
4345 if (isLineRTL(line: cursorPosition().line())) {
4346 m_viewInternal->wordNext(/*sel=*/true, subword);
4347 } else {
4348 m_viewInternal->wordPrev(/*sel=*/true, subword);
4349 }
4350}
4351
4352void KTextEditor::ViewPrivate::wordRight(bool subword)
4353{
4354 if (isLineRTL(line: cursorPosition().line())) {
4355 m_viewInternal->wordPrev(sel: m_markedSelection, subword);
4356 } else {
4357 m_viewInternal->wordNext(sel: m_markedSelection, subword);
4358 }
4359}
4360
4361void KTextEditor::ViewPrivate::shiftWordRight(bool subword)
4362{
4363 if (isLineRTL(line: cursorPosition().line())) {
4364 m_viewInternal->wordPrev(sel: true, subword);
4365 } else {
4366 m_viewInternal->wordNext(sel: true, subword);
4367 }
4368}
4369
4370void KTextEditor::ViewPrivate::markSelection()
4371{
4372 if (m_markedSelection && selection()) {
4373 setSelection(KTextEditor::Range::invalid());
4374 clearSecondarySelections();
4375 } else {
4376 m_markedSelection = !m_markedSelection;
4377 }
4378}
4379
4380void KTextEditor::ViewPrivate::home()
4381{
4382 m_viewInternal->home(sel: m_markedSelection);
4383}
4384
4385void KTextEditor::ViewPrivate::shiftHome()
4386{
4387 m_viewInternal->home(sel: true);
4388}
4389
4390void KTextEditor::ViewPrivate::end()
4391{
4392 m_viewInternal->end(sel: m_markedSelection);
4393}
4394
4395void KTextEditor::ViewPrivate::shiftEnd()
4396{
4397 m_viewInternal->end(sel: true);
4398}
4399
4400void KTextEditor::ViewPrivate::up()
4401{
4402 m_viewInternal->cursorUp(sel: m_markedSelection);
4403}
4404
4405void KTextEditor::ViewPrivate::shiftUp()
4406{
4407 m_viewInternal->cursorUp(sel: true);
4408}
4409
4410void KTextEditor::ViewPrivate::down()
4411{
4412 m_viewInternal->cursorDown(sel: m_markedSelection);
4413}
4414
4415void KTextEditor::ViewPrivate::shiftDown()
4416{
4417 m_viewInternal->cursorDown(sel: true);
4418}
4419
4420void KTextEditor::ViewPrivate::scrollUp()
4421{
4422 m_viewInternal->scrollUp();
4423}
4424
4425void KTextEditor::ViewPrivate::scrollDown()
4426{
4427 m_viewInternal->scrollDown();
4428}
4429
4430void KTextEditor::ViewPrivate::topOfView()
4431{
4432 m_viewInternal->topOfView();
4433}
4434
4435void KTextEditor::ViewPrivate::shiftTopOfView()
4436{
4437 m_viewInternal->topOfView(sel: true);
4438}
4439
4440void KTextEditor::ViewPrivate::bottomOfView()
4441{
4442 m_viewInternal->bottomOfView();
4443}
4444
4445void KTextEditor::ViewPrivate::shiftBottomOfView()
4446{
4447 m_viewInternal->bottomOfView(sel: true);
4448}
4449
4450void KTextEditor::ViewPrivate::pageUp()
4451{
4452 m_viewInternal->pageUp(sel: m_markedSelection);
4453}
4454
4455void KTextEditor::ViewPrivate::shiftPageUp()
4456{
4457 m_viewInternal->pageUp(sel: true);
4458}
4459
4460void KTextEditor::ViewPrivate::pageDown()
4461{
4462 m_viewInternal->pageDown(sel: m_markedSelection);
4463}
4464
4465void KTextEditor::ViewPrivate::shiftPageDown()
4466{
4467 m_viewInternal->pageDown(sel: true);
4468}
4469
4470void KTextEditor::ViewPrivate::top()
4471{
4472 m_viewInternal->top_home(sel: m_markedSelection);
4473}
4474
4475void KTextEditor::ViewPrivate::shiftTop()
4476{
4477 m_viewInternal->top_home(sel: true);
4478}
4479
4480void KTextEditor::ViewPrivate::bottom()
4481{
4482 m_viewInternal->bottom_end(sel: m_markedSelection);
4483}
4484
4485void KTextEditor::ViewPrivate::shiftBottom()
4486{
4487 m_viewInternal->bottom_end(sel: true);
4488}
4489
4490void KTextEditor::ViewPrivate::toMatchingBracket()
4491{
4492 m_viewInternal->cursorToMatchingBracket();
4493}
4494
4495void KTextEditor::ViewPrivate::shiftToMatchingBracket()
4496{
4497 m_viewInternal->cursorToMatchingBracket(sel: true);
4498}
4499
4500void KTextEditor::ViewPrivate::toPrevModifiedLine()
4501{
4502 const int startLine = cursorPosition().line() - 1;
4503 const int line = doc()->findTouchedLine(startLine, down: false);
4504 if (line >= 0) {
4505 KTextEditor::Cursor c(line, 0);
4506 m_viewInternal->updateSelection(c, keepSel: false);
4507 m_viewInternal->updateCursor(newCursor: c);
4508 }
4509}
4510
4511void KTextEditor::ViewPrivate::toNextModifiedLine()
4512{
4513 const int startLine = cursorPosition().line() + 1;
4514 const int line = doc()->findTouchedLine(startLine, down: true);
4515 if (line >= 0) {
4516 KTextEditor::Cursor c(line, 0);
4517 m_viewInternal->updateSelection(c, keepSel: false);
4518 m_viewInternal->updateCursor(newCursor: c);
4519 }
4520}
4521
4522KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const
4523{
4524 return m_selection;
4525}
4526
4527KTextEditor::Document *KTextEditor::ViewPrivate::document() const
4528{
4529 return m_doc;
4530}
4531
4532void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu)
4533{
4534 if (m_contextMenu) {
4535 disconnect(sender: m_contextMenu.data(), signal: &QMenu::aboutToShow, receiver: this, slot: &KTextEditor::ViewPrivate::aboutToShowContextMenu);
4536 disconnect(sender: m_contextMenu.data(), signal: &QMenu::aboutToHide, receiver: this, slot: &KTextEditor::ViewPrivate::aboutToHideContextMenu);
4537 }
4538 m_contextMenu = menu;
4539 m_userContextMenuSet = true;
4540
4541 if (m_contextMenu) {
4542 connect(sender: m_contextMenu.data(), signal: &QMenu::aboutToShow, context: this, slot: &KTextEditor::ViewPrivate::aboutToShowContextMenu);
4543 connect(sender: m_contextMenu.data(), signal: &QMenu::aboutToHide, context: this, slot: &KTextEditor::ViewPrivate::aboutToHideContextMenu);
4544 }
4545}
4546
4547QMenu *KTextEditor::ViewPrivate::contextMenu() const
4548{
4549 if (m_userContextMenuSet) {
4550 return m_contextMenu;
4551 } else {
4552 KXMLGUIClient *client = const_cast<KTextEditor::ViewPrivate *>(this);
4553 while (client->parentClient()) {
4554 client = client->parentClient();
4555 }
4556
4557 // qCDebug(LOG_KTE) << "looking up all menu containers";
4558 if (client->factory()) {
4559 const QList<QWidget *> menuContainers = client->factory()->containers(QStringLiteral("menu"));
4560 for (QWidget *w : menuContainers) {
4561 if (w->objectName() == QLatin1String("ktexteditor_popup")) {
4562 // perhaps optimize this block
4563 QMenu *menu = (QMenu *)w;
4564 // menu is a reusable instance shared among all views. Therefore,
4565 // disconnect the current receiver(s) from the menu show/hide signals
4566 // before connecting `this` view. This ensures that only the current
4567 // view gets a signal when the menu is about to be shown or hidden,
4568 // and not also the view(s) that previously had the menu open.
4569 disconnect(sender: menu, signal: &QMenu::aboutToShow, receiver: nullptr, zero: nullptr);
4570 disconnect(sender: menu, signal: &QMenu::aboutToHide, receiver: nullptr, zero: nullptr);
4571 connect(sender: menu, signal: &QMenu::aboutToShow, context: this, slot: &KTextEditor::ViewPrivate::aboutToShowContextMenu);
4572 connect(sender: menu, signal: &QMenu::aboutToHide, context: this, slot: &KTextEditor::ViewPrivate::aboutToHideContextMenu);
4573 return menu;
4574 }
4575 }
4576 }
4577 }
4578 return nullptr;
4579}
4580
4581QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const
4582{
4583 if (!menu) {
4584 menu = new QMenu(const_cast<KTextEditor::ViewPrivate *>(this));
4585 }
4586
4587 if (m_editUndo) {
4588 menu->addAction(action: m_editUndo);
4589 }
4590 if (m_editRedo) {
4591 menu->addAction(action: m_editRedo);
4592 }
4593
4594 menu->addSeparator();
4595 menu->addAction(action: m_cut);
4596 menu->addAction(action: m_copy);
4597 menu->addAction(action: m_paste);
4598 if (m_pasteSelection) {
4599 menu->addAction(action: m_pasteSelection);
4600 }
4601
4602 menu->addAction(action: m_screenshotSelection);
4603 menu->addAction(action: m_swapWithClipboard);
4604 menu->addSeparator();
4605 menu->addAction(action: m_selectAll);
4606 menu->addAction(action: m_deSelect);
4607 QAction *editing = actionCollection()->action(QStringLiteral("tools_scripts_Editing"));
4608 if (editing) {
4609 menu->addAction(action: editing);
4610 }
4611 if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) {
4612 menu->addSeparator();
4613 menu->addAction(action: spellingSuggestions);
4614 }
4615 if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) {
4616 menu->addSeparator();
4617 menu->addAction(action: bookmark);
4618 }
4619
4620 return menu;
4621}
4622
4623void KTextEditor::ViewPrivate::aboutToShowContextMenu()
4624{
4625 QMenu *menu = qobject_cast<QMenu *>(object: sender());
4626
4627 if (menu) {
4628 Q_EMIT contextMenuAboutToShow(view: this, menu);
4629 }
4630}
4631
4632void KTextEditor::ViewPrivate::aboutToHideContextMenu()
4633{
4634 m_spellingMenu->cleanUpAfterShown();
4635}
4636
4637// BEGIN ConfigInterface stff
4638QStringList KTextEditor::ViewPrivate::configKeys() const
4639{
4640 static const QStringList keys = {QStringLiteral("icon-bar"),
4641 QStringLiteral("line-numbers"),
4642 QStringLiteral("dynamic-word-wrap"),
4643 QStringLiteral("background-color"),
4644 QStringLiteral("selection-color"),
4645 QStringLiteral("search-highlight-color"),
4646 QStringLiteral("replace-highlight-color"),
4647 QStringLiteral("default-mark-type"),
4648 QStringLiteral("allow-mark-menu"),
4649 QStringLiteral("folding-bar"),
4650 QStringLiteral("folding-preview"),
4651 QStringLiteral("icon-border-color"),
4652 QStringLiteral("folding-marker-color"),
4653 QStringLiteral("line-number-color"),
4654 QStringLiteral("current-line-number-color"),
4655 QStringLiteral("modification-markers"),
4656 QStringLiteral("keyword-completion"),
4657 QStringLiteral("word-count"),
4658 QStringLiteral("line-count"),
4659 QStringLiteral("scrollbar-minimap"),
4660 QStringLiteral("scrollbar-preview"),
4661 QStringLiteral("font"),
4662 QStringLiteral("theme")};
4663 return keys;
4664}
4665
4666QVariant KTextEditor::ViewPrivate::configValue(const QString &key)
4667{
4668 if (key == QLatin1String("icon-bar")) {
4669 return config()->iconBar();
4670 } else if (key == QLatin1String("line-numbers")) {
4671 return config()->lineNumbers();
4672 } else if (key == QLatin1String("dynamic-word-wrap")) {
4673 return config()->dynWordWrap();
4674 } else if (key == QLatin1String("background-color")) {
4675 return rendererConfig()->backgroundColor();
4676 } else if (key == QLatin1String("selection-color")) {
4677 return rendererConfig()->selectionColor();
4678 } else if (key == QLatin1String("search-highlight-color")) {
4679 return rendererConfig()->searchHighlightColor();
4680 } else if (key == QLatin1String("replace-highlight-color")) {
4681 return rendererConfig()->replaceHighlightColor();
4682 } else if (key == QLatin1String("default-mark-type")) {
4683 return config()->defaultMarkType();
4684 } else if (key == QLatin1String("allow-mark-menu")) {
4685 return config()->allowMarkMenu();
4686 } else if (key == QLatin1String("folding-bar")) {
4687 return config()->foldingBar();
4688 } else if (key == QLatin1String("folding-preview")) {
4689 return config()->foldingPreview();
4690 } else if (key == QLatin1String("icon-border-color")) {
4691 return rendererConfig()->iconBarColor();
4692 } else if (key == QLatin1String("folding-marker-color")) {
4693 return rendererConfig()->foldingColor();
4694 } else if (key == QLatin1String("line-number-color")) {
4695 return rendererConfig()->lineNumberColor();
4696 } else if (key == QLatin1String("current-line-number-color")) {
4697 return rendererConfig()->currentLineNumberColor();
4698 } else if (key == QLatin1String("modification-markers")) {
4699 return config()->lineModification();
4700 } else if (key == QLatin1String("keyword-completion")) {
4701 return config()->keywordCompletion();
4702 } else if (key == QLatin1String("word-count")) {
4703 return config()->showWordCount();
4704 } else if (key == QLatin1String("line-count")) {
4705 return config()->showLineCount();
4706 } else if (key == QLatin1String("scrollbar-minimap")) {
4707 return config()->scrollBarMiniMap();
4708 } else if (key == QLatin1String("scrollbar-preview")) {
4709 return config()->scrollBarPreview();
4710 } else if (key == QLatin1String("font")) {
4711 return rendererConfig()->baseFont();
4712 } else if (key == QLatin1String("theme")) {
4713 return rendererConfig()->schema();
4714 }
4715
4716 // return invalid variant
4717 return QVariant();
4718}
4719
4720void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value)
4721{
4722 // First, try the new config interface
4723 if (config()->setValue(key, value)) {
4724 return;
4725
4726 } else if (rendererConfig()->setValue(key, value)) {
4727 return;
4728 }
4729
4730 // No success? Go the old way
4731 if (value.canConvert<QColor>()) {
4732 if (key == QLatin1String("background-color")) {
4733 rendererConfig()->setBackgroundColor(value.value<QColor>());
4734 } else if (key == QLatin1String("selection-color")) {
4735 rendererConfig()->setSelectionColor(value.value<QColor>());
4736 } else if (key == QLatin1String("search-highlight-color")) {
4737 rendererConfig()->setSearchHighlightColor(value.value<QColor>());
4738 } else if (key == QLatin1String("replace-highlight-color")) {
4739 rendererConfig()->setReplaceHighlightColor(value.value<QColor>());
4740 } else if (key == QLatin1String("icon-border-color")) {
4741 rendererConfig()->setIconBarColor(value.value<QColor>());
4742 } else if (key == QLatin1String("folding-marker-color")) {
4743 rendererConfig()->setFoldingColor(value.value<QColor>());
4744 } else if (key == QLatin1String("line-number-color")) {
4745 rendererConfig()->setLineNumberColor(value.value<QColor>());
4746 } else if (key == QLatin1String("current-line-number-color")) {
4747 rendererConfig()->setCurrentLineNumberColor(value.value<QColor>());
4748 }
4749 }
4750 if (value.userType() == QMetaType::Bool) {
4751 // Note explicit type check above. If we used canConvert, then
4752 // values of type UInt will be trapped here.
4753 if (key == QLatin1String("dynamic-word-wrap")) {
4754 config()->setDynWordWrap(value.toBool());
4755 } else if (key == QLatin1String("word-count")) {
4756 config()->setShowWordCount(value.toBool());
4757 } else if (key == QLatin1String("line-count")) {
4758 config()->setShowLineCount(value.toBool());
4759 }
4760 } else if (key == QLatin1String("font") && value.canConvert<QFont>()) {
4761 rendererConfig()->setFont(value.value<QFont>());
4762 } else if (key == QLatin1String("theme") && value.userType() == QMetaType::QString) {
4763 rendererConfig()->setSchema(value.toString());
4764 }
4765}
4766
4767// END ConfigInterface
4768
4769// NOLINTNEXTLINE(readability-make-member-function-const)
4770void KTextEditor::ViewPrivate::userInvokedCompletion()
4771{
4772 completionWidget()->userInvokedCompletion();
4773}
4774
4775KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const
4776{
4777 return m_bottomViewBar;
4778}
4779
4780KateGotoBar *KTextEditor::ViewPrivate::gotoBar()
4781{
4782 if (!m_gotoBar) {
4783 m_gotoBar = new KateGotoBar(this);
4784 bottomViewBar()->addBarWidget(newBarWidget: m_gotoBar);
4785 }
4786
4787 return m_gotoBar;
4788}
4789
4790KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar()
4791{
4792 if (!m_dictionaryBar) {
4793 m_dictionaryBar = new KateDictionaryBar(this);
4794 bottomViewBar()->addBarWidget(newBarWidget: m_dictionaryBar);
4795 }
4796
4797 return m_dictionaryBar;
4798}
4799
4800void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model)
4801{
4802 KTextEditor::AnnotationModel *oldmodel = m_annotationModel;
4803 m_annotationModel = model;
4804 m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, newmodel: m_annotationModel);
4805}
4806
4807KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const
4808{
4809 return m_annotationModel;
4810}
4811
4812void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible)
4813{
4814 m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible);
4815}
4816
4817bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const
4818{
4819 return m_viewInternal->m_leftBorder->annotationBorderOn();
4820}
4821
4822KTextEditor::AbstractAnnotationItemDelegate *KTextEditor::ViewPrivate::annotationItemDelegate() const
4823{
4824 return m_viewInternal->m_leftBorder->annotationItemDelegate();
4825}
4826
4827void KTextEditor::ViewPrivate::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate)
4828{
4829 m_viewInternal->m_leftBorder->setAnnotationItemDelegate(delegate);
4830}
4831
4832bool KTextEditor::ViewPrivate::uniformAnnotationItemSizes() const
4833{
4834 return m_viewInternal->m_leftBorder->uniformAnnotationItemSizes();
4835}
4836
4837void KTextEditor::ViewPrivate::setAnnotationUniformItemSizes(bool enable)
4838{
4839 m_viewInternal->m_leftBorder->setAnnotationUniformItemSizes(enable);
4840}
4841
4842KTextEditor::Range KTextEditor::ViewPrivate::visibleRange()
4843{
4844 // ensure that the view is up-to-date, otherwise 'endPos()' might fail!
4845 if (!m_viewInternal->endPos().isValid()) {
4846 m_viewInternal->updateView();
4847 }
4848 return KTextEditor::Range(m_viewInternal->toRealCursor(virtualCursor: m_viewInternal->startPos()), m_viewInternal->toRealCursor(virtualCursor: m_viewInternal->endPos()));
4849}
4850
4851bool KTextEditor::ViewPrivate::event(QEvent *e)
4852{
4853 switch (e->type()) {
4854 case QEvent::StyleChange:
4855 setupLayout();
4856 return true;
4857 default:
4858 return KTextEditor::View::event(event: e);
4859 }
4860}
4861
4862void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b)
4863{
4864 doc()->onTheFlySpellCheckingEnabled(enable: b);
4865}
4866
4867void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled)
4868{
4869 m_spellingMenu->setVisible(enabled);
4870 m_toggleOnTheFlySpellCheck->setChecked(enabled);
4871}
4872
4873KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu()
4874{
4875 return m_spellingMenu;
4876}
4877
4878void KTextEditor::ViewPrivate::notifyAboutRangeChange(KTextEditor::LineRange lineRange, bool needsRepaint, Kate::TextRange *deleteRange)
4879{
4880#ifdef VIEW_RANGE_DEBUG
4881 // output args
4882 qCDebug(LOG_KTE) << "trigger attribute changed in line range " << lineRange << "needsRepaint" << needsRepaint;
4883#endif
4884
4885 if (deleteRange) {
4886 m_rangesCaretIn.remove(value: deleteRange);
4887 m_rangesMouseIn.remove(value: deleteRange);
4888 }
4889
4890 // if we need repaint, we will need to collect the line ranges we will update
4891 if (needsRepaint && lineRange.isValid()) {
4892 if (m_lineToUpdateRange.isValid()) {
4893 m_lineToUpdateRange.expandToRange(range: lineRange);
4894 } else {
4895 m_lineToUpdateRange = lineRange;
4896 }
4897 }
4898
4899 // first call => trigger later update of view via delayed signal to group updates
4900 if (!m_delayedUpdateTimer.isActive()) {
4901 m_delayedUpdateTimer.start();
4902 }
4903}
4904
4905void KTextEditor::ViewPrivate::slotDelayedUpdateOfView()
4906{
4907#ifdef VIEW_RANGE_DEBUG
4908 // output args
4909 qCDebug(LOG_KTE) << "delayed attribute changed in line range" << m_lineToUpdateRange;
4910#endif
4911 // update ranges in
4912 updateRangesIn(activationType: KTextEditor::Attribute::ActivateMouseIn);
4913 updateRangesIn(activationType: KTextEditor::Attribute::ActivateCaretIn);
4914
4915 // update view, if valid line range, else only feedback update wanted anyway
4916 if (m_lineToUpdateRange.isValid()) {
4917 tagLines(lineRange: m_lineToUpdateRange, realLines: true);
4918 updateView(changed: true);
4919 }
4920
4921 // reset flags
4922 m_lineToUpdateRange = KTextEditor::LineRange::invalid();
4923}
4924
4925void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType)
4926{
4927 // new ranges with cursor in, default none
4928 QSet<Kate::TextRange *> newRangesIn;
4929
4930 // on which range set we work?
4931 QSet<Kate::TextRange *> &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn;
4932
4933 // which cursor position to honor?
4934 KTextEditor::Cursor currentCursor =
4935 (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->mousePosition() : m_viewInternal->cursorPosition();
4936
4937 // first: validate the remembered ranges
4938 QSet<Kate::TextRange *> validRanges = oldSet;
4939
4940 // cursor valid? else no new ranges can be found
4941 if (currentCursor.isValid() && currentCursor.line() < doc()->buffer().lines()) {
4942 // now: get current ranges for the line of cursor with an attribute
4943 const QList<Kate::TextRange *> rangesForCurrentCursor = doc()->buffer().rangesForLine(line: currentCursor.line(), view: this, rangesWithAttributeOnly: false);
4944
4945 // match which ranges really fit the given cursor
4946 for (Kate::TextRange *range : rangesForCurrentCursor) {
4947 // range has no dynamic attribute of right type and no feedback object
4948 auto attribute = range->attribute();
4949 if ((!attribute || !attribute->dynamicAttribute(type: activationType)) && !range->feedback()) {
4950 continue;
4951 }
4952
4953 // range doesn't contain cursor, not interesting
4954 if ((range->startInternal().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->toRange().start())
4955 : (currentCursor <= range->toRange().start())) {
4956 continue;
4957 }
4958
4959 if ((range->endInternal().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->toRange().end() <= currentCursor)
4960 : (range->toRange().end() < currentCursor)) {
4961 continue;
4962 }
4963
4964 // range contains cursor, was it already in old set?
4965 auto it = validRanges.find(value: range);
4966 if (it != validRanges.end()) {
4967 // insert in new, remove from old, be done with it
4968 newRangesIn.insert(value: range);
4969 validRanges.erase(i: it);
4970 continue;
4971 }
4972
4973 // oh, new range, trigger update and insert into new set
4974 newRangesIn.insert(value: range);
4975
4976 if (attribute && attribute->dynamicAttribute(type: activationType)) {
4977 notifyAboutRangeChange(lineRange: range->toLineRange(), needsRepaint: true, deleteRange: nullptr);
4978 }
4979
4980 // feedback
4981 if (range->feedback()) {
4982 if (activationType == KTextEditor::Attribute::ActivateMouseIn) {
4983 range->feedback()->mouseEnteredRange(range, view: this);
4984 } else {
4985 range->feedback()->caretEnteredRange(range, view: this);
4986 Q_EMIT caretChangedRange(this);
4987 }
4988 }
4989
4990#ifdef VIEW_RANGE_DEBUG
4991 // found new range for activation
4992 qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType;
4993#endif
4994 }
4995 }
4996
4997 // now: notify for left ranges!
4998 for (Kate::TextRange *range : std::as_const(t&: validRanges)) {
4999 // range valid + right dynamic attribute, trigger update
5000 if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(type: activationType)) {
5001 notifyAboutRangeChange(lineRange: range->toLineRange(), needsRepaint: true, deleteRange: nullptr);
5002 }
5003
5004 // feedback
5005 if (range->feedback()) {
5006 if (activationType == KTextEditor::Attribute::ActivateMouseIn) {
5007 range->feedback()->mouseExitedRange(range, view: this);
5008 } else {
5009 range->feedback()->caretExitedRange(range, view: this);
5010 Q_EMIT caretChangedRange(this);
5011 }
5012 }
5013 }
5014
5015 // set new ranges
5016 oldSet = newRangesIn;
5017}
5018
5019void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList<std::shared_ptr<QAction>> actions)
5020{
5021 // just forward to KateMessageWidget :-)
5022 auto messageWidget = m_messageWidgets[message->position()];
5023 if (!messageWidget) {
5024 // this branch is used for: TopInView, CenterInView, and BottomInView
5025 messageWidget = new KateMessageWidget(m_viewInternal, true);
5026 m_messageWidgets[message->position()] = messageWidget;
5027 m_notificationLayout->addWidget(widget: messageWidget, pos: message->position());
5028 connect(sender: this, signal: &KTextEditor::View::displayRangeChanged, context: messageWidget, slot: &KateMessageWidget::startAutoHideTimer);
5029 connect(sender: this, signal: &KTextEditor::ViewPrivate::cursorPositionChanged, context: messageWidget, slot: &KateMessageWidget::startAutoHideTimer);
5030 }
5031 messageWidget->postMessage(message, actions: std::move(actions));
5032}
5033
5034KateMessageWidget *KTextEditor::ViewPrivate::messageWidget()
5035{
5036 return m_messageWidgets[KTextEditor::Message::TopInView];
5037}
5038
5039void KTextEditor::ViewPrivate::saveFoldingState()
5040{
5041 m_savedFoldingState = m_textFolding.exportFoldingRanges();
5042}
5043
5044void KTextEditor::ViewPrivate::clearFoldingState()
5045{
5046 m_savedFoldingState = {};
5047}
5048
5049void KTextEditor::ViewPrivate::applyFoldingState()
5050{
5051 m_textFolding.importFoldingRanges(folds: m_savedFoldingState);
5052 m_savedFoldingState = QJsonDocument();
5053}
5054
5055void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file)
5056{
5057 KateExporter(this).exportToFile(file);
5058}
5059
5060void KTextEditor::ViewPrivate::exportHtmlToClipboard()
5061{
5062 KateExporter(this).exportToClipboard();
5063}
5064
5065void KTextEditor::ViewPrivate::exportHtmlToFile()
5066{
5067 const QString file = QFileDialog::getSaveFileName(parent: this, i18n("Export File as HTML"), dir: doc()->documentName());
5068 if (!file.isEmpty()) {
5069 KateExporter(this).exportToFile(file);
5070 }
5071}
5072
5073void KTextEditor::ViewPrivate::clearHighlights()
5074{
5075 m_rangesForHighlights.clear();
5076 m_currentTextForHighlights.clear();
5077}
5078
5079void KTextEditor::ViewPrivate::selectionChangedForHighlights()
5080{
5081 QString text;
5082 // If there are multiple selections it is pointless to create highlights
5083 if (!m_secondaryCursors.empty()) {
5084 for (const auto &cursor : m_secondaryCursors) {
5085 if (cursor.range) {
5086 return;
5087 }
5088 }
5089 }
5090
5091 // if text of selection is still the same, abort
5092 if (selection() && selectionRange().onSingleLine()) {
5093 text = selectionText();
5094 if (text == m_currentTextForHighlights) {
5095 return;
5096 }
5097 }
5098
5099 // text changed: remove all highlights + create new ones
5100 // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights
5101 m_rangesForHighlights.clear();
5102
5103 // do not highlight strings with leading and trailing spaces
5104 if (!text.isEmpty() && (text.at(i: 0).isSpace() || text.at(i: text.length() - 1).isSpace())) {
5105 return;
5106 }
5107
5108 // trigger creation of ranges for current view range
5109 m_currentTextForHighlights = text;
5110 createHighlights();
5111}
5112
5113void KTextEditor::ViewPrivate::createHighlights()
5114{
5115 // do nothing if no text to highlight
5116 if (m_currentTextForHighlights.isEmpty()) {
5117 return;
5118 }
5119
5120 // clear existing highlighting ranges, otherwise we stack over and over the same ones eventually
5121 m_rangesForHighlights.clear();
5122
5123 KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute());
5124 attr->setBackground(Qt::yellow);
5125
5126 // set correct highlight color from Kate's color schema
5127 QColor fgColor = defaultStyleAttribute(defaultStyle: KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground().color();
5128 QColor bgColor = rendererConfig()->searchHighlightColor();
5129 attr->setForeground(fgColor);
5130 attr->setBackground(bgColor);
5131
5132 KTextEditor::Cursor start(visibleRange().start());
5133 KTextEditor::Range searchRange;
5134
5135 // only add word boundary if we can find the text then
5136 // fixes $lala hl
5137 QString pattern = QRegularExpression::escape(str: m_currentTextForHighlights);
5138 if (m_currentTextForHighlights.contains(re: QRegularExpression(QLatin1String("\\b") + pattern, QRegularExpression::UseUnicodePropertiesOption))) {
5139 pattern.prepend(s: QLatin1String("\\b"));
5140 }
5141
5142 if (m_currentTextForHighlights.contains(re: QRegularExpression(pattern + QLatin1String("\\b"), QRegularExpression::UseUnicodePropertiesOption))) {
5143 pattern += QLatin1String("\\b");
5144 }
5145
5146 QList<KTextEditor::Range> matches;
5147 do {
5148 searchRange.setRange(start, end: visibleRange().end());
5149
5150 matches = doc()->searchText(range: searchRange, pattern, options: KTextEditor::Regex);
5151
5152 if (matches.first().isValid()) {
5153 if (matches.first() != selectionRange()) {
5154 std::unique_ptr<KTextEditor::MovingRange> mr(doc()->newMovingRange(range: matches.first()));
5155 mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection
5156 mr->setAttribute(attr);
5157 mr->setView(this);
5158 mr->setAttributeOnlyForViews(true);
5159 m_rangesForHighlights.push_back(x: std::move(mr));
5160 }
5161 start = matches.first().end();
5162 }
5163 } while (matches.first().isValid());
5164}
5165
5166KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const
5167{
5168 return m_viewInternal->m_currentInputMode;
5169}
5170
5171void KTextEditor::ViewPrivate::toggleInputMode()
5172{
5173 if (QAction *a = qobject_cast<QAction *>(object: sender())) {
5174 setInputMode(mode: static_cast<KTextEditor::View::InputMode>(a->data().toInt()));
5175 }
5176}
5177
5178void KTextEditor::ViewPrivate::cycleInputMode()
5179{
5180 InputMode current = currentInputMode()->viewInputMode();
5181 InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode;
5182 setInputMode(mode: to);
5183}
5184
5185// BEGIN KTextEditor::PrintInterface stuff
5186bool KTextEditor::ViewPrivate::print()
5187{
5188 return KatePrinter::print(doc: m_doc, view: this);
5189}
5190
5191void KTextEditor::ViewPrivate::printPreview()
5192{
5193 KatePrinter::printPreview(doc: m_doc, view: this);
5194}
5195
5196// END
5197
5198// BEGIN Inline Note Interface
5199void KTextEditor::ViewPrivate::registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider)
5200{
5201 if (std::find(first: m_inlineNoteProviders.cbegin(), last: m_inlineNoteProviders.cend(), val: provider) == m_inlineNoteProviders.cend()) {
5202 m_inlineNoteProviders.push_back(x: provider);
5203
5204 connect(sender: provider, signal: &KTextEditor::InlineNoteProvider::inlineNotesReset, context: this, slot: &KTextEditor::ViewPrivate::inlineNotesReset);
5205 connect(sender: provider, signal: &KTextEditor::InlineNoteProvider::inlineNotesChanged, context: this, slot: &KTextEditor::ViewPrivate::inlineNotesLineChanged);
5206
5207 inlineNotesReset();
5208 }
5209}
5210
5211void KTextEditor::ViewPrivate::unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider)
5212{
5213 auto it = std::find(first: m_inlineNoteProviders.cbegin(), last: m_inlineNoteProviders.cend(), val: provider);
5214 if (it != m_inlineNoteProviders.cend()) {
5215 m_inlineNoteProviders.erase(position: it);
5216 provider->disconnect(receiver: this);
5217
5218 inlineNotesReset();
5219 }
5220}
5221
5222QVarLengthArray<KateInlineNoteData, 8> KTextEditor::ViewPrivate::inlineNotes(int line) const
5223{
5224 QVarLengthArray<KateInlineNoteData, 8> allInlineNotes;
5225 for (KTextEditor::InlineNoteProvider *provider : m_inlineNoteProviders) {
5226 int index = 0;
5227 const auto columns = provider->inlineNotes(line);
5228 for (int column : columns) {
5229 const bool underMouse = Cursor(line, column) == m_viewInternal->m_activeInlineNote.m_position;
5230 KateInlineNoteData note =
5231 {provider, this, {line, column}, index, underMouse, m_viewInternal->renderer()->currentFont(), m_viewInternal->renderer()->lineHeight()};
5232 allInlineNotes.append(t: note);
5233 index++;
5234 }
5235 }
5236 return allInlineNotes;
5237}
5238
5239QRect KTextEditor::ViewPrivate::inlineNoteRect(const KateInlineNoteData &note) const
5240{
5241 return m_viewInternal->inlineNoteRect(note);
5242}
5243
5244void KTextEditor::ViewPrivate::inlineNotesReset()
5245{
5246 m_viewInternal->m_activeInlineNote = {};
5247 tagLines(lineRange: KTextEditor::LineRange(0, doc()->lastLine()), realLines: true);
5248}
5249
5250void KTextEditor::ViewPrivate::inlineNotesLineChanged(int line)
5251{
5252 if (line == m_viewInternal->m_activeInlineNote.m_position.line()) {
5253 m_viewInternal->m_activeInlineNote = {};
5254 }
5255 tagLines(lineRange: {line, line}, realLines: true);
5256}
5257
5258// END Inline Note Interface
5259
5260KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle defaultStyle) const
5261{
5262 KateRendererConfig *renderConfig = const_cast<KTextEditor::ViewPrivate *>(this)->rendererConfig();
5263
5264 KTextEditor::Attribute::Ptr style = doc()->highlight()->attributes(schema: renderConfig->schema()).at(i: defaultStyle);
5265 if (!style->hasProperty(propertyId: QTextFormat::BackgroundBrush)) {
5266 // make sure the returned style has the default background color set
5267 style = new KTextEditor::Attribute(*style);
5268 style->setBackground(QBrush(renderConfig->backgroundColor()));
5269 }
5270 return style;
5271}
5272
5273QList<KTextEditor::AttributeBlock> KTextEditor::ViewPrivate::lineAttributes(int line)
5274{
5275 QList<KTextEditor::AttributeBlock> attribs;
5276
5277 if (line < 0 || line >= doc()->lines()) {
5278 return attribs;
5279 }
5280
5281 const Kate::TextLine kateLine = doc()->kateTextLine(i: line);
5282 const auto &intAttrs = kateLine.attributesList();
5283 for (qsizetype i = 0; i < intAttrs.size(); ++i) {
5284 if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) {
5285 attribs << KTextEditor::AttributeBlock(intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(pos: intAttrs.at(i).attributeValue));
5286 }
5287 }
5288
5289 return attribs;
5290}
5291
5292void KTextEditor::ViewPrivate::copyFileLocation() const
5293{
5294 QGuiApplication::clipboard()->setText(m_doc->url().toString(options: QUrl::PreferLocalFile | QUrl::RemovePassword) + QStringLiteral(":")
5295 + QString::number(cursorPosition().line() + 1));
5296}
5297
5298#include "moc_kateview.cpp"
5299

source code of ktexteditor/src/view/kateview.cpp