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