File indexing completed on 2024-04-28 03:57:59
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2009 Michel Ludwig <michel.ludwig@kdemail.net> 0004 SPDX-FileCopyrightText: 2007 Mirko Stocker <me@misto.ch> 0005 SPDX-FileCopyrightText: 2003 Hamish Rodda <rodda@kde.org> 0006 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org> 0007 SPDX-FileCopyrightText: 2001-2004 Christoph Cullmann <cullmann@kde.org> 0008 SPDX-FileCopyrightText: 2001-2010 Joseph Wenninger <jowenn@kde.org> 0009 SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> 0010 0011 SPDX-License-Identifier: LGPL-2.0-only 0012 */ 0013 0014 // BEGIN includes 0015 #include "kateview.h" 0016 0017 #include "clipboardhistorydialog.h" 0018 #include "export/exporter.h" 0019 #include "inlinenotedata.h" 0020 #include "kateabstractinputmode.h" 0021 #include "kateautoindent.h" 0022 #include "katebookmarks.h" 0023 #include "katebuffer.h" 0024 #include "katecompletionwidget.h" 0025 #include "kateconfig.h" 0026 #include "katedialogs.h" 0027 #include "katedocument.h" 0028 #include "kateglobal.h" 0029 #include "katehighlight.h" 0030 #include "katehighlightmenu.h" 0031 #include "katekeywordcompletion.h" 0032 #include "katelayoutcache.h" 0033 #include "katemessagewidget.h" 0034 #include "katemodemenu.h" 0035 #include "katepartdebug.h" 0036 #include "katerenderer.h" 0037 #include "katestatusbar.h" 0038 #include "katetemplatehandler.h" 0039 #include "katetextline.h" 0040 #include "kateundomanager.h" 0041 #include "kateviewhelpers.h" 0042 #include "kateviewinternal.h" 0043 #include "katewordcompletion.h" 0044 #include "printing/kateprinter.h" 0045 #include "screenshotdialog.h" 0046 #include "script/katescriptaction.h" 0047 #include "script/katescriptmanager.h" 0048 #include "spellcheck/spellcheck.h" 0049 #include "spellcheck/spellcheckdialog.h" 0050 #include "spellcheck/spellingmenu.h" 0051 0052 #include <KTextEditor/Message> 0053 #include <ktexteditor/annotationinterface.h> 0054 #include <ktexteditor/inlinenoteprovider.h> 0055 #include <ktexteditor/texthintinterface.h> 0056 0057 #include <KActionCollection> 0058 #include <KConfig> 0059 #include <KConfigGroup> 0060 #include <KCursor> 0061 #include <KMessageBox> 0062 #include <KSelectAction> 0063 #include <KStandardAction> 0064 #include <KStandardShortcut> 0065 #include <KToggleAction> 0066 #include <KXMLGUIFactory> 0067 0068 #include <QActionGroup> 0069 #include <QApplication> 0070 #include <QClipboard> 0071 #include <QFileDialog> 0072 #include <QFileInfo> 0073 #include <QFont> 0074 #include <QInputDialog> 0075 #include <QKeyEvent> 0076 #include <QLayout> 0077 #include <QMimeData> 0078 #include <QPainter> 0079 #include <QRegularExpression> 0080 #include <QTextToSpeech> 0081 #include <QToolTip> 0082 0083 // #define VIEW_RANGE_DEBUG 0084 0085 // END includes 0086 0087 namespace 0088 { 0089 bool hasCommentInFirstLine(KTextEditor::DocumentPrivate *doc) 0090 { 0091 const Kate::TextLine line = doc->kateTextLine(0); 0092 return doc->isComment(0, line.firstChar()); 0093 } 0094 0095 } 0096 0097 void KTextEditor::ViewPrivate::blockFix(KTextEditor::Range &range) 0098 { 0099 if (range.start().column() > range.end().column()) { 0100 int tmp = range.start().column(); 0101 range.setStart(KTextEditor::Cursor(range.start().line(), range.end().column())); 0102 range.setEnd(KTextEditor::Cursor(range.end().line(), tmp)); 0103 } 0104 } 0105 0106 KTextEditor::ViewPrivate::ViewPrivate(KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow) 0107 : KTextEditor::View(this, parent) 0108 , m_completionWidget(nullptr) 0109 , m_annotationModel(nullptr) 0110 , m_markedSelection(false) 0111 , m_hasWrap(false) 0112 , m_doc(doc) 0113 , m_textFolding(doc->buffer()) 0114 , m_config(new KateViewConfig(this)) 0115 , m_renderer(new KateRenderer(doc, m_textFolding, this)) 0116 , m_viewInternal(new KateViewInternal(this)) 0117 , m_spell(new KateSpellCheckDialog(this)) 0118 , m_bookmarks(new KateBookmarks(this)) 0119 , m_topSpacer(new QSpacerItem(0, 0)) 0120 , m_leftSpacer(new QSpacerItem(0, 0)) 0121 , m_rightSpacer(new QSpacerItem(0, 0)) 0122 , m_bottomSpacer(new QSpacerItem(0, 0)) 0123 , m_startingUp(true) 0124 , m_updatingDocumentConfig(false) 0125 , m_selection(m_doc->buffer(), KTextEditor::Range::invalid(), Kate::TextRange::ExpandLeft, Kate::TextRange::AllowEmpty) 0126 , blockSelect(false) 0127 , m_bottomViewBar(nullptr) 0128 , m_gotoBar(nullptr) 0129 , m_dictionaryBar(nullptr) 0130 , m_spellingMenu(new KateSpellingMenu(this)) 0131 , m_userContextMenuSet(false) 0132 , m_lineToUpdateRange(KTextEditor::LineRange::invalid()) 0133 , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there! 0134 , m_statusBar(nullptr) 0135 , m_temporaryAutomaticInvocationDisabled(false) 0136 , m_autoFoldedFirstLine(false) 0137 { 0138 // queued connect to collapse view updates for range changes, INIT THIS EARLY ENOUGH! 0139 connect(this, &KTextEditor::ViewPrivate::delayedUpdateOfView, this, &KTextEditor::ViewPrivate::slotDelayedUpdateOfView, Qt::QueuedConnection); 0140 0141 m_delayedUpdateTimer.setSingleShot(true); 0142 m_delayedUpdateTimer.setInterval(0); 0143 connect(&m_delayedUpdateTimer, &QTimer::timeout, this, &KTextEditor::ViewPrivate::delayedUpdateOfView); 0144 0145 KXMLGUIClient::setComponentName(KTextEditor::EditorPrivate::self()->aboutData().componentName(), 0146 KTextEditor::EditorPrivate::self()->aboutData().displayName()); 0147 0148 // selection if for this view only and will invalidate if becoming empty 0149 m_selection.setView(this); 0150 0151 // use z depth defined in moving ranges interface 0152 m_selection.setZDepth(-100000.0); 0153 0154 KTextEditor::EditorPrivate::self()->registerView(this); 0155 0156 // try to let the main window, if any, create a view bar for this view 0157 QWidget *bottomBarParent = m_mainWindow->createViewBar(this); 0158 0159 m_bottomViewBar = new KateViewBar(bottomBarParent != nullptr, bottomBarParent ? bottomBarParent : this, this); 0160 0161 // ugly workaround: 0162 // Force the layout to be left-to-right even on RTL desktop, as discussed 0163 // on the mailing list. This will cause the lines and icons panel to be on 0164 // the left, even for Arabic/Hebrew/Farsi/whatever users. 0165 setLayoutDirection(Qt::LeftToRight); 0166 0167 m_bottomViewBar->installEventFilter(m_viewInternal); 0168 0169 // add KateMessageWidget for KTE::MessageInterface immediately above view 0170 m_messageWidgets[KTextEditor::Message::AboveView] = new KateMessageWidget(this); 0171 m_messageWidgets[KTextEditor::Message::AboveView]->setPosition(KateMessageWidget::Position::Header); 0172 m_messageWidgets[KTextEditor::Message::AboveView]->hide(); 0173 0174 // add KateMessageWidget for KTE::MessageInterface immediately above view 0175 m_messageWidgets[KTextEditor::Message::BelowView] = new KateMessageWidget(this); 0176 m_messageWidgets[KTextEditor::Message::BelowView]->setPosition(KateMessageWidget::Position::Footer); 0177 m_messageWidgets[KTextEditor::Message::BelowView]->hide(); 0178 0179 // add bottom viewbar... 0180 if (bottomBarParent) { 0181 m_mainWindow->addWidgetToViewBar(this, m_bottomViewBar); 0182 } 0183 0184 // add layout for floating message widgets to KateViewInternal 0185 m_notificationLayout = new KateMessageLayout(m_viewInternal); 0186 m_notificationLayout->setContentsMargins(20, 20, 20, 20); 0187 m_viewInternal->setLayout(m_notificationLayout); 0188 0189 // this really is needed :) 0190 m_viewInternal->updateView(); 0191 0192 doc->addView(this); 0193 0194 setFocusProxy(m_viewInternal); 0195 setFocusPolicy(Qt::StrongFocus); 0196 0197 setXMLFile(QStringLiteral("katepart5ui.rc")); 0198 0199 setupConnections(); 0200 setupActions(); 0201 0202 // auto word completion 0203 new KateWordCompletionView(this, actionCollection()); 0204 0205 // update the enabled state of the undo/redo actions... 0206 slotUpdateUndo(); 0207 0208 // create the status bar of this view 0209 // do this after action creation, we use some of them! 0210 toggleStatusBar(); 0211 0212 m_startingUp = false; 0213 updateConfig(); 0214 0215 slotHlChanged(); 0216 KCursor::setAutoHideCursor(m_viewInternal, true); 0217 0218 for (auto messageWidget : m_messageWidgets) { 0219 if (messageWidget) { 0220 // user interaction (scrolling) starts notification auto-hide timer 0221 connect(this, &KTextEditor::ViewPrivate::displayRangeChanged, messageWidget, &KateMessageWidget::startAutoHideTimer); 0222 0223 // user interaction (cursor navigation) starts notification auto-hide timer 0224 connect(this, &KTextEditor::ViewPrivate::cursorPositionChanged, messageWidget, &KateMessageWidget::startAutoHideTimer); 0225 } 0226 } 0227 0228 // folding restoration on reload 0229 connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::saveFoldingState); 0230 connect(m_doc, &KTextEditor::DocumentPrivate::reloaded, this, &KTextEditor::ViewPrivate::applyFoldingState); 0231 0232 connect(m_doc, &KTextEditor::DocumentPrivate::reloaded, this, &KTextEditor::ViewPrivate::slotDocumentReloaded); 0233 connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::slotDocumentAboutToReload); 0234 0235 // update highlights on scrolling and co 0236 connect(this, &KTextEditor::ViewPrivate::displayRangeChanged, this, &KTextEditor::ViewPrivate::createHighlights); 0237 0238 // clear highlights on reload 0239 connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::clearHighlights); 0240 0241 // setup layout 0242 setupLayout(); 0243 } 0244 0245 KTextEditor::ViewPrivate::~ViewPrivate() 0246 { 0247 // de-register views early from global collections 0248 // otherwise we might "use" them again during destruction in a half-valid state 0249 // see e.g. bug 422546 0250 // Kate::TextBuffer::notifyAboutRangeChange will access views() in a chain during 0251 // deletion of m_viewInternal 0252 doc()->removeView(this); 0253 KTextEditor::EditorPrivate::self()->deregisterView(this); 0254 0255 delete m_completionWidget; 0256 0257 // remove from xmlgui factory, to be safe 0258 if (factory()) { 0259 factory()->removeClient(this); 0260 } 0261 0262 // delete internal view before view bar! 0263 delete m_viewInternal; 0264 0265 // remove view bar again, if needed 0266 m_mainWindow->deleteViewBar(this); 0267 m_bottomViewBar = nullptr; 0268 0269 delete m_renderer; 0270 0271 delete m_config; 0272 } 0273 0274 void KTextEditor::ViewPrivate::toggleStatusBar() 0275 { 0276 // if there, delete it 0277 if (m_statusBar) { 0278 bottomViewBar()->removePermanentBarWidget(m_statusBar); 0279 delete m_statusBar; 0280 m_statusBar = nullptr; 0281 Q_EMIT statusBarEnabledChanged(this, false); 0282 return; 0283 } 0284 0285 // else: create it 0286 m_statusBar = new KateStatusBar(this); 0287 bottomViewBar()->addPermanentBarWidget(m_statusBar); 0288 Q_EMIT statusBarEnabledChanged(this, true); 0289 } 0290 0291 void KTextEditor::ViewPrivate::setupLayout() 0292 { 0293 // delete old layout if any 0294 if (layout()) { 0295 delete layout(); 0296 0297 // need to recreate spacers because they are deleted with 0298 // their belonging layout 0299 m_topSpacer = new QSpacerItem(0, 0); 0300 m_leftSpacer = new QSpacerItem(0, 0); 0301 m_rightSpacer = new QSpacerItem(0, 0); 0302 m_bottomSpacer = new QSpacerItem(0, 0); 0303 } 0304 0305 // set margins 0306 QStyleOptionFrame opt; 0307 opt.initFrom(this); 0308 opt.frameShape = QFrame::StyledPanel; 0309 opt.state |= QStyle::State_Sunken; 0310 const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); 0311 m_topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); 0312 m_leftSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); 0313 m_rightSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); 0314 m_bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); 0315 0316 // define layout 0317 QGridLayout *layout = new QGridLayout(this); 0318 layout->setContentsMargins(0, 0, 0, 0); 0319 layout->setSpacing(0); 0320 0321 const bool frameAroundContents = style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, this); 0322 if (frameAroundContents) { 0323 // top message widget 0324 layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); 0325 0326 // top spacer 0327 layout->addItem(m_topSpacer, 1, 0, 1, 4); 0328 0329 // left spacer 0330 layout->addItem(m_leftSpacer, 2, 0, 1, 1); 0331 0332 // left border 0333 layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); 0334 0335 // view 0336 layout->addWidget(m_viewInternal, 2, 2, 1, 1); 0337 0338 // right spacer 0339 layout->addItem(m_rightSpacer, 2, 3, 1, 1); 0340 0341 // bottom spacer 0342 layout->addItem(m_bottomSpacer, 3, 0, 1, 4); 0343 0344 // vertical scrollbar 0345 layout->addWidget(m_viewInternal->m_lineScroll, 1, 4, 3, 1); 0346 0347 // horizontal scrollbar 0348 layout->addWidget(m_viewInternal->m_columnScroll, 4, 0, 1, 4); 0349 0350 // dummy 0351 layout->addWidget(m_viewInternal->m_dummy, 4, 4, 1, 1); 0352 0353 // bottom message 0354 layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); 0355 0356 // bottom viewbar 0357 if (m_bottomViewBar->parentWidget() == this) { 0358 layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); 0359 } 0360 0361 // stretch 0362 layout->setColumnStretch(2, 1); 0363 layout->setRowStretch(2, 1); 0364 0365 // adjust scrollbar background 0366 m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Window); 0367 m_viewInternal->m_lineScroll->setAutoFillBackground(false); 0368 0369 m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Window); 0370 m_viewInternal->m_columnScroll->setAutoFillBackground(false); 0371 0372 } else { 0373 // top message widget 0374 layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); 0375 0376 // top spacer 0377 layout->addItem(m_topSpacer, 1, 0, 1, 5); 0378 0379 // left spacer 0380 layout->addItem(m_leftSpacer, 2, 0, 1, 1); 0381 0382 // left border 0383 layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); 0384 0385 // view 0386 layout->addWidget(m_viewInternal, 2, 2, 1, 1); 0387 0388 // vertical scrollbar 0389 layout->addWidget(m_viewInternal->m_lineScroll, 2, 3, 1, 1); 0390 0391 // right spacer 0392 layout->addItem(m_rightSpacer, 2, 4, 1, 1); 0393 0394 // horizontal scrollbar 0395 layout->addWidget(m_viewInternal->m_columnScroll, 3, 1, 1, 2); 0396 0397 // dummy 0398 layout->addWidget(m_viewInternal->m_dummy, 3, 3, 1, 1); 0399 0400 // bottom spacer 0401 layout->addItem(m_bottomSpacer, 4, 0, 1, 5); 0402 0403 // bottom message 0404 layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); 0405 0406 // bottom viewbar 0407 if (m_bottomViewBar->parentWidget() == this) { 0408 layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); 0409 } 0410 0411 // stretch 0412 layout->setColumnStretch(2, 1); 0413 layout->setRowStretch(2, 1); 0414 0415 // adjust scrollbar background 0416 m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Base); 0417 m_viewInternal->m_lineScroll->setAutoFillBackground(true); 0418 0419 m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Base); 0420 m_viewInternal->m_columnScroll->setAutoFillBackground(true); 0421 } 0422 } 0423 0424 void KTextEditor::ViewPrivate::setupConnections() 0425 { 0426 connect(m_doc, &KTextEditor::DocumentPrivate::undoChanged, this, &KTextEditor::ViewPrivate::slotUpdateUndo); 0427 connect(m_doc, &KTextEditor::DocumentPrivate::highlightingModeChanged, this, &KTextEditor::ViewPrivate::slotHlChanged); 0428 connect(m_doc, &KTextEditor::DocumentPrivate::canceled, this, &KTextEditor::ViewPrivate::slotSaveCanceled); 0429 connect(m_viewInternal, &KateViewInternal::dropEventPass, this, &KTextEditor::ViewPrivate::dropEventPass); 0430 0431 connect(m_doc, &KTextEditor::DocumentPrivate::annotationModelChanged, m_viewInternal->m_leftBorder, &KateIconBorder::annotationModelChanged); 0432 } 0433 0434 void KTextEditor::ViewPrivate::goToPreviousEditingPosition() 0435 { 0436 auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Previous, cursorPosition()); 0437 if (c.isValid()) { 0438 setCursorPosition(c); 0439 } 0440 } 0441 0442 void KTextEditor::ViewPrivate::goToNextEditingPosition() 0443 { 0444 auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Next, cursorPosition()); 0445 if (c.isValid()) { 0446 setCursorPosition(c); 0447 } 0448 } 0449 void KTextEditor::ViewPrivate::setupActions() 0450 { 0451 KActionCollection *ac = actionCollection(); 0452 QAction *a; 0453 0454 m_toggleWriteLock = nullptr; 0455 0456 m_cut = a = ac->addAction(KStandardAction::Cut, this, SLOT(cut())); 0457 a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard")); 0458 0459 m_paste = a = ac->addAction(KStandardAction::Paste, this, SLOT(paste())); 0460 a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents")); 0461 0462 m_copy = a = ac->addAction(KStandardAction::Copy, this, SLOT(copy())); 0463 a->setWhatsThis(i18n("Use this command to copy the currently selected text to the system clipboard.")); 0464 0465 m_clipboardHistory = a = ac->addAction(QStringLiteral("clipboard_history_paste"), this, [this] { 0466 ClipboardHistoryDialog chd(mainWindow()->window(), this); 0467 chd.openDialog(KTextEditor::EditorPrivate::self()->clipboardHistory()); 0468 }); 0469 a->setText(i18n("Clipboard &History Paste")); 0470 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::ALT | Qt::Key_V)); 0471 0472 if (QApplication::clipboard()->supportsSelection()) { 0473 m_pasteSelection = a = ac->addAction(QStringLiteral("edit_paste_selection"), this, SLOT(pasteSelection())); 0474 a->setText(i18n("Paste Selection")); 0475 ac->setDefaultShortcuts(a, KStandardShortcut::pasteSelection()); 0476 a->setWhatsThis(i18n("Paste previously mouse selection contents")); 0477 } 0478 0479 m_swapWithClipboard = a = ac->addAction(QStringLiteral("edit_swap_with_clipboard"), this, SLOT(swapWithClipboard())); 0480 a->setText(i18n("Swap with Clipboard Contents")); 0481 a->setWhatsThis(i18n("Swap the selected text with the clipboard contents")); 0482 0483 m_screenshotSelection = a = ac->addAction(QStringLiteral("text_screenshot_selection"), this, &KTextEditor::ViewPrivate::screenshot); 0484 a->setText(i18n("Take Screenshot of Selection")); 0485 0486 if (!doc()->readOnly()) { 0487 a = ac->addAction(KStandardAction::Save, m_doc, SLOT(documentSave())); 0488 a->setWhatsThis(i18n("Save the current document")); 0489 0490 a = m_editUndo = ac->addAction(KStandardAction::Undo, m_doc, SLOT(undo())); 0491 a->setWhatsThis(i18n("Revert the most recent editing actions")); 0492 0493 a = m_editRedo = ac->addAction(KStandardAction::Redo, m_doc, SLOT(redo())); 0494 a->setWhatsThis(i18n("Revert the most recent undo operation")); 0495 0496 // Tools > Scripts 0497 // stored inside scoped pointer to ensure we destruct it early enough as it does internal cleanups of other child objects 0498 m_scriptActionMenu.reset(new KateScriptActionMenu(this, i18n("&Scripts"))); 0499 ac->addAction(QStringLiteral("tools_scripts"), m_scriptActionMenu.get()); 0500 0501 a = ac->addAction(QStringLiteral("tools_apply_wordwrap")); 0502 a->setText(i18n("Apply &Word Wrap")); 0503 a->setWhatsThis( 0504 i18n("Use this to wrap the current line, or to reformat the selected lines as paragraph, " 0505 "to fit the 'Wrap words at' setting in the configuration dialog.<br /><br />" 0506 "This is a static word wrap, meaning the document is changed.")); 0507 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::applyWordWrap); 0508 0509 a = ac->addAction(QStringLiteral("tools_cleanIndent")); 0510 a->setText(i18n("&Clean Indentation")); 0511 a->setWhatsThis( 0512 i18n("Use this to clean the indentation of a selected block of text (only tabs/only spaces).<br /><br />" 0513 "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); 0514 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::cleanIndent); 0515 0516 a = ac->addAction(QStringLiteral("tools_formatIndent")); 0517 a->setText(i18n("&Format Indentation")); 0518 a->setWhatsThis(i18n("Use this to auto indent the current line or block of text to its proper indent level.")); 0519 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::formatIndent); 0520 0521 a = ac->addAction(QStringLiteral("tools_alignOn")); 0522 a->setText(i18n("&Align On...")); 0523 a->setWhatsThis( 0524 i18n("This command aligns lines in the selected block or whole document on the column given by a regular expression " 0525 "that you will be prompted for.<br /><br />" 0526 "If you give an empty pattern it will align on the first non-blank character by default.<br />" 0527 "If the pattern has a capture it will indent on the captured match.<br /><br />" 0528 "<i>Examples</i>:<br />" 0529 "With '-' it will insert spaces before the first '-' of each lines to align them all on the same column.<br />" 0530 "With ':\\s+(.)' it will insert spaces before the first non-blank character that occurs after a colon to align " 0531 "them all on the same column.")); 0532 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::alignOn); 0533 0534 a = ac->addAction(QStringLiteral("tools_comment")); 0535 a->setText(i18n("C&omment")); 0536 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_D)); 0537 a->setWhatsThis( 0538 i18n("This command comments out the current line or a selected block of text.<br /><br />" 0539 "The characters for single/multiple line comments are defined within the language's highlighting.")); 0540 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::comment); 0541 0542 a = ac->addAction(QStringLiteral("Previous Editing Line")); 0543 a->setText(i18n("Go to Previous Editing Location")); 0544 a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); 0545 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_E)); 0546 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::goToPreviousEditingPosition); 0547 0548 a = ac->addAction(QStringLiteral("Next Editing Line")); 0549 a->setText(i18n("Go to Next Editing Location")); 0550 a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); 0551 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_E)); 0552 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::goToNextEditingPosition); 0553 0554 a = ac->addAction(QStringLiteral("tools_uncomment")); 0555 a->setText(i18n("Unco&mment")); 0556 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_D)); 0557 a->setWhatsThis( 0558 i18n("This command removes comments from the current line or a selected block of text.<br /><br />" 0559 "The characters for single/multiple line comments are defined within the language's highlighting.")); 0560 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::uncomment); 0561 0562 a = ac->addAction(QStringLiteral("tools_toggle_comment")); 0563 a->setText(i18n("Toggle Comment")); 0564 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_Slash)); 0565 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleComment); 0566 0567 a = m_toggleWriteLock = new KToggleAction(i18n("&Read Only Mode"), this); 0568 a->setWhatsThis(i18n("Lock/unlock the document for writing")); 0569 a->setChecked(!doc()->isReadWrite()); 0570 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleWriteLock); 0571 ac->addAction(QStringLiteral("tools_toggle_write_lock"), a); 0572 0573 a = m_forceRTLDirection = new KToggleAction(i18n("&Force RTL Direction"), this); 0574 a->setWhatsThis(i18n("Force RTL Text Direction")); 0575 connect(a, &QAction::triggered, this, [this](bool checked) { 0576 m_forceRTL = checked; 0577 tagAll(); 0578 updateView(true); 0579 }); 0580 ac->addAction(QStringLiteral("force_rtl_direction"), a); 0581 0582 a = ac->addAction(QStringLiteral("tools_uppercase")); 0583 a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-uppercase"))); 0584 a->setText(i18n("Uppercase")); 0585 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_U)); 0586 a->setWhatsThis( 0587 i18n("Convert the selection to uppercase, or the character to the " 0588 "right of the cursor if no text is selected.")); 0589 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::uppercase); 0590 0591 a = ac->addAction(QStringLiteral("tools_lowercase")); 0592 a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-lowercase"))); 0593 a->setText(i18n("Lowercase")); 0594 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_U)); 0595 a->setWhatsThis( 0596 i18n("Convert the selection to lowercase, or the character to the " 0597 "right of the cursor if no text is selected.")); 0598 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::lowercase); 0599 0600 a = ac->addAction(QStringLiteral("tools_capitalize")); 0601 a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-capitalize"))); 0602 a->setText(i18n("Capitalize")); 0603 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_U)); 0604 a->setWhatsThis( 0605 i18n("Capitalize the selection, or the word under the " 0606 "cursor if no text is selected.")); 0607 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::capitalize); 0608 0609 a = ac->addAction(QStringLiteral("tools_join_lines")); 0610 a->setText(i18n("Join Lines")); 0611 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_J)); 0612 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::joinLines); 0613 0614 a = ac->addAction(QStringLiteral("tools_invoke_code_completion")); 0615 a->setText(i18n("Invoke Code Completion")); 0616 a->setWhatsThis(i18n("Manually invoke command completion, usually by using a shortcut bound to this action.")); 0617 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_Space)); 0618 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::userInvokedCompletion); 0619 0620 a = ac->addAction(QStringLiteral("remove_trailing_spaces")); 0621 a->setText(i18n("Remove Trailing Spaces")); 0622 connect(a, &QAction::triggered, this, [this] { 0623 doc()->removeAllTrailingSpaces(); 0624 }); 0625 } else { 0626 for (auto *action : {m_cut, m_paste, m_clipboardHistory, m_swapWithClipboard}) { 0627 action->setEnabled(false); 0628 } 0629 0630 if (m_pasteSelection) { 0631 m_pasteSelection->setEnabled(false); 0632 } 0633 0634 m_editUndo = nullptr; 0635 m_editRedo = nullptr; 0636 } 0637 0638 a = ac->addAction(KStandardAction::Print, this, SLOT(print())); 0639 a->setWhatsThis(i18n("Print the current document.")); 0640 0641 a = ac->addAction(KStandardAction::PrintPreview, this, SLOT(printPreview())); 0642 a->setWhatsThis(i18n("Show print preview of current document")); 0643 0644 a = ac->addAction(QStringLiteral("file_reload")); 0645 a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); 0646 a->setText(i18n("Reloa&d")); 0647 ac->setDefaultShortcuts(a, KStandardShortcut::reload()); 0648 a->setWhatsThis(i18n("Reload the current document from disk.")); 0649 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::reloadFile); 0650 0651 a = ac->addAction(KStandardAction::SaveAs, m_doc, SLOT(documentSaveAs())); 0652 a->setWhatsThis(i18n("Save the current document to disk, with a name of your choice.")); 0653 0654 a = new KateViewEncodingAction(m_doc, this, i18n("Save As with Encoding..."), this, true /* special mode for save as */); 0655 a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); 0656 ac->addAction(QStringLiteral("file_save_as_with_encoding"), a); 0657 0658 a = ac->addAction(QStringLiteral("file_save_copy_as")); 0659 a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); 0660 a->setText(i18n("Save &Copy As...")); 0661 a->setWhatsThis(i18n("Save a copy of the current document to disk.")); 0662 connect(a, &QAction::triggered, m_doc, &KTextEditor::DocumentPrivate::documentSaveCopyAs); 0663 0664 a = ac->addAction(KStandardAction::GotoLine, this, SLOT(gotoLine())); 0665 a->setWhatsThis(i18n("This command opens a dialog and lets you choose a line that you want the cursor to move to.")); 0666 0667 a = ac->addAction(QStringLiteral("modified_line_up")); 0668 a->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); 0669 a->setText(i18n("Go to Previous Modified Line")); 0670 a->setWhatsThis(i18n("Move upwards to the previous modified line.")); 0671 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toPrevModifiedLine); 0672 0673 a = ac->addAction(QStringLiteral("modified_line_down")); 0674 a->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); 0675 a->setText(i18n("Go to Next Modified Line")); 0676 a->setWhatsThis(i18n("Move downwards to the next modified line.")); 0677 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toNextModifiedLine); 0678 0679 a = ac->addAction(QStringLiteral("set_confdlg")); 0680 a->setText(i18n("&Configure Editor...")); 0681 a->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); 0682 a->setWhatsThis(i18n("Configure various aspects of this editor.")); 0683 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotConfigDialog); 0684 0685 m_modeAction = new KateModeMenu(i18n("&Mode"), this); 0686 ac->addAction(QStringLiteral("tools_mode"), m_modeAction); 0687 m_modeAction->setWhatsThis(i18n( 0688 "Here you can choose which mode should be used for the current document. This will influence the highlighting and folding being used, for example.")); 0689 m_modeAction->updateMenu(m_doc); 0690 0691 KateHighlightingMenu *menu = new KateHighlightingMenu(i18n("&Highlighting"), this); 0692 ac->addAction(QStringLiteral("tools_highlighting"), menu); 0693 menu->setWhatsThis(i18n("Here you can choose how the current document should be highlighted.")); 0694 menu->updateMenu(m_doc); 0695 0696 KateViewSchemaAction *schemaMenu = new KateViewSchemaAction(i18n("&Editor Color Theme"), this); 0697 schemaMenu->setIcon(QIcon::fromTheme(QStringLiteral("kcolorchooser"))); 0698 ac->addAction(QStringLiteral("view_schemas"), schemaMenu); 0699 schemaMenu->updateMenu(this); 0700 0701 // indentation menu 0702 KateViewIndentationAction *indentMenu = new KateViewIndentationAction(m_doc, i18n("&Indentation"), this); 0703 ac->addAction(QStringLiteral("tools_indentation"), indentMenu); 0704 0705 m_selectAll = ac->addAction(KStandardAction::SelectAll); 0706 connect(m_selectAll, &QAction::triggered, this, [this] { 0707 selectAll(); 0708 qApp->clipboard()->setText(selectionText(), QClipboard::Selection); 0709 }); 0710 a->setWhatsThis(i18n("Select the entire text of the current document.")); 0711 0712 m_deSelect = a = ac->addAction(KStandardAction::Deselect, this, SLOT(clearSelection())); 0713 a->setWhatsThis(i18n("If you have selected something within the current document, this will no longer be selected.")); 0714 0715 a = ac->addAction(QStringLiteral("view_inc_font_sizes")); 0716 a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); 0717 a->setText(i18n("Enlarge Font")); 0718 ac->setDefaultShortcuts(a, KStandardShortcut::zoomIn()); 0719 a->setWhatsThis(i18n("This increases the display font size.")); 0720 connect(a, &QAction::triggered, m_viewInternal, [this]() { 0721 m_viewInternal->slotIncFontSizes(); 0722 }); 0723 0724 a = ac->addAction(QStringLiteral("view_dec_font_sizes")); 0725 a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); 0726 a->setText(i18n("Shrink Font")); 0727 ac->setDefaultShortcuts(a, KStandardShortcut::zoomOut()); 0728 a->setWhatsThis(i18n("This decreases the display font size.")); 0729 connect(a, &QAction::triggered, m_viewInternal, [this]() { 0730 m_viewInternal->slotDecFontSizes(); 0731 }); 0732 0733 a = ac->addAction(QStringLiteral("view_reset_font_sizes")); 0734 a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original"))); 0735 a->setText(i18n("Reset Font Size")); 0736 ac->setDefaultShortcuts(a, KStandardShortcut::shortcut(KStandardShortcut::ActualSize)); 0737 a->setWhatsThis(i18n("This resets the display font size.")); 0738 connect(a, &QAction::triggered, m_viewInternal, &KateViewInternal::slotResetFontSizes); 0739 0740 a = m_toggleBlockSelection = new KToggleAction(i18n("Bl&ock Selection Mode"), this); 0741 ac->addAction(QStringLiteral("set_verticalSelect"), a); 0742 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_B)); 0743 a->setWhatsThis(i18n("This command allows switching between the normal (line based) selection mode and the block selection mode.")); 0744 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleBlockSelection); 0745 0746 a = ac->addAction(QStringLiteral("switch_next_input_mode")); 0747 a->setText(i18n("Switch to Next Input Mode")); 0748 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_V)); 0749 a->setWhatsThis(i18n("Switch to the next input mode.")); 0750 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::cycleInputMode); 0751 0752 a = m_toggleInsert = new KToggleAction(i18n("Overwr&ite Mode"), this); 0753 ac->addAction(QStringLiteral("set_insert"), a); 0754 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Insert)); 0755 a->setWhatsThis(i18n("Choose whether you want the text you type to be inserted or to overwrite existing text.")); 0756 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleInsert); 0757 0758 KToggleAction *toggleAction; 0759 a = m_toggleDynWrap = toggleAction = new KToggleAction(i18n("&Dynamic Word Wrap"), this); 0760 a->setIcon(QIcon::fromTheme(QStringLiteral("text-wrap"))); 0761 ac->addAction(QStringLiteral("view_dynamic_word_wrap"), a); 0762 a->setWhatsThis( 0763 i18n("If this option is checked, the text lines will be wrapped at the view border on the screen.<br /><br />" 0764 "This is only a view option, meaning the document will not changed.")); 0765 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleDynWordWrap); 0766 0767 a = m_setDynWrapIndicators = new KSelectAction(i18n("Dynamic Word Wrap Indicators"), this); 0768 ac->addAction(QStringLiteral("dynamic_word_wrap_indicators"), a); 0769 a->setWhatsThis(i18n("Choose when the Dynamic Word Wrap Indicators should be displayed")); 0770 connect(m_setDynWrapIndicators, &KSelectAction::indexTriggered, this, &KTextEditor::ViewPrivate::setDynWrapIndicators); 0771 const QStringList list2{i18n("&Off"), i18n("Follow &Line Numbers"), i18n("&Always On")}; 0772 m_setDynWrapIndicators->setItems(list2); 0773 m_setDynWrapIndicators->setEnabled(m_toggleDynWrap->isChecked()); // only synced on real change, later 0774 0775 a = toggleAction = new KToggleAction(i18n("Static Word Wrap"), this); 0776 ac->addAction(QStringLiteral("view_static_word_wrap"), a); 0777 a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the column defined in the editing properties.")); 0778 connect(a, &KToggleAction::triggered, [this] { 0779 if (m_doc) { 0780 m_doc->setWordWrap(!m_doc->wordWrap()); 0781 } 0782 }); 0783 0784 a = toggleAction = m_toggleWWMarker = new KToggleAction(i18n("Show Static &Word Wrap Marker"), this); 0785 ac->addAction(QStringLiteral("view_word_wrap_marker"), a); 0786 a->setWhatsThis( 0787 i18n("Show/hide the Word Wrap Marker, a vertical line drawn at the word " 0788 "wrap column as defined in the editing properties")); 0789 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleWWMarker); 0790 0791 a = toggleAction = m_toggleFoldingMarkers = new KToggleAction(i18n("Show Folding &Markers"), this); 0792 ac->addAction(QStringLiteral("view_folding_markers"), a); 0793 a->setWhatsThis(i18n("You can choose if the codefolding marks should be shown, if codefolding is possible.")); 0794 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleFoldingMarkers); 0795 0796 a = m_toggleIconBar = toggleAction = new KToggleAction(i18n("Show &Icon Border"), this); 0797 ac->addAction(QStringLiteral("view_border"), a); 0798 a->setWhatsThis(i18n("Show/hide the icon border.<br /><br />The icon border shows bookmark symbols, for instance.")); 0799 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleIconBorder); 0800 0801 a = toggleAction = m_toggleLineNumbers = new KToggleAction(i18n("Show &Line Numbers"), this); 0802 ac->addAction(QStringLiteral("view_line_numbers"), a); 0803 a->setWhatsThis(i18n("Show/hide the line numbers on the left hand side of the view.")); 0804 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleLineNumbersOn); 0805 0806 a = m_toggleScrollBarMarks = toggleAction = new KToggleAction(i18n("Show Scroll&bar Marks"), this); 0807 ac->addAction(QStringLiteral("view_scrollbar_marks"), a); 0808 a->setWhatsThis(i18n("Show/hide the marks on the vertical scrollbar.<br /><br />The marks show bookmarks, for instance.")); 0809 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleScrollBarMarks); 0810 0811 a = m_toggleScrollBarMiniMap = toggleAction = new KToggleAction(i18n("Show Scrollbar Mini-Map"), this); 0812 ac->addAction(QStringLiteral("view_scrollbar_minimap"), a); 0813 a->setWhatsThis(i18n("Show/hide the mini-map on the vertical scrollbar.<br /><br />The mini-map shows an overview of the whole document.")); 0814 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleScrollBarMiniMap); 0815 0816 a = m_doc->autoReloadToggleAction(); 0817 ac->addAction(QStringLiteral("view_auto_reload"), a); 0818 0819 // a = m_toggleScrollBarMiniMapAll = toggleAction = new KToggleAction(i18n("Show the whole document in the Mini-Map"), this); 0820 // ac->addAction(QLatin1String("view_scrollbar_minimap_all"), a); 0821 // 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 0822 // mini-map.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMapAll())); connect(m_toggleScrollBarMiniMap, SIGNAL(triggered(bool)), 0823 // m_toggleScrollBarMiniMapAll, SLOT(setEnabled(bool))); 0824 0825 a = m_toggleNPSpaces = new KToggleAction(i18n("Show Non-Printable Spaces"), this); 0826 ac->addAction(QStringLiteral("view_non_printable_spaces"), a); 0827 a->setWhatsThis(i18n("Show/hide bounding box around non-printable spaces")); 0828 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleNPSpaces); 0829 0830 a = m_switchCmdLine = ac->addAction(QStringLiteral("switch_to_cmd_line")); 0831 a->setText(i18n("Switch to Command Line")); 0832 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F7)); 0833 a->setWhatsThis(i18n("Show/hide the command line on the bottom of the view.")); 0834 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::switchToCmdLine); 0835 0836 KActionMenu *am = new KActionMenu(i18n("Input Modes"), this); 0837 m_inputModeActions = new QActionGroup(am); 0838 ac->addAction(QStringLiteral("view_input_modes"), am); 0839 auto switchInputModeAction = ac->action(QStringLiteral("switch_next_input_mode")); 0840 am->addAction(switchInputModeAction); 0841 am->addSeparator(); 0842 for (const auto &mode : m_viewInternal->m_inputModes) { 0843 a = new QAction(mode->viewInputModeHuman(), m_inputModeActions); 0844 am->addAction(a); 0845 a->setWhatsThis(i18n("Activate/deactivate %1", mode->viewInputModeHuman())); 0846 const InputMode im = mode->viewInputMode(); 0847 a->setData(static_cast<int>(im)); 0848 a->setCheckable(true); 0849 if (im == m_config->inputMode()) { 0850 a->setChecked(true); 0851 } 0852 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleInputMode); 0853 } 0854 0855 a = m_setEndOfLine = new KSelectAction(i18n("&End of Line"), this); 0856 ac->addAction(QStringLiteral("set_eol"), a); 0857 a->setWhatsThis(i18n("Choose which line endings should be used, when you save the document")); 0858 const QStringList list{i18nc("@item:inmenu End of Line", "&UNIX"), 0859 i18nc("@item:inmenu End of Line", "&Windows/DOS"), 0860 i18nc("@item:inmenu End of Line", "&Macintosh")}; 0861 m_setEndOfLine->setItems(list); 0862 m_setEndOfLine->setCurrentItem(doc()->config()->eol()); 0863 connect(m_setEndOfLine, &KSelectAction::indexTriggered, this, &KTextEditor::ViewPrivate::setEol); 0864 0865 a = m_addBom = new KToggleAction(i18n("Add &Byte Order Mark (BOM)"), this); 0866 m_addBom->setChecked(doc()->config()->bom()); 0867 ac->addAction(QStringLiteral("add_bom"), a); 0868 a->setWhatsThis(i18n("Enable/disable adding of byte order marks for UTF-8/UTF-16 encoded files while saving")); 0869 connect(m_addBom, &KToggleAction::triggered, this, &KTextEditor::ViewPrivate::setAddBom); 0870 0871 // encoding menu 0872 m_encodingAction = new KateViewEncodingAction(m_doc, this, i18n("E&ncoding"), this); 0873 ac->addAction(QStringLiteral("set_encoding"), m_encodingAction); 0874 0875 a = ac->addAction(KStandardAction::Find, this, SLOT(find())); 0876 a->setWhatsThis(i18n("Look up the first occurrence of a piece of text or regular expression.")); 0877 addAction(a); 0878 0879 a = ac->addAction(QStringLiteral("edit_find_selected")); 0880 a->setText(i18n("Find Selected")); 0881 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_H)); 0882 a->setWhatsThis(i18n("Finds next occurrence of selected text.")); 0883 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findSelectedForwards); 0884 0885 a = ac->addAction(QStringLiteral("edit_find_selected_backwards")); 0886 a->setText(i18n("Find Selected Backwards")); 0887 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_H)); 0888 a->setWhatsThis(i18n("Finds previous occurrence of selected text.")); 0889 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findSelectedBackwards); 0890 0891 a = ac->addAction(QStringLiteral("edit_find_multicursor_next_occurrence")); 0892 a->setText(i18n("Find and Select Next Occurrence")); 0893 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_J)); 0894 a->setWhatsThis(i18n("Finds next occurrence of the word under cursor and add it to selection.")); 0895 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findNextOccurunceAndSelect); 0896 0897 a = ac->addAction(QStringLiteral("edit_skip_multicursor_current_occurrence")); 0898 a->setText(i18n("Mark Currently Selected Occurrence as Skipped")); 0899 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_K)); 0900 a->setWhatsThis(i18n("Marks the currently selected word as skipped.")); 0901 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::skipCurrentOccurunceSelection); 0902 0903 a = ac->addAction(QStringLiteral("edit_find_multicursor_all_occurrences")); 0904 a->setText(i18n("Find and Select All Occurrences")); 0905 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::CTRL | Qt::Key_J)); 0906 a->setWhatsThis(i18n("Finds all occurrences of the word under cursor and selects them.")); 0907 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::findAllOccuruncesAndSelect); 0908 0909 a = ac->addAction(KStandardAction::FindNext, this, SLOT(findNext())); 0910 a->setWhatsThis(i18n("Look up the next occurrence of the search phrase.")); 0911 addAction(a); 0912 0913 a = ac->addAction(KStandardAction::FindPrev, QStringLiteral("edit_find_prev"), this, SLOT(findPrevious())); 0914 a->setWhatsThis(i18n("Look up the previous occurrence of the search phrase.")); 0915 addAction(a); 0916 0917 a = ac->addAction(KStandardAction::Replace, this, SLOT(replace())); 0918 a->setWhatsThis(i18n("Look up a piece of text or regular expression and replace the result with some given text.")); 0919 0920 a = ac->addAction(QStringLiteral("edit_create_multi_cursor_from_sel")); 0921 a->setText(i18n("Add Cursors to Line Ends")); 0922 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_I)); 0923 a->setWhatsThis(i18n("Creates a cursor at the end of every line in selection.")); 0924 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::createMultiCursorsFromSelection); 0925 0926 a = ac->addAction(QStringLiteral("edit_create_multi_cursor_down")); 0927 a->setText(i18n("Add Caret below Cursor")); 0928 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Down)); 0929 a->setWhatsThis(i18n("Adds a caret in the line below the current caret.")); 0930 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::addSecondaryCursorDown); 0931 0932 a = ac->addAction(QStringLiteral("edit_create_multi_cursor_up")); 0933 a->setText(i18n("Add Caret above Cursor")); 0934 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Up)); 0935 a->setWhatsThis(i18n("Adds a caret in the line above the current caret.")); 0936 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::addSecondaryCursorUp); 0937 0938 a = ac->addAction(QStringLiteral("edit_toggle_camel_case_cursor")); 0939 a->setText(i18n("Toggle Camel Case Cursor Movement")); 0940 a->setWhatsThis(i18n("Toggle between normal word movement and camel case cursor movement.")); 0941 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::toggleCamelCaseCursor); 0942 0943 a = ac->addAction(QStringLiteral("edit_remove_cursors_from_empty_lines")); 0944 a->setText(i18n("Remove Cursors from Empty Lines")); 0945 a->setWhatsThis(i18n("Remove cursors from empty lines")); 0946 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::removeCursorsFromEmptyLines); 0947 0948 m_spell->createActions(ac); 0949 m_toggleOnTheFlySpellCheck = new KToggleAction(i18n("Automatic Spell Checking"), this); 0950 m_toggleOnTheFlySpellCheck->setWhatsThis(i18n("Enable/disable automatic spell checking")); 0951 connect(m_toggleOnTheFlySpellCheck, &KToggleAction::triggered, this, &KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck); 0952 ac->addAction(QStringLiteral("tools_toggle_automatic_spell_checking"), m_toggleOnTheFlySpellCheck); 0953 ac->setDefaultShortcut(m_toggleOnTheFlySpellCheck, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_O)); 0954 0955 a = ac->addAction(QStringLiteral("tools_change_dictionary")); 0956 a->setText(i18n("Change Dictionary...")); 0957 a->setWhatsThis(i18n("Change the dictionary that is used for spell checking.")); 0958 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::changeDictionary); 0959 0960 a = ac->addAction(QStringLiteral("tools_clear_dictionary_ranges")); 0961 a->setText(i18n("Clear Dictionary Ranges")); 0962 a->setEnabled(false); 0963 a->setWhatsThis(i18n("Remove all the separate dictionary ranges that were set for spell checking.")); 0964 connect(a, &QAction::triggered, m_doc, &KTextEditor::DocumentPrivate::clearDictionaryRanges); 0965 connect(m_doc, &KTextEditor::DocumentPrivate::dictionaryRangesPresent, a, &QAction::setEnabled); 0966 0967 m_copyHtmlAction = ac->addAction(QStringLiteral("edit_copy_html"), this, SLOT(exportHtmlToClipboard())); 0968 m_copyHtmlAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); 0969 m_copyHtmlAction->setText(i18n("Copy as &HTML")); 0970 m_copyHtmlAction->setWhatsThis(i18n("Use this command to copy the currently selected text as HTML to the system clipboard.")); 0971 0972 a = ac->addAction(QStringLiteral("file_export_html"), this, SLOT(exportHtmlToFile())); 0973 a->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); 0974 a->setText(i18n("E&xport as HTML...")); 0975 a->setWhatsThis( 0976 i18n("This command allows you to export the current document" 0977 " with all highlighting information into a HTML document.")); 0978 0979 m_spellingMenu->createActions(ac); 0980 0981 m_bookmarks->createActions(ac); 0982 0983 slotSelectionChanged(); 0984 0985 // Now setup the editing actions before adding the associated 0986 // widget and setting the shortcut context 0987 setupEditActions(); 0988 setupCodeFolding(); 0989 setupSpeechActions(); 0990 0991 ac->addAssociatedWidget(m_viewInternal); 0992 0993 const auto actions = ac->actions(); 0994 for (QAction *action : actions) { 0995 action->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0996 } 0997 0998 connect(this, &KTextEditor::ViewPrivate::selectionChanged, this, &KTextEditor::ViewPrivate::slotSelectionChanged); 0999 } 1000 1001 void KTextEditor::ViewPrivate::slotConfigDialog() 1002 { 1003 // invoke config dialog, will auto-save configuration to katepartrc 1004 KTextEditor::EditorPrivate::self()->configDialog(this); 1005 } 1006 1007 void 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")); 1016 ac->setDefaultShortcuts(a, KStandardShortcut::backwardWord()); 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")); 1022 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_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")); 1028 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_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")); 1034 ac->setDefaultShortcuts(a, KStandardShortcut::forwardWord()); 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")); 1040 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_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")); 1046 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_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")); 1058 ac->setDefaultShortcuts(a, KStandardShortcut::beginningOfLine()); 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")); 1064 ac->setDefaultShortcuts(a, KStandardShortcut::begin()); 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")); 1070 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Home)); 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")); 1076 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Home)); 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")); 1082 ac->setDefaultShortcuts(a, KStandardShortcut::endOfLine()); 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")); 1088 ac->setDefaultShortcuts(a, KStandardShortcut::end()); 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")); 1094 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_End)); 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")); 1100 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_End)); 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")); 1106 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Up)); 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")); 1112 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_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")); 1118 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Down)); 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")); 1124 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Up)); 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")); 1130 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_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")); 1136 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_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")); 1142 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_Down)); 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")); 1148 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_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")); 1154 ac->setDefaultShortcuts(a, KStandardShortcut::prior()); 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")); 1160 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_PageUp)); 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")); 1166 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_Home)); 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")); 1172 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_Home)); 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")); 1178 ac->setDefaultShortcuts(a, KStandardShortcut::next()); 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")); 1184 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::Key_PageDown)); 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")); 1190 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::Key_End)); 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")); 1196 ac->setDefaultShortcut(a, QKeySequence(Qt::ALT | Qt::SHIFT | Qt::Key_End)); 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")); 1202 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_6)); 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")); 1208 ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_6)); 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")); 1226 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_K)); 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")); 1232 ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordBack()); 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")); 1238 ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordForward()); 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")); 1244 ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Delete)); 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; 1251 scuts << QKeySequence(Qt::Key_Backspace) << QKeySequence(Qt::SHIFT | Qt::Key_Backspace); 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(); 1265 scuts << QKeySequence(Qt::SHIFT | Qt::Key_Return) << QKeySequence(Qt::SHIFT | Qt::Key_Enter); 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(); 1274 scuts << QKeySequence(Qt::CTRL | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::Key_Enter); 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(); 1283 scuts << QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Enter); 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(); 1292 scuts << QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Return) << QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Enter); 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.")); 1303 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_I)); 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.")); 1310 ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_I)); 1311 connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::unIndent); 1312 } 1313 1314 if (hasFocus()) { 1315 slotGotFocus(); 1316 } else { 1317 slotLostFocus(); 1318 } 1319 } 1320 1321 void 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 1343 void 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]() { 1360 KTextEditor::EditorPrivate::self()->speechEngine(this)->stop(); 1361 }); 1362 1363 a = ac->addAction(QStringLiteral("tools_speech_pause")); 1364 a->setText(i18n("Pause current output")); 1365 connect(a, &QAction::triggered, this, [this]() { 1366 KTextEditor::EditorPrivate::self()->speechEngine(this)->pause(); 1367 }); 1368 1369 a = ac->addAction(QStringLiteral("tools_speech_resume")); 1370 a->setText(i18n("Resume current output")); 1371 connect(a, &QAction::triggered, this, [this]() { 1372 KTextEditor::EditorPrivate::self()->speechEngine(this)->resume(); 1373 }); 1374 } 1375 1376 void 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 1385 void KTextEditor::ViewPrivate::slotExpandToplevelNodes() 1386 { 1387 const auto topLevelRanges(textFolding().foldingRangesForParentRange()); 1388 for (const auto &range : topLevelRanges) { 1389 textFolding().unfoldRange(range.first); 1390 } 1391 } 1392 1393 void 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 1405 void KTextEditor::ViewPrivate::slotToggleFoldingsInRange() 1406 { 1407 int line = cursorPosition().line(); 1408 while (!toggleFoldingsInRange(line) && (line > -1)) { 1409 --line; 1410 } 1411 } 1412 1413 KTextEditor::Range KTextEditor::ViewPrivate::foldLine(int line) 1414 { 1415 KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line); 1416 if (!foldingRange.isValid()) { 1417 return foldingRange; 1418 } 1419 1420 // Ensure not to fold the end marker to avoid a deceptive look, but only on token based folding 1421 // ensure we don't compute an invalid line by moving outside of the foldingRange range by checking onSingleLine(), see bug 417890 1422 if (!m_doc->buffer().isFoldingStartingOnLine(line).second && !foldingRange.onSingleLine()) { 1423 const int adjustedLine = foldingRange.end().line() - 1; 1424 foldingRange.setEnd(KTextEditor::Cursor(adjustedLine, doc()->buffer().plainLine(adjustedLine).length())); 1425 } 1426 1427 // Don't try to fold a single line, which can happens due to adjustment above 1428 // FIXME Avoid to offer such a folding marker 1429 if (!foldingRange.onSingleLine()) { 1430 textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); 1431 } 1432 1433 return foldingRange; 1434 } 1435 1436 bool KTextEditor::ViewPrivate::unfoldLine(int line) 1437 { 1438 bool actionDone = false; 1439 const KTextEditor::Cursor currentCursor = cursorPosition(); 1440 1441 // ask the folding info for this line, if any folds are around! 1442 // auto = QList<QPair<qint64, Kate::TextFolding::FoldingRangeFlags>> 1443 auto startingRanges = textFolding().foldingRangesStartingOnLine(line); 1444 for (int i = 0; i < startingRanges.size() && !actionDone; ++i) { 1445 // Avoid jumping view in case of a big unfold and ensure nice highlight of folding marker 1446 setCursorPosition(textFolding().foldingRange(startingRanges[i].first).start()); 1447 1448 actionDone |= textFolding().unfoldRange(startingRanges[i].first); 1449 } 1450 1451 if (!actionDone) { 1452 // Nothing unfolded? Restore old cursor position! 1453 setCursorPosition(currentCursor); 1454 } 1455 1456 return actionDone; 1457 } 1458 1459 bool KTextEditor::ViewPrivate::toggleFoldingOfLine(int line) 1460 { 1461 bool actionDone = unfoldLine(line); 1462 if (!actionDone) { 1463 actionDone = foldLine(line).isValid(); 1464 } 1465 1466 return actionDone; 1467 } 1468 1469 bool KTextEditor::ViewPrivate::toggleFoldingsInRange(int line) 1470 { 1471 KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line); 1472 if (!foldingRange.isValid()) { 1473 // Either line is not valid or there is no start range 1474 return false; 1475 } 1476 1477 bool actionDone = false; // Track success 1478 const KTextEditor::Cursor currentCursor = cursorPosition(); 1479 1480 // Don't be too eager but obliging! Only toggle containing ranges which are 1481 // visible -> Be done when the range is folded 1482 actionDone |= unfoldLine(line); 1483 1484 if (!actionDone) { 1485 // Unfold all in range, but not the range itself 1486 for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) { 1487 actionDone |= unfoldLine(ln); 1488 } 1489 1490 if (actionDone) { 1491 // In most cases we want now a not moved cursor 1492 setCursorPosition(currentCursor); 1493 } 1494 } 1495 1496 if (!actionDone) { 1497 // Fold all in range, but not the range itself 1498 for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) { 1499 KTextEditor::Range fr = foldLine(ln); 1500 if (fr.isValid()) { 1501 // qMax to avoid infinite loop in case of range without content 1502 ln = qMax(ln, fr.end().line() - 1); 1503 actionDone = true; 1504 } 1505 } 1506 } 1507 1508 if (!actionDone) { 1509 // At this point was an unfolded range clicked which contains no "childs" 1510 // We assume the user want to fold it by the wrong button, be obliging! 1511 actionDone |= foldLine(line).isValid(); 1512 } 1513 1514 // At this point we should be always true 1515 return actionDone; 1516 } 1517 1518 KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const 1519 { 1520 return currentInputMode()->viewMode(); 1521 } 1522 1523 QString KTextEditor::ViewPrivate::viewModeHuman() const 1524 { 1525 QString currentMode = currentInputMode()->viewModeHuman(); 1526 1527 // append read-only if needed 1528 if (!doc()->isReadWrite()) { 1529 currentMode = i18n("(R/O) %1", currentMode); 1530 } 1531 1532 // return full mode 1533 return currentMode; 1534 } 1535 1536 KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const 1537 { 1538 return currentInputMode()->viewInputMode(); 1539 } 1540 1541 QString KTextEditor::ViewPrivate::viewInputModeHuman() const 1542 { 1543 return currentInputMode()->viewInputModeHuman(); 1544 } 1545 1546 void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode, const bool rememberInConfig) 1547 { 1548 if (currentInputMode()->viewInputMode() == mode) { 1549 return; 1550 } 1551 1552 // No multi cursors for vi 1553 if (mode == KTextEditor::View::InputMode::ViInputMode) { 1554 clearSecondaryCursors(); 1555 } 1556 1557 m_viewInternal->m_currentInputMode->deactivate(); 1558 m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode].get(); 1559 m_viewInternal->m_currentInputMode->activate(); 1560 1561 // remember in local config if requested, we skip this for the calls in updateConfig 1562 if (rememberInConfig) { 1563 config()->setValue(KateViewConfig::InputMode, mode); 1564 } 1565 1566 /* small duplication, but need to do this if not toggled by action */ 1567 const auto inputModeActions = m_inputModeActions->actions(); 1568 for (QAction *action : inputModeActions) { 1569 if (static_cast<InputMode>(action->data().toInt()) == mode) { 1570 action->setChecked(true); 1571 break; 1572 } 1573 } 1574 1575 /* inform the rest of the system about the change */ 1576 Q_EMIT viewInputModeChanged(this, mode); 1577 Q_EMIT viewModeChanged(this, viewMode()); 1578 } 1579 1580 void KTextEditor::ViewPrivate::slotDocumentAboutToReload() 1581 { 1582 if (doc()->isAutoReload()) { 1583 const int lastVisibleLine = m_viewInternal->endLine(); 1584 const int currentLine = cursorPosition().line(); 1585 m_gotoBottomAfterReload = (lastVisibleLine == currentLine) && (currentLine == doc()->lastLine()); 1586 if (!m_gotoBottomAfterReload) { 1587 // Ensure the view jumps not back when user scrolls around 1588 const int firstVisibleLine = 1 + lastVisibleLine - m_viewInternal->linesDisplayed(); 1589 const int newLine = qBound(firstVisibleLine, currentLine, lastVisibleLine); 1590 setCursorPositionVisual(KTextEditor::Cursor(newLine, cursorPosition().column())); 1591 } 1592 } else { 1593 m_gotoBottomAfterReload = false; 1594 } 1595 } 1596 1597 void KTextEditor::ViewPrivate::slotDocumentReloaded() 1598 { 1599 if (m_gotoBottomAfterReload) { 1600 bottom(); 1601 } 1602 } 1603 1604 void KTextEditor::ViewPrivate::slotGotFocus() 1605 { 1606 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus"; 1607 currentInputMode()->gotFocus(); 1608 1609 // update current view and scrollbars 1610 // it is needed for styles that implement different frame and scrollbar 1611 // rendering when focused 1612 update(); 1613 if (m_viewInternal->m_lineScroll->isVisible()) { 1614 m_viewInternal->m_lineScroll->update(); 1615 } 1616 1617 if (m_viewInternal->m_columnScroll->isVisible()) { 1618 m_viewInternal->m_columnScroll->update(); 1619 } 1620 1621 Q_EMIT focusIn(this); 1622 } 1623 1624 void KTextEditor::ViewPrivate::slotLostFocus() 1625 { 1626 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus"; 1627 currentInputMode()->lostFocus(); 1628 1629 // update current view and scrollbars 1630 // it is needed for styles that implement different frame and scrollbar 1631 // rendering when focused 1632 update(); 1633 if (m_viewInternal->m_lineScroll->isVisible()) { 1634 m_viewInternal->m_lineScroll->update(); 1635 } 1636 1637 if (m_viewInternal->m_columnScroll->isVisible()) { 1638 m_viewInternal->m_columnScroll->update(); 1639 } 1640 1641 if (doc()->config()->autoSave() && doc()->config()->autoSaveOnFocusOut() && doc()->isModified() && doc()->url().isLocalFile()) { 1642 doc()->documentSave(); 1643 } 1644 1645 Q_EMIT focusOut(this); 1646 } 1647 1648 void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode) 1649 { 1650 config()->setValue(KateViewConfig::DynWordWrapIndicators, mode); 1651 } 1652 1653 bool KTextEditor::ViewPrivate::isOverwriteMode() const 1654 { 1655 return doc()->config()->ovr(); 1656 } 1657 1658 void KTextEditor::ViewPrivate::reloadFile() 1659 { 1660 // bookmarks and cursor positions are temporarily saved by the document 1661 doc()->documentReload(); 1662 } 1663 1664 void KTextEditor::ViewPrivate::slotReadWriteChanged() 1665 { 1666 if (m_toggleWriteLock) { 1667 m_toggleWriteLock->setChecked(!doc()->isReadWrite()); 1668 } 1669 1670 m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut())); 1671 m_paste->setEnabled(doc()->isReadWrite()); 1672 if (m_pasteSelection) { 1673 m_pasteSelection->setEnabled(doc()->isReadWrite()); 1674 } 1675 m_swapWithClipboard->setEnabled(doc()->isReadWrite()); 1676 m_setEndOfLine->setEnabled(doc()->isReadWrite()); 1677 1678 static const auto l = {QStringLiteral("edit_replace"), 1679 QStringLiteral("tools_spelling"), 1680 QStringLiteral("tools_indent"), 1681 QStringLiteral("tools_unindent"), 1682 QStringLiteral("tools_cleanIndent"), 1683 QStringLiteral("tools_formatIndet"), 1684 QStringLiteral("tools_alignOn"), 1685 QStringLiteral("tools_comment"), 1686 QStringLiteral("tools_uncomment"), 1687 QStringLiteral("tools_toggle_comment"), 1688 QStringLiteral("tools_uppercase"), 1689 QStringLiteral("tools_lowercase"), 1690 QStringLiteral("tools_capitalize"), 1691 QStringLiteral("tools_join_lines"), 1692 QStringLiteral("tools_apply_wordwrap"), 1693 QStringLiteral("tools_spelling_from_cursor"), 1694 QStringLiteral("tools_spelling_selection")}; 1695 1696 for (const auto &action : l) { 1697 QAction *a = actionCollection()->action(action); 1698 if (a) { 1699 a->setEnabled(doc()->isReadWrite()); 1700 } 1701 } 1702 slotUpdateUndo(); 1703 1704 currentInputMode()->readWriteChanged(doc()->isReadWrite()); 1705 1706 // => view mode changed 1707 Q_EMIT viewModeChanged(this, viewMode()); 1708 Q_EMIT viewInputModeChanged(this, viewInputMode()); 1709 } 1710 1711 void KTextEditor::ViewPrivate::toggleCamelCaseCursor() 1712 { 1713 const auto enabled = doc()->config()->camelCursor(); 1714 doc()->config()->setCamelCursor(!enabled); 1715 KTextEditor::Message *m; 1716 if (enabled) { 1717 m = new KTextEditor::Message(i18n("Camel case movement disabled")); 1718 } else { 1719 m = new KTextEditor::Message(i18n("Camel case movement enabled")); 1720 } 1721 m->setPosition(KTextEditor::Message::TopInView); 1722 m->setAutoHide(1000); 1723 m->setAutoHideMode(KTextEditor::Message::Immediate); 1724 doc()->postMessage(m); 1725 } 1726 1727 void KTextEditor::ViewPrivate::slotUpdateUndo() 1728 { 1729 if (doc()->readOnly()) { 1730 return; 1731 } 1732 1733 m_editUndo->setEnabled(doc()->isReadWrite() && doc()->undoCount() > 0); 1734 m_editRedo->setEnabled(doc()->isReadWrite() && doc()->redoCount() > 0); 1735 } 1736 1737 bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor position, uint tabwidth, bool calledExternally) 1738 { 1739 if (position.line() < 0 || position.line() >= doc()->lines()) { 1740 return false; 1741 } 1742 1743 Kate::TextLine l = doc()->kateTextLine(position.line()); 1744 const QString line_str = l.text(); 1745 1746 int x = 0; 1747 int z = 0; 1748 for (; z < line_str.length() && z < position.column(); z++) { 1749 if (line_str[z] == QLatin1Char('\t')) { 1750 x += tabwidth - (x % tabwidth); 1751 } else { 1752 x++; 1753 } 1754 } 1755 1756 if (blockSelection()) { 1757 if (z < position.column()) { 1758 x += position.column() - z; 1759 } 1760 } 1761 1762 m_viewInternal->updateCursor(KTextEditor::Cursor(position.line(), x), 1763 false, 1764 calledExternally /* force center for external calls, see bug 408418 */, 1765 calledExternally); 1766 1767 return true; 1768 } 1769 1770 void KTextEditor::ViewPrivate::toggleInsert() 1771 { 1772 doc()->config()->setOvr(!doc()->config()->ovr()); 1773 m_toggleInsert->setChecked(isOverwriteMode()); 1774 1775 // No multi cursors for overwrite mode 1776 if (isOverwriteMode()) { 1777 clearSecondaryCursors(); 1778 } 1779 1780 Q_EMIT viewModeChanged(this, viewMode()); 1781 Q_EMIT viewInputModeChanged(this, viewInputMode()); 1782 } 1783 1784 void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error) 1785 { 1786 if (!error.isEmpty()) { // happens when canceling a job 1787 KMessageBox::error(this, error); 1788 } 1789 } 1790 1791 void KTextEditor::ViewPrivate::gotoLine() 1792 { 1793 gotoBar()->updateData(); 1794 bottomViewBar()->showBarWidget(gotoBar()); 1795 } 1796 1797 void KTextEditor::ViewPrivate::changeDictionary() 1798 { 1799 dictionaryBar()->updateData(); 1800 bottomViewBar()->showBarWidget(dictionaryBar()); 1801 } 1802 1803 void KTextEditor::ViewPrivate::joinLines() 1804 { 1805 int first = selectionRange().start().line(); 1806 int last = selectionRange().end().line(); 1807 // int left = doc()->line( last ).length() - doc()->selEndCol(); 1808 if (first == last) { 1809 first = cursorPosition().line(); 1810 last = first + 1; 1811 } 1812 doc()->joinLines(first, last); 1813 } 1814 1815 void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet<QString> &flags) 1816 { 1817 Q_UNUSED(flags) 1818 1819 // cursor position 1820 KTextEditor::Cursor savedPosition(config.readEntry("CursorLine", 0), config.readEntry("CursorColumn", 0)); 1821 setCursorPositionInternal(savedPosition); 1822 1823 // scroll position 1824 const int scroll = config.readEntry("ScrollLine", -1); 1825 if (scroll >= 0 && scroll < doc()->lines() && savedPosition.line() < doc()->lines()) { 1826 setScrollPositionInternal(KTextEditor::Cursor(scroll, 0)); 1827 } 1828 1829 m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false)); 1830 1831 // restore text folding 1832 m_savedFoldingState = QJsonDocument::fromJson(config.readEntry("TextFolding", QByteArray())); 1833 applyFoldingState(); 1834 1835 m_forceRTL = config.readEntry("Force RTL Direction", false); 1836 m_forceRTLDirection->setChecked(m_forceRTL); 1837 1838 for (const auto &mode : m_viewInternal->m_inputModes) { 1839 mode->readSessionConfig(config); 1840 } 1841 } 1842 1843 void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet<QString> &flags) 1844 { 1845 Q_UNUSED(flags) 1846 1847 // cursor position 1848 config.writeEntry("CursorLine", cursorPosition().line()); 1849 config.writeEntry("CursorColumn", cursorPosition().column()); 1850 1851 // scroll position 1852 config.writeEntry("ScrollLine", firstDisplayedLineInternal(LineType::RealLine)); 1853 1854 config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap()); 1855 1856 // save text folding state 1857 saveFoldingState(); 1858 config.writeEntry("TextFolding", m_savedFoldingState.toJson(QJsonDocument::Compact)); 1859 m_savedFoldingState = QJsonDocument(); 1860 1861 config.writeEntry("Force RTL Direction", m_forceRTL); 1862 1863 for (const auto &mode : m_viewInternal->m_inputModes) { 1864 mode->writeSessionConfig(config); 1865 } 1866 } 1867 1868 int KTextEditor::ViewPrivate::getEol() const 1869 { 1870 return doc()->config()->eol(); 1871 } 1872 1873 QMenu *KTextEditor::ViewPrivate::getEolMenu() 1874 { 1875 return m_setEndOfLine->menu(); 1876 } 1877 1878 void KTextEditor::ViewPrivate::setEol(int eol) 1879 { 1880 if (!doc()->isReadWrite()) { 1881 return; 1882 } 1883 1884 if (m_updatingDocumentConfig) { 1885 return; 1886 } 1887 1888 if (eol != doc()->config()->eol()) { 1889 doc()->setModified(true); // mark modified (bug #143120) 1890 doc()->config()->setEol(eol); 1891 } 1892 } 1893 1894 void KTextEditor::ViewPrivate::setAddBom(bool enabled) 1895 { 1896 if (!doc()->isReadWrite()) { 1897 return; 1898 } 1899 1900 if (m_updatingDocumentConfig) { 1901 return; 1902 } 1903 1904 doc()->config()->setBom(enabled); 1905 doc()->bomSetByUser(); 1906 } 1907 1908 void KTextEditor::ViewPrivate::setIconBorder(bool enable) 1909 { 1910 config()->setValue(KateViewConfig::ShowIconBar, enable); 1911 } 1912 1913 void KTextEditor::ViewPrivate::toggleIconBorder() 1914 { 1915 config()->setValue(KateViewConfig::ShowIconBar, !config()->iconBar()); 1916 } 1917 1918 void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable) 1919 { 1920 config()->setValue(KateViewConfig::ShowLineNumbers, enable); 1921 } 1922 1923 void KTextEditor::ViewPrivate::toggleLineNumbersOn() 1924 { 1925 config()->setValue(KateViewConfig::ShowLineNumbers, !config()->lineNumbers()); 1926 } 1927 1928 void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable) 1929 { 1930 config()->setValue(KateViewConfig::ShowScrollBarMarks, enable); 1931 } 1932 1933 void KTextEditor::ViewPrivate::toggleScrollBarMarks() 1934 { 1935 config()->setValue(KateViewConfig::ShowScrollBarMarks, !config()->scrollBarMarks()); 1936 } 1937 1938 void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable) 1939 { 1940 config()->setValue(KateViewConfig::ShowScrollBarMiniMap, enable); 1941 } 1942 1943 void KTextEditor::ViewPrivate::toggleScrollBarMiniMap() 1944 { 1945 config()->setValue(KateViewConfig::ShowScrollBarMiniMap, !config()->scrollBarMiniMap()); 1946 } 1947 1948 void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable) 1949 { 1950 config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, enable); 1951 } 1952 1953 void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll() 1954 { 1955 config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, !config()->scrollBarMiniMapAll()); 1956 } 1957 1958 void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width) 1959 { 1960 config()->setValue(KateViewConfig::ScrollBarMiniMapWidth, width); 1961 } 1962 1963 void KTextEditor::ViewPrivate::toggleDynWordWrap() 1964 { 1965 config()->setDynWordWrap(!config()->dynWordWrap()); 1966 } 1967 1968 void KTextEditor::ViewPrivate::toggleWWMarker() 1969 { 1970 m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker()); 1971 } 1972 1973 void KTextEditor::ViewPrivate::toggleNPSpaces() 1974 { 1975 m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces()); 1976 m_viewInternal->update(); // force redraw 1977 } 1978 1979 void KTextEditor::ViewPrivate::toggleWordCount(bool on) 1980 { 1981 config()->setShowWordCount(on); 1982 } 1983 1984 void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable) 1985 { 1986 config()->setValue(KateViewConfig::ShowFoldingBar, enable); 1987 } 1988 1989 void KTextEditor::ViewPrivate::toggleFoldingMarkers() 1990 { 1991 config()->setValue(KateViewConfig::ShowFoldingBar, !config()->foldingBar()); 1992 } 1993 1994 bool KTextEditor::ViewPrivate::iconBorder() 1995 { 1996 return m_viewInternal->m_leftBorder->iconBorderOn(); 1997 } 1998 1999 bool KTextEditor::ViewPrivate::lineNumbersOn() 2000 { 2001 return m_viewInternal->m_leftBorder->lineNumbersOn(); 2002 } 2003 2004 bool KTextEditor::ViewPrivate::scrollBarMarks() 2005 { 2006 return m_viewInternal->m_lineScroll->showMarks(); 2007 } 2008 2009 bool KTextEditor::ViewPrivate::scrollBarMiniMap() 2010 { 2011 return m_viewInternal->m_lineScroll->showMiniMap(); 2012 } 2013 2014 int KTextEditor::ViewPrivate::dynWrapIndicators() 2015 { 2016 return m_viewInternal->m_leftBorder->dynWrapIndicators(); 2017 } 2018 2019 bool KTextEditor::ViewPrivate::foldingMarkersOn() 2020 { 2021 return m_viewInternal->m_leftBorder->foldingMarkersOn(); 2022 } 2023 2024 bool KTextEditor::ViewPrivate::forceRTLDirection() const 2025 { 2026 return m_forceRTL; 2027 } 2028 2029 void KTextEditor::ViewPrivate::toggleWriteLock() 2030 { 2031 doc()->setReadWrite(!doc()->isReadWrite()); 2032 } 2033 2034 void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider) 2035 { 2036 m_viewInternal->registerTextHintProvider(provider); 2037 } 2038 2039 void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) 2040 { 2041 m_viewInternal->unregisterTextHintProvider(provider); 2042 } 2043 2044 void KTextEditor::ViewPrivate::setTextHintDelay(int delay) 2045 { 2046 m_viewInternal->setTextHintDelay(delay); 2047 } 2048 2049 int KTextEditor::ViewPrivate::textHintDelay() const 2050 { 2051 return m_viewInternal->textHintDelay(); 2052 } 2053 2054 // NOLINTNEXTLINE(readability-make-member-function-const) 2055 void KTextEditor::ViewPrivate::find() 2056 { 2057 currentInputMode()->find(); 2058 } 2059 2060 // NOLINTNEXTLINE(readability-make-member-function-const) 2061 void KTextEditor::ViewPrivate::findSelectedForwards() 2062 { 2063 currentInputMode()->findSelectedForwards(); 2064 } 2065 2066 // NOLINTNEXTLINE(readability-make-member-function-const) 2067 void KTextEditor::ViewPrivate::findSelectedBackwards() 2068 { 2069 currentInputMode()->findSelectedBackwards(); 2070 } 2071 2072 void KTextEditor::ViewPrivate::skipCurrentOccurunceSelection() 2073 { 2074 if (isMulticursorNotAllowed()) { 2075 return; 2076 } 2077 m_skipCurrentSelection = true; 2078 } 2079 2080 void KTextEditor::ViewPrivate::findNextOccurunceAndSelect() 2081 { 2082 if (isMulticursorNotAllowed()) { 2083 return; 2084 } 2085 2086 const auto text = selection() ? doc()->text(selectionRange()) : QString(); 2087 if (text.isEmpty()) { 2088 const auto selection = doc()->wordRangeAt(cursorPosition()); 2089 // We don't want matching word highlights 2090 setSelection(selection); 2091 setCursorPosition(selection.end()); 2092 clearHighlights(); 2093 2094 for (auto &c : m_secondaryCursors) { 2095 const auto range = doc()->wordRangeAt(c.cursor()); 2096 if (!c.range && !c.anchor.isValid()) { 2097 c.anchor = range.start(); 2098 c.range.reset(newSecondarySelectionRange(range)); 2099 c.pos->setPosition(range.end()); 2100 } 2101 tagLines(range); 2102 } 2103 return; 2104 } else if (!m_rangesForHighlights.empty()) { 2105 clearHighlights(); 2106 } 2107 2108 // Use selection range end as starting point 2109 const auto lastSelectionRange = selectionRange(); 2110 2111 KTextEditor::Range searchRange(lastSelectionRange.end(), doc()->documentRange().end()); 2112 QList<KTextEditor::Range> matches = doc()->searchText(searchRange, text, KTextEditor::Default); 2113 if (!matches.isEmpty() && !matches.constFirst().isValid()) { 2114 searchRange.setRange(doc()->documentRange().start(), lastSelectionRange.end()); 2115 matches = doc()->searchText(searchRange, text, KTextEditor::Default); 2116 } 2117 2118 // No match found or only one possible match 2119 if (matches.empty() || !matches.constFirst().isValid() || matches.constFirst() == selectionRange()) { 2120 return; 2121 } 2122 2123 auto it = std::find_if(m_secondaryCursors.begin(), m_secondaryCursors.end(), [&](const SecondaryCursor &c) { 2124 return c.range && c.range->toRange() == matches.constFirst(); 2125 }); 2126 2127 if (it != m_secondaryCursors.end()) { 2128 m_secondaryCursors.erase(it); 2129 } 2130 2131 // Move our primary to cursor to this match and select it 2132 // Ensure we don't create occurence highlights 2133 setSelection(matches.constFirst()); 2134 setCursorPosition(matches.constFirst().end()); 2135 clearHighlights(); 2136 2137 // If we are skipping this selection, then we don't have to do anything 2138 if (!m_skipCurrentSelection) { 2139 PlainSecondaryCursor c; 2140 c.pos = lastSelectionRange.end(); 2141 c.range = lastSelectionRange; 2142 // make our previous primary selection a secondary 2143 addSecondaryCursorsWithSelection({c}); 2144 } 2145 // reset value 2146 m_skipCurrentSelection = false; 2147 } 2148 2149 void KTextEditor::ViewPrivate::findAllOccuruncesAndSelect() 2150 { 2151 if (isMulticursorNotAllowed()) { 2152 return; 2153 } 2154 2155 QString text = selection() ? doc()->text(selectionRange()) : QString(); 2156 if (text.isEmpty()) { 2157 const auto selection = doc()->wordRangeAt(cursorPosition()); 2158 setSelection(selection); 2159 setCursorPosition(selection.end()); 2160 clearHighlights(); 2161 text = doc()->text(selection); 2162 2163 for (auto &c : m_secondaryCursors) { 2164 const auto range = doc()->wordRangeAt(c.cursor()); 2165 if (!c.range && !c.anchor.isValid()) { 2166 c.anchor = range.start(); 2167 c.range.reset(newSecondarySelectionRange(range)); 2168 c.pos->setPosition(range.end()); 2169 } 2170 tagLines(range); 2171 } 2172 } 2173 2174 KTextEditor::Range searchRange(doc()->documentRange()); 2175 QList<KTextEditor::Range> matches; 2176 QList<PlainSecondaryCursor> resultRanges; 2177 do { 2178 matches = doc()->searchText(searchRange, text, KTextEditor::Default); 2179 2180 if (matches.constFirst().isValid()) { 2181 // Dont add if matches primary selection 2182 if (matches.constFirst() != selectionRange()) { 2183 PlainSecondaryCursor c; 2184 c.pos = matches.constFirst().end(); 2185 c.range = matches.constFirst(); 2186 resultRanges.push_back(c); 2187 } 2188 searchRange.setStart(matches.constFirst().end()); 2189 } 2190 } while (matches.first().isValid()); 2191 2192 // ensure to clear occurence highlights 2193 if (!resultRanges.empty()) { 2194 clearHighlights(); 2195 } 2196 2197 clearSecondaryCursors(); 2198 addSecondaryCursorsWithSelection(resultRanges); 2199 } 2200 2201 // NOLINTNEXTLINE(readability-make-member-function-const) 2202 void KTextEditor::ViewPrivate::replace() 2203 { 2204 currentInputMode()->findReplace(); 2205 } 2206 2207 // NOLINTNEXTLINE(readability-make-member-function-const) 2208 void KTextEditor::ViewPrivate::findNext() 2209 { 2210 currentInputMode()->findNext(); 2211 } 2212 2213 // NOLINTNEXTLINE(readability-make-member-function-const) 2214 void KTextEditor::ViewPrivate::findPrevious() 2215 { 2216 currentInputMode()->findPrevious(); 2217 } 2218 2219 void KTextEditor::ViewPrivate::showSearchWrappedHint(bool isReverseSearch) 2220 { 2221 // show message widget when wrapping 2222 const QIcon icon = isReverseSearch ? QIcon::fromTheme(QStringLiteral("go-up-search")) : QIcon::fromTheme(QStringLiteral("go-down-search")); 2223 2224 if (!m_wrappedMessage || m_isLastSearchReversed != isReverseSearch) { 2225 m_isLastSearchReversed = isReverseSearch; 2226 m_wrappedMessage = new KTextEditor::Message(i18n("Search wrapped"), KTextEditor::Message::Information); 2227 m_wrappedMessage->setIcon(icon); 2228 m_wrappedMessage->setPosition(KTextEditor::Message::BottomInView); 2229 m_wrappedMessage->setAutoHide(2000); 2230 m_wrappedMessage->setAutoHideMode(KTextEditor::Message::Immediate); 2231 m_wrappedMessage->setView(this); 2232 this->doc()->postMessage(m_wrappedMessage); 2233 } 2234 } 2235 2236 void KTextEditor::ViewPrivate::createMultiCursorsFromSelection() 2237 { 2238 if (!selection() || selectionRange().isEmpty()) { 2239 return; 2240 } 2241 // Is this really needed? 2242 // Lets just clear them now for simplicity 2243 clearSecondaryCursors(); 2244 2245 const auto range = selectionRange(); 2246 QList<KTextEditor::Cursor> cursorsToAdd; 2247 const auto start = range.start().line() < 0 ? 0 : range.start().line(); 2248 const auto end = range.end().line() > doc()->lines() ? doc()->lines() : range.end().line(); 2249 const auto currentLine = cursorPosition().line(); 2250 setCursorPosition({currentLine, doc()->lineLength(currentLine)}); 2251 for (int line = start; line <= end; ++line) { 2252 if (line != currentLine) { 2253 cursorsToAdd.push_back({line, doc()->lineLength(line)}); 2254 } 2255 } 2256 // clear selection 2257 setSelection({}); 2258 setSecondaryCursors(cursorsToAdd); 2259 } 2260 2261 void KTextEditor::ViewPrivate::removeCursorsFromEmptyLines() 2262 { 2263 if (!m_secondaryCursors.empty()) { 2264 std::vector<KTextEditor::Cursor> cursorsToRemove; 2265 for (const auto &c : m_secondaryCursors) { 2266 auto cursor = c.cursor(); 2267 if (doc()->lineLength(cursor.line()) == 0) { 2268 cursorsToRemove.push_back(cursor); 2269 } 2270 } 2271 removeSecondaryCursors(cursorsToRemove); 2272 } 2273 } 2274 2275 void KTextEditor::ViewPrivate::slotSelectionChanged() 2276 { 2277 m_copy->setEnabled(selection() || m_config->smartCopyCut()); 2278 m_deSelect->setEnabled(selection()); 2279 m_copyHtmlAction->setEnabled(selection()); 2280 2281 // update highlighting of current selected word 2282 selectionChangedForHighlights(); 2283 2284 if (doc()->readOnly()) { 2285 return; 2286 } 2287 2288 m_cut->setEnabled(selection() || m_config->smartCopyCut()); 2289 m_screenshotSelection->setVisible(selection()); 2290 m_screenshotSelection->setEnabled(selection()); 2291 } 2292 2293 // NOLINTNEXTLINE(readability-make-member-function-const) 2294 void KTextEditor::ViewPrivate::switchToCmdLine() 2295 { 2296 currentInputMode()->activateCommandLine(); 2297 } 2298 2299 KateRenderer *KTextEditor::ViewPrivate::renderer() 2300 { 2301 return m_renderer; 2302 } 2303 2304 KateRendererConfig *KTextEditor::ViewPrivate::rendererConfig() 2305 { 2306 return m_renderer->config(); 2307 } 2308 2309 void KTextEditor::ViewPrivate::updateConfig() 2310 { 2311 if (m_startingUp) { 2312 return; 2313 } 2314 2315 // dyn. word wrap & markers 2316 if (m_hasWrap != config()->dynWordWrap()) { 2317 m_hasWrap = config()->dynWordWrap(); 2318 2319 m_viewInternal->dynWrapChanged(); 2320 2321 m_setDynWrapIndicators->setEnabled(config()->dynWordWrap()); 2322 m_toggleDynWrap->setChecked(config()->dynWordWrap()); 2323 } 2324 2325 m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators()); 2326 m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators()); 2327 2328 // line numbers 2329 m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers()); 2330 m_toggleLineNumbers->setChecked(config()->lineNumbers()); 2331 2332 // icon bar 2333 m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar()); 2334 m_toggleIconBar->setChecked(config()->iconBar()); 2335 2336 // scrollbar marks 2337 m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks()); 2338 m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks()); 2339 2340 // scrollbar mini-map 2341 m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap()); 2342 m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap()); 2343 2344 // scrollbar mini-map - (whole document) 2345 m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll()); 2346 // m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() ); 2347 2348 // scrollbar mini-map.width 2349 m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth()); 2350 2351 // misc edit 2352 m_toggleBlockSelection->setChecked(blockSelection()); 2353 m_toggleInsert->setChecked(isOverwriteMode()); 2354 2355 updateFoldingConfig(); 2356 2357 // bookmark 2358 m_bookmarks->setSorting((KateBookmarks::Sorting)config()->bookmarkSort()); 2359 2360 m_viewInternal->setAutoCenterLines(config()->autoCenterLines()); 2361 2362 for (const auto &input : m_viewInternal->m_inputModes) { 2363 input->updateConfig(); 2364 } 2365 2366 setInputMode(config()->inputMode(), false /* don't remember in config for these calls */); 2367 2368 reflectOnTheFlySpellCheckStatus(doc()->isOnTheFlySpellCheckingEnabled()); 2369 2370 // register/unregister word completion... 2371 bool wc = config()->wordCompletion(); 2372 if (wc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->wordCompletionModel())) { 2373 if (wc) { 2374 registerCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); 2375 } else { 2376 unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); 2377 } 2378 } 2379 2380 bool kc = config()->keywordCompletion(); 2381 if (kc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->keywordCompletionModel())) { 2382 if (kc) { 2383 registerCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel()); 2384 } else { 2385 unregisterCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel()); 2386 } 2387 } 2388 2389 m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut())); 2390 m_copy->setEnabled(selection() || m_config->smartCopyCut()); 2391 2392 m_accessibilityEnabled = m_config->value(KateViewConfig::EnableAccessibility).toBool(); 2393 2394 // if not disabled, update status bar 2395 if (m_statusBar) { 2396 m_statusBar->updateStatus(); 2397 } 2398 2399 // now redraw... 2400 m_viewInternal->cache()->clear(); 2401 tagAll(); 2402 updateView(true); 2403 2404 Q_EMIT configChanged(this); 2405 } 2406 2407 void KTextEditor::ViewPrivate::updateDocumentConfig() 2408 { 2409 if (m_startingUp) { 2410 return; 2411 } 2412 2413 m_updatingDocumentConfig = true; 2414 2415 m_setEndOfLine->setCurrentItem(doc()->config()->eol()); 2416 2417 m_addBom->setChecked(doc()->config()->bom()); 2418 2419 m_updatingDocumentConfig = false; 2420 2421 // maybe block selection or wrap-cursor mode changed 2422 ensureCursorColumnValid(); 2423 2424 // first change this 2425 m_renderer->setTabWidth(doc()->config()->tabWidth()); 2426 m_renderer->setIndentWidth(doc()->config()->indentationWidth()); 2427 2428 // now redraw... 2429 m_viewInternal->cache()->clear(); 2430 tagAll(); 2431 updateView(true); 2432 } 2433 2434 void KTextEditor::ViewPrivate::updateRendererConfig() 2435 { 2436 if (m_startingUp) { 2437 return; 2438 } 2439 2440 m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker()); 2441 2442 m_viewInternal->updateBracketMarkAttributes(); 2443 m_viewInternal->updateBracketMarks(); 2444 2445 // now redraw... 2446 m_viewInternal->cache()->clear(); 2447 tagAll(); 2448 m_viewInternal->updateView(true); 2449 2450 // update the left border right, for example linenumbers 2451 m_viewInternal->m_leftBorder->updateFont(); 2452 m_viewInternal->m_leftBorder->repaint(); 2453 2454 m_viewInternal->m_lineScroll->queuePixmapUpdate(); 2455 2456 currentInputMode()->updateRendererConfig(); 2457 2458 // @@ showIndentLines is not cached anymore. 2459 // m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines()); 2460 Q_EMIT configChanged(this); 2461 } 2462 2463 void KTextEditor::ViewPrivate::updateFoldingConfig() 2464 { 2465 // folding bar 2466 m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar()); 2467 m_toggleFoldingMarkers->setChecked(config()->foldingBar()); 2468 2469 if (hasCommentInFirstLine(m_doc)) { 2470 if (config()->foldFirstLine() && !m_autoFoldedFirstLine) { 2471 foldLine(0); 2472 m_autoFoldedFirstLine = true; 2473 } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) { 2474 unfoldLine(0); 2475 m_autoFoldedFirstLine = false; 2476 } 2477 } else { 2478 m_autoFoldedFirstLine = false; 2479 } 2480 2481 #if 0 2482 // FIXME: FOLDING 2483 const QStringList l = { 2484 QStringLiteral("folding_toplevel") 2485 , QStringLiteral("folding_expandtoplevel") 2486 , QStringLiteral("folding_toggle_current") 2487 , QStringLiteral("folding_toggle_in_current") 2488 }; 2489 2490 QAction *a = 0; 2491 for (int z = 0; z < l.size(); z++) 2492 if ((a = actionCollection()->action(l[z].toAscii().constData()))) { 2493 a->setEnabled(doc()->highlight() && doc()->highlight()->allowsFolding()); 2494 } 2495 #endif 2496 } 2497 2498 void KTextEditor::ViewPrivate::ensureCursorColumnValid() 2499 { 2500 KTextEditor::Cursor c = m_viewInternal->cursorPosition(); 2501 2502 // make sure the cursor is valid: 2503 // - in block selection mode or if wrap cursor is off, the column is arbitrary 2504 // - otherwise: it's bounded by the line length 2505 if (!blockSelection() && wrapCursor() && (!c.isValid() || c.column() > doc()->lineLength(c.line()))) { 2506 c.setColumn(doc()->lineLength(cursorPosition().line())); 2507 setCursorPosition(c); 2508 } 2509 } 2510 2511 // BEGIN EDIT STUFF 2512 void KTextEditor::ViewPrivate::editStart() 2513 { 2514 m_viewInternal->editStart(); 2515 } 2516 2517 void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) 2518 { 2519 m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom); 2520 textFolding().editEnd(editTagLineStart, editTagLineEnd, [this](int line) { 2521 return m_doc->buffer().isFoldingStartingOnLine(line).first; 2522 }); 2523 } 2524 2525 void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor cursor) 2526 { 2527 m_viewInternal->editSetCursor(cursor); 2528 } 2529 // END 2530 2531 // BEGIN TAG & CLEAR 2532 bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor virtualCursor) 2533 { 2534 return m_viewInternal->tagLine(virtualCursor); 2535 } 2536 2537 bool KTextEditor::ViewPrivate::tagRange(KTextEditor::Range range, bool realLines) 2538 { 2539 return m_viewInternal->tagRange(range, realLines); 2540 } 2541 2542 bool KTextEditor::ViewPrivate::tagLines(KTextEditor::LineRange lineRange, bool realLines) 2543 { 2544 return m_viewInternal->tagLines(lineRange.start(), lineRange.end(), realLines); 2545 } 2546 2547 bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) 2548 { 2549 return m_viewInternal->tagLines(start, end, realCursors); 2550 } 2551 2552 void KTextEditor::ViewPrivate::tagAll() 2553 { 2554 m_viewInternal->tagAll(); 2555 } 2556 2557 void KTextEditor::ViewPrivate::clear() 2558 { 2559 m_viewInternal->clear(); 2560 } 2561 2562 void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty) 2563 { 2564 if (paintOnlyDirty) { 2565 m_viewInternal->updateDirty(); 2566 } else { 2567 m_viewInternal->update(); 2568 } 2569 } 2570 2571 void KTextEditor::ViewPrivate::updateView(bool changed) 2572 { 2573 // qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView"; 2574 m_viewInternal->updateView(changed); 2575 m_viewInternal->m_leftBorder->update(); 2576 } 2577 2578 // END 2579 2580 void KTextEditor::ViewPrivate::slotHlChanged() 2581 { 2582 KateHighlighting *hl = doc()->highlight(); 2583 bool ok(!hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty()); 2584 2585 if (actionCollection()->action(QStringLiteral("tools_comment"))) { 2586 actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok); 2587 } 2588 2589 if (actionCollection()->action(QStringLiteral("tools_uncomment"))) { 2590 actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok); 2591 } 2592 2593 if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) { 2594 actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok); 2595 } 2596 2597 // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry 2598 updateFoldingConfig(); 2599 } 2600 2601 int KTextEditor::ViewPrivate::virtualCursorColumn() const 2602 { 2603 return doc()->toVirtualColumn(m_viewInternal->cursorPosition()); 2604 } 2605 2606 void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor newPosition) 2607 { 2608 Q_EMIT mousePositionChanged(this, newPosition); 2609 } 2610 2611 // BEGIN KTextEditor::SelectionInterface stuff 2612 2613 bool KTextEditor::ViewPrivate::setSelection(KTextEditor::Range selection) 2614 { 2615 // anything to do? 2616 if (selection == m_selection) { 2617 return true; 2618 } 2619 2620 // backup old range 2621 KTextEditor::Range oldSelection = m_selection; 2622 2623 // set new range 2624 m_selection.setRange(selection.isEmpty() ? KTextEditor::Range::invalid() : selection); 2625 2626 // trigger update of correct area 2627 tagSelection(oldSelection); 2628 repaintText(true); 2629 2630 // emit holy signal 2631 Q_EMIT selectionChanged(this); 2632 2633 // be done 2634 return true; 2635 } 2636 2637 bool KTextEditor::ViewPrivate::clearSelection() 2638 { 2639 return clearSelection(true); 2640 } 2641 2642 bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection) 2643 { 2644 // no selection, nothing to do... 2645 if (!selection()) { 2646 return false; 2647 } 2648 2649 // backup old range 2650 KTextEditor::Range oldSelection = m_selection; 2651 2652 // invalidate current selection 2653 m_selection.setRange(KTextEditor::Range::invalid()); 2654 2655 // trigger update of correct area 2656 tagSelection(oldSelection); 2657 if (redraw) { 2658 repaintText(true); 2659 } 2660 2661 // emit holy signal 2662 if (finishedChangingSelection) { 2663 Q_EMIT selectionChanged(this); 2664 } 2665 2666 m_viewInternal->m_selChangedByUser = false; 2667 // be done 2668 return true; 2669 } 2670 2671 bool KTextEditor::ViewPrivate::selection() const 2672 { 2673 if (!wrapCursor()) { 2674 return m_selection != KTextEditor::Range::invalid(); 2675 } else { 2676 return m_selection.toRange().isValid(); 2677 } 2678 } 2679 2680 QString KTextEditor::ViewPrivate::selectionText() const 2681 { 2682 if (blockSelect) { 2683 return doc()->text(m_selection, blockSelect); 2684 } 2685 2686 QVarLengthArray<KTextEditor::Range, 16> ranges; 2687 for (const auto &c : m_secondaryCursors) { 2688 if (c.range) { 2689 ranges.push_back(c.range->toRange()); 2690 } 2691 } 2692 ranges.push_back(m_selection.toRange()); 2693 std::sort(ranges.begin(), ranges.end()); 2694 2695 QString text; 2696 text.reserve(ranges.size() * m_selection.toRange().columnWidth()); 2697 for (int i = 0; i < ranges.size() - 1; ++i) { 2698 text += doc()->text(ranges[i]) + QStringLiteral("\n"); 2699 } 2700 text += doc()->text(ranges.last()); 2701 2702 return text; 2703 } 2704 2705 bool KTextEditor::ViewPrivate::removeSelectedText() 2706 { 2707 if (!hasSelections()) { 2708 return false; 2709 } 2710 2711 KTextEditor::Document::EditingTransaction t(doc()); 2712 2713 bool removed = false; 2714 // Handle multicursors selection removal 2715 if (!blockSelect) { 2716 completionWidget()->setIgnoreBufferSignals(true); 2717 for (auto &c : m_secondaryCursors) { 2718 if (c.range) { 2719 removed = true; 2720 doc()->removeText(c.range->toRange()); 2721 c.clearSelection(); 2722 } 2723 } 2724 completionWidget()->setIgnoreBufferSignals(false); 2725 } 2726 2727 // Optimization: clear selection before removing text 2728 KTextEditor::Range selection = m_selection; 2729 if (!selection.isValid()) { 2730 return removed; 2731 } 2732 doc()->removeText(selection, blockSelect); 2733 removed = true; 2734 2735 // don't redraw the cleared selection - that's done in editEnd(). 2736 if (blockSelect) { 2737 int selectionColumn = qMin(doc()->toVirtualColumn(selection.start()), doc()->toVirtualColumn(selection.end())); 2738 KTextEditor::Range newSelection = selection; 2739 newSelection.setStart(KTextEditor::Cursor(newSelection.start().line(), doc()->fromVirtualColumn(newSelection.start().line(), selectionColumn))); 2740 newSelection.setEnd(KTextEditor::Cursor(newSelection.end().line(), doc()->fromVirtualColumn(newSelection.end().line(), selectionColumn))); 2741 setSelection(newSelection); 2742 setCursorPositionInternal(newSelection.start()); 2743 } else { 2744 clearSecondarySelections(); 2745 clearSelection(false); 2746 } 2747 2748 return removed; 2749 } 2750 2751 bool KTextEditor::ViewPrivate::selectAll() 2752 { 2753 clearSecondaryCursors(); 2754 setBlockSelection(false); 2755 // We use setSelection here to ensure we don't scroll anywhere 2756 // The cursor stays in place i.e., it doesn't move to end of selection 2757 // that is okay and expected. 2758 // The idea here is to maintain scroll position in case select all was 2759 // mistakenly triggered, and also to if you just want to copy text, 2760 // there is no need to scroll anywhere. 2761 setSelection(doc()->documentRange()); 2762 m_viewInternal->moveCursorToSelectionEdge(/*scroll=*/false); 2763 m_viewInternal->updateMicroFocus(); 2764 return true; 2765 } 2766 2767 bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor cursor) 2768 { 2769 KTextEditor::Cursor ret = cursor; 2770 if ((!blockSelect) && (ret.column() < 0)) { 2771 ret.setColumn(0); 2772 } 2773 2774 if (blockSelect) { 2775 return cursor.line() >= m_selection.start().line() && ret.line() <= m_selection.end().line() && ret.column() >= m_selection.start().column() 2776 && ret.column() <= m_selection.end().column(); 2777 } else { 2778 return m_selection.toRange().contains(cursor) || m_selection.end() == cursor; 2779 } 2780 } 2781 2782 bool KTextEditor::ViewPrivate::lineSelected(int line) 2783 { 2784 return !blockSelect && m_selection.toRange().containsLine(line); 2785 } 2786 2787 bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor lineEndPos) 2788 { 2789 return (!blockSelect) 2790 && (lineEndPos.line() > m_selection.start().line() 2791 || (lineEndPos.line() == m_selection.start().line() && (m_selection.start().column() < lineEndPos.column() || lineEndPos.column() == -1))) 2792 && (lineEndPos.line() < m_selection.end().line() 2793 || (lineEndPos.line() == m_selection.end().line() && (lineEndPos.column() <= m_selection.end().column() && lineEndPos.column() != -1))); 2794 } 2795 2796 bool KTextEditor::ViewPrivate::lineHasSelected(int line) 2797 { 2798 return selection() && m_selection.toRange().containsLine(line); 2799 } 2800 2801 bool KTextEditor::ViewPrivate::lineIsSelection(int line) 2802 { 2803 return (line == m_selection.start().line() && line == m_selection.end().line()); 2804 } 2805 2806 void KTextEditor::ViewPrivate::tagSelection(KTextEditor::Range oldSelection) 2807 { 2808 if (selection()) { 2809 if (oldSelection.start().line() == -1) { 2810 // We have to tag the whole lot if 2811 // 1) we have a selection, and: 2812 // a) it's new; or 2813 tagLines(m_selection, true); 2814 2815 } else if (blockSelection() 2816 && (oldSelection.start().column() != m_selection.start().column() || oldSelection.end().column() != m_selection.end().column())) { 2817 // b) we're in block selection mode and the columns have changed 2818 tagLines(m_selection, true); 2819 tagLines(oldSelection, true); 2820 2821 } else { 2822 if (oldSelection.start() != m_selection.start()) { 2823 tagLines(KTextEditor::LineRange(oldSelection.start().line(), m_selection.start().line()), true); 2824 } 2825 2826 if (oldSelection.end() != m_selection.end()) { 2827 tagLines(KTextEditor::LineRange(oldSelection.end().line(), m_selection.end().line()), true); 2828 } 2829 } 2830 2831 } else { 2832 // No more selection, clean up 2833 tagLines(oldSelection, true); 2834 } 2835 } 2836 2837 void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor cursor) 2838 { 2839 setSelection(doc()->wordRangeAt(cursor)); 2840 } 2841 2842 void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor cursor) 2843 { 2844 int line = cursor.line(); 2845 if (line + 1 >= doc()->lines()) { 2846 setSelection(KTextEditor::Range(line, 0, line, doc()->lineLength(line))); 2847 } else { 2848 setSelection(KTextEditor::Range(line, 0, line + 1, 0)); 2849 } 2850 } 2851 2852 void KTextEditor::ViewPrivate::cut() 2853 { 2854 if (!selection() && !m_config->smartCopyCut()) { 2855 return; 2856 } 2857 2858 // If markedSelection is true, copy() invalidates the selection, 2859 // which would obviate the removeSelectedText() here below. 2860 m_markedSelection = false; 2861 2862 copy(); 2863 if (!selection()) { 2864 selectLine(cursorPosition()); 2865 } 2866 removeSelectedText(); 2867 } 2868 2869 void KTextEditor::ViewPrivate::copy() 2870 { 2871 QString text; 2872 KTextEditor::EditorPrivate::self()->copyToMulticursorClipboard({}); 2873 2874 if (!selection()) { 2875 if (!m_config->smartCopyCut()) { 2876 return; 2877 } 2878 text = doc()->line(cursorPosition().line()) + QLatin1Char('\n'); 2879 m_viewInternal->moveEdge(KateViewInternal::left, false); 2880 } else { 2881 text = selectionText(); 2882 2883 // Multicursor copy 2884 if (!m_secondaryCursors.empty()) { 2885 QVarLengthArray<KTextEditor::Range, 16> ranges; 2886 for (const auto &c : m_secondaryCursors) { 2887 if (c.range) { 2888 ranges.push_back(c.range->toRange()); 2889 } 2890 } 2891 ranges.push_back(m_selection.toRange()); 2892 std::sort(ranges.begin(), ranges.end()); 2893 QStringList texts; 2894 for (auto range : ranges) { 2895 texts.append(doc()->text(range)); 2896 } 2897 KTextEditor::EditorPrivate::self()->copyToMulticursorClipboard(texts); 2898 } 2899 2900 if (m_markedSelection) { 2901 setSelection(KTextEditor::Range::invalid()); 2902 m_markedSelection = false; 2903 } 2904 } 2905 2906 // copy to clipboard and our history! 2907 KTextEditor::EditorPrivate::self()->copyToClipboard(text, m_doc->url().fileName()); 2908 } 2909 2910 void KTextEditor::ViewPrivate::screenshot() 2911 { 2912 if (!selection()) { 2913 return; 2914 } 2915 2916 ScreenshotDialog d(selectionRange(), this); 2917 d.renderScreenshot(m_renderer); 2918 d.exec(); 2919 } 2920 2921 void KTextEditor::ViewPrivate::pasteSelection() 2922 { 2923 m_temporaryAutomaticInvocationDisabled = true; 2924 doc()->paste(this, QApplication::clipboard()->text(QClipboard::Selection)); 2925 m_temporaryAutomaticInvocationDisabled = false; 2926 } 2927 2928 void KTextEditor::ViewPrivate::swapWithClipboard() 2929 { 2930 m_temporaryAutomaticInvocationDisabled = true; 2931 2932 // get text to paste 2933 const auto text = QApplication::clipboard()->text(QClipboard::Clipboard); 2934 2935 // do copy 2936 copy(); 2937 2938 // do paste of "previous" clipboard content we saved 2939 doc()->paste(this, text); 2940 2941 m_temporaryAutomaticInvocationDisabled = false; 2942 } 2943 2944 void KTextEditor::ViewPrivate::applyWordWrap() 2945 { 2946 int first = selectionRange().start().line(); 2947 int last = selectionRange().end().line(); 2948 2949 if (first == last) { 2950 // Either no selection or only one line selected, wrap only the current line 2951 first = cursorPosition().line(); 2952 last = first; 2953 } 2954 2955 doc()->wrapParagraph(first, last); 2956 } 2957 2958 // END 2959 2960 // BEGIN KTextEditor::BlockSelectionInterface stuff 2961 2962 bool KTextEditor::ViewPrivate::blockSelection() const 2963 { 2964 return blockSelect; 2965 } 2966 2967 bool KTextEditor::ViewPrivate::setBlockSelection(bool on) 2968 { 2969 if (on != blockSelect) { 2970 blockSelect = on; 2971 2972 KTextEditor::Range oldSelection = m_selection; 2973 2974 const bool hadSelection = clearSelection(false, false); 2975 2976 setSelection(oldSelection); 2977 2978 m_toggleBlockSelection->setChecked(blockSelection()); 2979 2980 // when leaving block selection mode, if cursor is at an invalid position or past the end of the 2981 // line, move the cursor to the last column of the current line unless cursor wrapping is off 2982 ensureCursorColumnValid(); 2983 2984 if (!hadSelection) { 2985 // emit selectionChanged() according to the KTextEditor::View api 2986 // documentation also if there is no selection around. This is needed, 2987 // as e.g. the Kate App status bar uses this signal to update the state 2988 // of the selection mode (block selection, line based selection) 2989 Q_EMIT selectionChanged(this); 2990 } 2991 } 2992 2993 return true; 2994 } 2995 2996 bool KTextEditor::ViewPrivate::toggleBlockSelection() 2997 { 2998 // no multicursors for blockselect 2999 clearSecondaryCursors(); 3000 3001 m_toggleBlockSelection->setChecked(!blockSelect); 3002 return setBlockSelection(!blockSelect); 3003 } 3004 3005 bool KTextEditor::ViewPrivate::wrapCursor() const 3006 { 3007 return !blockSelection(); 3008 } 3009 3010 // END 3011 3012 void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor position, const QString &text) 3013 { 3014 Q_EMIT textInserted(view, position, text); 3015 } 3016 3017 bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor c, const QString &templateString, const QString &script) 3018 { 3019 // no empty templates 3020 if (templateString.isEmpty()) { 3021 return false; 3022 } 3023 3024 // not for read-only docs 3025 if (!doc()->isReadWrite()) { 3026 return false; 3027 } 3028 3029 // only one handler maybe active at a time; store it in the document. 3030 // Clear it first to make sure at no time two handlers are active at once 3031 doc()->setActiveTemplateHandler(nullptr); 3032 doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, doc()->undoManager())); 3033 return true; 3034 } 3035 3036 bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange) 3037 { 3038 return tagLines(range.start(), range.end(), realRange); 3039 } 3040 3041 void KTextEditor::ViewPrivate::deactivateEditActions() 3042 { 3043 for (QAction *action : std::as_const(m_editActions)) { 3044 action->setEnabled(false); 3045 } 3046 } 3047 3048 void KTextEditor::ViewPrivate::activateEditActions() 3049 { 3050 for (QAction *action : std::as_const(m_editActions)) { 3051 action->setEnabled(true); 3052 } 3053 } 3054 3055 bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const 3056 { 3057 // FIXME support 3058 return true; 3059 } 3060 3061 bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool) 3062 { 3063 // FIXME support 3064 return true; 3065 } 3066 3067 bool KTextEditor::ViewPrivate::isMulticursorNotAllowed() const 3068 { 3069 return blockSelection() || isOverwriteMode() || currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode; 3070 } 3071 3072 void KTextEditor::ViewPrivate::addSecondaryCursor(KTextEditor::Cursor pos) 3073 { 3074 auto primaryCursor = cursorPosition(); 3075 const bool overlapsOrOnPrimary = pos == primaryCursor || (selection() && selectionRange().contains(pos)); 3076 if (overlapsOrOnPrimary && m_secondaryCursors.empty()) { 3077 // Clicking on primary cursor while it is the only cursor, 3078 // we do nothing 3079 return; 3080 } else if (overlapsOrOnPrimary) { 3081 // Clicking on primary cursor, we have secondaries 3082 // so just make the last secondary cursor primary 3083 // and remove caret at current primary cursor position 3084 auto &last = m_secondaryCursors.back(); 3085 setCursorPosition(last.cursor()); 3086 if (last.range) { 3087 setSelection(last.range->toRange()); 3088 Q_ASSERT(last.anchor.isValid()); 3089 m_viewInternal->m_selectAnchor = last.anchor; 3090 } 3091 m_secondaryCursors.pop_back(); 3092 return; 3093 } 3094 3095 // If there are any existing cursors at this position 3096 // remove them and be done i.e., if you click on an 3097 // existing cursor it is removed. 3098 if (removeSecondaryCursors({pos}, /*removeIfSelectionOverlap=*/true)) { 3099 return; 3100 } 3101 3102 // We are adding a new cursor! 3103 // - Move primary cursor to the position where the click happened 3104 // - Old primary cursor becomes a secondary cursor 3105 // Doing it like this makes multi mouse selections very easy 3106 setCursorPosition(pos); 3107 KTextEditor::ViewPrivate::PlainSecondaryCursor p; 3108 p.pos = primaryCursor; 3109 p.range = selection() ? selectionRange() : KTextEditor::Range::invalid(); 3110 clearSelection(); 3111 addSecondaryCursorsWithSelection({p}); 3112 } 3113 3114 void KTextEditor::ViewPrivate::setSecondaryCursors(const QList<KTextEditor::Cursor> &positions) 3115 { 3116 clearSecondaryCursors(); 3117 3118 if (positions.isEmpty() || isMulticursorNotAllowed()) { 3119 return; 3120 } 3121 3122 const auto totalLines = doc()->lines(); 3123 for (auto p : positions) { 3124 if (p != cursorPosition() && p.line() < totalLines) { 3125 SecondaryCursor c; 3126 c.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(p))); 3127 m_secondaryCursors.push_back(std::move(c)); 3128 tagLine(p); 3129 } 3130 } 3131 sortCursors(); 3132 paintCursors(); 3133 } 3134 3135 void KTextEditor::ViewPrivate::clearSecondarySelections() 3136 { 3137 for (auto &c : m_secondaryCursors) { 3138 c.range.reset(); 3139 c.anchor = KTextEditor::Cursor::invalid(); 3140 } 3141 } 3142 3143 void KTextEditor::ViewPrivate::clearSecondaryCursors() 3144 { 3145 if (m_secondaryCursors.empty()) { 3146 return; 3147 } 3148 for (const auto &c : m_secondaryCursors) { 3149 tagLine(c.cursor()); 3150 } 3151 m_secondaryCursors.clear(); 3152 m_viewInternal->updateDirty(); 3153 } 3154 3155 const std::vector<KTextEditor::ViewPrivate::SecondaryCursor> &KTextEditor::ViewPrivate::secondaryCursors() const 3156 { 3157 return m_secondaryCursors; 3158 } 3159 3160 QList<KTextEditor::ViewPrivate::PlainSecondaryCursor> KTextEditor::ViewPrivate::plainSecondaryCursors() const 3161 { 3162 QList<PlainSecondaryCursor> cursors; 3163 cursors.reserve(m_secondaryCursors.size()); 3164 std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(cursors), [](const SecondaryCursor &c) { 3165 if (c.range) { 3166 return PlainSecondaryCursor{c.cursor(), c.range->toRange()}; 3167 } 3168 return PlainSecondaryCursor{c.cursor(), KTextEditor::Range::invalid()}; 3169 }); 3170 return cursors; 3171 } 3172 3173 bool KTextEditor::ViewPrivate::removeSecondaryCursors(const std::vector<KTextEditor::Cursor> &cursorsToRemove, bool removeIfOverlapsSelection) 3174 { 3175 Q_ASSERT(std::is_sorted(cursorsToRemove.begin(), cursorsToRemove.end())); 3176 3177 QVarLengthArray<KTextEditor::Cursor, 8> linesToTag; 3178 3179 if (removeIfOverlapsSelection) { 3180 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(), 3181 m_secondaryCursors.end(), 3182 [&](const SecondaryCursor &c) { 3183 auto it = std::find_if(cursorsToRemove.begin(), cursorsToRemove.end(), [&c](KTextEditor::Cursor pos) { 3184 return c.cursor() == pos || (c.range && c.range->contains(pos)); 3185 }); 3186 const bool match = it != cursorsToRemove.end(); 3187 if (match) { 3188 linesToTag.push_back(c.cursor()); 3189 } 3190 return match; 3191 }), 3192 m_secondaryCursors.end()); 3193 } else { 3194 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(), 3195 m_secondaryCursors.end(), 3196 [&](const SecondaryCursor &c) { 3197 auto it = std::find_if(cursorsToRemove.begin(), cursorsToRemove.end(), [&c](KTextEditor::Cursor pos) { 3198 return c.cursor() == pos; 3199 }); 3200 const bool match = it != cursorsToRemove.end(); 3201 if (match) { 3202 linesToTag.push_back(c.cursor()); 3203 } 3204 return match; 3205 }), 3206 m_secondaryCursors.end()); 3207 } 3208 3209 for (const auto &c : linesToTag) { 3210 tagLine(m_viewInternal->toVirtualCursor(c)); 3211 } 3212 return !linesToTag.empty(); 3213 3214 for (auto cur : cursorsToRemove) { 3215 auto &sec = m_secondaryCursors; 3216 auto it = std::find_if(sec.begin(), sec.end(), [cur](const SecondaryCursor &c) { 3217 return c.cursor() == cur; 3218 }); 3219 if (it != sec.end()) { 3220 // removedAny = true; 3221 m_secondaryCursors.erase(it); 3222 tagLine(m_viewInternal->toVirtualCursor(cur)); 3223 } 3224 } 3225 3226 // if (removedAny) { 3227 m_viewInternal->updateDirty(); 3228 if (cursorPosition() == KTextEditor::Cursor(0, 0)) { 3229 m_viewInternal->paintCursor(); 3230 } 3231 return !linesToTag.empty(); 3232 // } 3233 // return removedAny; 3234 } 3235 3236 void KTextEditor::ViewPrivate::ensureUniqueCursors(bool matchLine) 3237 { 3238 if (m_secondaryCursors.empty()) { 3239 return; 3240 } 3241 3242 std::vector<SecondaryCursor>::iterator it; 3243 if (matchLine) { 3244 auto matchLine = [](const SecondaryCursor &l, const SecondaryCursor &r) { 3245 return l.cursor().line() == r.cursor().line(); 3246 }; 3247 it = std::unique(m_secondaryCursors.begin(), m_secondaryCursors.end(), matchLine); 3248 } else { 3249 it = std::unique(m_secondaryCursors.begin(), m_secondaryCursors.end()); 3250 } 3251 if (it != m_secondaryCursors.end()) { 3252 m_secondaryCursors.erase(it, m_secondaryCursors.end()); 3253 } 3254 3255 if (matchLine) { 3256 const int ln = cursorPosition().line(); 3257 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(), 3258 m_secondaryCursors.end(), 3259 [ln](const SecondaryCursor &c) { 3260 return c.cursor().line() == ln; 3261 }), 3262 m_secondaryCursors.end()); 3263 } else { 3264 const auto cp = cursorPosition(); 3265 m_secondaryCursors.erase(std::remove_if(m_secondaryCursors.begin(), 3266 m_secondaryCursors.end(), 3267 [cp](const SecondaryCursor &c) { 3268 return c.cursor() == cp; 3269 }), 3270 m_secondaryCursors.end()); 3271 } 3272 } 3273 3274 void KTextEditor::ViewPrivate::addSecondaryCursorsWithSelection(const QList<PlainSecondaryCursor> &cursorsWithSelection) 3275 { 3276 if (isMulticursorNotAllowed() || cursorsWithSelection.isEmpty()) { 3277 return; 3278 } 3279 3280 for (const auto &c : cursorsWithSelection) { 3281 // We don't want to add on top of primary cursor 3282 if (c.pos == cursorPosition()) { 3283 continue; 3284 } 3285 SecondaryCursor n; 3286 n.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(c.pos))); 3287 if (c.range.isValid()) { 3288 n.range.reset(newSecondarySelectionRange(c.range)); 3289 n.anchor = c.range.start() == c.pos ? c.range.end() : c.range.start(); 3290 } 3291 m_secondaryCursors.push_back(std::move(n)); 3292 } 3293 sortCursors(); 3294 paintCursors(); 3295 } 3296 3297 Kate::TextRange *KTextEditor::ViewPrivate::newSecondarySelectionRange(KTextEditor::Range selRange) 3298 { 3299 constexpr auto expandBehaviour = KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight; 3300 auto range = new Kate::TextRange(doc()->buffer(), selRange, expandBehaviour); 3301 static KTextEditor::Attribute::Ptr selAttr; 3302 if (!selAttr) { 3303 selAttr = new KTextEditor::Attribute; 3304 auto color = QColor::fromRgba(theme().editorColor(KSyntaxHighlighting::Theme::TextSelection)); 3305 selAttr->setBackground(color); 3306 } 3307 range->setZDepth(-999999.); 3308 range->setAttribute(selAttr); 3309 return range; 3310 } 3311 3312 bool KTextEditor::ViewPrivate::hasSelections() const 3313 { 3314 if (selection()) 3315 return true; 3316 return std::any_of(m_secondaryCursors.cbegin(), m_secondaryCursors.cend(), [](const SecondaryCursor &c) { 3317 return c.range && !c.range->isEmpty(); 3318 }); 3319 } 3320 3321 void KTextEditor::ViewPrivate::addSecondaryCursorDown() 3322 { 3323 KTextEditor::Cursor last = cursorPosition(); 3324 const auto &secondary = secondaryCursors(); 3325 if (!secondary.empty()) { 3326 last = secondary.back().cursor(); 3327 last = std::max(cursorPosition(), last); 3328 } 3329 if (last.line() >= doc()->lastLine()) { 3330 return; 3331 } 3332 3333 auto nextRange = m_viewInternal->nextLayout(last); 3334 if (!nextRange.isValid()) { 3335 return; 3336 } 3337 auto primaryCursorLineLayout = m_viewInternal->currentLayout(cursorPosition()); 3338 if (!primaryCursorLineLayout.isValid()) { 3339 return; 3340 } 3341 3342 int x = renderer()->cursorToX(primaryCursorLineLayout, cursorPosition().column(), !wrapCursor()); 3343 auto next = renderer()->xToCursor(nextRange, x, !wrapCursor()); 3344 addSecondaryCursor(next); 3345 } 3346 3347 void KTextEditor::ViewPrivate::addSecondaryCursorUp() 3348 { 3349 KTextEditor::Cursor last = cursorPosition(); 3350 const auto &secondary = secondaryCursors(); 3351 if (!secondary.empty()) { 3352 last = secondary.front().cursor(); 3353 last = std::min(cursorPosition(), last); 3354 } 3355 if (last.line() == 0) { 3356 return; 3357 } 3358 auto nextRange = m_viewInternal->previousLayout(last); 3359 if (!nextRange.isValid()) { 3360 return; 3361 } 3362 3363 auto primaryCursorLineLayout = m_viewInternal->currentLayout(cursorPosition()); 3364 if (!primaryCursorLineLayout.isValid()) { 3365 return; 3366 } 3367 3368 int x = renderer()->cursorToX(primaryCursorLineLayout, cursorPosition().column(), !wrapCursor()); 3369 auto next = renderer()->xToCursor(nextRange, x, !wrapCursor()); 3370 addSecondaryCursor(next); 3371 } 3372 3373 QList<KTextEditor::Cursor> KTextEditor::ViewPrivate::cursors() const 3374 { 3375 QList<KTextEditor::Cursor> ret; 3376 ret.reserve(m_secondaryCursors.size() + 1); 3377 ret << cursorPosition(); 3378 std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(ret), [](const SecondaryCursor &c) { 3379 return c.cursor(); 3380 }); 3381 return ret; 3382 } 3383 3384 QList<KTextEditor::Range> KTextEditor::ViewPrivate::selectionRanges() const 3385 { 3386 if (!selection()) { 3387 return {}; 3388 } 3389 3390 QList<KTextEditor::Range> ret; 3391 ret.reserve(m_secondaryCursors.size() + 1); 3392 ret << selectionRange(); 3393 std::transform(m_secondaryCursors.begin(), m_secondaryCursors.end(), std::back_inserter(ret), [](const SecondaryCursor &c) { 3394 if (!c.range) { 3395 qWarning() << "selectionRanges(): Unexpected null selection range, please fix"; 3396 return KTextEditor::Range::invalid(); 3397 } 3398 return c.range->toRange(); 3399 }); 3400 return ret; 3401 } 3402 3403 void KTextEditor::ViewPrivate::setCursors(const QList<KTextEditor::Cursor> &cursorPositions) 3404 { 3405 if (isMulticursorNotAllowed()) { 3406 qWarning() << "setCursors failed: Multicursors not allowed because one of the following is true" 3407 << ", blockSelection: " << blockSelection() << ", overwriteMode: " << isOverwriteMode() 3408 << ", viMode: " << (currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode); 3409 return; 3410 } 3411 3412 clearSecondaryCursors(); 3413 if (cursorPositions.empty()) { 3414 return; 3415 } 3416 3417 const auto primary = cursorPositions.front(); 3418 // We clear primary selection because primary and secondary 3419 // cursors should always have same selection state 3420 setSelection({}); 3421 setCursorPosition(primary); 3422 // First will be auto ignored because it equals cursorPosition() 3423 setSecondaryCursors(cursorPositions); 3424 } 3425 3426 void KTextEditor::ViewPrivate::setSelections(const QList<KTextEditor::Range> &selectionRanges) 3427 { 3428 if (isMulticursorNotAllowed()) { 3429 qWarning() << "setSelections failed: Multicursors not allowed because one of the following is true" 3430 << ", blockSelection: " << blockSelection() << ", overwriteMode: " << isOverwriteMode() 3431 << ", viMode: " << (currentInputMode()->viewInputMode() == KTextEditor::View::InputMode::ViInputMode); 3432 return; 3433 } 3434 3435 clearSecondaryCursors(); 3436 setSelection({}); 3437 if (selectionRanges.isEmpty()) { 3438 return; 3439 } 3440 3441 auto first = selectionRanges.front(); 3442 setCursorPosition(first.end()); 3443 setSelection(first); 3444 3445 if (selectionRanges.size() == 1) { 3446 return; 3447 } 3448 3449 const auto docRange = doc()->documentRange(); 3450 for (auto it = selectionRanges.begin() + 1; it != selectionRanges.end(); ++it) { 3451 KTextEditor::Range r = *it; 3452 KTextEditor::Cursor c = r.end(); 3453 if (c == cursorPosition() || !r.isValid() || r.isEmpty() || !docRange.contains(r)) { 3454 continue; 3455 } 3456 3457 SecondaryCursor n; 3458 n.pos.reset(static_cast<Kate::TextCursor *>(doc()->newMovingCursor(c))); 3459 n.range.reset(newSecondarySelectionRange(r)); 3460 n.anchor = r.start(); 3461 m_secondaryCursors.push_back(std::move(n)); 3462 } 3463 m_viewInternal->mergeSelections(); 3464 3465 sortCursors(); 3466 paintCursors(); 3467 } 3468 3469 void KTextEditor::ViewPrivate::sortCursors() 3470 { 3471 std::sort(m_secondaryCursors.begin(), m_secondaryCursors.end()); 3472 ensureUniqueCursors(); 3473 } 3474 3475 void KTextEditor::ViewPrivate::paintCursors() 3476 { 3477 if (m_viewInternal->m_cursorTimer.isActive()) { 3478 if (QApplication::cursorFlashTime() > 0) { 3479 m_viewInternal->m_cursorTimer.start(QApplication::cursorFlashTime() / 2); 3480 } 3481 renderer()->setDrawCaret(true); 3482 } 3483 m_viewInternal->paintCursor(); 3484 } 3485 3486 bool KTextEditor::ViewPrivate::isCompletionActive() const 3487 { 3488 return completionWidget()->isCompletionActive(); 3489 } 3490 3491 KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const 3492 { 3493 if (!m_completionWidget) { 3494 m_completionWidget = new KateCompletionWidget(const_cast<KTextEditor::ViewPrivate *>(this)); 3495 } 3496 3497 return m_completionWidget; 3498 } 3499 3500 void KTextEditor::ViewPrivate::startCompletion(KTextEditor::Range word, KTextEditor::CodeCompletionModel *model) 3501 { 3502 completionWidget()->startCompletion(word, model); 3503 } 3504 3505 void KTextEditor::ViewPrivate::startCompletion(const Range &word, 3506 const QList<KTextEditor::CodeCompletionModel *> &models, 3507 KTextEditor::CodeCompletionModel::InvocationType invocationType) 3508 { 3509 completionWidget()->startCompletion(word, models, invocationType); 3510 } 3511 3512 void KTextEditor::ViewPrivate::abortCompletion() 3513 { 3514 completionWidget()->abortCompletion(); 3515 } 3516 3517 void KTextEditor::ViewPrivate::forceCompletion() 3518 { 3519 completionWidget()->execute(); 3520 } 3521 3522 void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model) 3523 { 3524 completionWidget()->registerCompletionModel(model); 3525 } 3526 3527 void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) 3528 { 3529 completionWidget()->unregisterCompletionModel(model); 3530 } 3531 3532 bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const 3533 { 3534 return completionWidget()->isCompletionModelRegistered(model); 3535 } 3536 3537 QList<KTextEditor::CodeCompletionModel *> KTextEditor::ViewPrivate::codeCompletionModels() const 3538 { 3539 return completionWidget()->codeCompletionModels(); 3540 } 3541 3542 bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const 3543 { 3544 return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation(); 3545 } 3546 3547 void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled) 3548 { 3549 config()->setValue(KateViewConfig::AutomaticCompletionInvocation, enabled); 3550 } 3551 3552 void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index) 3553 { 3554 Q_EMIT completionExecuted(this, position, model, index); 3555 } 3556 3557 void KTextEditor::ViewPrivate::sendCompletionAborted() 3558 { 3559 Q_EMIT completionAborted(this); 3560 } 3561 3562 void KTextEditor::ViewPrivate::paste(const QString *textToPaste) 3563 { 3564 const int cursorCount = m_secondaryCursors.size() + 1; // 1 primary cursor 3565 const auto multicursorClipboard = KTextEditor::EditorPrivate::self()->multicursorClipboard(); 3566 if (cursorCount == multicursorClipboard.size() && !textToPaste) { 3567 if (doc()->multiPaste(this, multicursorClipboard)) { 3568 return; 3569 } 3570 } else if (!textToPaste && cursorCount > 1) { 3571 // We still have multiple cursors, but the amount 3572 // of multicursors doesn't match the entry count in clipboard 3573 QStringList texts; 3574 texts.reserve(cursorCount); 3575 QString clipboard = QApplication::clipboard()->text(QClipboard::Clipboard); 3576 for (int i = 0; i < cursorCount; ++i) { 3577 texts << clipboard; 3578 } 3579 // It might still fail for e.g., if we are in block mode, 3580 // in that case we will fallback to normal pasting below 3581 if (doc()->multiPaste(this, texts)) { 3582 return; 3583 } 3584 } 3585 3586 m_temporaryAutomaticInvocationDisabled = true; 3587 doc()->paste(this, textToPaste ? *textToPaste : QApplication::clipboard()->text(QClipboard::Clipboard)); 3588 m_temporaryAutomaticInvocationDisabled = false; 3589 } 3590 3591 bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position) 3592 { 3593 return setCursorPositionInternal(position, 1, true); 3594 } 3595 3596 KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const 3597 { 3598 return m_viewInternal->cursorPosition(); 3599 } 3600 3601 KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const 3602 { 3603 return KTextEditor::Cursor(m_viewInternal->cursorPosition().line(), virtualCursorColumn()); 3604 } 3605 3606 QPoint KTextEditor::ViewPrivate::cursorToCoordinate(KTextEditor::Cursor cursor) const 3607 { 3608 // map from ViewInternal to View coordinates 3609 const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, true, false); 3610 return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); 3611 } 3612 3613 KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const 3614 { 3615 // map from View to ViewInternal coordinates 3616 return m_viewInternal->coordinatesToCursor(m_viewInternal->mapFromParent(coords), false); 3617 } 3618 3619 QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const 3620 { 3621 // map from ViewInternal to View coordinates 3622 const QPoint pt = m_viewInternal->cursorCoordinates(false); 3623 return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); 3624 } 3625 3626 void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor cursor) 3627 { 3628 m_viewInternal->scrollPos(cursor, false, true, false); 3629 } 3630 3631 void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x) 3632 { 3633 m_viewInternal->scrollColumns(x); 3634 } 3635 3636 KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const 3637 { 3638 return m_viewInternal->maxStartPos(true); 3639 } 3640 3641 int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const 3642 { 3643 if (lineType == RealLine) { 3644 return m_textFolding.visibleLineToLine(m_viewInternal->startLine()); 3645 } else { 3646 return m_viewInternal->startLine(); 3647 } 3648 } 3649 3650 int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const 3651 { 3652 if (lineType == RealLine) { 3653 return m_textFolding.visibleLineToLine(m_viewInternal->endLine()); 3654 } else { 3655 return m_viewInternal->endLine(); 3656 } 3657 } 3658 3659 QRect KTextEditor::ViewPrivate::textAreaRectInternal() const 3660 { 3661 const auto sourceRect = m_viewInternal->rect(); 3662 const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft()); 3663 const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight()); 3664 return {topLeft, bottomRight}; 3665 } 3666 3667 bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor position) 3668 { 3669 return setCursorPositionInternal(position, doc()->config()->tabWidth(), true); 3670 } 3671 3672 QScrollBar *KTextEditor::ViewPrivate::verticalScrollBar() const 3673 { 3674 return m_viewInternal->m_lineScroll; 3675 } 3676 3677 QScrollBar *KTextEditor::ViewPrivate::horizontalScrollBar() const 3678 { 3679 return m_viewInternal->m_columnScroll; 3680 } 3681 3682 bool KTextEditor::ViewPrivate::isLineRTL(int line) const 3683 { 3684 const QString s = doc()->line(line); 3685 if (s.isEmpty()) { 3686 int line = cursorPosition().line(); 3687 if (line == 0) { 3688 const int count = doc()->lines(); 3689 for (int i = 1; i < count; ++i) { 3690 const QString ln = doc()->line(i); 3691 if (ln.isEmpty()) { 3692 continue; 3693 } 3694 return ln.isRightToLeft(); 3695 } 3696 } else { 3697 int line = cursorPosition().line(); 3698 for (; line >= 0; --line) { 3699 const QString s = doc()->line(line); 3700 if (s.isEmpty()) { 3701 continue; 3702 } 3703 return s.isRightToLeft(); 3704 } 3705 } 3706 return false; 3707 } else { 3708 return s.isRightToLeft(); 3709 } 3710 } 3711 3712 QTextLayout *KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor pos) const 3713 { 3714 KateLineLayout *thisLine = m_viewInternal->cache()->line(pos.line()); 3715 return thisLine && thisLine->isValid() ? thisLine->layout() : nullptr; 3716 } 3717 3718 void KTextEditor::ViewPrivate::indent() 3719 { 3720 KTextEditor::Cursor c(cursorPosition().line(), 0); 3721 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); 3722 doc()->indent(r, 1); 3723 } 3724 3725 void KTextEditor::ViewPrivate::unIndent() 3726 { 3727 KTextEditor::Cursor c(cursorPosition().line(), 0); 3728 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); 3729 doc()->indent(r, -1); 3730 } 3731 3732 void KTextEditor::ViewPrivate::cleanIndent() 3733 { 3734 KTextEditor::Cursor c(cursorPosition().line(), 0); 3735 KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); 3736 doc()->indent(r, 0); 3737 } 3738 3739 void KTextEditor::ViewPrivate::formatIndent() 3740 { 3741 // no selection: align current line; selection: use selection range 3742 const int line = cursorPosition().line(); 3743 KTextEditor::Range formatRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0)); 3744 if (selection()) { 3745 formatRange = selectionRange(); 3746 } 3747 3748 doc()->align(this, formatRange); 3749 } 3750 3751 // alias of formatIndent, for backward compatibility 3752 void KTextEditor::ViewPrivate::align() 3753 { 3754 formatIndent(); 3755 } 3756 3757 void KTextEditor::ViewPrivate::alignOn() 3758 { 3759 static QString pattern; 3760 KTextEditor::Range range; 3761 if (!selection()) { 3762 range = doc()->documentRange(); 3763 } else { 3764 range = selectionRange(); 3765 } 3766 bool ok; 3767 pattern = QInputDialog::getText(window(), i18n("Align On"), i18n("Alignment pattern:"), QLineEdit::Normal, pattern, &ok); 3768 if (!ok) { 3769 return; 3770 } 3771 doc()->alignOn(range, pattern, this->blockSelection()); 3772 } 3773 3774 void KTextEditor::ViewPrivate::comment() 3775 { 3776 m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); 3777 doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::Comment); 3778 m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); 3779 } 3780 3781 void KTextEditor::ViewPrivate::uncomment() 3782 { 3783 doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::UnComment); 3784 } 3785 3786 void KTextEditor::ViewPrivate::toggleComment() 3787 { 3788 m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); 3789 doc()->comment(this, cursorPosition().line(), cursorPosition().column(), DocumentPrivate::ToggleComment); 3790 m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); 3791 } 3792 3793 void KTextEditor::ViewPrivate::uppercase() 3794 { 3795 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Uppercase); 3796 } 3797 3798 void KTextEditor::ViewPrivate::killLine() 3799 { 3800 std::vector<int> linesToRemove; 3801 if (m_selection.isEmpty()) { 3802 // collect lines of all cursors 3803 linesToRemove.reserve(m_secondaryCursors.size() + 1); 3804 for (const auto &c : m_secondaryCursors) { 3805 linesToRemove.push_back(c.pos->line()); 3806 } 3807 // add primary cursor line 3808 linesToRemove.push_back(cursorPosition().line()); 3809 } else { 3810 linesToRemove.reserve(m_secondaryCursors.size() + 1); 3811 for (const auto &c : m_secondaryCursors) { 3812 const auto &range = c.range; 3813 if (!range) { 3814 continue; 3815 } 3816 for (int line = range->end().line(); line >= range->start().line(); line--) { 3817 linesToRemove.push_back(line); 3818 } 3819 } 3820 3821 // cache endline, else that moves and we might delete complete document if last line is selected! 3822 for (int line = m_selection.end().line(), endLine = m_selection.start().line(); line >= endLine; line--) { 3823 linesToRemove.push_back(line); 3824 } 3825 } 3826 3827 std::sort(linesToRemove.begin(), linesToRemove.end(), std::greater{}); 3828 linesToRemove.erase(std::unique(linesToRemove.begin(), linesToRemove.end()), linesToRemove.end()); 3829 3830 doc()->editStart(); 3831 std::for_each(linesToRemove.begin(), linesToRemove.end(), [this](int line) { 3832 doc()->removeLine(line); 3833 }); 3834 doc()->editEnd(); 3835 3836 ensureUniqueCursors(); 3837 } 3838 3839 void KTextEditor::ViewPrivate::lowercase() 3840 { 3841 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase); 3842 } 3843 3844 void KTextEditor::ViewPrivate::capitalize() 3845 { 3846 doc()->editStart(); 3847 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase); 3848 doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Capitalize); 3849 doc()->editEnd(); 3850 } 3851 3852 void KTextEditor::ViewPrivate::keyReturn() 3853 { 3854 doc()->newLine(this); 3855 m_viewInternal->iconBorder()->updateForCursorLineChange(); 3856 m_viewInternal->updateView(); 3857 } 3858 3859 void KTextEditor::ViewPrivate::smartNewline() 3860 { 3861 const KTextEditor::Cursor cursor = cursorPosition(); 3862 const int ln = cursor.line(); 3863 Kate::TextLine line = doc()->kateTextLine(ln); 3864 int col = qMin(cursor.column(), line.firstChar()); 3865 if (col != -1) { 3866 while (line.length() > col && !(line.at(col).isLetterOrNumber() || line.at(col) == QLatin1Char('_')) && col < cursor.column()) { 3867 ++col; 3868 } 3869 } else { 3870 col = line.length(); // stay indented 3871 } 3872 doc()->editStart(); 3873 doc()->editWrapLine(ln, cursor.column()); 3874 doc()->insertText(KTextEditor::Cursor(ln + 1, 0), line.string(0, col)); 3875 doc()->editEnd(); 3876 3877 m_viewInternal->updateView(); 3878 } 3879 3880 void KTextEditor::ViewPrivate::noIndentNewline() 3881 { 3882 doc()->newLine(this, KTextEditor::DocumentPrivate::NoIndent); 3883 m_viewInternal->iconBorder()->updateForCursorLineChange(); 3884 m_viewInternal->updateView(); 3885 } 3886 3887 void KTextEditor::ViewPrivate::newLineAbove() 3888 { 3889 doc()->newLine(this, KTextEditor::DocumentPrivate::Indent, KTextEditor::DocumentPrivate::Above); 3890 m_viewInternal->iconBorder()->updateForCursorLineChange(); 3891 m_viewInternal->updateView(); 3892 } 3893 3894 void KTextEditor::ViewPrivate::newLineBelow() 3895 { 3896 doc()->newLine(this, KTextEditor::DocumentPrivate::Indent, KTextEditor::DocumentPrivate::Below); 3897 m_viewInternal->iconBorder()->updateForCursorLineChange(); 3898 m_viewInternal->updateView(); 3899 } 3900 3901 void KTextEditor::ViewPrivate::backspace() 3902 { 3903 // Will take care of both multi and primary cursors 3904 doc()->backspace(this); 3905 } 3906 3907 void KTextEditor::ViewPrivate::insertTab() 3908 { 3909 doc()->insertTab(this, cursorPosition()); 3910 } 3911 3912 void KTextEditor::ViewPrivate::deleteWordLeft() 3913 { 3914 doc()->editStart(); 3915 m_viewInternal->wordPrev(true); 3916 KTextEditor::Range selection = selectionRange(); 3917 removeSelectedText(); 3918 doc()->editEnd(); 3919 3920 ensureUniqueCursors(); 3921 3922 m_viewInternal->tagRange(selection, true); 3923 m_viewInternal->updateDirty(); 3924 } 3925 3926 void KTextEditor::ViewPrivate::keyDelete() 3927 { 3928 KTextEditor::Document::EditingTransaction t(doc()); 3929 3930 if (blockSelect) { 3931 KTextEditor::Range selection = m_selection; 3932 if (selection.isValid() && selection.start().column() == selection.end().column()) { 3933 KTextEditor::Cursor end = {selection.end().line(), selection.end().column() + 1}; 3934 selection = {selection.start(), end}; 3935 doc()->removeText(selection, blockSelect); 3936 return; 3937 } 3938 } 3939 3940 // multi cursor 3941 3942 if (removeSelectedText()) { 3943 return; 3944 } 3945 3946 for (const auto &c : m_secondaryCursors) { 3947 if (c.range) { 3948 doc()->removeText(c.range->toRange()); 3949 } else { 3950 doc()->del(this, c.cursor()); 3951 } 3952 } 3953 3954 // primary cursor 3955 doc()->del(this, cursorPosition()); 3956 3957 ensureUniqueCursors(); 3958 } 3959 3960 void KTextEditor::ViewPrivate::deleteWordRight() 3961 { 3962 doc()->editStart(); 3963 m_viewInternal->wordNext(true); 3964 KTextEditor::Range selection = selectionRange(); 3965 removeSelectedText(); 3966 doc()->editEnd(); 3967 3968 ensureUniqueCursors(); 3969 3970 m_viewInternal->tagRange(selection, true); 3971 m_viewInternal->updateDirty(); 3972 } 3973 3974 void KTextEditor::ViewPrivate::transpose() 3975 { 3976 doc()->editStart(); 3977 for (const auto &c : m_secondaryCursors) { 3978 doc()->transpose(c.cursor()); 3979 } 3980 doc()->transpose(cursorPosition()); 3981 doc()->editEnd(); 3982 } 3983 3984 void KTextEditor::ViewPrivate::transposeWord() 3985 { 3986 const KTextEditor::Cursor originalCurPos = cursorPosition(); 3987 const KTextEditor::Range firstWord = doc()->wordRangeAt(originalCurPos); 3988 if (!firstWord.isValid()) { 3989 return; 3990 } 3991 3992 auto wordIsInvalid = [](QStringView word) { 3993 for (const QChar &character : word) { 3994 if (character.isLetterOrNumber()) { 3995 return false; 3996 } 3997 } 3998 return true; 3999 }; 4000 4001 if (wordIsInvalid(doc()->text(firstWord))) { 4002 return; 4003 } 4004 4005 setCursorPosition(firstWord.end()); 4006 wordRight(); 4007 KTextEditor::Cursor curPos = cursorPosition(); 4008 // swap with the word to the right if it exists, otherwise try to swap with word to the left 4009 if (curPos.line() != firstWord.end().line() || curPos.column() == firstWord.end().column()) { 4010 setCursorPosition(firstWord.start()); 4011 wordLeft(); 4012 curPos = cursorPosition(); 4013 // if there is still no next word in this line, no swapping will be done 4014 if (curPos.line() != firstWord.start().line() || curPos.column() == firstWord.start().column() || wordIsInvalid(doc()->wordAt(curPos))) { 4015 setCursorPosition(originalCurPos); 4016 return; 4017 } 4018 } 4019 4020 if (wordIsInvalid(doc()->wordAt(curPos))) { 4021 setCursorPosition(originalCurPos); 4022 return; 4023 } 4024 4025 const KTextEditor::Range secondWord = doc()->wordRangeAt(curPos); 4026 doc()->swapTextRanges(firstWord, secondWord); 4027 4028 // return cursor to its original position inside the word before swap 4029 // after the swap, the cursor will be at the end of the word, so we compute the position relative to the end of the word 4030 const int offsetFromWordEnd = firstWord.end().column() - originalCurPos.column(); 4031 setCursorPosition(cursorPosition() - KTextEditor::Cursor(0, offsetFromWordEnd)); 4032 } 4033 4034 void KTextEditor::ViewPrivate::cursorLeft() 4035 { 4036 if (selection() && !config()->persistentSelection() && !m_markedSelection) { 4037 if (isLineRTL(cursorPosition().line())) { 4038 m_viewInternal->updateCursor(selectionRange().end()); 4039 setSelection(KTextEditor::Range::invalid()); 4040 } else { 4041 m_viewInternal->updateCursor(selectionRange().start()); 4042 setSelection(KTextEditor::Range::invalid()); 4043 } 4044 4045 for (const auto &c : m_secondaryCursors) { 4046 if (!c.range) { 4047 continue; 4048 } 4049 const bool rtl = isLineRTL(c.cursor().line()); 4050 c.pos->setPosition(rtl ? c.range->end() : c.range->start()); 4051 } 4052 clearSecondarySelections(); 4053 } else { 4054 if (isLineRTL(cursorPosition().line())) { 4055 m_viewInternal->cursorNextChar(m_markedSelection); 4056 } else { 4057 m_viewInternal->cursorPrevChar(m_markedSelection); 4058 } 4059 } 4060 } 4061 4062 void KTextEditor::ViewPrivate::shiftCursorLeft() 4063 { 4064 if (isLineRTL(cursorPosition().line())) { 4065 m_viewInternal->cursorNextChar(true); 4066 } else { 4067 m_viewInternal->cursorPrevChar(true); 4068 } 4069 } 4070 4071 void KTextEditor::ViewPrivate::cursorRight() 4072 { 4073 if (selection() && !config()->persistentSelection() && !m_markedSelection) { 4074 if (isLineRTL(cursorPosition().line())) { 4075 m_viewInternal->updateCursor(selectionRange().start()); 4076 setSelection(KTextEditor::Range::invalid()); 4077 } else { 4078 m_viewInternal->updateCursor(selectionRange().end()); 4079 setSelection(KTextEditor::Range::invalid()); 4080 } 4081 4082 for (const auto &c : m_secondaryCursors) { 4083 if (!c.range) { 4084 continue; 4085 } 4086 const bool rtl = doc()->line(c.cursor().line()).isRightToLeft(); 4087 c.pos->setPosition(rtl ? c.range->start() : c.range->end()); 4088 } 4089 clearSecondarySelections(); 4090 } else { 4091 if (isLineRTL(cursorPosition().line())) { 4092 m_viewInternal->cursorPrevChar(m_markedSelection); 4093 } else { 4094 m_viewInternal->cursorNextChar(m_markedSelection); 4095 } 4096 } 4097 } 4098 4099 void KTextEditor::ViewPrivate::shiftCursorRight() 4100 { 4101 if (isLineRTL(cursorPosition().line())) { 4102 m_viewInternal->cursorPrevChar(true); 4103 } else { 4104 m_viewInternal->cursorNextChar(true); 4105 } 4106 } 4107 4108 void KTextEditor::ViewPrivate::wordLeft() 4109 { 4110 if (isLineRTL(cursorPosition().line())) { 4111 m_viewInternal->wordNext(m_markedSelection); 4112 } else { 4113 m_viewInternal->wordPrev(m_markedSelection); 4114 } 4115 } 4116 4117 void KTextEditor::ViewPrivate::shiftWordLeft() 4118 { 4119 if (isLineRTL(cursorPosition().line())) { 4120 m_viewInternal->wordNext(true); 4121 } else { 4122 m_viewInternal->wordPrev(true); 4123 } 4124 } 4125 4126 void KTextEditor::ViewPrivate::wordRight() 4127 { 4128 if (isLineRTL(cursorPosition().line())) { 4129 m_viewInternal->wordPrev(m_markedSelection); 4130 } else { 4131 m_viewInternal->wordNext(m_markedSelection); 4132 } 4133 } 4134 4135 void KTextEditor::ViewPrivate::shiftWordRight() 4136 { 4137 if (isLineRTL(cursorPosition().line())) { 4138 m_viewInternal->wordPrev(true); 4139 } else { 4140 m_viewInternal->wordNext(true); 4141 } 4142 } 4143 4144 void KTextEditor::ViewPrivate::markSelection() 4145 { 4146 if (m_markedSelection && selection()) { 4147 setSelection(KTextEditor::Range::invalid()); 4148 clearSecondarySelections(); 4149 } else { 4150 m_markedSelection = !m_markedSelection; 4151 } 4152 } 4153 4154 void KTextEditor::ViewPrivate::home() 4155 { 4156 m_viewInternal->home(m_markedSelection); 4157 } 4158 4159 void KTextEditor::ViewPrivate::shiftHome() 4160 { 4161 m_viewInternal->home(true); 4162 } 4163 4164 void KTextEditor::ViewPrivate::end() 4165 { 4166 m_viewInternal->end(m_markedSelection); 4167 } 4168 4169 void KTextEditor::ViewPrivate::shiftEnd() 4170 { 4171 m_viewInternal->end(true); 4172 } 4173 4174 void KTextEditor::ViewPrivate::up() 4175 { 4176 m_viewInternal->cursorUp(m_markedSelection); 4177 } 4178 4179 void KTextEditor::ViewPrivate::shiftUp() 4180 { 4181 m_viewInternal->cursorUp(true); 4182 } 4183 4184 void KTextEditor::ViewPrivate::down() 4185 { 4186 m_viewInternal->cursorDown(m_markedSelection); 4187 } 4188 4189 void KTextEditor::ViewPrivate::shiftDown() 4190 { 4191 m_viewInternal->cursorDown(true); 4192 } 4193 4194 void KTextEditor::ViewPrivate::scrollUp() 4195 { 4196 m_viewInternal->scrollUp(); 4197 } 4198 4199 void KTextEditor::ViewPrivate::scrollDown() 4200 { 4201 m_viewInternal->scrollDown(); 4202 } 4203 4204 void KTextEditor::ViewPrivate::topOfView() 4205 { 4206 m_viewInternal->topOfView(); 4207 } 4208 4209 void KTextEditor::ViewPrivate::shiftTopOfView() 4210 { 4211 m_viewInternal->topOfView(true); 4212 } 4213 4214 void KTextEditor::ViewPrivate::bottomOfView() 4215 { 4216 m_viewInternal->bottomOfView(); 4217 } 4218 4219 void KTextEditor::ViewPrivate::shiftBottomOfView() 4220 { 4221 m_viewInternal->bottomOfView(true); 4222 } 4223 4224 void KTextEditor::ViewPrivate::pageUp() 4225 { 4226 m_viewInternal->pageUp(m_markedSelection); 4227 } 4228 4229 void KTextEditor::ViewPrivate::shiftPageUp() 4230 { 4231 m_viewInternal->pageUp(true); 4232 } 4233 4234 void KTextEditor::ViewPrivate::pageDown() 4235 { 4236 m_viewInternal->pageDown(m_markedSelection); 4237 } 4238 4239 void KTextEditor::ViewPrivate::shiftPageDown() 4240 { 4241 m_viewInternal->pageDown(true); 4242 } 4243 4244 void KTextEditor::ViewPrivate::top() 4245 { 4246 m_viewInternal->top_home(m_markedSelection); 4247 } 4248 4249 void KTextEditor::ViewPrivate::shiftTop() 4250 { 4251 m_viewInternal->top_home(true); 4252 } 4253 4254 void KTextEditor::ViewPrivate::bottom() 4255 { 4256 m_viewInternal->bottom_end(m_markedSelection); 4257 } 4258 4259 void KTextEditor::ViewPrivate::shiftBottom() 4260 { 4261 m_viewInternal->bottom_end(true); 4262 } 4263 4264 void KTextEditor::ViewPrivate::toMatchingBracket() 4265 { 4266 m_viewInternal->cursorToMatchingBracket(); 4267 } 4268 4269 void KTextEditor::ViewPrivate::shiftToMatchingBracket() 4270 { 4271 m_viewInternal->cursorToMatchingBracket(true); 4272 } 4273 4274 void KTextEditor::ViewPrivate::toPrevModifiedLine() 4275 { 4276 const int startLine = cursorPosition().line() - 1; 4277 const int line = doc()->findTouchedLine(startLine, false); 4278 if (line >= 0) { 4279 KTextEditor::Cursor c(line, 0); 4280 m_viewInternal->updateSelection(c, false); 4281 m_viewInternal->updateCursor(c); 4282 } 4283 } 4284 4285 void KTextEditor::ViewPrivate::toNextModifiedLine() 4286 { 4287 const int startLine = cursorPosition().line() + 1; 4288 const int line = doc()->findTouchedLine(startLine, true); 4289 if (line >= 0) { 4290 KTextEditor::Cursor c(line, 0); 4291 m_viewInternal->updateSelection(c, false); 4292 m_viewInternal->updateCursor(c); 4293 } 4294 } 4295 4296 KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const 4297 { 4298 return m_selection; 4299 } 4300 4301 KTextEditor::Document *KTextEditor::ViewPrivate::document() const 4302 { 4303 return m_doc; 4304 } 4305 4306 void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu) 4307 { 4308 if (m_contextMenu) { 4309 disconnect(m_contextMenu.data(), &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu); 4310 disconnect(m_contextMenu.data(), &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu); 4311 } 4312 m_contextMenu = menu; 4313 m_userContextMenuSet = true; 4314 4315 if (m_contextMenu) { 4316 connect(m_contextMenu.data(), &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu); 4317 connect(m_contextMenu.data(), &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu); 4318 } 4319 } 4320 4321 QMenu *KTextEditor::ViewPrivate::contextMenu() const 4322 { 4323 if (m_userContextMenuSet) { 4324 return m_contextMenu; 4325 } else { 4326 KXMLGUIClient *client = const_cast<KTextEditor::ViewPrivate *>(this); 4327 while (client->parentClient()) { 4328 client = client->parentClient(); 4329 } 4330 4331 // qCDebug(LOG_KTE) << "looking up all menu containers"; 4332 if (client->factory()) { 4333 const QList<QWidget *> menuContainers = client->factory()->containers(QStringLiteral("menu")); 4334 for (QWidget *w : menuContainers) { 4335 if (w->objectName() == QLatin1String("ktexteditor_popup")) { 4336 // perhaps optimize this block 4337 QMenu *menu = (QMenu *)w; 4338 // menu is a reusable instance shared among all views. Therefore, 4339 // disconnect the current receiver(s) from the menu show/hide signals 4340 // before connecting `this` view. This ensures that only the current 4341 // view gets a signal when the menu is about to be shown or hidden, 4342 // and not also the view(s) that previously had the menu open. 4343 disconnect(menu, &QMenu::aboutToShow, nullptr, nullptr); 4344 disconnect(menu, &QMenu::aboutToHide, nullptr, nullptr); 4345 connect(menu, &QMenu::aboutToShow, this, &KTextEditor::ViewPrivate::aboutToShowContextMenu); 4346 connect(menu, &QMenu::aboutToHide, this, &KTextEditor::ViewPrivate::aboutToHideContextMenu); 4347 return menu; 4348 } 4349 } 4350 } 4351 } 4352 return nullptr; 4353 } 4354 4355 QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const 4356 { 4357 if (!menu) { 4358 menu = new QMenu(const_cast<KTextEditor::ViewPrivate *>(this)); 4359 } 4360 4361 if (m_editUndo) { 4362 menu->addAction(m_editUndo); 4363 } 4364 if (m_editRedo) { 4365 menu->addAction(m_editRedo); 4366 } 4367 4368 menu->addSeparator(); 4369 menu->addAction(m_cut); 4370 menu->addAction(m_copy); 4371 menu->addAction(m_paste); 4372 if (m_pasteSelection) { 4373 menu->addAction(m_pasteSelection); 4374 } 4375 4376 menu->addAction(m_screenshotSelection); 4377 menu->addAction(m_swapWithClipboard); 4378 menu->addSeparator(); 4379 menu->addAction(m_selectAll); 4380 menu->addAction(m_deSelect); 4381 QAction *editing = actionCollection()->action(QStringLiteral("tools_scripts_Editing")); 4382 if (editing) { 4383 menu->addAction(editing); 4384 } 4385 if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) { 4386 menu->addSeparator(); 4387 menu->addAction(spellingSuggestions); 4388 } 4389 if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) { 4390 menu->addSeparator(); 4391 menu->addAction(bookmark); 4392 } 4393 4394 return menu; 4395 } 4396 4397 void KTextEditor::ViewPrivate::aboutToShowContextMenu() 4398 { 4399 QMenu *menu = qobject_cast<QMenu *>(sender()); 4400 4401 if (menu) { 4402 Q_EMIT contextMenuAboutToShow(this, menu); 4403 } 4404 } 4405 4406 void KTextEditor::ViewPrivate::aboutToHideContextMenu() 4407 { 4408 m_spellingMenu->cleanUpAfterShown(); 4409 } 4410 4411 // BEGIN ConfigInterface stff 4412 QStringList KTextEditor::ViewPrivate::configKeys() const 4413 { 4414 static const QStringList keys = {QStringLiteral("icon-bar"), 4415 QStringLiteral("line-numbers"), 4416 QStringLiteral("dynamic-word-wrap"), 4417 QStringLiteral("background-color"), 4418 QStringLiteral("selection-color"), 4419 QStringLiteral("search-highlight-color"), 4420 QStringLiteral("replace-highlight-color"), 4421 QStringLiteral("default-mark-type"), 4422 QStringLiteral("allow-mark-menu"), 4423 QStringLiteral("folding-bar"), 4424 QStringLiteral("folding-preview"), 4425 QStringLiteral("icon-border-color"), 4426 QStringLiteral("folding-marker-color"), 4427 QStringLiteral("line-number-color"), 4428 QStringLiteral("current-line-number-color"), 4429 QStringLiteral("modification-markers"), 4430 QStringLiteral("keyword-completion"), 4431 QStringLiteral("word-count"), 4432 QStringLiteral("line-count"), 4433 QStringLiteral("scrollbar-minimap"), 4434 QStringLiteral("scrollbar-preview"), 4435 QStringLiteral("font"), 4436 QStringLiteral("theme")}; 4437 return keys; 4438 } 4439 4440 QVariant KTextEditor::ViewPrivate::configValue(const QString &key) 4441 { 4442 if (key == QLatin1String("icon-bar")) { 4443 return config()->iconBar(); 4444 } else if (key == QLatin1String("line-numbers")) { 4445 return config()->lineNumbers(); 4446 } else if (key == QLatin1String("dynamic-word-wrap")) { 4447 return config()->dynWordWrap(); 4448 } else if (key == QLatin1String("background-color")) { 4449 return rendererConfig()->backgroundColor(); 4450 } else if (key == QLatin1String("selection-color")) { 4451 return rendererConfig()->selectionColor(); 4452 } else if (key == QLatin1String("search-highlight-color")) { 4453 return rendererConfig()->searchHighlightColor(); 4454 } else if (key == QLatin1String("replace-highlight-color")) { 4455 return rendererConfig()->replaceHighlightColor(); 4456 } else if (key == QLatin1String("default-mark-type")) { 4457 return config()->defaultMarkType(); 4458 } else if (key == QLatin1String("allow-mark-menu")) { 4459 return config()->allowMarkMenu(); 4460 } else if (key == QLatin1String("folding-bar")) { 4461 return config()->foldingBar(); 4462 } else if (key == QLatin1String("folding-preview")) { 4463 return config()->foldingPreview(); 4464 } else if (key == QLatin1String("icon-border-color")) { 4465 return rendererConfig()->iconBarColor(); 4466 } else if (key == QLatin1String("folding-marker-color")) { 4467 return rendererConfig()->foldingColor(); 4468 } else if (key == QLatin1String("line-number-color")) { 4469 return rendererConfig()->lineNumberColor(); 4470 } else if (key == QLatin1String("current-line-number-color")) { 4471 return rendererConfig()->currentLineNumberColor(); 4472 } else if (key == QLatin1String("modification-markers")) { 4473 return config()->lineModification(); 4474 } else if (key == QLatin1String("keyword-completion")) { 4475 return config()->keywordCompletion(); 4476 } else if (key == QLatin1String("word-count")) { 4477 return config()->showWordCount(); 4478 } else if (key == QLatin1String("line-count")) { 4479 return config()->showLineCount(); 4480 } else if (key == QLatin1String("scrollbar-minimap")) { 4481 return config()->scrollBarMiniMap(); 4482 } else if (key == QLatin1String("scrollbar-preview")) { 4483 return config()->scrollBarPreview(); 4484 } else if (key == QLatin1String("font")) { 4485 return rendererConfig()->baseFont(); 4486 } else if (key == QLatin1String("theme")) { 4487 return rendererConfig()->schema(); 4488 } 4489 4490 // return invalid variant 4491 return QVariant(); 4492 } 4493 4494 void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value) 4495 { 4496 // First, try the new config interface 4497 if (config()->setValue(key, value)) { 4498 return; 4499 4500 } else if (rendererConfig()->setValue(key, value)) { 4501 return; 4502 } 4503 4504 // No success? Go the old way 4505 if (value.canConvert<QColor>()) { 4506 if (key == QLatin1String("background-color")) { 4507 rendererConfig()->setBackgroundColor(value.value<QColor>()); 4508 } else if (key == QLatin1String("selection-color")) { 4509 rendererConfig()->setSelectionColor(value.value<QColor>()); 4510 } else if (key == QLatin1String("search-highlight-color")) { 4511 rendererConfig()->setSearchHighlightColor(value.value<QColor>()); 4512 } else if (key == QLatin1String("replace-highlight-color")) { 4513 rendererConfig()->setReplaceHighlightColor(value.value<QColor>()); 4514 } else if (key == QLatin1String("icon-border-color")) { 4515 rendererConfig()->setIconBarColor(value.value<QColor>()); 4516 } else if (key == QLatin1String("folding-marker-color")) { 4517 rendererConfig()->setFoldingColor(value.value<QColor>()); 4518 } else if (key == QLatin1String("line-number-color")) { 4519 rendererConfig()->setLineNumberColor(value.value<QColor>()); 4520 } else if (key == QLatin1String("current-line-number-color")) { 4521 rendererConfig()->setCurrentLineNumberColor(value.value<QColor>()); 4522 } 4523 } 4524 if (value.userType() == QMetaType::Bool) { 4525 // Note explicit type check above. If we used canConvert, then 4526 // values of type UInt will be trapped here. 4527 if (key == QLatin1String("dynamic-word-wrap")) { 4528 config()->setDynWordWrap(value.toBool()); 4529 } else if (key == QLatin1String("word-count")) { 4530 config()->setShowWordCount(value.toBool()); 4531 } else if (key == QLatin1String("line-count")) { 4532 config()->setShowLineCount(value.toBool()); 4533 } 4534 } else if (key == QLatin1String("font") && value.canConvert<QFont>()) { 4535 rendererConfig()->setFont(value.value<QFont>()); 4536 } else if (key == QLatin1String("theme") && value.userType() == QMetaType::QString) { 4537 rendererConfig()->setSchema(value.toString()); 4538 } 4539 } 4540 4541 // END ConfigInterface 4542 4543 // NOLINTNEXTLINE(readability-make-member-function-const) 4544 void KTextEditor::ViewPrivate::userInvokedCompletion() 4545 { 4546 completionWidget()->userInvokedCompletion(); 4547 } 4548 4549 KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const 4550 { 4551 return m_bottomViewBar; 4552 } 4553 4554 KateGotoBar *KTextEditor::ViewPrivate::gotoBar() 4555 { 4556 if (!m_gotoBar) { 4557 m_gotoBar = new KateGotoBar(this); 4558 bottomViewBar()->addBarWidget(m_gotoBar); 4559 } 4560 4561 return m_gotoBar; 4562 } 4563 4564 KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar() 4565 { 4566 if (!m_dictionaryBar) { 4567 m_dictionaryBar = new KateDictionaryBar(this); 4568 bottomViewBar()->addBarWidget(m_dictionaryBar); 4569 } 4570 4571 return m_dictionaryBar; 4572 } 4573 4574 void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) 4575 { 4576 KTextEditor::AnnotationModel *oldmodel = m_annotationModel; 4577 m_annotationModel = model; 4578 m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, m_annotationModel); 4579 } 4580 4581 KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const 4582 { 4583 return m_annotationModel; 4584 } 4585 4586 void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible) 4587 { 4588 m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible); 4589 } 4590 4591 bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const 4592 { 4593 return m_viewInternal->m_leftBorder->annotationBorderOn(); 4594 } 4595 4596 KTextEditor::AbstractAnnotationItemDelegate *KTextEditor::ViewPrivate::annotationItemDelegate() const 4597 { 4598 return m_viewInternal->m_leftBorder->annotationItemDelegate(); 4599 } 4600 4601 void KTextEditor::ViewPrivate::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate) 4602 { 4603 m_viewInternal->m_leftBorder->setAnnotationItemDelegate(delegate); 4604 } 4605 4606 bool KTextEditor::ViewPrivate::uniformAnnotationItemSizes() const 4607 { 4608 return m_viewInternal->m_leftBorder->uniformAnnotationItemSizes(); 4609 } 4610 4611 void KTextEditor::ViewPrivate::setAnnotationUniformItemSizes(bool enable) 4612 { 4613 m_viewInternal->m_leftBorder->setAnnotationUniformItemSizes(enable); 4614 } 4615 4616 KTextEditor::Range KTextEditor::ViewPrivate::visibleRange() 4617 { 4618 // ensure that the view is up-to-date, otherwise 'endPos()' might fail! 4619 if (!m_viewInternal->endPos().isValid()) { 4620 m_viewInternal->updateView(); 4621 } 4622 return KTextEditor::Range(m_viewInternal->toRealCursor(m_viewInternal->startPos()), m_viewInternal->toRealCursor(m_viewInternal->endPos())); 4623 } 4624 4625 bool KTextEditor::ViewPrivate::event(QEvent *e) 4626 { 4627 switch (e->type()) { 4628 case QEvent::StyleChange: 4629 setupLayout(); 4630 return true; 4631 default: 4632 return KTextEditor::View::event(e); 4633 } 4634 } 4635 4636 void KTextEditor::ViewPrivate::paintEvent(QPaintEvent *e) 4637 { 4638 // base class 4639 KTextEditor::View::paintEvent(e); 4640 4641 if (!config()->showFocusFrame()) { 4642 return; 4643 } 4644 4645 const QRect contentsRect = m_topSpacer->geometry() | m_bottomSpacer->geometry() | m_leftSpacer->geometry() | m_rightSpacer->geometry(); 4646 4647 if (contentsRect.isValid()) { 4648 QStyleOptionFrame opt; 4649 opt.initFrom(this); 4650 opt.frameShape = QFrame::StyledPanel; 4651 opt.state |= QStyle::State_Sunken; 4652 4653 // clear mouseOver and focus state 4654 // update from relevant widgets 4655 opt.state &= ~(QStyle::State_HasFocus | QStyle::State_MouseOver); 4656 const QList<QWidget *> widgets = QList<QWidget *>() 4657 << m_viewInternal << m_viewInternal->m_leftBorder << m_viewInternal->m_lineScroll << m_viewInternal->m_columnScroll; 4658 for (const QWidget *w : widgets) { 4659 if (w->hasFocus()) { 4660 opt.state |= QStyle::State_HasFocus; 4661 } 4662 if (w->underMouse()) { 4663 opt.state |= QStyle::State_MouseOver; 4664 } 4665 } 4666 4667 // update rect 4668 opt.rect = contentsRect; 4669 4670 // render 4671 QPainter paint(this); 4672 paint.setClipRegion(e->region()); 4673 paint.setRenderHints(QPainter::Antialiasing); 4674 style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this); 4675 } 4676 } 4677 4678 void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b) 4679 { 4680 doc()->onTheFlySpellCheckingEnabled(b); 4681 } 4682 4683 void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled) 4684 { 4685 m_spellingMenu->setVisible(enabled); 4686 m_toggleOnTheFlySpellCheck->setChecked(enabled); 4687 } 4688 4689 KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu() 4690 { 4691 return m_spellingMenu; 4692 } 4693 4694 void KTextEditor::ViewPrivate::notifyAboutRangeChange(KTextEditor::LineRange lineRange, bool needsRepaint) 4695 { 4696 #ifdef VIEW_RANGE_DEBUG 4697 // output args 4698 qCDebug(LOG_KTE) << "trigger attribute changed in line range " << lineRange << "needsRepaint" << needsRepaint; 4699 #endif 4700 4701 // if we need repaint, we will need to collect the line ranges we will update 4702 if (needsRepaint && lineRange.isValid()) { 4703 if (m_lineToUpdateRange.isValid()) { 4704 m_lineToUpdateRange.expandToRange(lineRange); 4705 } else { 4706 m_lineToUpdateRange = lineRange; 4707 } 4708 } 4709 4710 // first call => trigger later update of view via delayed signal to group updates 4711 if (!m_delayedUpdateTimer.isActive()) { 4712 m_delayedUpdateTimer.start(); 4713 } 4714 } 4715 4716 void KTextEditor::ViewPrivate::slotDelayedUpdateOfView() 4717 { 4718 #ifdef VIEW_RANGE_DEBUG 4719 // output args 4720 qCDebug(LOG_KTE) << "delayed attribute changed in line range" << m_lineToUpdateRange; 4721 #endif 4722 // update ranges in 4723 updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); 4724 updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); 4725 4726 // update view, if valid line range, else only feedback update wanted anyway 4727 if (m_lineToUpdateRange.isValid()) { 4728 tagLines(m_lineToUpdateRange, true); 4729 updateView(true); 4730 } 4731 4732 // reset flags 4733 m_lineToUpdateRange = KTextEditor::LineRange::invalid(); 4734 } 4735 4736 void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType) 4737 { 4738 // new ranges with cursor in, default none 4739 QSet<Kate::TextRange *> newRangesIn; 4740 4741 // on which range set we work? 4742 QSet<Kate::TextRange *> &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn; 4743 4744 // which cursor position to honor? 4745 KTextEditor::Cursor currentCursor = 4746 (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->mousePosition() : m_viewInternal->cursorPosition(); 4747 4748 // first: validate the remembered ranges 4749 QSet<Kate::TextRange *> validRanges; 4750 for (Kate::TextRange *range : std::as_const(oldSet)) { 4751 if (doc()->buffer().rangePointerValid(range)) { 4752 validRanges.insert(range); 4753 } 4754 } 4755 4756 // cursor valid? else no new ranges can be found 4757 if (currentCursor.isValid() && currentCursor.line() < doc()->buffer().lines()) { 4758 // now: get current ranges for the line of cursor with an attribute 4759 const QList<Kate::TextRange *> rangesForCurrentCursor = doc()->buffer().rangesForLine(currentCursor.line(), this, false); 4760 4761 // match which ranges really fit the given cursor 4762 for (Kate::TextRange *range : rangesForCurrentCursor) { 4763 // range has no dynamic attribute of right type and no feedback object 4764 auto attribute = range->attribute(); 4765 if ((!attribute || !attribute->dynamicAttribute(activationType)) && !range->feedback()) { 4766 continue; 4767 } 4768 4769 // range doesn't contain cursor, not interesting 4770 if ((range->startInternal().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->toRange().start()) 4771 : (currentCursor <= range->toRange().start())) { 4772 continue; 4773 } 4774 4775 if ((range->endInternal().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->toRange().end() <= currentCursor) 4776 : (range->toRange().end() < currentCursor)) { 4777 continue; 4778 } 4779 4780 // range contains cursor, was it already in old set? 4781 auto it = validRanges.find(range); 4782 if (it != validRanges.end()) { 4783 // insert in new, remove from old, be done with it 4784 newRangesIn.insert(range); 4785 validRanges.erase(it); 4786 continue; 4787 } 4788 4789 // oh, new range, trigger update and insert into new set 4790 newRangesIn.insert(range); 4791 4792 if (attribute && attribute->dynamicAttribute(activationType)) { 4793 notifyAboutRangeChange(range->toLineRange(), true); 4794 } 4795 4796 // feedback 4797 if (range->feedback()) { 4798 if (activationType == KTextEditor::Attribute::ActivateMouseIn) { 4799 range->feedback()->mouseEnteredRange(range, this); 4800 } else { 4801 range->feedback()->caretEnteredRange(range, this); 4802 Q_EMIT caretChangedRange(this); 4803 } 4804 } 4805 4806 #ifdef VIEW_RANGE_DEBUG 4807 // found new range for activation 4808 qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType; 4809 #endif 4810 } 4811 } 4812 4813 // now: notify for left ranges! 4814 for (Kate::TextRange *range : std::as_const(validRanges)) { 4815 // range valid + right dynamic attribute, trigger update 4816 if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(activationType)) { 4817 notifyAboutRangeChange(range->toLineRange(), true); 4818 } 4819 4820 // feedback 4821 if (range->feedback()) { 4822 if (activationType == KTextEditor::Attribute::ActivateMouseIn) { 4823 range->feedback()->mouseExitedRange(range, this); 4824 } else { 4825 range->feedback()->caretExitedRange(range, this); 4826 Q_EMIT caretChangedRange(this); 4827 } 4828 } 4829 } 4830 4831 // set new ranges 4832 oldSet = newRangesIn; 4833 } 4834 4835 void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList<std::shared_ptr<QAction>> actions) 4836 { 4837 // just forward to KateMessageWidget :-) 4838 auto messageWidget = m_messageWidgets[message->position()]; 4839 if (!messageWidget) { 4840 // this branch is used for: TopInView, CenterInView, and BottomInView 4841 messageWidget = new KateMessageWidget(m_viewInternal, true); 4842 m_messageWidgets[message->position()] = messageWidget; 4843 m_notificationLayout->addWidget(messageWidget, message->position()); 4844 connect(this, &KTextEditor::ViewPrivate::displayRangeChanged, messageWidget, &KateMessageWidget::startAutoHideTimer); 4845 connect(this, &KTextEditor::ViewPrivate::cursorPositionChanged, messageWidget, &KateMessageWidget::startAutoHideTimer); 4846 } 4847 messageWidget->postMessage(message, std::move(actions)); 4848 } 4849 4850 KateMessageWidget *KTextEditor::ViewPrivate::messageWidget() 4851 { 4852 return m_messageWidgets[KTextEditor::Message::TopInView]; 4853 } 4854 4855 void KTextEditor::ViewPrivate::saveFoldingState() 4856 { 4857 m_savedFoldingState = m_textFolding.exportFoldingRanges(); 4858 } 4859 4860 void KTextEditor::ViewPrivate::clearFoldingState() 4861 { 4862 m_savedFoldingState = {}; 4863 } 4864 4865 void KTextEditor::ViewPrivate::applyFoldingState() 4866 { 4867 m_textFolding.importFoldingRanges(m_savedFoldingState); 4868 m_savedFoldingState = QJsonDocument(); 4869 } 4870 4871 void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file) 4872 { 4873 KateExporter(this).exportToFile(file); 4874 } 4875 4876 void KTextEditor::ViewPrivate::exportHtmlToClipboard() 4877 { 4878 KateExporter(this).exportToClipboard(); 4879 } 4880 4881 void KTextEditor::ViewPrivate::exportHtmlToFile() 4882 { 4883 const QString file = QFileDialog::getSaveFileName(this, i18n("Export File as HTML"), doc()->documentName()); 4884 if (!file.isEmpty()) { 4885 KateExporter(this).exportToFile(file); 4886 } 4887 } 4888 4889 void KTextEditor::ViewPrivate::clearHighlights() 4890 { 4891 m_rangesForHighlights.clear(); 4892 m_currentTextForHighlights.clear(); 4893 } 4894 4895 void KTextEditor::ViewPrivate::selectionChangedForHighlights() 4896 { 4897 QString text; 4898 // if text of selection is still the same, abort 4899 if (selection() && selectionRange().onSingleLine()) { 4900 text = selectionText(); 4901 if (text == m_currentTextForHighlights) { 4902 return; 4903 } 4904 } 4905 4906 // text changed: remove all highlights + create new ones 4907 // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights 4908 m_rangesForHighlights.clear(); 4909 4910 // do not highlight strings with leading and trailing spaces 4911 if (!text.isEmpty() && (text.at(0).isSpace() || text.at(text.length() - 1).isSpace())) { 4912 return; 4913 } 4914 4915 // trigger creation of ranges for current view range 4916 m_currentTextForHighlights = text; 4917 createHighlights(); 4918 } 4919 4920 void KTextEditor::ViewPrivate::createHighlights() 4921 { 4922 // do nothing if no text to highlight 4923 if (m_currentTextForHighlights.isEmpty()) { 4924 return; 4925 } 4926 4927 // clear existing highlighting ranges, otherwise we stack over and over the same ones eventually 4928 m_rangesForHighlights.clear(); 4929 4930 KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); 4931 attr->setBackground(Qt::yellow); 4932 4933 // set correct highlight color from Kate's color schema 4934 QColor fgColor = defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal)->foreground().color(); 4935 QColor bgColor = rendererConfig()->searchHighlightColor(); 4936 attr->setForeground(fgColor); 4937 attr->setBackground(bgColor); 4938 4939 KTextEditor::Cursor start(visibleRange().start()); 4940 KTextEditor::Range searchRange; 4941 4942 // only add word boundary if we can find the text then 4943 // fixes $lala hl 4944 QString pattern = QRegularExpression::escape(m_currentTextForHighlights); 4945 if (m_currentTextForHighlights.contains(QRegularExpression(QLatin1String("\\b") + pattern, QRegularExpression::UseUnicodePropertiesOption))) { 4946 pattern.prepend(QLatin1String("\\b")); 4947 } 4948 4949 if (m_currentTextForHighlights.contains(QRegularExpression(pattern + QLatin1String("\\b"), QRegularExpression::UseUnicodePropertiesOption))) { 4950 pattern += QLatin1String("\\b"); 4951 } 4952 4953 QList<KTextEditor::Range> matches; 4954 do { 4955 searchRange.setRange(start, visibleRange().end()); 4956 4957 matches = doc()->searchText(searchRange, pattern, KTextEditor::Regex); 4958 4959 if (matches.first().isValid()) { 4960 if (matches.first() != selectionRange()) { 4961 std::unique_ptr<KTextEditor::MovingRange> mr(doc()->newMovingRange(matches.first())); 4962 mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection 4963 mr->setAttribute(attr); 4964 mr->setView(this); 4965 mr->setAttributeOnlyForViews(true); 4966 m_rangesForHighlights.push_back(std::move(mr)); 4967 } 4968 start = matches.first().end(); 4969 } 4970 } while (matches.first().isValid()); 4971 } 4972 4973 KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const 4974 { 4975 return m_viewInternal->m_currentInputMode; 4976 } 4977 4978 void KTextEditor::ViewPrivate::toggleInputMode() 4979 { 4980 if (QAction *a = qobject_cast<QAction *>(sender())) { 4981 setInputMode(static_cast<KTextEditor::View::InputMode>(a->data().toInt())); 4982 } 4983 } 4984 4985 void KTextEditor::ViewPrivate::cycleInputMode() 4986 { 4987 InputMode current = currentInputMode()->viewInputMode(); 4988 InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode; 4989 setInputMode(to); 4990 } 4991 4992 // BEGIN KTextEditor::PrintInterface stuff 4993 bool KTextEditor::ViewPrivate::print() 4994 { 4995 return KatePrinter::print(this); 4996 } 4997 4998 void KTextEditor::ViewPrivate::printPreview() 4999 { 5000 KatePrinter::printPreview(this); 5001 } 5002 5003 // END 5004 5005 // BEGIN Inline Note Interface 5006 void KTextEditor::ViewPrivate::registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) 5007 { 5008 if (std::find(m_inlineNoteProviders.cbegin(), m_inlineNoteProviders.cend(), provider) == m_inlineNoteProviders.cend()) { 5009 m_inlineNoteProviders.push_back(provider); 5010 5011 connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesReset, this, &KTextEditor::ViewPrivate::inlineNotesReset); 5012 connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesChanged, this, &KTextEditor::ViewPrivate::inlineNotesLineChanged); 5013 5014 inlineNotesReset(); 5015 } 5016 } 5017 5018 void KTextEditor::ViewPrivate::unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) 5019 { 5020 auto it = std::find(m_inlineNoteProviders.cbegin(), m_inlineNoteProviders.cend(), provider); 5021 if (it != m_inlineNoteProviders.cend()) { 5022 m_inlineNoteProviders.erase(it); 5023 provider->disconnect(this); 5024 5025 inlineNotesReset(); 5026 } 5027 } 5028 5029 QVarLengthArray<KateInlineNoteData, 8> KTextEditor::ViewPrivate::inlineNotes(int line) const 5030 { 5031 QVarLengthArray<KateInlineNoteData, 8> allInlineNotes; 5032 for (KTextEditor::InlineNoteProvider *provider : m_inlineNoteProviders) { 5033 int index = 0; 5034 const auto columns = provider->inlineNotes(line); 5035 for (int column : columns) { 5036 const bool underMouse = Cursor(line, column) == m_viewInternal->m_activeInlineNote.m_position; 5037 KateInlineNoteData note = 5038 {provider, this, {line, column}, index, underMouse, m_viewInternal->renderer()->currentFont(), m_viewInternal->renderer()->lineHeight()}; 5039 allInlineNotes.append(note); 5040 index++; 5041 } 5042 } 5043 return allInlineNotes; 5044 } 5045 5046 QRect KTextEditor::ViewPrivate::inlineNoteRect(const KateInlineNoteData ¬e) const 5047 { 5048 return m_viewInternal->inlineNoteRect(note); 5049 } 5050 5051 void KTextEditor::ViewPrivate::inlineNotesReset() 5052 { 5053 m_viewInternal->m_activeInlineNote = {}; 5054 tagLines(KTextEditor::LineRange(0, doc()->lastLine()), true); 5055 } 5056 5057 void KTextEditor::ViewPrivate::inlineNotesLineChanged(int line) 5058 { 5059 if (line == m_viewInternal->m_activeInlineNote.m_position.line()) { 5060 m_viewInternal->m_activeInlineNote = {}; 5061 } 5062 tagLines({line, line}, true); 5063 } 5064 5065 // END Inline Note Interface 5066 5067 KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle defaultStyle) const 5068 { 5069 KateRendererConfig *renderConfig = const_cast<KTextEditor::ViewPrivate *>(this)->rendererConfig(); 5070 5071 KTextEditor::Attribute::Ptr style = doc()->highlight()->attributes(renderConfig->schema()).at(defaultStyle); 5072 if (!style->hasProperty(QTextFormat::BackgroundBrush)) { 5073 // make sure the returned style has the default background color set 5074 style = new KTextEditor::Attribute(*style); 5075 style->setBackground(QBrush(renderConfig->backgroundColor())); 5076 } 5077 return style; 5078 } 5079 5080 QList<KTextEditor::AttributeBlock> KTextEditor::ViewPrivate::lineAttributes(int line) 5081 { 5082 QList<KTextEditor::AttributeBlock> attribs; 5083 5084 if (line < 0 || line >= doc()->lines()) { 5085 return attribs; 5086 } 5087 5088 const Kate::TextLine kateLine = doc()->kateTextLine(line); 5089 const auto &intAttrs = kateLine.attributesList(); 5090 for (qsizetype i = 0; i < intAttrs.size(); ++i) { 5091 if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) { 5092 attribs << KTextEditor::AttributeBlock(intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(intAttrs.at(i).attributeValue)); 5093 } 5094 } 5095 5096 return attribs; 5097 } 5098 5099 #include "moc_kateview.cpp"