KTextEditor

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