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

KDE's Doxygen guidelines are available online.