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

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