KTextEditor

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

KDE's Doxygen guidelines are available online.