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."));
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 // Check if the discovered fold is already folded up.
1428 // If so, we should not fold the same range again!
1429 // This can lead to issues where we need to open the fold multiple times
1430 // in order to actually open it.
1431 auto folds = textFolding().foldingRangesStartingOnLine(line);
1432 for (int i = 0; i < folds.size(); ++i) {
1433 KTextEditor::Range fold = textFolding().foldingRange(folds[i].first);
1434 if (fold == foldingRange) {
1435 return foldingRange;
1436 }
1437 }
1438
1439 // Don't try to fold a single line, which can happens due to adjustment above
1440 // FIXME Avoid to offer such a folding marker
1441 if (!foldingRange.onSingleLine()) {
1442 textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded);
1443 }
1444
1445 return foldingRange;
1446}
1447
1448bool KTextEditor::ViewPrivate::unfoldLine(int line)
1449{
1450 bool actionDone = false;
1451 const KTextEditor::Cursor currentCursor = cursorPosition();
1452
1453 // ask the folding info for this line, if any folds are around!
1454 // auto = QList<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>>
1455 auto startingRanges = textFolding().foldingRangesStartingOnLine(line);
1456 for (int i = 0; i < startingRanges.size() && !actionDone; ++i) {
1457 // Avoid jumping view in case of a big unfold and ensure nice highlight of folding marker
1458 setCursorPosition(textFolding().foldingRange(startingRanges[i].first).start());
1459
1460 actionDone |= textFolding().unfoldRange(startingRanges[i].first);
1461 }
1462
1463 if (!actionDone) {
1464 // Nothing unfolded? Restore old cursor position!
1465 setCursorPosition(currentCursor);
1466 }
1467
1468 return actionDone;
1469}
1470
1471bool KTextEditor::ViewPrivate::toggleFoldingOfLine(int line)
1472{
1473 bool actionDone = unfoldLine(line);
1474 if (!actionDone) {
1475 actionDone = foldLine(line).isValid();
1476 }
1477
1478 return actionDone;
1479}
1480
1481bool KTextEditor::ViewPrivate::toggleFoldingsInRange(int line)
1482{
1483 KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line);
1484 if (!foldingRange.isValid()) {
1485 // Either line is not valid or there is no start range
1486 return false;
1487 }
1488
1489 bool actionDone = false; // Track success
1490 const KTextEditor::Cursor currentCursor = cursorPosition();
1491
1492 // Don't be too eager but obliging! Only toggle containing ranges which are
1493 // visible -> Be done when the range is folded
1494 actionDone |= unfoldLine(line);
1495
1496 if (!actionDone) {
1497 // Unfold all in range, but not the range itself
1498 for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) {
1499 actionDone |= unfoldLine(ln);
1500 }
1501
1502 if (actionDone) {
1503 // In most cases we want now a not moved cursor
1504 setCursorPosition(currentCursor);
1505 }
1506 }
1507
1508 if (!actionDone) {
1509 // Fold all in range, but not the range itself
1510 for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) {
1511 KTextEditor::Range fr = foldLine(ln);
1512 if (fr.isValid()) {
1513 // qMax to avoid infinite loop in case of range without content
1514 ln = qMax(ln, fr.end().line() - 1);
1515 actionDone = true;
1516 }
1517 }
1518 }
1519
1520 if (!actionDone) {
1521 // At this point was an unfolded range clicked which contains no "childs"
1522 // We assume the user want to fold it by the wrong button, be obliging!
1523 actionDone |= foldLine(line).isValid();
1524 }
1525
1526 // At this point we should be always true
1527 return actionDone;
1528}
1529
1530KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const
1531{
1532 return currentInputMode()->viewMode();
1533}
1534
1535QString KTextEditor::ViewPrivate::viewModeHuman() const
1536{
1537 QString currentMode = currentInputMode()->viewModeHuman();
1538
1539 // append read-only if needed
1540 if (!doc()->isReadWrite()) {
1541 currentMode = i18n("(R/O) %1", currentMode);
1542 }
1543
1544 // return full mode
1545 return currentMode;
1546}
1547
1548KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const
1549{
1550 return currentInputMode()->viewInputMode();
1551}
1552
1553QString KTextEditor::ViewPrivate::viewInputModeHuman() const
1554{
1555 return currentInputMode()->viewInputModeHuman();
1556}
1557
1558void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode, const bool rememberInConfig)
1559{
1560 if (currentInputMode()->viewInputMode() == mode) {
1561 return;
1562 }
1563
1564 // No multi cursors for vi
1566 clearSecondaryCursors();
1567 }
1568
1569 m_viewInternal->m_currentInputMode->deactivate();
1570 m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode].get();
1571 m_viewInternal->m_currentInputMode->activate();
1572
1573 // remember in local config if requested, we skip this for the calls in updateConfig
1574 if (rememberInConfig) {
1575 config()->setValue(KateViewConfig::InputMode, mode);
1576 }
1577
1578 /* small duplication, but need to do this if not toggled by action */
1579 const auto inputModeActions = m_inputModeActions->actions();
1580 for (QAction *action : inputModeActions) {
1581 if (static_cast<InputMode>(action->data().toInt()) == mode) {
1582 action->setChecked(true);
1583 break;
1584 }
1585 }
1586
1587 /* inform the rest of the system about the change */
1588 Q_EMIT viewInputModeChanged(this, mode);
1589 Q_EMIT viewModeChanged(this, viewMode());
1590}
1591
1592void KTextEditor::ViewPrivate::slotDocumentAboutToReload()
1593{
1594 if (doc()->isAutoReload()) {
1595 const int lastVisibleLine = m_viewInternal->endLine();
1596 const int currentLine = cursorPosition().line();
1597 m_gotoBottomAfterReload = (lastVisibleLine == currentLine) && (currentLine == doc()->lastLine());
1598 if (!m_gotoBottomAfterReload) {
1599 // Ensure the view jumps not back when user scrolls around
1600 const int firstVisibleLine = 1 + lastVisibleLine - m_viewInternal->linesDisplayed();
1601 const int newLine = qBound(firstVisibleLine, currentLine, lastVisibleLine);
1602 setCursorPositionVisual(KTextEditor::Cursor(newLine, cursorPosition().column()));
1603 }
1604 } else {
1605 m_gotoBottomAfterReload = false;
1606 }
1607}
1608
1609void KTextEditor::ViewPrivate::slotDocumentReloaded()
1610{
1611 if (m_gotoBottomAfterReload) {
1612 bottom();
1613 }
1614}
1615
1616void KTextEditor::ViewPrivate::slotGotFocus()
1617{
1618 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus";
1619 currentInputMode()->gotFocus();
1620
1621 // update current view and scrollbars
1622 // it is needed for styles that implement different frame and scrollbar
1623 // rendering when focused
1624 update();
1625 if (m_viewInternal->m_lineScroll->isVisible()) {
1626 m_viewInternal->m_lineScroll->update();
1627 }
1628
1629 if (m_viewInternal->m_columnScroll->isVisible()) {
1630 m_viewInternal->m_columnScroll->update();
1631 }
1632
1633 Q_EMIT focusIn(this);
1634}
1635
1636void KTextEditor::ViewPrivate::slotLostFocus()
1637{
1638 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus";
1639 currentInputMode()->lostFocus();
1640
1641 // update current view and scrollbars
1642 // it is needed for styles that implement different frame and scrollbar
1643 // rendering when focused
1644 update();
1645 if (m_viewInternal->m_lineScroll->isVisible()) {
1646 m_viewInternal->m_lineScroll->update();
1647 }
1648
1649 if (m_viewInternal->m_columnScroll->isVisible()) {
1650 m_viewInternal->m_columnScroll->update();
1651 }
1652
1653 if (doc()->config()->autoSave() && doc()->config()->autoSaveOnFocusOut() && doc()->isModified() && doc()->url().isLocalFile()) {
1654 doc()->documentSave();
1655 }
1656
1657 Q_EMIT focusOut(this);
1658}
1659
1660void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode)
1661{
1662 config()->setValue(KateViewConfig::DynWordWrapIndicators, mode);
1663}
1664
1665bool KTextEditor::ViewPrivate::isOverwriteMode() const
1666{
1667 return doc()->config()->ovr();
1668}
1669
1670void KTextEditor::ViewPrivate::reloadFile()
1671{
1672 // bookmarks and cursor positions are temporarily saved by the document
1673 doc()->documentReload();
1674}
1675
1676void KTextEditor::ViewPrivate::slotReadWriteChanged()
1677{
1678 if (m_toggleWriteLock) {
1679 m_toggleWriteLock->setChecked(!doc()->isReadWrite());
1680 }
1681
1682 m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut()));
1683 m_paste->setEnabled(doc()->isReadWrite());
1684 if (m_pasteSelection) {
1685 m_pasteSelection->setEnabled(doc()->isReadWrite());
1686 }
1687 m_swapWithClipboard->setEnabled(doc()->isReadWrite());
1688 m_setEndOfLine->setEnabled(doc()->isReadWrite());
1689
1690 static const auto l = {QStringLiteral("edit_replace"),
1691 QStringLiteral("tools_spelling"),
1692 QStringLiteral("tools_indent"),
1693 QStringLiteral("tools_unindent"),
1694 QStringLiteral("tools_cleanIndent"),
1695 QStringLiteral("tools_formatIndet"),
1696 QStringLiteral("tools_alignOn"),
1697 QStringLiteral("tools_comment"),
1698 QStringLiteral("tools_uncomment"),
1699 QStringLiteral("tools_toggle_comment"),
1700 QStringLiteral("tools_uppercase"),
1701 QStringLiteral("tools_lowercase"),
1702 QStringLiteral("tools_capitalize"),
1703 QStringLiteral("tools_join_lines"),
1704 QStringLiteral("tools_apply_wordwrap"),
1705 QStringLiteral("tools_spelling_from_cursor"),
1706 QStringLiteral("tools_spelling_selection")};
1707
1708 for (const auto &action : l) {
1709 QAction *a = actionCollection()->action(action);
1710 if (a) {
1711 a->setEnabled(doc()->isReadWrite());
1712 }
1713 }
1714 slotUpdateUndo();
1715
1716 currentInputMode()->readWriteChanged(doc()->isReadWrite());
1717
1718 // => view mode changed
1719 Q_EMIT viewModeChanged(this, viewMode());
1720 Q_EMIT viewInputModeChanged(this, viewInputMode());
1721}
1722
1723void KTextEditor::ViewPrivate::toggleCamelCaseCursor()
1724{
1725 const auto enabled = doc()->config()->camelCursor();
1726 doc()->config()->setCamelCursor(!enabled);
1728 if (enabled) {
1729 m = new KTextEditor::Message(i18n("Camel case movement disabled"));
1730 } else {
1731 m = new KTextEditor::Message(i18n("Camel case movement enabled"));
1732 }
1734 m->setAutoHide(1000);
1736 doc()->postMessage(m);
1737}
1738
1739void KTextEditor::ViewPrivate::slotUpdateUndo()
1740{
1741 if (doc()->readOnly()) {
1742 return;
1743 }
1744
1745 m_editUndo->setEnabled(doc()->isReadWrite() && doc()->undoCount() > 0);
1746 m_editRedo->setEnabled(doc()->isReadWrite() && doc()->redoCount() > 0);
1747}
1748
1749bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor position, uint tabwidth, bool calledExternally)
1750{
1751 if (position.line() < 0 || position.line() >= doc()->lines()) {
1752 return false;
1753 }
1754
1755 Kate::TextLine l = doc()->kateTextLine(position.line());
1756 const QString line_str = l.text();
1757
1758 int x = 0;
1759 int z = 0;
1760 for (; z < line_str.length() && z < position.column(); z++) {
1761 if (line_str[z] == QLatin1Char('\t')) {
1762 x += tabwidth - (x % tabwidth);
1763 } else {
1764 x++;
1765 }
1766 }
1767
1768 if (blockSelection()) {
1769 if (z < position.column()) {
1770 x += position.column() - z;
1771 }
1772 }
1773
1774 m_viewInternal->updateCursor(KTextEditor::Cursor(position.line(), x),
1775 false,
1776 calledExternally /* force center for external calls, see bug 408418 */,
1777 calledExternally);
1778
1779 return true;
1780}
1781
1782void KTextEditor::ViewPrivate::toggleInsert()
1783{
1784 doc()->config()->setOvr(!doc()->config()->ovr());
1785 m_toggleInsert->setChecked(isOverwriteMode());
1786
1787 // No multi cursors for overwrite mode
1788 if (isOverwriteMode()) {
1789 clearSecondaryCursors();
1790 }
1791
1792 Q_EMIT viewModeChanged(this, viewMode());
1793 Q_EMIT viewInputModeChanged(this, viewInputMode());
1794}
1795
1796void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error)
1797{
1798 if (!error.isEmpty()) { // happens when canceling a job
1799 KMessageBox::error(this, error);
1800 }
1801}
1802
1803void KTextEditor::ViewPrivate::gotoLine()
1804{
1805 gotoBar()->updateData();
1806 bottomViewBar()->showBarWidget(gotoBar());
1807}
1808
1809void KTextEditor::ViewPrivate::changeDictionary()
1810{
1811 dictionaryBar()->updateData();
1812 bottomViewBar()->showBarWidget(dictionaryBar());
1813}
1814
1815void KTextEditor::ViewPrivate::joinLines()
1816{
1817 int first = selectionRange().start().line();
1818 int last = selectionRange().end().line();
1819 // int left = doc()->line( last ).length() - doc()->selEndCol();
1820 if (first == last) {
1821 first = cursorPosition().line();
1822 last = first + 1;
1823 }
1824 doc()->joinLines(first, last);
1825}
1826
1827void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet<QString> &flags)
1828{
1829 Q_UNUSED(flags)
1830
1831 // cursor position
1832 KTextEditor::Cursor savedPosition(config.readEntry("CursorLine", 0), config.readEntry("CursorColumn", 0));
1833 setCursorPositionInternal(savedPosition);
1834
1835 // scroll position
1836 const int scroll = config.readEntry("ScrollLine", -1);
1837 if (scroll >= 0 && scroll < doc()->lines() && savedPosition.line() < doc()->lines()) {
1838 setScrollPositionInternal(KTextEditor::Cursor(scroll, 0));
1839 }
1840
1841 m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false));
1842
1843 // restore text folding
1844 m_savedFoldingState = QJsonDocument::fromJson(config.readEntry("TextFolding", QByteArray()));
1845 applyFoldingState();
1846
1847 m_forceRTL = config.readEntry("Force RTL Direction", false);
1848 m_forceRTLDirection->setChecked(m_forceRTL);
1849
1850 for (const auto &mode : m_viewInternal->m_inputModes) {
1851 mode->readSessionConfig(config);
1852 }
1853}
1854
1855void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet<QString> &)
1856{
1857 // ensure we don't amass stuff
1858 config.deleteGroup();
1859
1860 // cursor position
1861 const auto cursor = cursorPosition();
1862 if (cursor.isValid() && cursor != KTextEditor::Cursor(0, 0)) {
1863 config.writeEntry("CursorLine", cursor.line());
1864 config.writeEntry("CursorColumn", cursor.column());
1865 }
1866
1867 // save scroll position if its different from cursorPosition
1868 const int scrollLine = firstDisplayedLineInternal(LineType::RealLine);
1869 if (scrollLine > 0 && scrollLine != cursor.line()) {
1870 config.writeEntry("ScrollLine", scrollLine);
1871 }
1872
1873 // only write if set in this view
1874 if (m_config->isSet(KateViewConfig::DynamicWordWrap)) {
1875 config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap());
1876 }
1877
1878 // save text folding state
1879 saveFoldingState();
1880 if (!m_savedFoldingState.object().value(QLatin1String("ranges")).toArray().isEmpty()) {
1881 config.writeEntry("TextFolding", m_savedFoldingState.toJson(QJsonDocument::Compact));
1882 m_savedFoldingState = QJsonDocument();
1883 }
1884
1885 if (m_forceRTL) {
1886 config.writeEntry("Force RTL Direction", m_forceRTL);
1887 }
1888
1889 for (const auto &mode : m_viewInternal->m_inputModes) {
1890 mode->writeSessionConfig(config);
1891 }
1892}
1893
1894int KTextEditor::ViewPrivate::getEol() const
1895{
1896 return doc()->config()->eol();
1897}
1898
1899QMenu *KTextEditor::ViewPrivate::getEolMenu()
1900{
1901 return m_setEndOfLine->menu();
1902}
1903
1904void KTextEditor::ViewPrivate::setEol(int eol)
1905{
1906 if (!doc()->isReadWrite()) {
1907 return;
1908 }
1909
1910 if (m_updatingDocumentConfig) {
1911 return;
1912 }
1913
1914 if (eol != doc()->config()->eol()) {
1915 doc()->setModified(true); // mark modified (bug #143120)
1916 doc()->config()->setEol(eol);
1917 }
1918}
1919
1920void KTextEditor::ViewPrivate::setAddBom(bool enabled)
1921{
1922 if (!doc()->isReadWrite()) {
1923 return;
1924 }
1925
1926 if (m_updatingDocumentConfig) {
1927 return;
1928 }
1929
1930 doc()->config()->setBom(enabled);
1931 doc()->bomSetByUser();
1932}
1933
1934void KTextEditor::ViewPrivate::setIconBorder(bool enable)
1935{
1936 config()->setValue(KateViewConfig::ShowIconBar, enable);
1937}
1938
1939void KTextEditor::ViewPrivate::toggleIconBorder()
1940{
1941 config()->setValue(KateViewConfig::ShowIconBar, !config()->iconBar());
1942}
1943
1944void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable)
1945{
1946 config()->setValue(KateViewConfig::ShowLineNumbers, enable);
1947}
1948
1949void KTextEditor::ViewPrivate::toggleLineNumbersOn()
1950{
1951 config()->setValue(KateViewConfig::ShowLineNumbers, !config()->lineNumbers());
1952}
1953
1954void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable)
1955{
1956 config()->setValue(KateViewConfig::ShowScrollBarMarks, enable);
1957}
1958
1959void KTextEditor::ViewPrivate::toggleScrollBarMarks()
1960{
1961 config()->setValue(KateViewConfig::ShowScrollBarMarks, !config()->scrollBarMarks());
1962}
1963
1964void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable)
1965{
1966 config()->setValue(KateViewConfig::ShowScrollBarMiniMap, enable);
1967}
1968
1969void KTextEditor::ViewPrivate::toggleScrollBarMiniMap()
1970{
1971 config()->setValue(KateViewConfig::ShowScrollBarMiniMap, !config()->scrollBarMiniMap());
1972}
1973
1974void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable)
1975{
1976 config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, enable);
1977}
1978
1979void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll()
1980{
1981 config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, !config()->scrollBarMiniMapAll());
1982}
1983
1984void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width)
1985{
1986 config()->setValue(KateViewConfig::ScrollBarMiniMapWidth, width);
1987}
1988
1989void KTextEditor::ViewPrivate::toggleDynWordWrap()
1990{
1991 config()->setDynWordWrap(!config()->dynWordWrap());
1992}
1993
1994void KTextEditor::ViewPrivate::toggleWWMarker()
1995{
1996 m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker());
1997}
1998
1999void KTextEditor::ViewPrivate::toggleNPSpaces()
2000{
2001 m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces());
2002 m_viewInternal->update(); // force redraw
2003}
2004
2005void KTextEditor::ViewPrivate::toggleWordCount(bool on)
2006{
2007 config()->setShowWordCount(on);
2008}
2009
2010void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable)
2011{
2012 config()->setValue(KateViewConfig::ShowFoldingBar, enable);
2013}
2014
2015void KTextEditor::ViewPrivate::toggleFoldingMarkers()
2016{
2017 config()->setValue(KateViewConfig::ShowFoldingBar, !config()->foldingBar());
2018}
2019
2020bool KTextEditor::ViewPrivate::iconBorder()
2021{
2022 return m_viewInternal->m_leftBorder->iconBorderOn();
2023}
2024
2025bool KTextEditor::ViewPrivate::lineNumbersOn()
2026{
2027 return m_viewInternal->m_leftBorder->lineNumbersOn();
2028}
2029
2030bool KTextEditor::ViewPrivate::scrollBarMarks()
2031{
2032 return m_viewInternal->m_lineScroll->showMarks();
2033}
2034
2035bool KTextEditor::ViewPrivate::scrollBarMiniMap()
2036{
2037 return m_viewInternal->m_lineScroll->showMiniMap();
2038}
2039
2040int KTextEditor::ViewPrivate::dynWrapIndicators()
2041{
2042 return m_viewInternal->m_leftBorder->dynWrapIndicators();
2043}
2044
2045bool KTextEditor::ViewPrivate::foldingMarkersOn()
2046{
2047 return m_viewInternal->m_leftBorder->foldingMarkersOn();
2048}
2049
2050bool KTextEditor::ViewPrivate::forceRTLDirection() const
2051{
2052 return m_forceRTL;
2053}
2054
2055void KTextEditor::ViewPrivate::toggleWriteLock()
2056{
2057 doc()->setReadWrite(!doc()->isReadWrite());
2058}
2059
2060void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider)
2061{
2062 m_viewInternal->registerTextHintProvider(provider);
2063}
2064
2065void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider)
2066{
2067 m_viewInternal->unregisterTextHintProvider(provider);
2068}
2069
2070void KTextEditor::ViewPrivate::setTextHintDelay(int delay)
2071{
2072 m_viewInternal->setTextHintDelay(delay);
2073}
2074
2075int KTextEditor::ViewPrivate::textHintDelay() const
2076{
2077 return m_viewInternal->textHintDelay();
2078}
2079
2080// NOLINTNEXTLINE(readability-make-member-function-const)
2081void KTextEditor::ViewPrivate::find()
2082{
2083 currentInputMode()->find();
2084}
2085
2086// NOLINTNEXTLINE(readability-make-member-function-const)
2087void KTextEditor::ViewPrivate::findSelectedForwards()
2088{
2089 currentInputMode()->findSelectedForwards();
2090}
2091
2092// NOLINTNEXTLINE(readability-make-member-function-const)
2093void KTextEditor::ViewPrivate::findSelectedBackwards()
2094{
2095 currentInputMode()->findSelectedBackwards();
2096}
2097
2098void KTextEditor::ViewPrivate::skipCurrentOccurunceSelection()
2099{
2100 if (isMulticursorNotAllowed()) {
2101 return;
2102 }
2103 m_skipCurrentSelection = true;
2104}
2105
2106void KTextEditor::ViewPrivate::findNextOccurunceAndSelect()
2107{
2108 if (isMulticursorNotAllowed()) {
2109 return;
2110 }
2111
2112 const auto text = selection() ? doc()->text(selectionRange()) : QString();
2113 if (text.isEmpty()) {
2114 const auto selection = doc()->wordRangeAt(cursorPosition());
2115 // We don't want matching word highlights
2116 setSelection(selection);
2117 setCursorPosition(selection.end());
2118 clearHighlights();
2119
2120 for (auto &c : m_secondaryCursors) {
2121 const auto range = doc()->wordRangeAt(c.cursor());
2122 if (!c.range && !c.anchor.isValid()) {
2123 c.anchor = range.start();
2124 c.range.reset(newSecondarySelectionRange(range));
2125 c.pos->setPosition(range.end());
2126 }
2127 tagLines(range);
2128 }
2129 return;
2130 } else if (!m_rangesForHighlights.empty()) {
2131 clearHighlights();
2132 }
2133
2134 // Use selection range end as starting point
2135 const auto lastSelectionRange = selectionRange();
2136
2137 KTextEditor::Range searchRange(lastSelectionRange.end(), doc()->documentRange().end());
2138 QList<KTextEditor::Range> matches = doc()->searchText(searchRange, text, KTextEditor::Default);
2139 if (!matches.isEmpty() && !matches.constFirst().isValid()) {
2140 searchRange.setRange(doc()->documentRange().start(), lastSelectionRange.end());
2141 matches = doc()->searchText(searchRange, text, KTextEditor::Default);
2142 }
2143
2144 // No match found or only one possible match
2145 if (matches.empty() || !matches.constFirst().isValid() || matches.constFirst() == selectionRange()) {
2146 return;
2147 }
2148
2149 auto it = std::find_if(m_secondaryCursors.begin(), m_secondaryCursors.end(), [&](const SecondaryCursor &c) {
2150 return c.range && c.range->toRange() == matches.constFirst();
2151 });
2152
2153 if (it != m_secondaryCursors.end()) {
2154 m_secondaryCursors.erase(it);
2155 }
2156
2157 // Move our primary to cursor to this match and select it
2158 // Ensure we don't create occurence highlights
2159 setSelection(matches.constFirst());
2160 setCursorPosition(matches.constFirst().end());
2161 clearHighlights();
2162
2163 // If we are skipping this selection, then we don't have to do anything
2164 if (!m_skipCurrentSelection) {
2165 PlainSecondaryCursor c;
2166 c.pos = lastSelectionRange.end();
2167 c.range = lastSelectionRange;
2168 // make our previous primary selection a secondary
2169 addSecondaryCursorsWithSelection({c});
2170 }
2171 // reset value
2172 m_skipCurrentSelection = false;
2173}
2174
2175void KTextEditor::ViewPrivate::findAllOccuruncesAndSelect()
2176{
2177 if (isMulticursorNotAllowed()) {
2178 return;
2179 }
2180
2181 QString text = selection() ? doc()->text(selectionRange()) : QString();
2182 if (text.isEmpty()) {
2183 const auto selection = doc()->wordRangeAt(cursorPosition());
2184 setSelection(selection);
2185 setCursorPosition(selection.end());
2186 clearHighlights();
2187 text = doc()->text(selection);
2188
2189 for (auto &c : m_secondaryCursors) {
2190 const auto range = doc()->wordRangeAt(c.cursor());
2191 if (!c.range && !c.anchor.isValid()) {
2192 c.anchor = range.start();
2193 c.range.reset(newSecondarySelectionRange(range));
2194 c.pos->setPosition(range.end());
2195 }
2196 tagLines(range);
2197 }
2198 }
2199
2200 KTextEditor::Range searchRange(doc()->documentRange());
2202 QList<PlainSecondaryCursor> resultRanges;
2203 do {
2204 matches = doc()->searchText(searchRange, text, KTextEditor::Default);
2205
2206 if (matches.constFirst().isValid()) {
2207 // Dont add if matches primary selection
2208 if (matches.constFirst() != selectionRange()) {
2209 PlainSecondaryCursor c;
2210 c.pos = matches.constFirst().end();
2211 c.range = matches.constFirst();
2212 resultRanges.push_back(c);
2213 }
2214 searchRange.setStart(matches.constFirst().end());
2215 }
2216 } while (matches.first().isValid());
2217
2218 // ensure to clear occurence highlights
2219 if (!resultRanges.empty()) {
2220 clearHighlights();
2221 }
2222
2223 clearSecondaryCursors();
2224 addSecondaryCursorsWithSelection(resultRanges);
2225}
2226
2227// NOLINTNEXTLINE(readability-make-member-function-const)
2228void KTextEditor::ViewPrivate::replace()
2229{
2230 currentInputMode()->findReplace();
2231}
2232
2233// NOLINTNEXTLINE(readability-make-member-function-const)
2234void KTextEditor::ViewPrivate::findNext()
2235{
2236 currentInputMode()->findNext();
2237}
2238
2239// NOLINTNEXTLINE(readability-make-member-function-const)
2240void KTextEditor::ViewPrivate::findPrevious()
2241{
2242 currentInputMode()->findPrevious();
2243}
2244
2245void KTextEditor::ViewPrivate::showSearchWrappedHint(bool isReverseSearch)
2246{
2247 // show message widget when wrapping
2248 const QIcon icon = isReverseSearch ? QIcon::fromTheme(QStringLiteral("go-up-search")) : QIcon::fromTheme(QStringLiteral("go-down-search"));
2249
2250 if (!m_wrappedMessage || m_isLastSearchReversed != isReverseSearch) {
2251 m_isLastSearchReversed = isReverseSearch;
2252 m_wrappedMessage = new KTextEditor::Message(i18n("Search wrapped"), KTextEditor::Message::Information);
2253 m_wrappedMessage->setIcon(icon);
2254 m_wrappedMessage->setPosition(KTextEditor::Message::BottomInView);
2255 m_wrappedMessage->setAutoHide(2000);
2256 m_wrappedMessage->setAutoHideMode(KTextEditor::Message::Immediate);
2257 m_wrappedMessage->setView(this);
2258 this->doc()->postMessage(m_wrappedMessage);
2259 }
2260}
2261
2262void KTextEditor::ViewPrivate::createMultiCursorsFromSelection()
2263{
2264 if (!selection() || selectionRange().isEmpty()) {
2265 return;
2266 }
2267 // Is this really needed?
2268 // Lets just clear them now for simplicity
2269 clearSecondaryCursors();
2270
2271 const auto range = selectionRange();
2272 QList<KTextEditor::Cursor> cursorsToAdd;
2273 const auto start = range.start().line() < 0 ? 0 : range.start().line();
2274 const auto end = range.end().line() > doc()->lines() ? doc()->lines() : range.end().line();
2275 const auto currentLine = cursorPosition().line();
2276 setCursorPosition({currentLine, doc()->lineLength(currentLine)});
2277 for (int line = start; line <= end; ++line) {
2278 if (line != currentLine) {
2279 cursorsToAdd.push_back({line, doc()->lineLength(line)});
2280 }
2281 }
2282 // clear selection
2283 setSelection({});
2284 setSecondaryCursors(cursorsToAdd);
2285}
2286
2287void KTextEditor::ViewPrivate::removeCursorsFromEmptyLines()
2288{
2289 if (!m_secondaryCursors.empty()) {
2290 std::vector<KTextEditor::Cursor> cursorsToRemove;
2291 for (const auto &c : m_secondaryCursors) {
2292 auto cursor = c.cursor();
2293 if (doc()->lineLength(cursor.line()) == 0) {
2294 cursorsToRemove.push_back(cursor);
2295 }
2296 }
2297 removeSecondaryCursors(cursorsToRemove);
2298 }
2299}
2300
2301void KTextEditor::ViewPrivate::slotSelectionChanged()
2302{
2303 m_copy->setEnabled(selection() || m_config->smartCopyCut());
2304 m_deSelect->setEnabled(selection());
2305 m_copyHtmlAction->setEnabled(selection());
2306
2307 // update highlighting of current selected word
2308 selectionChangedForHighlights();
2309
2310 if (doc()->readOnly()) {
2311 return;
2312 }
2313
2314 m_cut->setEnabled(selection() || m_config->smartCopyCut());
2315 m_screenshotSelection->setVisible(selection());
2316 m_screenshotSelection->setEnabled(selection());
2317}
2318
2319// NOLINTNEXTLINE(readability-make-member-function-const)
2320void KTextEditor::ViewPrivate::switchToCmdLine()
2321{
2322 currentInputMode()->activateCommandLine();
2323}
2324
2325KateRenderer *KTextEditor::ViewPrivate::renderer()
2326{
2327 return m_renderer;
2328}
2329
2330KateRendererConfig *KTextEditor::ViewPrivate::rendererConfig()
2331{
2332 return m_renderer->config();
2333}
2334
2335void KTextEditor::ViewPrivate::updateConfig()
2336{
2337 if (m_startingUp) {
2338 return;
2339 }
2340
2341 // dyn. word wrap & markers
2342 if (m_hasWrap != config()->dynWordWrap()) {
2343 m_hasWrap = config()->dynWordWrap();
2344
2345 m_viewInternal->dynWrapChanged();
2346
2347 m_setDynWrapIndicators->setEnabled(config()->dynWordWrap());
2348 m_toggleDynWrap->setChecked(config()->dynWordWrap());
2349 }
2350
2351 m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators());
2352 m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators());
2353
2354 // line numbers
2355 m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers());
2356 m_toggleLineNumbers->setChecked(config()->lineNumbers());
2357
2358 // icon bar
2359 m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar());
2360 m_toggleIconBar->setChecked(config()->iconBar());
2361
2362 // scrollbar marks
2363 m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks());
2364 m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks());
2365
2366 // scrollbar mini-map
2367 m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap());
2368 m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap());
2369
2370 // scrollbar mini-map - (whole document)
2371 m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll());
2372 // m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() );
2373
2374 // scrollbar mini-map.width
2375 m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth());
2376
2377 // misc edit
2378 m_toggleBlockSelection->setChecked(blockSelection());
2379 m_toggleInsert->setChecked(isOverwriteMode());
2380
2381 updateFoldingConfig();
2382
2383 // bookmark
2384 m_bookmarks->setSorting((KateBookmarks::Sorting)config()->bookmarkSort());
2385
2386 m_viewInternal->setAutoCenterLines(config()->autoCenterLines());
2387
2388 for (const auto &input : m_viewInternal->m_inputModes) {
2389 input->updateConfig();
2390 }
2391
2392 setInputMode(config()->inputMode(), false /* don't remember in config for these calls */);
2393
2394 reflectOnTheFlySpellCheckStatus(doc()->isOnTheFlySpellCheckingEnabled());
2395
2396 // register/unregister word completion...
2397 bool wc = config()->wordCompletion();
2398 if (wc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->wordCompletionModel())) {
2399 if (wc) {
2400 registerCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel());
2401 } else {
2402 unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel());
2403 }
2404 }
2405
2406 bool kc = config()->keywordCompletion();
2407 if (kc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->keywordCompletionModel())) {
2408 if (kc) {
2409 registerCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel());
2410 } else {
2411 unregisterCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel());
2412 }
2413 }
2414
2415 m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut()));
2416 m_copy->setEnabled(selection() || m_config->smartCopyCut());
2417
2418 m_accessibilityEnabled = m_config->value(KateViewConfig::EnableAccessibility).toBool();
2419
2420 // if not disabled, update status bar
2421 if (m_statusBar) {
2422 m_statusBar->updateStatus();
2423 }
2424
2425 // now redraw...
2426 m_viewInternal->cache()->clear();
2427 tagAll();
2428 updateView(true);
2429
2430 Q_EMIT configChanged(this);
2431}
2432
2433void KTextEditor::ViewPrivate::updateDocumentConfig()
2434{
2435 if (m_startingUp) {
2436 return;
2437 }
2438
2439 m_updatingDocumentConfig = true;
2440
2441 m_setEndOfLine->setCurrentItem(doc()->config()->eol());
2442
2443 m_addBom->setChecked(doc()->config()->bom());
2444
2445 m_updatingDocumentConfig = false;
2446
2447 // maybe block selection or wrap-cursor mode changed
2448 ensureCursorColumnValid();
2449
2450 // first change this
2451 m_renderer->setTabWidth(doc()->config()->tabWidth());
2452 m_renderer->setIndentWidth(doc()->config()->indentationWidth());
2453
2454 // now redraw...
2455 m_viewInternal->cache()->clear();
2456 tagAll();
2457 updateView(true);
2458}
2459
2460void KTextEditor::ViewPrivate::updateRendererConfig()
2461{
2462 if (m_startingUp) {
2463 return;
2464 }
2465
2466 m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker());
2467
2468 m_viewInternal->updateBracketMarkAttributes();
2469 m_viewInternal->updateBracketMarks();
2470
2471 // now redraw...
2472 m_viewInternal->cache()->clear();
2473 tagAll();
2474 m_viewInternal->updateView(true);
2475
2476 // update the left border right, for example linenumbers
2477 m_viewInternal->m_leftBorder->updateFont();
2478 m_viewInternal->m_leftBorder->repaint();
2479
2480 m_viewInternal->m_lineScroll->queuePixmapUpdate();
2481
2482 currentInputMode()->updateRendererConfig();
2483
2484 // @@ showIndentLines is not cached anymore.
2485 // m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines());
2486 Q_EMIT configChanged(this);
2487}
2488
2489void KTextEditor::ViewPrivate::updateFoldingConfig()
2490{
2491 // folding bar
2492 m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar());
2493 m_toggleFoldingMarkers->setChecked(config()->foldingBar());
2494
2495 if (hasCommentInFirstLine(m_doc)) {
2496 if (config()->foldFirstLine() && !m_autoFoldedFirstLine) {
2497 foldLine(0);
2498 m_autoFoldedFirstLine = true;
2499 } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) {
2500 unfoldLine(0);
2501 m_autoFoldedFirstLine = false;
2502 }
2503 } else {
2504 m_autoFoldedFirstLine = false;
2505 }
2506
2507#if 0
2508 // FIXME: FOLDING
2509 const QStringList l = {
2510 QStringLiteral("folding_toplevel")
2511 , QStringLiteral("folding_expandtoplevel")
2512 , QStringLiteral("folding_toggle_current")
2513 , QStringLiteral("folding_toggle_in_current")
2514 };
2515
2516 QAction *a = 0;
2517 for (int z = 0; z < l.size(); z++)
2518 if ((a = actionCollection()->action(l[z].toAscii().constData()))) {
2519 a->setEnabled(doc()->highlight() && doc()->highlight()->allowsFolding());
2520 }
2521#endif
2522}
2523
2524void KTextEditor::ViewPrivate::ensureCursorColumnValid()
2525{
2526 KTextEditor::Cursor c = m_viewInternal->cursorPosition();
2527
2528 // make sure the cursor is valid:
2529 // - in block selection mode or if wrap cursor is off, the column is arbitrary
2530 // - otherwise: it's bounded by the line length
2531 if (!blockSelection() && wrapCursor() && (!c.isValid() || c.column() > doc()->lineLength(c.line()))) {
2532 c.setColumn(doc()->lineLength(cursorPosition().line()));
2533 setCursorPosition(c);
2534 }
2535}
2536
2537// BEGIN EDIT STUFF
2538void KTextEditor::ViewPrivate::editStart()
2539{
2540 m_viewInternal->editStart();
2541}
2542
2543void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom)
2544{
2545 m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom);
2546 textFolding().editEnd(editTagLineStart, editTagLineEnd, [this](int line) {
2547 return m_doc->buffer().isFoldingStartingOnLine(line).first;
2548 });
2549}
2550
2551void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor cursor)
2552{
2553 m_viewInternal->editSetCursor(cursor);
2554}
2555// END
2556
2557// BEGIN TAG & CLEAR
2558bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor virtualCursor)
2559{
2560 return m_viewInternal->tagLine(virtualCursor);
2561}
2562
2563bool KTextEditor::ViewPrivate::tagRange(KTextEditor::Range range, bool realLines)
2564{
2565 return m_viewInternal->tagRange(range, realLines);
2566}
2567
2568bool KTextEditor::ViewPrivate::tagLines(KTextEditor::LineRange lineRange, bool realLines)
2569{
2570 return m_viewInternal->tagLines(lineRange.start(), lineRange.end(), realLines);
2571}
2572
2573bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors)
2574{
2575 return m_viewInternal->tagLines(start, end, realCursors);
2576}
2577
2578void KTextEditor::ViewPrivate::tagAll()
2579{
2580 m_viewInternal->tagAll();
2581}
2582
2583void KTextEditor::ViewPrivate::clear()
2584{
2585 m_viewInternal->clear();
2586}
2587
2588void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty)
2589{
2590 if (paintOnlyDirty) {
2591 m_viewInternal->updateDirty();
2592 } else {
2593 m_viewInternal->update();
2594 }
2595}
2596
2597void KTextEditor::ViewPrivate::updateView(bool changed)
2598{
2599 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView";
2600 m_viewInternal->updateView(changed);
2601 m_viewInternal->m_leftBorder->update();
2602}
2603
2604// END
2605
2606void KTextEditor::ViewPrivate::slotHlChanged()
2607{
2608 KateHighlighting *hl = doc()->highlight();
2609 bool ok(!hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty());
2610
2611 if (actionCollection()->action(QStringLiteral("tools_comment"))) {
2612 actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok);
2613 }
2614
2615 if (actionCollection()->action(QStringLiteral("tools_uncomment"))) {
2616 actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok);
2617 }
2618
2619 if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) {
2620 actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok);
2621 }
2622
2623 // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry
2624 updateFoldingConfig();
2625}
2626
2627int KTextEditor::ViewPrivate::virtualCursorColumn() const
2628{
2629 return doc()->toVirtualColumn(m_viewInternal->cursorPosition());
2630}
2631
2632void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor newPosition)
2633{
2634 Q_EMIT mousePositionChanged(this, newPosition);
2635}
2636
2637// BEGIN KTextEditor::SelectionInterface stuff
2638
2639bool KTextEditor::ViewPrivate::setSelection(KTextEditor::Range selection)
2640{
2641 // anything to do?
2642 if (selection == m_selection) {
2643 return true;
2644 }
2645
2646 // backup old range
2647 KTextEditor::Range oldSelection = m_selection;
2648
2649 // set new range
2650 m_selection.setRange(selection.isEmpty() ? KTextEditor::Range::invalid() : selection);
2651
2652 // trigger update of correct area
2653 tagSelection(oldSelection);
2654 repaintText(true);
2655
2656 // emit holy signal
2657 Q_EMIT selectionChanged(this);
2658
2659 // be done
2660 return true;
2661}
2662
2663bool KTextEditor::ViewPrivate::clearSelection()
2664{
2665 return clearSelection(true);
2666}
2667
2668bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection)
2669{
2670 // no selection, nothing to do...
2671 if (!selection()) {
2672 return false;
2673 }
2674
2675 // backup old range
2676 KTextEditor::Range oldSelection = m_selection;
2677
2678 // invalidate current selection
2680
2681 // trigger update of correct area
2682 tagSelection(oldSelection);
2683 if (redraw) {
2684 repaintText(true);
2685 }
2686
2687 // emit holy signal
2688 if (finishedChangingSelection) {
2689 Q_EMIT selectionChanged(this);
2690 }
2691
2692 m_viewInternal->m_selChangedByUser = false;
2693 // be done
2694 return true;
2695}
2696
2697bool KTextEditor::ViewPrivate::selection() const
2698{
2699 if (!wrapCursor()) {
2700 return m_selection != KTextEditor::Range::invalid();
2701 } else {
2702 return m_selection.toRange().isValid();
2703 }
2704}
2705
2706QString KTextEditor::ViewPrivate::selectionText() const
2707{
2708 if (blockSelect) {
2709 return doc()->text(m_selection, blockSelect);
2710 }
2711
2713 for (const auto &c : m_secondaryCursors) {
2714 if (c.range) {
2715 ranges.push_back(c.range->toRange());
2716 }
2717 }
2718 ranges.push_back(m_selection.toRange());
2719 std::sort(ranges.begin(), ranges.end());
2720
2721 QString text;
2722 text.reserve(ranges.size() * m_selection.toRange().columnWidth());
2723 for (int i = 0; i < ranges.size() - 1; ++i) {
2724 text += doc()->text(ranges[i]) + QStringLiteral("\n");
2725 }
2726 text += doc()->text(ranges.last());
2727
2728 return text;
2729}
2730
2731bool KTextEditor::ViewPrivate::removeSelectedText()
2732{
2733 if (!hasSelections()) {
2734 return false;
2735 }
2736
2738
2739 bool removed = false;
2740 // Handle multicursors selection removal
2741 if (!blockSelect) {
2742 completionWidget()->setIgnoreBufferSignals(true);
2743 for (auto &c : m_secondaryCursors) {
2744 if (c.range) {
2745 removed = true;
2746 doc()->removeText(c.range->toRange());
2747 c.clearSelection();
2748 }
2749 }
2750 completionWidget()->setIgnoreBufferSignals(false);
2751 }
2752
2753 // Optimization: clear selection before removing text
2754 KTextEditor::Range selection = m_selection;
2755 if (!selection.isValid()) {
2756 return removed;
2757 }
2758 doc()->removeText(selection, blockSelect);
2759 removed = true;
2760
2761 // don't redraw the cleared selection - that's done in editEnd().
2762 if (blockSelect) {
2763 int selectionColumn = qMin(doc()->toVirtualColumn(selection.start()), doc()->toVirtualColumn(selection.end()));
2764 KTextEditor::Range newSelection = selection;
2765 newSelection.setStart(KTextEditor::Cursor(newSelection.start().line(), doc()->fromVirtualColumn(newSelection.start().line(), selectionColumn)));
2766 newSelection.setEnd(KTextEditor::Cursor(newSelection.end().line(), doc()->fromVirtualColumn(newSelection.end().line(), selectionColumn)));
2767 setSelection(newSelection);
2768 setCursorPositionInternal(newSelection.start());
2769 } else {
2770 clearSecondarySelections();
2771 clearSelection(false);
2772 }
2773
2774 return removed;
2775}
2776
2777bool KTextEditor::ViewPrivate::selectAll()
2778{
2779 clearSecondaryCursors();
2780 setBlockSelection(false);
2781 // We use setSelection here to ensure we don't scroll anywhere
2782 // The cursor stays in place i.e., it doesn't move to end of selection
2783 // that is okay and expected.
2784 // The idea here is to maintain scroll position in case select all was
2785 // mistakenly triggered, and also to if you just want to copy text,
2786 // there is no need to scroll anywhere.
2787 setSelection(doc()->documentRange());
2788 m_viewInternal->moveCursorToSelectionEdge(/*scroll=*/false);
2789 m_viewInternal->updateMicroFocus();
2790 return true;
2791}
2792
2793bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor cursor)
2794{
2795 KTextEditor::Cursor ret = cursor;
2796 if ((!blockSelect) && (ret.column() < 0)) {
2797 ret.setColumn(0);
2798 }
2799
2800 if (blockSelect) {
2801 return cursor.line() >= m_selection.start().line() && ret.line() <= m_selection.end().line() && ret.column() >= m_selection.start().column()
2802 && ret.column() <= m_selection.end().column();
2803 } else {
2804 return m_selection.toRange().contains(cursor) || m_selection.end() == cursor;
2805 }
2806}
2807
2808bool KTextEditor::ViewPrivate::lineSelected(int line)
2809{
2810 return !blockSelect && m_selection.toRange().containsLine(line);
2811}
2812
2813bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor lineEndPos)
2814{
2815 return (!blockSelect)
2816 && (lineEndPos.line() > m_selection.start().line()
2817 || (lineEndPos.line() == m_selection.start().line() && (m_selection.start().column() < lineEndPos.column() || lineEndPos.column() == -1)))
2818 && (lineEndPos.line() < m_selection.end().line()
2819 || (lineEndPos.line() == m_selection.end().line() && (lineEndPos.column() <= m_selection.end().column() && lineEndPos.column() != -1)));
2820}
2821
2822bool KTextEditor::ViewPrivate::lineHasSelected(int line)
2823{
2824 return selection() && m_selection.toRange().containsLine(line);
2825}
2826
2827bool KTextEditor::ViewPrivate::lineIsSelection(int line)
2828{
2829 return (line == m_selection.start().line() && line == m_selection.end().line());
2830}
2831
2832void KTextEditor::ViewPrivate::tagSelection(KTextEditor::Range oldSelection)
2833{
2834 if (selection()) {
2835 if (oldSelection.start().line() == -1) {
2836 // We have to tag the whole lot if
2837 // 1) we have a selection, and:
2838 // a) it's new; or
2839 tagLines(m_selection, true);
2840
2841 } else if (blockSelection()
2842 && (oldSelection.start().column() != m_selection.start().column() || oldSelection.end().column() != m_selection.end().column())) {
2843 // b) we're in block selection mode and the columns have changed
2844 tagLines(m_selection, true);
2845 tagLines(oldSelection, true);
2846
2847 } else {
2848 if (oldSelection.start() != m_selection.start()) {
2849 tagLines(KTextEditor::LineRange(oldSelection.start().line(), m_selection.start().line()), true);
2850 }
2851
2852 if (oldSelection.end() != m_selection.end()) {
2853 tagLines(KTextEditor::LineRange(oldSelection.end().line(), m_selection.end().line()), true);
2854 }
2855 }
2856
2857 } else {
2858 // No more selection, clean up
2859 tagLines(oldSelection, true);
2860 }
2861}
2862
2863void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor cursor)
2864{
2865 setSelection(doc()->wordRangeAt(cursor));
2866}
2867
2868void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor cursor)
2869{
2870 int line = cursor.line();
2871 if (line + 1 >= doc()->lines()) {
2872 setSelection(KTextEditor::Range(line, 0, line, doc()->lineLength(line)));
2873 } else {
2874 setSelection(KTextEditor::Range(line, 0, line + 1, 0));
2875 }
2876}
2877
2878void KTextEditor::ViewPrivate::cut()
2879{
2880 if (!selection() && !m_config->smartCopyCut()) {
2881 return;
2882 }
2883
2884 // If markedSelection is true, copy() invalidates the selection,
2885 // which would obviate the removeSelectedText() here below.
2886 m_markedSelection = false;
2887
2888 copy();
2889 if (!selection()) {
2890 selectLine(cursorPosition());
2891 }
2892 removeSelectedText();
2893}
2894
2895void KTextEditor::ViewPrivate::copy()
2896{
2897 QString text;
2898 KTextEditor::EditorPrivate::self()->copyToMulticursorClipboard({});
2899
2900 if (!selection()) {
2901 if (!m_config->smartCopyCut()) {
2902 return;
2903 }
2904 text = doc()->line(cursorPosition().line()) + QLatin1Char('\n');
2905 m_viewInternal->moveEdge(KateViewInternal::left, false);
2906 } else {
2907 text = selectionText();
2908
2909 // Multicursor copy
2910 if (!m_secondaryCursors.empty()) {
2912 for (const auto &c : m_secondaryCursors) {
2913 if (c.range) {
2914 ranges.push_back(c.range->toRange());
2915 }
2916 }
2917 ranges.push_back(m_selection.toRange());
2918 std::sort(ranges.begin(), ranges.end());
2919 QStringList texts;
2920 for (auto range : ranges) {
2921 texts.append(doc()->text(range));
2922 }
2923 KTextEditor::EditorPrivate::self()->copyToMulticursorClipboard(texts);
2924 }
2925
2926 if (m_markedSelection) {
2927 setSelection(KTextEditor::Range::invalid());
2928 m_markedSelection = false;
2929 }
2930 }
2931
2932 // copy to clipboard and our history!
2933 KTextEditor::EditorPrivate::self()->copyToClipboard(text, m_doc->url().fileName());
2934}
2935
2936void KTextEditor::ViewPrivate::screenshot()
2937{
2938 if (!selection()) {
2939 return;
2940 }
2941
2942 ScreenshotDialog d(selectionRange(), this);
2943 d.renderScreenshot(m_renderer);
2944 d.exec();
2945}
2946
2947void KTextEditor::ViewPrivate::pasteSelection()
2948{
2949 m_temporaryAutomaticInvocationDisabled = true;
2950 doc()->paste(this, QApplication::clipboard()->text(QClipboard::Selection));
2951 m_temporaryAutomaticInvocationDisabled = false;
2952}
2953
2954void KTextEditor::ViewPrivate::swapWithClipboard()
2955{
2956 m_temporaryAutomaticInvocationDisabled = true;
2957
2958 // get text to paste
2960
2961 // do copy
2962 copy();
2963
2964 // do paste of "previous" clipboard content we saved
2965 doc()->paste(this, text);
2966
2967 m_temporaryAutomaticInvocationDisabled = false;
2968}
2969
2970void KTextEditor::ViewPrivate::applyWordWrap()
2971{
2972 int first = selectionRange().start().line();
2973 int last = selectionRange().end().line();
2974
2975 if (first == last) {
2976 // Either no selection or only one line selected, wrap only the current line
2977 first = cursorPosition().line();
2978 last = first;
2979 }
2980
2981 doc()->wrapParagraph(first, last);
2982}
2983
2984// END
2985
2986// BEGIN KTextEditor::BlockSelectionInterface stuff
2987
2988bool KTextEditor::ViewPrivate::blockSelection() const
2989{
2990 return blockSelect;
2991}
2992
2993bool KTextEditor::ViewPrivate::setBlockSelection(bool on)
2994{
2995 if (on != blockSelect) {
2996 blockSelect = on;
2997
2998 KTextEditor::Range oldSelection = m_selection;
2999
3000 const bool hadSelection = clearSelection(false, false);
3001
3002 setSelection(oldSelection);
3003
3004 m_toggleBlockSelection->setChecked(blockSelection());
3005
3006 // when leaving block selection mode, if cursor is at an invalid position or past the end of the
3007 // line, move the cursor to the last column of the current line unless cursor wrapping is off
3008 ensureCursorColumnValid();
3009
3010 if (!hadSelection) {
3011 // emit selectionChanged() according to the KTextEditor::View api
3012 // documentation also if there is no selection around. This is needed,
3013 // as e.g. the Kate App status bar uses this signal to update the state
3014 // of the selection mode (block selection, line based selection)
3015 Q_EMIT selectionChanged(this);
3016 }
3017 }
3018
3019 return true;
3020}
3021
3022bool KTextEditor::ViewPrivate::toggleBlockSelection()
3023{
3024 // no multicursors for blockselect
3025 clearSecondaryCursors();
3026
3027 m_toggleBlockSelection->setChecked(!blockSelect);
3028 return setBlockSelection(!blockSelect);
3029}
3030
3031bool KTextEditor::ViewPrivate::wrapCursor() const
3032{
3033 return !blockSelection();
3034}
3035
3036// END
3037
3038void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor position, const QString &text)
3039{
3040 Q_EMIT textInserted(view, position, text);
3041}
3042
3043bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor c, const QString &templateString, const QString &script)
3044{
3045 // no empty templates
3046 if (templateString.isEmpty()) {
3047 return false;
3048 }
3049
3050 // not for read-only docs
3051 if (!doc()->isReadWrite()) {
3052 return false;
3053 }
3054
3055 // only one handler maybe active at a time; store it in the document.
3056 // Clear it first to make sure at no time two handlers are active at once
3057 doc()->setActiveTemplateHandler(nullptr);
3058 doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, doc()->undoManager()));
3059 return true;
3060}
3061
3062bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange)
3063{
3064 return tagLines(range.start(), range.end(), realRange);
3065}
3066
3067void KTextEditor::ViewPrivate::deactivateEditActions()
3068{
3069 for (QAction *action : std::as_const(m_editActions)) {
3070 action->setEnabled(false);
3071 }
3072}
3073
3074void KTextEditor::ViewPrivate::activateEditActions()
3075{
3076 for (QAction *action : std::as_const(m_editActions)) {
3077 action->setEnabled(true);
3078 }
3079}
3080
3081bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const
3082{
3083 // FIXME support
3084 return true;
3085}
3086
3087bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool)
3088{
3089 // FIXME support
3090 return true;
3091}
3092
3093bool KTextEditor::ViewPrivate::isMulticursorNotAllowed() const
3094{
3095 return blockSelection() || isOverwriteMode() || currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode;
3096}
3097
3098void KTextEditor::ViewPrivate::addSecondaryCursor(KTextEditor::Cursor pos)
3099{
3100 auto primaryCursor = cursorPosition();
3101 const bool overlapsOrOnPrimary = pos == primaryCursor || (selection() && selectionRange().contains(pos));
3102 if (overlapsOrOnPrimary && m_secondaryCursors.empty()) {
3103 // Clicking on primary cursor while it is the only cursor,
3104 // we do nothing
3105 return;
3106 } else if (overlapsOrOnPrimary) {
3107 // Clicking on primary cursor, we have secondaries
3108 // so just make the last secondary cursor primary
3109 // and remove caret at current primary cursor position
3110 auto &last = m_secondaryCursors.back();
3111 setCursorPosition(last.cursor());
3112 if (last.range) {
3113 setSelection(last.range->toRange());
3114 Q_ASSERT(last.anchor.isValid());
3115 m_viewInternal->m_selectAnchor = last.anchor;
3116 }
3117 m_secondaryCursors.pop_back();
3118 return;
3119 }
3120
3121 // If there are any existing cursors at this position
3122 // remove them and be done i.e., if you click on an
3123 // existing cursor it is removed.
3124 if (removeSecondaryCursors({pos}, /*removeIfSelectionOverlap=*/true)) {
3125 return;
3126 }
3127
3128 // We are adding a new cursor!
3129 // - Move primary cursor to the position where the click happened
3130 // - Old primary cursor becomes a secondary cursor
3131 // Doing it like this makes multi mouse selections very easy
3132 setCursorPosition(pos);
3133 KTextEditor::ViewPrivate::PlainSecondaryCursor p;
3134 p.pos = primaryCursor;
3135 p.range = selection() ? selectionRange() : KTextEditor::Range::invalid();
3136 clearSelection();
3137 addSecondaryCursorsWithSelection({p});
3138}
3139
3140void KTextEditor::ViewPrivate::setSecondaryCursors(const QList<KTextEditor::Cursor> &positions)
3141{
3142 clearSecondaryCursors();
3143
3144 if (positions.isEmpty() || isMulticursorNotAllowed()) {
3145 return;
3146 }
3147
3148 const auto totalLines = doc()->lines();
3149 for (auto p : positions) {
3150 if (p != cursorPosition() && p.line() < totalLines) {
3151 SecondaryCursor c;
3152 c.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(p)));
3153 m_secondaryCursors.push_back(std::move(c));
3154 tagLine(p);
3155 }
3156 }
3157 sortCursors();
3158 paintCursors();
3159}
3160
3161void KTextEditor::ViewPrivate::clearSecondarySelections()
3162{
3163 for (auto &c : m_secondaryCursors) {
3164 c.range.reset();
3165 c.anchor = KTextEditor::Cursor::invalid();
3166 }
3167}
3168
3169void KTextEditor::ViewPrivate::clearSecondaryCursors()
3170{
3171 if (m_secondaryCursors.empty()) {
3172 return;
3173 }
3174 for (const auto &c : m_secondaryCursors) {
3175 tagLine(c.cursor());
3176 }
3177 m_secondaryCursors.clear();
3178 m_viewInternal->updateDirty();
3179}
3180
3181const std::vector<KTextEditor::ViewPrivate::SecondaryCursor> &KTextEditor::ViewPrivate::secondaryCursors() const
3182{
3183 return m_secondaryCursors;
3184}
3185
3186QList<KTextEditor::ViewPrivate::PlainSecondaryCursor> KTextEditor::ViewPrivate::plainSecondaryCursors() const
3187{
3189 cursors.reserve(m_secondaryCursors.size());
3190 std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(cursors), [](const SecondaryCursor &c) {
3191 if (c.range) {
3192 return PlainSecondaryCursor{c.cursor(), c.range->toRange()};
3193 }
3194 return PlainSecondaryCursor{c.cursor(), KTextEditor::Range::invalid()};
3195 });
3196 return cursors;
3197}
3198
3199bool KTextEditor::ViewPrivate::removeSecondaryCursors(const std::vector<KTextEditor::Cursor> &cursorsToRemove, bool removeIfOverlapsSelection)
3200{
3201 Q_ASSERT(std::is_sorted(cursorsToRemove.begin(), cursorsToRemove.end()));
3202
3204
3205 if (removeIfOverlapsSelection) {
3206 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(),
3207 m_secondaryCursors.end(),
3208 [&](const SecondaryCursor &c) {
3209 auto it = std::find_if(cursorsToRemove.begin(), cursorsToRemove.end(), [&c](KTextEditor::Cursor pos) {
3210 return c.cursor() == pos || (c.range && c.range->contains(pos));
3211 });
3212 const bool match = it != cursorsToRemove.end();
3213 if (match) {
3214 linesToTag.push_back(c.cursor());
3215 }
3216 return match;
3217 }),
3218 m_secondaryCursors.end());
3219 } else {
3220 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(),
3221 m_secondaryCursors.end(),
3222 [&](const SecondaryCursor &c) {
3223 auto it = std::find_if(cursorsToRemove.begin(), cursorsToRemove.end(), [&c](KTextEditor::Cursor pos) {
3224 return c.cursor() == pos;
3225 });
3226 const bool match = it != cursorsToRemove.end();
3227 if (match) {
3228 linesToTag.push_back(c.cursor());
3229 }
3230 return match;
3231 }),
3232 m_secondaryCursors.end());
3233 }
3234
3235 for (const auto &c : linesToTag) {
3236 tagLine(m_viewInternal->toVirtualCursor(c));
3237 }
3238 return !linesToTag.empty();
3239
3240 for (auto cur : cursorsToRemove) {
3241 auto &sec = m_secondaryCursors;
3242 auto it = std::find_if(sec.begin(), sec.end(), [cur](const SecondaryCursor &c) {
3243 return c.cursor() == cur;
3244 });
3245 if (it != sec.end()) {
3246 // removedAny = true;
3247 m_secondaryCursors.erase(it);
3248 tagLine(m_viewInternal->toVirtualCursor(cur));
3249 }
3250 }
3251
3252 // if (removedAny) {
3253 m_viewInternal->updateDirty();
3254 if (cursorPosition() == KTextEditor::Cursor(0, 0)) {
3255 m_viewInternal->paintCursor();
3256 }
3257 return !linesToTag.empty();
3258 // }
3259 // return removedAny;
3260}
3261
3262void KTextEditor::ViewPrivate::ensureUniqueCursors(bool matchLine)
3263{
3264 if (m_secondaryCursors.empty()) {
3265 return;
3266 }
3267
3268 std::vector<SecondaryCursor>::iterator it;
3269 if (matchLine) {
3270 auto matchLine = [](const SecondaryCursor &l, const SecondaryCursor &r) {
3271 return l.cursor().line() == r.cursor().line();
3272 };
3273 it = std::unique(m_secondaryCursors.begin(), m_secondaryCursors.end(), matchLine);
3274 } else {
3275 it = std::unique(m_secondaryCursors.begin(), m_secondaryCursors.end());
3276 }
3277 if (it != m_secondaryCursors.end()) {
3278 m_secondaryCursors.erase(it, m_secondaryCursors.end());
3279 }
3280
3281 if (matchLine) {
3282 const int ln = cursorPosition().line();
3283 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(),
3284 m_secondaryCursors.end(),
3285 [ln](const SecondaryCursor &c) {
3286 return c.cursor().line() == ln;
3287 }),
3288 m_secondaryCursors.end());
3289 } else {
3290 const auto cp = cursorPosition();
3291 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(),
3292 m_secondaryCursors.end(),
3293 [cp](const SecondaryCursor &c) {
3294 return c.cursor() == cp;
3295 }),
3296 m_secondaryCursors.end());
3297 }
3298}
3299
3300void KTextEditor::ViewPrivate::addSecondaryCursorsWithSelection(const QList<PlainSecondaryCursor> &cursorsWithSelection)
3301{
3302 if (isMulticursorNotAllowed() || cursorsWithSelection.isEmpty()) {
3303 return;
3304 }
3305
3306 for (const auto &c : cursorsWithSelection) {
3307 // We don't want to add on top of primary cursor
3308 if (c.pos == cursorPosition()) {
3309 continue;
3310 }
3311 SecondaryCursor n;
3312 n.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(c.pos)));
3313 if (c.range.isValid()) {
3314 n.range.reset(newSecondarySelectionRange(c.range));
3315 n.anchor = c.range.start() == c.pos ? c.range.end() : c.range.start();
3316 }
3317 m_secondaryCursors.push_back(std::move(n));
3318 }
3319 sortCursors();
3320 paintCursors();
3321}
3322
3323Kate::TextRange *KTextEditor::ViewPrivate::newSecondarySelectionRange(KTextEditor::Range selRange)
3324{
3326 auto range = new Kate::TextRange(doc()->buffer(), selRange, expandBehaviour);
3327 static KTextEditor::Attribute::Ptr selAttr;
3328 if (!selAttr) {
3329 selAttr = new KTextEditor::Attribute;
3330 auto color = QColor::fromRgba(theme().editorColor(KSyntaxHighlighting::Theme::TextSelection));
3331 selAttr->setBackground(color);
3332 }
3333 range->setZDepth(-999999.);
3334 range->setAttribute(selAttr);
3335 return range;
3336}
3337
3338bool KTextEditor::ViewPrivate::hasSelections() const
3339{
3340 if (selection())
3341 return true;
3342 return std::any_of(m_secondaryCursors.cbegin(), m_secondaryCursors.cend(), [](const SecondaryCursor &c) {
3343 return c.range && !c.range->isEmpty();
3344 });
3345}
3346
3347void KTextEditor::ViewPrivate::addSecondaryCursorDown()
3348{
3349 KTextEditor::Cursor last = cursorPosition();
3350 const auto &secondary = secondaryCursors();
3351 if (!secondary.empty()) {
3352 last = secondary.back().cursor();
3353 last = std::max(cursorPosition(), last);
3354 }
3355 if (last.line() >= doc()->lastLine()) {
3356 return;
3357 }
3358
3359 auto nextRange = m_viewInternal->nextLayout(last);
3360 if (!nextRange.isValid()) {
3361 return;
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
3373void KTextEditor::ViewPrivate::addSecondaryCursorUp()
3374{
3375 KTextEditor::Cursor last = cursorPosition();
3376 const auto &secondary = secondaryCursors();
3377 if (!secondary.empty()) {
3378 last = secondary.front().cursor();
3379 last = std::min(cursorPosition(), last);
3380 }
3381 if (last.line() == 0) {
3382 return;
3383 }
3384 auto nextRange = m_viewInternal->previousLayout(last);
3385 if (!nextRange.isValid()) {
3386 return;
3387 }
3388
3389 auto primaryCursorLineLayout = m_viewInternal->currentLayout(cursorPosition());
3390 if (!primaryCursorLineLayout.isValid()) {
3391 return;
3392 }
3393
3394 int x = renderer()->cursorToX(primaryCursorLineLayout, cursorPosition().column(), !wrapCursor());
3395 auto next = renderer()->xToCursor(nextRange, x, !wrapCursor());
3396 addSecondaryCursor(next);
3397}
3398
3399QList<KTextEditor::Cursor> KTextEditor::ViewPrivate::cursors() const
3400{
3402 ret.reserve(m_secondaryCursors.size() + 1);
3403 ret << cursorPosition();
3404 std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(ret), [](const SecondaryCursor &c) {
3405 return c.cursor();
3406 });
3407 return ret;
3408}
3409
3410QList<KTextEditor::Range> KTextEditor::ViewPrivate::selectionRanges() const
3411{
3412 if (!selection()) {
3413 return {};
3414 }
3415
3417 ret.reserve(m_secondaryCursors.size() + 1);
3418 ret << selectionRange();
3419 std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(ret), [](const SecondaryCursor &c) {
3420 if (!c.range) {
3421 qWarning() << "selectionRanges(): Unexpected null selection range, please fix";
3422 return KTextEditor::Range::invalid();
3423 }
3424 return c.range->toRange();
3425 });
3426 return ret;
3427}
3428
3429void KTextEditor::ViewPrivate::setCursors(const QList<KTextEditor::Cursor> &cursorPositions)
3430{
3431 if (isMulticursorNotAllowed()) {
3432 qWarning() << "setCursors failed: Multicursors not allowed because one of the following is true"
3433 << ", blockSelection: " << blockSelection() << ", overwriteMode: " << isOverwriteMode()
3434 << ", viMode: " << (currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode);
3435 return;
3436 }
3437
3438 clearSecondaryCursors();
3439 if (cursorPositions.empty()) {
3440 return;
3441 }
3442
3443 const auto primary = cursorPositions.front();
3444 // We clear primary selection because primary and secondary
3445 // cursors should always have same selection state
3446 setSelection({});
3447 setCursorPosition(primary);
3448 // First will be auto ignored because it equals cursorPosition()
3449 setSecondaryCursors(cursorPositions);
3450}
3451
3452void KTextEditor::ViewPrivate::setSelections(const QList<KTextEditor::Range> &selectionRanges)
3453{
3454 if (isMulticursorNotAllowed()) {
3455 qWarning() << "setSelections failed: Multicursors not allowed because one of the following is true"
3456 << ", blockSelection: " << blockSelection() << ", overwriteMode: " << isOverwriteMode()
3457 << ", viMode: " << (currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode);
3458 return;
3459 }
3460
3461 clearSecondaryCursors();
3462 setSelection({});
3463 if (selectionRanges.isEmpty()) {
3464 return;
3465 }
3466
3467 auto first = selectionRanges.front();
3468 setCursorPosition(first.end());
3469 setSelection(first);
3470
3471 if (selectionRanges.size() == 1) {
3472 return;
3473 }
3474
3475 const auto docRange = doc()->documentRange();
3476 for (auto it = selectionRanges.begin() + 1; it != selectionRanges.end(); ++it) {
3477 KTextEditor::Range r = *it;
3478 KTextEditor::Cursor c = r.end();
3479 if (c == cursorPosition() || !r.isValid() || r.isEmpty() || !docRange.contains(r)) {
3480 continue;
3481 }
3482
3483 SecondaryCursor n;
3484 n.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(c)));
3485 n.range.reset(newSecondarySelectionRange(r));
3486 n.anchor = r.start();
3487 m_secondaryCursors.push_back(std::move(n));
3488 }
3489 m_viewInternal->mergeSelections();
3490
3491 sortCursors();
3492 paintCursors();
3493}
3494
3495void KTextEditor::ViewPrivate::sortCursors()
3496{
3497 std::sort(m_secondaryCursors.begin(), m_secondaryCursors.end());
3498 ensureUniqueCursors();
3499}
3500
3501void KTextEditor::ViewPrivate::paintCursors()
3502{
3503 if (m_viewInternal->m_cursorTimer.isActive()) {
3505 m_viewInternal->m_cursorTimer.start(QApplication::cursorFlashTime() / 2);
3506 }
3507 renderer()->setDrawCaret(true);
3508 }
3509 m_viewInternal->paintCursor();
3510}
3511
3512bool KTextEditor::ViewPrivate::isCompletionActive() const
3513{
3514 return completionWidget()->isCompletionActive();
3515}
3516
3517KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const
3518{
3519 if (!m_completionWidget) {
3520 m_completionWidget = new KateCompletionWidget(const_cast<KTextEditor::ViewPrivate *>(this));
3521 }
3522
3523 return m_completionWidget;
3524}
3525
3526void KTextEditor::ViewPrivate::startCompletion(KTextEditor::Range word, KTextEditor::CodeCompletionModel *model)
3527{
3528 completionWidget()->startCompletion(word, model);
3529}
3530
3531void KTextEditor::ViewPrivate::startCompletion(const Range &word,
3533 KTextEditor::CodeCompletionModel::InvocationType invocationType)
3534{
3535 completionWidget()->startCompletion(word, models, invocationType);
3536}
3537
3538void KTextEditor::ViewPrivate::abortCompletion()
3539{
3540 completionWidget()->abortCompletion();
3541}
3542
3543void KTextEditor::ViewPrivate::forceCompletion()
3544{
3545 completionWidget()->execute();
3546}
3547
3548void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model)
3549{
3550 completionWidget()->registerCompletionModel(model);
3551}
3552
3553void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model)
3554{
3555 completionWidget()->unregisterCompletionModel(model);
3556}
3557
3558bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const
3559{
3560 return completionWidget()->isCompletionModelRegistered(model);
3561}
3562
3563QList<KTextEditor::CodeCompletionModel *> KTextEditor::ViewPrivate::codeCompletionModels() const
3564{
3565 return completionWidget()->codeCompletionModels();
3566}
3567
3568bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const
3569{
3570 return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation();
3571}
3572
3573void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled)
3574{
3575 config()->setValue(KateViewConfig::AutomaticCompletionInvocation, enabled);
3576}
3577
3578void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index)
3579{
3580 Q_EMIT completionExecuted(this, position, model, index);
3581}
3582
3583void KTextEditor::ViewPrivate::sendCompletionAborted()
3584{
3585 Q_EMIT completionAborted(this);
3586}
3587
3588void KTextEditor::ViewPrivate::paste(const QString *textToPaste)
3589{
3590 const int cursorCount = m_secondaryCursors.size() + 1; // 1 primary cursor
3591 const auto multicursorClipboard = KTextEditor::EditorPrivate::self()->multicursorClipboard();
3592 if (cursorCount == multicursorClipboard.size() && !textToPaste) {
3593 if (doc()->multiPaste(this, multicursorClipboard)) {
3594 return;
3595 }
3596 } else if (!textToPaste && cursorCount > 1) {
3597 // We still have multiple cursors, but the amount
3598 // of multicursors doesn't match the entry count in clipboard
3599 QStringList texts;
3600 texts.reserve(cursorCount);
3602 for (int i = 0; i < cursorCount; ++i) {
3603 texts << clipboard;
3604 }
3605 // It might still fail for e.g., if we are in block mode,
3606 // in that case we will fallback to normal pasting below
3607 if (doc()->multiPaste(this, texts)) {
3608 return;
3609 }
3610 }
3611
3612 m_temporaryAutomaticInvocationDisabled = true;
3613 doc()->paste(this, textToPaste ? *textToPaste : QApplication::clipboard()->text(QClipboard::Clipboard));
3614 m_temporaryAutomaticInvocationDisabled = false;
3615}
3616
3617bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position)
3618{
3619 return setCursorPositionInternal(position, 1, true);
3620}
3621
3622KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const
3623{
3624 return m_viewInternal->cursorPosition();
3625}
3626
3627KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const
3628{
3629 return KTextEditor::Cursor(m_viewInternal->cursorPosition().line(), virtualCursorColumn());
3630}
3631
3632QPoint KTextEditor::ViewPrivate::cursorToCoordinate(KTextEditor::Cursor cursor) const
3633{
3634 // map from ViewInternal to View coordinates
3635 const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, true, false);
3636 return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt);
3637}
3638
3639KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const
3640{
3641 // map from View to ViewInternal coordinates
3642 return m_viewInternal->coordinatesToCursor(m_viewInternal->mapFromParent(coords), false);
3643}
3644
3645QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const
3646{
3647 // map from ViewInternal to View coordinates
3648 const QPoint pt = m_viewInternal->cursorCoordinates(false);
3649 return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt);
3650}
3651
3652void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor cursor)
3653{
3654 m_viewInternal->scrollPos(cursor, false, true, false);
3655}
3656
3657void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x)
3658{
3659 m_viewInternal->scrollColumns(x);
3660}
3661
3662KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const
3663{
3664 return m_viewInternal->maxStartPos(true);
3665}
3666
3667int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const
3668{
3669 if (lineType == RealLine) {
3670 return m_textFolding.visibleLineToLine(m_viewInternal->startLine());
3671 } else {
3672 return m_viewInternal->startLine();
3673 }
3674}
3675
3676int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const
3677{
3678 if (lineType == RealLine) {
3679 return m_textFolding.visibleLineToLine(m_viewInternal->endLine());
3680 } else {
3681 return m_viewInternal->endLine();
3682 }
3683}
3684
3685QRect KTextEditor::ViewPrivate::textAreaRectInternal() const
3686{
3687 const auto sourceRect = m_viewInternal->rect();
3688 const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft());
3689 const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight());
3690 return {topLeft, bottomRight};
3691}
3692
3693bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor position)
3694{
3695 return setCursorPositionInternal(position, doc()->config()->tabWidth(), true);
3696}
3697
3698QScrollBar *KTextEditor::ViewPrivate::verticalScrollBar() const
3699{
3700 return m_viewInternal->m_lineScroll;
3701}
3702
3703QScrollBar *KTextEditor::ViewPrivate::horizontalScrollBar() const
3704{
3705 return m_viewInternal->m_columnScroll;
3706}
3707
3708bool KTextEditor::ViewPrivate::isLineRTL(int line) const
3709{
3710 const QString s = doc()->line(line);
3711 if (s.isEmpty()) {
3712 int line = cursorPosition().line();
3713 if (line == 0) {
3714 const int count = doc()->lines();
3715 for (int i = 1; i < count; ++i) {
3716 const QString ln = doc()->line(i);
3717 if (ln.isEmpty()) {
3718 continue;
3719 }
3720 return ln.isRightToLeft();
3721 }
3722 } else {
3723 int line = cursorPosition().line();
3724 for (; line >= 0; --line) {
3725 const QString s = doc()->line(line);
3726 if (s.isEmpty()) {
3727 continue;
3728 }
3729 return s.isRightToLeft();
3730 }
3731 }
3732 return false;
3733 } else {
3734 return s.isRightToLeft();
3735 }
3736}
3737
3738QTextLayout *KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor pos) const
3739{
3740 KateLineLayout *thisLine = m_viewInternal->cache()->line(pos.line());
3741 return thisLine && thisLine->isValid() ? thisLine->layout() : nullptr;
3742}
3743
3744void KTextEditor::ViewPrivate::indent()
3745{
3746 KTextEditor::Cursor c(cursorPosition().line(), 0);
3747 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c);
3748 doc()->indent(r, 1);
3749}
3750
3751void KTextEditor::ViewPrivate::unIndent()
3752{
3753 KTextEditor::Cursor c(cursorPosition().line(), 0);
3754 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c);
3755 doc()->indent(r, -1);
3756}
3757
3758void KTextEditor::ViewPrivate::cleanIndent()
3759{
3760 KTextEditor::Cursor c(cursorPosition().line(), 0);
3761 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c);
3762 doc()->indent(r, 0);
3763}
3764
3765void KTextEditor::ViewPrivate::formatIndent()
3766{
3767 // no selection: align current line; selection: use selection range
3768 const int line = cursorPosition().line();
3769 KTextEditor::Range formatRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0));
3770 if (selection()) {
3771 formatRange = selectionRange();
3772 }
3773
3774 doc()->align(this, formatRange);
3775}
3776
3777// alias of formatIndent, for backward compatibility
3778void KTextEditor::ViewPrivate::align()
3779{
3780 formatIndent();
3781}
3782
3783void KTextEditor::ViewPrivate::alignOn()
3784{
3785 static QString pattern;
3786 KTextEditor::Range range;
3787 if (!selection()) {
3788 range = doc()->documentRange();
3789 } else {
3790 range = selectionRange();
3791 }
3792 bool ok;
3793 pattern = QInputDialog::getText(window(), i18n("Align On"), i18n("Alignment pattern:"), QLineEdit::Normal, pattern, &ok);
3794 if (!ok) {
3795 return;
3796 }
3797 doc()->alignOn(range, pattern, this->blockSelection());
3798}
3799
3800void KTextEditor::ViewPrivate::comment()
3801{
3802 m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight);
3803 doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::Comment);
3804 m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight);
3805}
3806
3807void KTextEditor::ViewPrivate::uncomment()
3808{
3809 doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::UnComment);
3810}
3811
3812void KTextEditor::ViewPrivate::toggleComment()
3813{
3814 m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight);
3815 doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::ToggleComment);
3816 m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight);
3817}
3818
3819void KTextEditor::ViewPrivate::uppercase()
3820{
3821 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Uppercase);
3822}
3823
3824void KTextEditor::ViewPrivate::killLine()
3825{
3826 std::vector<int> linesToRemove;
3827 if (m_selection.isEmpty()) {
3828 // collect lines of all cursors
3829 linesToRemove.reserve(m_secondaryCursors.size() + 1);
3830 for (const auto &c : m_secondaryCursors) {
3831 linesToRemove.push_back(c.pos->line());
3832 }
3833 // add primary cursor line
3834 linesToRemove.push_back(cursorPosition().line());
3835 } else {
3836 linesToRemove.reserve(m_secondaryCursors.size() + 1);
3837 for (const auto &c : m_secondaryCursors) {
3838 const auto &range = c.range;
3839 if (!range) {
3840 continue;
3841 }
3842 for (int line = range->end().line(); line >= range->start().line(); line--) {
3843 linesToRemove.push_back(line);
3844 }
3845 }
3846
3847 // cache endline, else that moves and we might delete complete document if last line is selected!
3848 for (int line = m_selection.end().line(), endLine = m_selection.start().line(); line >= endLine; line--) {
3849 linesToRemove.push_back(line);
3850 }
3851 }
3852
3853 std::sort(linesToRemove.begin(), linesToRemove.end(), std::greater{});
3854 linesToRemove.erase(std::unique(linesToRemove.begin(), linesToRemove.end()), linesToRemove.end());
3855
3856 doc()->editStart();
3857 std::for_each(linesToRemove.begin(), linesToRemove.end(), [this](int line) {
3858 doc()->removeLine(line);
3859 });
3860 doc()->editEnd();
3861
3862 ensureUniqueCursors();
3863}
3864
3865void KTextEditor::ViewPrivate::lowercase()
3866{
3867 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase);
3868}
3869
3870void KTextEditor::ViewPrivate::capitalize()
3871{
3872 doc()->editStart();
3873 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase);
3874 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Capitalize);
3875 doc()->editEnd();
3876}
3877
3878void KTextEditor::ViewPrivate::keyReturn()
3879{
3880 doc()->newLine(this);
3881 m_viewInternal->iconBorder()->updateForCursorLineChange();
3882 m_viewInternal->updateView();
3883}
3884
3885void KTextEditor::ViewPrivate::smartNewline()
3886{
3887 const KTextEditor::Cursor cursor = cursorPosition();
3888 const int ln = cursor.line();
3889 Kate::TextLine line = doc()->kateTextLine(ln);
3890 int col = qMin(cursor.column(), line.firstChar());
3891 if (col != -1) {
3892 while (line.length() > col && !(line.at(col).isLetterOrNumber() || line.at(col) == QLatin1Char('_')) && col < cursor.column()) {
3893 ++col;
3894 }
3895 } else {
3896 col = line.length(); // stay indented
3897 }
3898 doc()->editStart();
3899 doc()->editWrapLine(ln, cursor.column());
3900 doc()->insertText(KTextEditor::Cursor(ln + 1, 0), line.string(0, col));
3901 doc()->editEnd();
3902
3903 m_viewInternal->updateView();
3904}
3905
3906void KTextEditor::ViewPrivate::noIndentNewline()
3907{
3908 doc()->newLine(this, KTextEditor::DocumentPrivate::NoIndent);
3909 m_viewInternal->iconBorder()->updateForCursorLineChange();
3910 m_viewInternal->updateView();
3911}
3912
3913void KTextEditor::ViewPrivate::newLineAbove()
3914{
3915 doc()->newLine(this, KTextEditor::DocumentPrivate::Indent, KTextEditor::DocumentPrivate::Above);
3916 m_viewInternal->iconBorder()->updateForCursorLineChange();
3917 m_viewInternal->updateView();
3918}
3919
3920void KTextEditor::ViewPrivate::newLineBelow()
3921{
3922 doc()->newLine(this, KTextEditor::DocumentPrivate::Indent, KTextEditor::DocumentPrivate::Below);
3923 m_viewInternal->iconBorder()->updateForCursorLineChange();
3924 m_viewInternal->updateView();
3925}
3926
3927void KTextEditor::ViewPrivate::backspace()
3928{
3929 // Will take care of both multi and primary cursors
3930 doc()->backspace(this);
3931}
3932
3933void KTextEditor::ViewPrivate::insertTab()
3934{
3935 doc()->insertTab(this, cursorPosition());
3936}
3937
3938void KTextEditor::ViewPrivate::deleteWordLeft()
3939{
3940 doc()->editStart();
3941 m_viewInternal->wordPrev(true);
3942 KTextEditor::Range selection = selectionRange();
3943 removeSelectedText();
3944 doc()->editEnd();
3945
3946 ensureUniqueCursors();
3947
3948 m_viewInternal->tagRange(selection, true);
3949 m_viewInternal->updateDirty();
3950}
3951
3952void KTextEditor::ViewPrivate::keyDelete()
3953{
3955
3956 if (blockSelect) {
3957 KTextEditor::Range selection = m_selection;
3958 if (selection.isValid() && selection.start().column() == selection.end().column()) {
3959 KTextEditor::Cursor end = {selection.end().line(), selection.end().column() + 1};
3960 selection = {selection.start(), end};
3961 doc()->removeText(selection, blockSelect);
3962 return;
3963 }
3964 }
3965
3966 // multi cursor
3967
3968 if (removeSelectedText()) {
3969 return;
3970 }
3971
3972 for (const auto &c : m_secondaryCursors) {
3973 if (c.range) {
3974 doc()->removeText(c.range->toRange());
3975 } else {
3976 doc()->del(this, c.cursor());
3977 }
3978 }
3979
3980 // primary cursor
3981 doc()->del(this, cursorPosition());
3982
3983 ensureUniqueCursors();
3984}
3985
3986void KTextEditor::ViewPrivate::deleteWordRight()
3987{
3988 doc()->editStart();
3989 m_viewInternal->wordNext(true);
3990 KTextEditor::Range selection = selectionRange();
3991 removeSelectedText();
3992 doc()->editEnd();
3993
3994 ensureUniqueCursors();
3995
3996 m_viewInternal->tagRange(selection, true);
3997 m_viewInternal->updateDirty();
3998}
3999
4000void KTextEditor::ViewPrivate::transpose()
4001{
4002 doc()->editStart();
4003 for (const auto &c : m_secondaryCursors) {
4004 doc()->transpose(c.cursor());
4005 }
4006 doc()->transpose(cursorPosition());
4007 doc()->editEnd();
4008}
4009
4010void KTextEditor::ViewPrivate::transposeWord()
4011{
4012 const KTextEditor::Cursor originalCurPos = cursorPosition();
4013 const KTextEditor::Range firstWord = doc()->wordRangeAt(originalCurPos);
4014 if (!firstWord.isValid()) {
4015 return;
4016 }
4017
4018 auto wordIsInvalid = [](QStringView word) {
4019 for (const QChar &character : word) {
4020 if (character.isLetterOrNumber()) {
4021 return false;
4022 }
4023 }
4024 return true;
4025 };
4026
4027 if (wordIsInvalid(doc()->text(firstWord))) {
4028 return;
4029 }
4030
4031 setCursorPosition(firstWord.end());
4032 wordRight();
4033 KTextEditor::Cursor curPos = cursorPosition();
4034 // swap with the word to the right if it exists, otherwise try to swap with word to the left
4035 if (curPos.line() != firstWord.end().line() || curPos.column() == firstWord.end().column()) {
4036 setCursorPosition(firstWord.start());
4037 wordLeft();
4038 curPos = cursorPosition();
4039 // if there is still no next word in this line, no swapping will be done
4040 if (curPos.line() != firstWord.start().line() || curPos.column() == firstWord.start().column() || wordIsInvalid(doc()->wordAt(curPos))) {
4041 setCursorPosition(originalCurPos);
4042 return;
4043 }
4044 }
4045
4046 if (wordIsInvalid(doc()->wordAt(curPos))) {
4047 setCursorPosition(originalCurPos);
4048 return;
4049 }
4050
4051 const KTextEditor::Range secondWord = doc()->wordRangeAt(curPos);
4052 doc()->swapTextRanges(firstWord, secondWord);
4053
4054 // return cursor to its original position inside the word before swap
4055 // 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
4056 const int offsetFromWordEnd = firstWord.end().column() - originalCurPos.column();
4057 setCursorPosition(cursorPosition() - KTextEditor::Cursor(0, offsetFromWordEnd));
4058}
4059
4060void KTextEditor::ViewPrivate::cursorLeft()
4061{
4062 if (selection() && !config()->persistentSelection() && !m_markedSelection) {
4063 if (isLineRTL(cursorPosition().line())) {
4064 m_viewInternal->updateCursor(selectionRange().end());
4065 setSelection(KTextEditor::Range::invalid());
4066 } else {
4067 m_viewInternal->updateCursor(selectionRange().start());
4068 setSelection(KTextEditor::Range::invalid());
4069 }
4070
4071 for (const auto &c : m_secondaryCursors) {
4072 if (!c.range) {
4073 continue;
4074 }
4075 const bool rtl = isLineRTL(c.cursor().line());
4076 c.pos->setPosition(rtl ? c.range->end() : c.range->start());
4077 }
4078 clearSecondarySelections();
4079 } else {
4080 if (isLineRTL(cursorPosition().line())) {
4081 m_viewInternal->cursorNextChar(m_markedSelection);
4082 } else {
4083 m_viewInternal->cursorPrevChar(m_markedSelection);
4084 }
4085 }
4086}
4087
4088void KTextEditor::ViewPrivate::shiftCursorLeft()
4089{
4090 if (isLineRTL(cursorPosition().line())) {
4091 m_viewInternal->cursorNextChar(true);
4092 } else {
4093 m_viewInternal->cursorPrevChar(true);
4094 }
4095}
4096
4097void KTextEditor::ViewPrivate::cursorRight()
4098{
4099 if (selection() && !config()->persistentSelection() && !m_markedSelection) {
4100 if (isLineRTL(cursorPosition().line())) {
4101 m_viewInternal->updateCursor(selectionRange().start());
4102 setSelection(KTextEditor::Range::invalid());
4103 } else {
4104 m_viewInternal->updateCursor(selectionRange().end());
4105 setSelection(KTextEditor::Range::invalid());
4106 }
4107
4108 for (const auto &c : m_secondaryCursors) {
4109 if (!c.range) {
4110 continue;
4111 }
4112 const bool rtl = doc()->line(c.cursor().line()).isRightToLeft();
4113 c.pos->setPosition(rtl ? c.range->start() : c.range->end());
4114 }
4115 clearSecondarySelections();
4116 } else {
4117 if (isLineRTL(cursorPosition().line())) {
4118 m_viewInternal->cursorPrevChar(m_markedSelection);
4119 } else {
4120 m_viewInternal->cursorNextChar(m_markedSelection);
4121 }
4122 }
4123}
4124
4125void KTextEditor::ViewPrivate::shiftCursorRight()
4126{
4127 if (isLineRTL(cursorPosition().line())) {
4128 m_viewInternal->cursorPrevChar(true);
4129 } else {
4130 m_viewInternal->cursorNextChar(true);
4131 }
4132}
4133
4134void KTextEditor::ViewPrivate::wordLeft()
4135{
4136 if (isLineRTL(cursorPosition().line())) {
4137 m_viewInternal->wordNext(m_markedSelection);
4138 } else {
4139 m_viewInternal->wordPrev(m_markedSelection);
4140 }
4141}
4142
4143void KTextEditor::ViewPrivate::shiftWordLeft()
4144{
4145 if (isLineRTL(cursorPosition().line())) {
4146 m_viewInternal->wordNext(true);
4147 } else {
4148 m_viewInternal->wordPrev(true);
4149 }
4150}
4151
4152void KTextEditor::ViewPrivate::wordRight()
4153{
4154 if (isLineRTL(cursorPosition().line())) {
4155 m_viewInternal->wordPrev(m_markedSelection);
4156 } else {
4157 m_viewInternal->wordNext(m_markedSelection);
4158 }
4159}
4160
4161void KTextEditor::ViewPrivate::shiftWordRight()
4162{
4163 if (isLineRTL(cursorPosition().line())) {
4164 m_viewInternal->wordPrev(true);
4165 } else {
4166 m_viewInternal->wordNext(true);
4167 }
4168}
4169
4170void KTextEditor::ViewPrivate::markSelection()
4171{
4172 if (m_markedSelection && selection()) {
4173 setSelection(KTextEditor::Range::invalid());
4174 clearSecondarySelections();
4175 } else {
4176 m_markedSelection = !m_markedSelection;
4177 }
4178}
4179
4180void KTextEditor::ViewPrivate::home()
4181{
4182 m_viewInternal->home(m_markedSelection);
4183}
4184
4185void KTextEditor::ViewPrivate::shiftHome()
4186{
4187 m_viewInternal->home(true);
4188}
4189
4190void KTextEditor::ViewPrivate::end()
4191{
4192 m_viewInternal->end(m_markedSelection);
4193}
4194
4195void KTextEditor::ViewPrivate::shiftEnd()
4196{
4197 m_viewInternal->end(true);
4198}
4199
4200void KTextEditor::ViewPrivate::up()
4201{
4202 m_viewInternal->cursorUp(m_markedSelection);
4203}
4204
4205void KTextEditor::ViewPrivate::shiftUp()
4206{
4207 m_viewInternal->cursorUp(true);
4208}
4209
4210void KTextEditor::ViewPrivate::down()
4211{
4212 m_viewInternal->cursorDown(m_markedSelection);
4213}
4214
4215void KTextEditor::ViewPrivate::shiftDown()
4216{
4217 m_viewInternal->cursorDown(true);
4218}
4219
4220void KTextEditor::ViewPrivate::scrollUp()
4221{
4222 m_viewInternal->scrollUp();
4223}
4224
4225void KTextEditor::ViewPrivate::scrollDown()
4226{
4227 m_viewInternal->scrollDown();
4228}
4229
4230void KTextEditor::ViewPrivate::topOfView()
4231{
4232 m_viewInternal->topOfView();
4233}
4234
4235void KTextEditor::ViewPrivate::shiftTopOfView()
4236{
4237 m_viewInternal->topOfView(true);
4238}
4239
4240void KTextEditor::ViewPrivate::bottomOfView()
4241{
4242 m_viewInternal->bottomOfView();
4243}
4244
4245void KTextEditor::ViewPrivate::shiftBottomOfView()
4246{
4247 m_viewInternal->bottomOfView(true);
4248}
4249
4250void KTextEditor::ViewPrivate::pageUp()
4251{
4252 m_viewInternal->pageUp(m_markedSelection);
4253}
4254
4255void KTextEditor::ViewPrivate::shiftPageUp()
4256{
4257 m_viewInternal->pageUp(true);
4258}
4259
4260void KTextEditor::ViewPrivate::pageDown()
4261{
4262 m_viewInternal->pageDown(m_markedSelection);
4263}
4264
4265void KTextEditor::ViewPrivate::shiftPageDown()
4266{
4267 m_viewInternal->pageDown(true);
4268}
4269
4270void KTextEditor::ViewPrivate::top()
4271{
4272 m_viewInternal->top_home(m_markedSelection);
4273}
4274
4275void KTextEditor::ViewPrivate::shiftTop()
4276{
4277 m_viewInternal->top_home(true);
4278}
4279
4280void KTextEditor::ViewPrivate::bottom()
4281{
4282 m_viewInternal->bottom_end(m_markedSelection);
4283}
4284
4285void KTextEditor::ViewPrivate::shiftBottom()
4286{
4287 m_viewInternal->bottom_end(true);
4288}
4289
4290void KTextEditor::ViewPrivate::toMatchingBracket()
4291{
4292 m_viewInternal->cursorToMatchingBracket();
4293}
4294
4295void KTextEditor::ViewPrivate::shiftToMatchingBracket()
4296{
4297 m_viewInternal->cursorToMatchingBracket(true);
4298}
4299
4300void KTextEditor::ViewPrivate::toPrevModifiedLine()
4301{
4302 const int startLine = cursorPosition().line() - 1;
4303 const int line = doc()->findTouchedLine(startLine, false);
4304 if (line >= 0) {
4305 KTextEditor::Cursor c(line, 0);
4306 m_viewInternal->updateSelection(c, false);
4307 m_viewInternal->updateCursor(c);
4308 }
4309}
4310
4311void KTextEditor::ViewPrivate::toNextModifiedLine()
4312{
4313 const int startLine = cursorPosition().line() + 1;
4314 const int line = doc()->findTouchedLine(startLine, true);
4315 if (line >= 0) {
4316 KTextEditor::Cursor c(line, 0);
4317 m_viewInternal->updateSelection(c, false);
4318 m_viewInternal->updateCursor(c);
4319 }
4320}
4321
4322KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const
4323{
4324 return m_selection;
4325}
4326
4327KTextEditor::Document *KTextEditor::ViewPrivate::document() const
4328{
4329 return m_doc;
4330}
4331
4332void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu)
4333{
4334 if (m_contextMenu) {
4335 disconnect(m_contextMenu.data(), &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu);
4336 disconnect(m_contextMenu.data(), &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu);
4337 }
4338 m_contextMenu = menu;
4339 m_userContextMenuSet = true;
4340
4341 if (m_contextMenu) {
4342 connect(m_contextMenu.data(), &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu);
4343 connect(m_contextMenu.data(), &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu);
4344 }
4345}
4346
4347QMenu *KTextEditor::ViewPrivate::contextMenu() const
4348{
4349 if (m_userContextMenuSet) {
4350 return m_contextMenu;
4351 } else {
4352 KXMLGUIClient *client = const_cast<KTextEditor::ViewPrivate *>(this);
4353 while (client->parentClient()) {
4354 client = client->parentClient();
4355 }
4356
4357 // qCDebug(LOG_KTE) << "looking up all menu containers";
4358 if (client->factory()) {
4359 const QList<QWidget *> menuContainers = client->factory()->containers(QStringLiteral("menu"));
4360 for (QWidget *w : menuContainers) {
4361 if (w->objectName() == QLatin1String("ktexteditor_popup")) {
4362 // perhaps optimize this block
4363 QMenu *menu = (QMenu *)w;
4364 // menu is a reusable instance shared among all views. Therefore,
4365 // disconnect the current receiver(s) from the menu show/hide signals
4366 // before connecting `this` view. This ensures that only the current
4367 // view gets a signal when the menu is about to be shown or hidden,
4368 // and not also the view(s) that previously had the menu open.
4369 disconnect(menu, &QMenu::aboutToShow, nullptr, nullptr);
4370 disconnect(menu, &QMenu::aboutToHide, nullptr, nullptr);
4371 connect(menu, &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu);
4372 connect(menu, &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu);
4373 return menu;
4374 }
4375 }
4376 }
4377 }
4378 return nullptr;
4379}
4380
4381QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const
4382{
4383 if (!menu) {
4384 menu = new QMenu(const_cast<KTextEditor::ViewPrivate *>(this));
4385 }
4386
4387 if (m_editUndo) {
4388 menu->addAction(m_editUndo);
4389 }
4390 if (m_editRedo) {
4391 menu->addAction(m_editRedo);
4392 }
4393
4394 menu->addSeparator();
4395 menu->addAction(m_cut);
4396 menu->addAction(m_copy);
4397 menu->addAction(m_paste);
4398 if (m_pasteSelection) {
4399 menu->addAction(m_pasteSelection);
4400 }
4401
4402 menu->addAction(m_screenshotSelection);
4403 menu->addAction(m_swapWithClipboard);
4404 menu->addSeparator();
4405 menu->addAction(m_selectAll);
4406 menu->addAction(m_deSelect);
4407 QAction *editing = actionCollection()->action(QStringLiteral("tools_scripts_Editing"));
4408 if (editing) {
4409 menu->addAction(editing);
4410 }
4411 if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) {
4412 menu->addSeparator();
4413 menu->addAction(spellingSuggestions);
4414 }
4415 if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) {
4416 menu->addSeparator();
4417 menu->addAction(bookmark);
4418 }
4419
4420 return menu;
4421}
4422
4423void KTextEditor::ViewPrivate::aboutToShowContextMenu()
4424{
4425 QMenu *menu = qobject_cast<QMenu *>(sender());
4426
4427 if (menu) {
4428 Q_EMIT contextMenuAboutToShow(this, menu);
4429 }
4430}
4431
4432void KTextEditor::ViewPrivate::aboutToHideContextMenu()
4433{
4434 m_spellingMenu->cleanUpAfterShown();
4435}
4436
4437// BEGIN ConfigInterface stff
4438QStringList KTextEditor::ViewPrivate::configKeys() const
4439{
4440 static const QStringList keys = {QStringLiteral("icon-bar"),
4441 QStringLiteral("line-numbers"),
4442 QStringLiteral("dynamic-word-wrap"),
4443 QStringLiteral("background-color"),
4444 QStringLiteral("selection-color"),
4445 QStringLiteral("search-highlight-color"),
4446 QStringLiteral("replace-highlight-color"),
4447 QStringLiteral("default-mark-type"),
4448 QStringLiteral("allow-mark-menu"),
4449 QStringLiteral("folding-bar"),
4450 QStringLiteral("folding-preview"),
4451 QStringLiteral("icon-border-color"),
4452 QStringLiteral("folding-marker-color"),
4453 QStringLiteral("line-number-color"),
4454 QStringLiteral("current-line-number-color"),
4455 QStringLiteral("modification-markers"),
4456 QStringLiteral("keyword-completion"),
4457 QStringLiteral("word-count"),
4458 QStringLiteral("line-count"),
4459 QStringLiteral("scrollbar-minimap"),
4460 QStringLiteral("scrollbar-preview"),
4461 QStringLiteral("font"),
4462 QStringLiteral("theme")};
4463 return keys;
4464}
4465
4466QVariant KTextEditor::ViewPrivate::configValue(const QString &key)
4467{
4468 if (key == QLatin1String("icon-bar")) {
4469 return config()->iconBar();
4470 } else if (key == QLatin1String("line-numbers")) {
4471 return config()->lineNumbers();
4472 } else if (key == QLatin1String("dynamic-word-wrap")) {
4473 return config()->dynWordWrap();
4474 } else if (key == QLatin1String("background-color")) {
4475 return rendererConfig()->backgroundColor();
4476 } else if (key == QLatin1String("selection-color")) {
4477 return rendererConfig()->selectionColor();
4478 } else if (key == QLatin1String("search-highlight-color")) {
4479 return rendererConfig()->searchHighlightColor();
4480 } else if (key == QLatin1String("replace-highlight-color")) {
4481 return rendererConfig()->replaceHighlightColor();
4482 } else if (key == QLatin1String("default-mark-type")) {
4483 return config()->defaultMarkType();
4484 } else if (key == QLatin1String("allow-mark-menu")) {
4485 return config()->allowMarkMenu();
4486 } else if (key == QLatin1String("folding-bar")) {
4487 return config()->foldingBar();
4488 } else if (key == QLatin1String("folding-preview")) {
4489 return config()->foldingPreview();
4490 } else if (key == QLatin1String("icon-border-color")) {
4491 return rendererConfig()->iconBarColor();
4492 } else if (key == QLatin1String("folding-marker-color")) {
4493 return rendererConfig()->foldingColor();
4494 } else if (key == QLatin1String("line-number-color")) {
4495 return rendererConfig()->lineNumberColor();
4496 } else if (key == QLatin1String("current-line-number-color")) {
4497 return rendererConfig()->currentLineNumberColor();
4498 } else if (key == QLatin1String("modification-markers")) {
4499 return config()->lineModification();
4500 } else if (key == QLatin1String("keyword-completion")) {
4501 return config()->keywordCompletion();
4502 } else if (key == QLatin1String("word-count")) {
4503 return config()->showWordCount();
4504 } else if (key == QLatin1String("line-count")) {
4505 return config()->showLineCount();
4506 } else if (key == QLatin1String("scrollbar-minimap")) {
4507 return config()->scrollBarMiniMap();
4508 } else if (key == QLatin1String("scrollbar-preview")) {
4509 return config()->scrollBarPreview();
4510 } else if (key == QLatin1String("font")) {
4511 return rendererConfig()->baseFont();
4512 } else if (key == QLatin1String("theme")) {
4513 return rendererConfig()->schema();
4514 }
4515
4516 // return invalid variant
4517 return QVariant();
4518}
4519
4520void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value)
4521{
4522 // First, try the new config interface
4523 if (config()->setValue(key, value)) {
4524 return;
4525
4526 } else if (rendererConfig()->setValue(key, value)) {
4527 return;
4528 }
4529
4530 // No success? Go the old way
4531 if (value.canConvert<QColor>()) {
4532 if (key == QLatin1String("background-color")) {
4533 rendererConfig()->setBackgroundColor(value.value<QColor>());
4534 } else if (key == QLatin1String("selection-color")) {
4535 rendererConfig()->setSelectionColor(value.value<QColor>());
4536 } else if (key == QLatin1String("search-highlight-color")) {
4537 rendererConfig()->setSearchHighlightColor(value.value<QColor>());
4538 } else if (key == QLatin1String("replace-highlight-color")) {
4539 rendererConfig()->setReplaceHighlightColor(value.value<QColor>());
4540 } else if (key == QLatin1String("icon-border-color")) {
4541 rendererConfig()->setIconBarColor(value.value<QColor>());
4542 } else if (key == QLatin1String("folding-marker-color")) {
4543 rendererConfig()->setFoldingColor(value.value<QColor>());
4544 } else if (key == QLatin1String("line-number-color")) {
4545 rendererConfig()->setLineNumberColor(value.value<QColor>());
4546 } else if (key == QLatin1String("current-line-number-color")) {
4547 rendererConfig()->setCurrentLineNumberColor(value.value<QColor>());
4548 }
4549 }
4550 if (value.userType() == QMetaType::Bool) {
4551 // Note explicit type check above. If we used canConvert, then
4552 // values of type UInt will be trapped here.
4553 if (key == QLatin1String("dynamic-word-wrap")) {
4554 config()->setDynWordWrap(value.toBool());
4555 } else if (key == QLatin1String("word-count")) {
4556 config()->setShowWordCount(value.toBool());
4557 } else if (key == QLatin1String("line-count")) {
4558 config()->setShowLineCount(value.toBool());
4559 }
4560 } else if (key == QLatin1String("font") && value.canConvert<QFont>()) {
4561 rendererConfig()->setFont(value.value<QFont>());
4562 } else if (key == QLatin1String("theme") && value.userType() == QMetaType::QString) {
4563 rendererConfig()->setSchema(value.toString());
4564 }
4565}
4566
4567// END ConfigInterface
4568
4569// NOLINTNEXTLINE(readability-make-member-function-const)
4570void KTextEditor::ViewPrivate::userInvokedCompletion()
4571{
4572 completionWidget()->userInvokedCompletion();
4573}
4574
4575KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const
4576{
4577 return m_bottomViewBar;
4578}
4579
4580KateGotoBar *KTextEditor::ViewPrivate::gotoBar()
4581{
4582 if (!m_gotoBar) {
4583 m_gotoBar = new KateGotoBar(this);
4584 bottomViewBar()->addBarWidget(m_gotoBar);
4585 }
4586
4587 return m_gotoBar;
4588}
4589
4590KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar()
4591{
4592 if (!m_dictionaryBar) {
4593 m_dictionaryBar = new KateDictionaryBar(this);
4594 bottomViewBar()->addBarWidget(m_dictionaryBar);
4595 }
4596
4597 return m_dictionaryBar;
4598}
4599
4600void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model)
4601{
4602 KTextEditor::AnnotationModel *oldmodel = m_annotationModel;
4603 m_annotationModel = model;
4604 m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, m_annotationModel);
4605}
4606
4607KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const
4608{
4609 return m_annotationModel;
4610}
4611
4612void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible)
4613{
4614 m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible);
4615}
4616
4617bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const
4618{
4619 return m_viewInternal->m_leftBorder->annotationBorderOn();
4620}
4621
4622KTextEditor::AbstractAnnotationItemDelegate *KTextEditor::ViewPrivate::annotationItemDelegate() const
4623{
4624 return m_viewInternal->m_leftBorder->annotationItemDelegate();
4625}
4626
4627void KTextEditor::ViewPrivate::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate)
4628{
4629 m_viewInternal->m_leftBorder->setAnnotationItemDelegate(delegate);
4630}
4631
4632bool KTextEditor::ViewPrivate::uniformAnnotationItemSizes() const
4633{
4634 return m_viewInternal->m_leftBorder->uniformAnnotationItemSizes();
4635}
4636
4637void KTextEditor::ViewPrivate::setAnnotationUniformItemSizes(bool enable)
4638{
4639 m_viewInternal->m_leftBorder->setAnnotationUniformItemSizes(enable);
4640}
4641
4642KTextEditor::Range KTextEditor::ViewPrivate::visibleRange()
4643{
4644 // ensure that the view is up-to-date, otherwise 'endPos()' might fail!
4645 if (!m_viewInternal->endPos().isValid()) {
4646 m_viewInternal->updateView();
4647 }
4648 return KTextEditor::Range(m_viewInternal->toRealCursor(m_viewInternal->startPos()), m_viewInternal->toRealCursor(m_viewInternal->endPos()));
4649}
4650
4651bool KTextEditor::ViewPrivate::event(QEvent *e)
4652{
4653 switch (e->type()) {
4655 setupLayout();
4656 return true;
4657 default:
4658 return KTextEditor::View::event(e);
4659 }
4660}
4661
4662void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b)
4663{
4664 doc()->onTheFlySpellCheckingEnabled(b);
4665}
4666
4667void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled)
4668{
4669 m_spellingMenu->setVisible(enabled);
4670 m_toggleOnTheFlySpellCheck->setChecked(enabled);
4671}
4672
4673KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu()
4674{
4675 return m_spellingMenu;
4676}
4677
4678void KTextEditor::ViewPrivate::notifyAboutRangeChange(KTextEditor::LineRange lineRange, bool needsRepaint)
4679{
4680#ifdef VIEW_RANGE_DEBUG
4681 // output args
4682 qCDebug(LOG_KTE) << "trigger attribute changed in line range " << lineRange << "needsRepaint" << needsRepaint;
4683#endif
4684
4685 // if we need repaint, we will need to collect the line ranges we will update
4686 if (needsRepaint && lineRange.isValid()) {
4687 if (m_lineToUpdateRange.isValid()) {
4688 m_lineToUpdateRange.expandToRange(lineRange);
4689 } else {
4690 m_lineToUpdateRange = lineRange;
4691 }
4692 }
4693
4694 // first call => trigger later update of view via delayed signal to group updates
4695 if (!m_delayedUpdateTimer.isActive()) {
4696 m_delayedUpdateTimer.start();
4697 }
4698}
4699
4700void KTextEditor::ViewPrivate::slotDelayedUpdateOfView()
4701{
4702#ifdef VIEW_RANGE_DEBUG
4703 // output args
4704 qCDebug(LOG_KTE) << "delayed attribute changed in line range" << m_lineToUpdateRange;
4705#endif
4706 // update ranges in
4709
4710 // update view, if valid line range, else only feedback update wanted anyway
4711 if (m_lineToUpdateRange.isValid()) {
4712 tagLines(m_lineToUpdateRange, true);
4713 updateView(true);
4714 }
4715
4716 // reset flags
4717 m_lineToUpdateRange = KTextEditor::LineRange::invalid();
4718}
4719
4720void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType)
4721{
4722 // new ranges with cursor in, default none
4723 QSet<Kate::TextRange *> newRangesIn;
4724
4725 // on which range set we work?
4726 QSet<Kate::TextRange *> &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn;
4727
4728 // which cursor position to honor?
4729 KTextEditor::Cursor currentCursor =
4730 (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->mousePosition() : m_viewInternal->cursorPosition();
4731
4732 // first: validate the remembered ranges
4733 QSet<Kate::TextRange *> validRanges;
4734 for (Kate::TextRange *range : std::as_const(oldSet)) {
4735 if (doc()->buffer().rangePointerValid(range)) {
4736 validRanges.insert(range);
4737 }
4738 }
4739
4740 // cursor valid? else no new ranges can be found
4741 if (currentCursor.isValid() && currentCursor.line() < doc()->buffer().lines()) {
4742 // now: get current ranges for the line of cursor with an attribute
4743 const QList<Kate::TextRange *> rangesForCurrentCursor = doc()->buffer().rangesForLine(currentCursor.line(), this, false);
4744
4745 // match which ranges really fit the given cursor
4746 for (Kate::TextRange *range : rangesForCurrentCursor) {
4747 // range has no dynamic attribute of right type and no feedback object
4748 auto attribute = range->attribute();
4749 if ((!attribute || !attribute->dynamicAttribute(activationType)) && !range->feedback()) {
4750 continue;
4751 }
4752
4753 // range doesn't contain cursor, not interesting
4754 if ((range->startInternal().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->toRange().start())
4755 : (currentCursor <= range->toRange().start())) {
4756 continue;
4757 }
4758
4759 if ((range->endInternal().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->toRange().end() <= currentCursor)
4760 : (range->toRange().end() < currentCursor)) {
4761 continue;
4762 }
4763
4764 // range contains cursor, was it already in old set?
4765 auto it = validRanges.find(range);
4766 if (it != validRanges.end()) {
4767 // insert in new, remove from old, be done with it
4768 newRangesIn.insert(range);
4769 validRanges.erase(it);
4770 continue;
4771 }
4772
4773 // oh, new range, trigger update and insert into new set
4774 newRangesIn.insert(range);
4775
4776 if (attribute && attribute->dynamicAttribute(activationType)) {
4777 notifyAboutRangeChange(range->toLineRange(), true);
4778 }
4779
4780 // feedback
4781 if (range->feedback()) {
4782 if (activationType == KTextEditor::Attribute::ActivateMouseIn) {
4783 range->feedback()->mouseEnteredRange(range, this);
4784 } else {
4785 range->feedback()->caretEnteredRange(range, this);
4786 Q_EMIT caretChangedRange(this);
4787 }
4788 }
4789
4790#ifdef VIEW_RANGE_DEBUG
4791 // found new range for activation
4792 qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType;
4793#endif
4794 }
4795 }
4796
4797 // now: notify for left ranges!
4798 for (Kate::TextRange *range : std::as_const(validRanges)) {
4799 // range valid + right dynamic attribute, trigger update
4800 if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(activationType)) {
4801 notifyAboutRangeChange(range->toLineRange(), true);
4802 }
4803
4804 // feedback
4805 if (range->feedback()) {
4806 if (activationType == KTextEditor::Attribute::ActivateMouseIn) {
4807 range->feedback()->mouseExitedRange(range, this);
4808 } else {
4809 range->feedback()->caretExitedRange(range, this);
4810 Q_EMIT caretChangedRange(this);
4811 }
4812 }
4813 }
4814
4815 // set new ranges
4816 oldSet = newRangesIn;
4817}
4818
4819void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList<std::shared_ptr<QAction>> actions)
4820{
4821 // just forward to KateMessageWidget :-)
4822 auto messageWidget = m_messageWidgets[message->position()];
4823 if (!messageWidget) {
4824 // this branch is used for: TopInView, CenterInView, and BottomInView
4825 messageWidget = new KateMessageWidget(m_viewInternal, true);
4826 m_messageWidgets[message->position()] = messageWidget;
4827 m_notificationLayout->addWidget(messageWidget, message->position());
4828 connect(this, &KTextEditor::ViewPrivate::displayRangeChanged, messageWidget, &KateMessageWidget::startAutoHideTimer);
4830 }
4831 messageWidget->postMessage(message, std::move(actions));
4832}
4833
4834KateMessageWidget *KTextEditor::ViewPrivate::messageWidget()
4835{
4836 return m_messageWidgets[KTextEditor::Message::TopInView];
4837}
4838
4839void KTextEditor::ViewPrivate::saveFoldingState()
4840{
4841 m_savedFoldingState = m_textFolding.exportFoldingRanges();
4842}
4843
4844void KTextEditor::ViewPrivate::clearFoldingState()
4845{
4846 m_savedFoldingState = {};
4847}
4848
4849void KTextEditor::ViewPrivate::applyFoldingState()
4850{
4851 m_textFolding.importFoldingRanges(m_savedFoldingState);
4852 m_savedFoldingState = QJsonDocument();
4853}
4854
4855void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file)
4856{
4857 KateExporter(this).exportToFile(file);
4858}
4859
4860void KTextEditor::ViewPrivate::exportHtmlToClipboard()
4861{
4862 KateExporter(this).exportToClipboard();
4863}
4864
4865void KTextEditor::ViewPrivate::exportHtmlToFile()
4866{
4867 const QString file = QFileDialog::getSaveFileName(this, i18n("Export File as HTML"), doc()->documentName());
4868 if (!file.isEmpty()) {
4869 KateExporter(this).exportToFile(file);
4870 }
4871}
4872
4873void KTextEditor::ViewPrivate::clearHighlights()
4874{
4875 m_rangesForHighlights.clear();
4876 m_currentTextForHighlights.clear();
4877}
4878
4879void KTextEditor::ViewPrivate::selectionChangedForHighlights()
4880{
4881 QString text;
4882 // if text of selection is still the same, abort
4883 if (selection() && selectionRange().onSingleLine()) {
4884 text = selectionText();
4885 if (text == m_currentTextForHighlights) {
4886 return;
4887 }
4888 }
4889
4890 // text changed: remove all highlights + create new ones
4891 // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights
4892 m_rangesForHighlights.clear();
4893
4894 // do not highlight strings with leading and trailing spaces
4895 if (!text.isEmpty() && (text.at(0).isSpace() || text.at(text.length() - 1).isSpace())) {
4896 return;
4897 }
4898
4899 // trigger creation of ranges for current view range
4900 m_currentTextForHighlights = text;
4901 createHighlights();
4902}
4903
4904void KTextEditor::ViewPrivate::createHighlights()
4905{
4906 // do nothing if no text to highlight
4907 if (m_currentTextForHighlights.isEmpty()) {
4908 return;
4909 }
4910
4911 // clear existing highlighting ranges, otherwise we stack over and over the same ones eventually
4912 m_rangesForHighlights.clear();
4913
4915 attr->setBackground(Qt::yellow);
4916
4917 // set correct highlight color from Kate's color schema
4918 QColor fgColor = defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground().color();
4919 QColor bgColor = rendererConfig()->searchHighlightColor();
4920 attr->setForeground(fgColor);
4921 attr->setBackground(bgColor);
4922
4923 KTextEditor::Cursor start(visibleRange().start());
4924 KTextEditor::Range searchRange;
4925
4926 // only add word boundary if we can find the text then
4927 // fixes $lala hl
4928 QString pattern = QRegularExpression::escape(m_currentTextForHighlights);
4929 if (m_currentTextForHighlights.contains(QRegularExpression(QLatin1String("\\b") + pattern, QRegularExpression::UseUnicodePropertiesOption))) {
4930 pattern.prepend(QLatin1String("\\b"));
4931 }
4932
4933 if (m_currentTextForHighlights.contains(QRegularExpression(pattern + QLatin1String("\\b"), QRegularExpression::UseUnicodePropertiesOption))) {
4934 pattern += QLatin1String("\\b");
4935 }
4936
4938 do {
4939 searchRange.setRange(start, visibleRange().end());
4940
4941 matches = doc()->searchText(searchRange, pattern, KTextEditor::Regex);
4942
4943 if (matches.first().isValid()) {
4944 if (matches.first() != selectionRange()) {
4945 std::unique_ptr<KTextEditor::MovingRange> mr(doc()->newMovingRange(matches.first()));
4946 mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection
4947 mr->setAttribute(attr);
4948 mr->setView(this);
4949 mr->setAttributeOnlyForViews(true);
4950 m_rangesForHighlights.push_back(std::move(mr));
4951 }
4952 start = matches.first().end();
4953 }
4954 } while (matches.first().isValid());
4955}
4956
4957KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const
4958{
4959 return m_viewInternal->m_currentInputMode;
4960}
4961
4962void KTextEditor::ViewPrivate::toggleInputMode()
4963{
4964 if (QAction *a = qobject_cast<QAction *>(sender())) {
4965 setInputMode(static_cast<KTextEditor::View::InputMode>(a->data().toInt()));
4966 }
4967}
4968
4969void KTextEditor::ViewPrivate::cycleInputMode()
4970{
4971 InputMode current = currentInputMode()->viewInputMode();
4972 InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode;
4973 setInputMode(to);
4974}
4975
4976// BEGIN KTextEditor::PrintInterface stuff
4977bool KTextEditor::ViewPrivate::print()
4978{
4979 return KatePrinter::print(this);
4980}
4981
4982void KTextEditor::ViewPrivate::printPreview()
4983{
4984 KatePrinter::printPreview(this);
4985}
4986
4987// END
4988
4989// BEGIN Inline Note Interface
4990void KTextEditor::ViewPrivate::registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider)
4991{
4992 if (std::find(m_inlineNoteProviders.cbegin(), m_inlineNoteProviders.cend(), provider) == m_inlineNoteProviders.cend()) {
4993 m_inlineNoteProviders.push_back(provider);
4994
4995 connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesReset, this, &KTextEditor::ViewPrivate::inlineNotesReset);
4996 connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesChanged, this, &KTextEditor::ViewPrivate::inlineNotesLineChanged);
4997
4998 inlineNotesReset();
4999 }
5000}
5001
5002void KTextEditor::ViewPrivate::unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider)
5003{
5004 auto it = std::find(m_inlineNoteProviders.cbegin(), m_inlineNoteProviders.cend(), provider);
5005 if (it != m_inlineNoteProviders.cend()) {
5006 m_inlineNoteProviders.erase(it);
5007 provider->disconnect(this);
5008
5009 inlineNotesReset();
5010 }
5011}
5012
5013QVarLengthArray<KateInlineNoteData, 8> KTextEditor::ViewPrivate::inlineNotes(int line) const
5014{
5016 for (KTextEditor::InlineNoteProvider *provider : m_inlineNoteProviders) {
5017 int index = 0;
5018 const auto columns = provider->inlineNotes(line);
5019 for (int column : columns) {
5020 const bool underMouse = Cursor(line, column) == m_viewInternal->m_activeInlineNote.m_position;
5021 KateInlineNoteData note =
5022 {provider, this, {line, column}, index, underMouse, m_viewInternal->renderer()->currentFont(), m_viewInternal->renderer()->lineHeight()};
5023 allInlineNotes.append(note);
5024 index++;
5025 }
5026 }
5027 return allInlineNotes;
5028}
5029
5030QRect KTextEditor::ViewPrivate::inlineNoteRect(const KateInlineNoteData &note) const
5031{
5032 return m_viewInternal->inlineNoteRect(note);
5033}
5034
5035void KTextEditor::ViewPrivate::inlineNotesReset()
5036{
5037 m_viewInternal->m_activeInlineNote = {};
5038 tagLines(KTextEditor::LineRange(0, doc()->lastLine()), true);
5039}
5040
5041void KTextEditor::ViewPrivate::inlineNotesLineChanged(int line)
5042{
5043 if (line == m_viewInternal->m_activeInlineNote.m_position.line()) {
5044 m_viewInternal->m_activeInlineNote = {};
5045 }
5046 tagLines({line, line}, true);
5047}
5048
5049// END Inline Note Interface
5050
5051KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle defaultStyle) const
5052{
5053 KateRendererConfig *renderConfig = const_cast<KTextEditor::ViewPrivate *>(this)->rendererConfig();
5054
5055 KTextEditor::Attribute::Ptr style = doc()->highlight()->attributes(renderConfig->schema()).at(defaultStyle);
5056 if (!style->hasProperty(QTextFormat::BackgroundBrush)) {
5057 // make sure the returned style has the default background color set
5058 style = new KTextEditor::Attribute(*style);
5059 style->setBackground(QBrush(renderConfig->backgroundColor()));
5060 }
5061 return style;
5062}
5063
5064QList<KTextEditor::AttributeBlock> KTextEditor::ViewPrivate::lineAttributes(int line)
5065{
5067
5068 if (line < 0 || line >= doc()->lines()) {
5069 return attribs;
5070 }
5071
5072 const Kate::TextLine kateLine = doc()->kateTextLine(line);
5073 const auto &intAttrs = kateLine.attributesList();
5074 for (qsizetype i = 0; i < intAttrs.size(); ++i) {
5075 if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) {
5076 attribs << KTextEditor::AttributeBlock(intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(intAttrs.at(i).attributeValue));
5077 }
5078 }
5079
5080 return attribs;
5081}
5082
5083#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)
void deleteGroup(const QString &group, WriteConfigFlags flags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
static void setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter=false)
void indexTriggered(int index)
A delegate for rendering line annotation information and handling events.
An model for providing line annotation information.
Attributes of a part of a line.
Definition attribute.h:307
A class which provides customized text decorations.
Definition attribute.h:51
ActivationType
Several automatic activation mechanisms exist for associated attributes.
Definition attribute.h:244
@ ActivateMouseIn
Activate attribute on mouse in.
Definition attribute.h:246
@ ActivateCaretIn
Activate attribute on caret in.
Definition attribute.h:248
An item model for providing code completion, and meta information for enhanced presentation.
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
void setColumn(int column) noexcept
Set the cursor column to column.
Definition cursor.h:201
void setPosition(Cursor position) noexcept
Set the current cursor position to position.
Definition cursor.h:150
constexpr bool isValid() const noexcept
Returns whether the current position of this cursor is a valid position (line + column must both be >...
Definition cursor.h:102
static constexpr Cursor start() noexcept
Returns a cursor representing the start of any document - i.e., line 0, column 0.
Definition cursor.h:120
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
static constexpr Cursor invalid() noexcept
Returns an invalid cursor.
Definition cursor.h:112
Editing transaction support.
Definition document.h:574
A KParts derived class representing a text document.
Definition document.h:284
void copyToClipboard(const QString &text, const QString &fileName)
Copy text to clipboard an remember it in the history.
void deregisterView(KTextEditor::ViewPrivate *view)
unregister view at the factory
QTextToSpeech * speechEngine(KTextEditor::ViewPrivate *view)
text to speech engine to be use by the view actions, constructed on demand.
void configDialog(QWidget *parent) override
Configuration management.
void registerView(KTextEditor::ViewPrivate *view)
register view at the factory this allows us to loop over all views for example on config changes
static KTextEditor::EditorPrivate * self()
Kate Part Internal stuff ;)
A source of inline notes for a document.
void inlineNotesReset()
The provider should emit the signal inlineNotesReset() when almost all inline notes changed.
void inlineNotesChanged(int line)
The provider should emit the signal inlineNotesChanged() whenever one or more InlineNotes on the line...
virtual QList< int > inlineNotes(int line) const =0
Get list of inline notes for given line.
An object representing lines from a start line to an end line.
Definition linerange.h:41
constexpr int start() const noexcept
Get the start line of this line range.
Definition linerange.h:105
static constexpr LineRange invalid() noexcept
Returns an invalid line range.
Definition linerange.h:73
constexpr bool isValid() const noexcept
Validity check.
Definition linerange.h:65
constexpr int end() const noexcept
Get the end line of this line range.
Definition linerange.h:115
This class allows the application that embeds the KTextEditor component to allow it to access parts o...
Definition mainwindow.h:47
This class holds a Message to display in Views.
Definition message.h:94
@ BelowView
show message below view.
Definition message.h:121
@ AboveView
show message above view.
Definition message.h:119
@ TopInView
show message as view overlay in the top right corner.
Definition message.h:123
@ BottomInView
show message as view overlay in the bottom right corner.
Definition message.h:125
void setAutoHide(int delay=0)
Set the auto hide time to delay milliseconds.
void setAutoHideMode(KTextEditor::Message::AutoHideMode mode)
Sets the auto hide mode to mode.
MessagePosition position() const
Returns the message position of this message.
@ Information
information message type
Definition message.h:108
@ Immediate
auto-hide is triggered as soon as the message is shown
Definition message.h:136
void setPosition(MessagePosition position)
Sets the position of the message to position.
@ StayOnInsert
stay on insert
@ ExpandRight
Expand to encapsulate new characters to the right of the range.
@ ExpandLeft
Expand to encapsulate new characters to the left of the range.
An object representing a section of text, from one Cursor to another.
constexpr LineRange toLineRange() const noexcept
Convert this Range to a LineRange.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
void setEnd(Cursor end) noexcept
Set the end cursor to end.
constexpr bool isEmpty() const noexcept
Returns true if this range contains no characters, ie.
void setRange(Range range) noexcept
Set the start and end cursors to range.start() and range.end() respectively.
constexpr bool onSingleLine() const noexcept
Check whether this range is wholly contained within one line, ie.
static constexpr Range invalid() noexcept
Returns an invalid range.
constexpr bool isValid() const noexcept
Validity check.
constexpr bool containsLine(int line) const noexcept
Returns true if this range wholly encompasses line.
constexpr bool contains(Range range) const noexcept
Check whether the this range wholly encompasses range.
void setStart(Cursor start) noexcept
Set the start cursor to start.
Class to provide text hints for a View.
A text widget with KXMLGUIClient that represents a Document.
Definition view.h:244
ViewMode
Possible view modes These correspond to various modes the text editor might be in.
Definition view.h:295
InputMode
Possible input modes.
Definition view.h:286
@ ViInputMode
Vi mode.
Definition view.h:288
@ NormalInputMode
Normal Mode.
Definition view.h:287
void cursorPositionChanged(KTextEditor::View *view, KTextEditor::Cursor newPosition)
This signal is emitted whenever the view's cursor position changed.
void selectionChanged(KTextEditor::View *view)
This signal is emitted whenever the view's selection changes.
KateBuffer & buffer()
Get access to buffer of this document.
int lastLine() const
gets the last line number (lines() - 1)
KTextEditor::MovingRange * newMovingRange(KTextEditor::Range range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors=KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::EmptyBehavior emptyBehavior=KTextEditor::MovingRange::AllowEmpty) override
Create a new moving range for this document.
KateDocumentConfig * config()
Configuration.
QString mode() const override
Return the name of the currently used mode.
KTextEditor::MovingCursor * newMovingCursor(KTextEditor::Cursor position, KTextEditor::MovingCursor::InsertBehavior insertBehavior=KTextEditor::MovingCursor::MoveOnInsert) override
Create a new moving cursor for this document.
KXMLGUIClient * parentClient() const
KXMLGUIFactory * factory() const
virtual void setComponentName(const QString &componentName, const QString &componentDisplayName)
void removeClient(KXMLGUIClient *client)
This is the code completion's main widget, and also contains the core interface logic.
bool setValue(const int key, const QVariant &value)
Set a config value.
Internal data container for KTextEditor::InlineNote interface.
Class to layout KTextEditor::Messages in KateView.
This class implements a message widget based on KMessageWidget.
@ Header
Message positioned at the top of the view.
@ Footer
Message positioned at the bottom of the view.
void startAutoHideTimer()
Start autoHide timer if requested.
Handles all of the work of rendering the text (used for the views and printing)
Tools > Scripts menu This menu is filled with the command line scripts exported via the scripting sup...
Inserts a template and offers advanced snippet features, like navigation and mirroring.
This action provides a list of available indenters and gets plugged into the KTextEditor::ViewPrivate...
Class representing a 'clever' text cursor.
@ Folded
Range is folded away.
Class representing a single text line.
const QString & text() const
Accessor to the text contained in this line.
const QList< Attribute > & attributesList() const
Accessor to attributes.
QString string(int column, int length) const
Returns the substring with length beginning at the given column.
int length() const
Returns the line's length.
QChar at(int column) const
Returns the character at the given column.
int firstChar() const
Returns the position of the first non-whitespace character.
Class representing a 'clever' text range.
Q_SCRIPTABLE Q_NOREPLY void start()
Q_SCRIPTABLE bool focusOut(int ms=-1)
Q_SCRIPTABLE bool focusIn(int ms=-1)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
void update(Part *part, const QByteArray &data, qint64 dataSize)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QWidget * window(QObject *job)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
const QList< QKeySequence > & beginningOfLine()
const QList< QKeySequence > & begin()
const QList< QKeySequence > & reload()
const QList< QKeySequence > & cut()
const QList< QKeySequence > & zoomIn()
const QList< QKeySequence > & zoomOut()
const QList< QKeySequence > & next()
const QList< QKeySequence > & deleteWordBack()
const QList< QKeySequence > & find()
const QList< QKeySequence > & end()
const QList< QKeySequence > & copy()
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 > & findNext()
const QList< QKeySequence > & prior()
const QList< QKeySequence > & selectAll()
const QList< QKeySequence > & replace()
const QList< QKeySequence > & gotoLine()
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(char32_t ucs4)
bool isSpace(char32_t ucs4)
bool supportsSelection() const const
QString text(Mode mode) const const
QColor fromRgba(QRgb rgba)
Type type() const const
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
virtual void addItem(QLayoutItem *item) override
void addWidget(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment)
void setColumnStretch(int column, int stretch)
void setRowStretch(int row, int stretch)
virtual void setSpacing(int spacing) override
QClipboard * clipboard()
QIcon fromTheme(const QString &name)
QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode, const QString &text, bool *ok, Qt::WindowFlags flags, Qt::InputMethodHints inputMethodHints)
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
void setContentsMargins(const QMargins &margins)
void append(QList< T > &&value)
iterator begin()
void clear()
const T & constFirst() const const
bool empty() const const
iterator end()
T & first()
reference front()
bool isEmpty() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
qsizetype size() const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
void aboutToHide()
void aboutToShow()
QAction * addSeparator()
bool disconnect(const QMetaObject::Connection &connection)
QString escape(QStringView str)
iterator end()
iterator erase(const_iterator pos)
iterator find(const T &value)
iterator insert(const T &value)
const QChar at(qsizetype position) const const
bool isEmpty() const const
bool isRightToLeft() const const
qsizetype length() const const
QString & prepend(QChar ch)
void reserve(qsizetype size)
PM_DefaultFrameWidth
SH_ScrollView_FrameOnlyAroundContents
void initFrom(const QWidget *widget)
QueuedConnection
StrongFocus
LeftToRight
WidgetWithChildrenShortcut
QTextStream & bom(QTextStream &stream)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
bool canConvert() const const
bool toBool() const const
int toInt(bool *ok) const const
QString toString() const const
int userType() const const
T value() const const
void append(T &&t)
iterator begin()
bool empty() const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
void push_back(T &&t)
qsizetype size() const const
virtual bool event(QEvent *event) override
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:15:44 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.