KTextEditor

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