diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp index db59c644..42cb25eb 100644 --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -1,6081 +1,6084 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Mirko Stocker Copyright (C) 2009-2010 Michel Ludwig Copyright (C) 2013 Gerald Senarclens de Grancy Copyright (C) 2013 Andrey Matveyakin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-13020, USA. */ //BEGIN includes #include "config.h" #include "katedocument.h" #include "kateglobal.h" #include "katedialogs.h" #include "katehighlight.h" #include "kateview.h" #include "kateautoindent.h" #include "katetextline.h" #include "katerenderer.h" #include "kateregexp.h" #include "kateplaintextsearch.h" #include "kateregexpsearch.h" #include "kateconfig.h" #include "katemodemanager.h" #include "kateschema.h" #include "katebuffer.h" #include "kateundomanager.h" #include "spellcheck/prefixstore.h" #include "spellcheck/ontheflycheck.h" #include "spellcheck/spellcheck.h" #include "katescriptmanager.h" #include "kateswapfile.h" #include "katepartdebug.h" #include "printing/kateprinter.h" #include "kateabstractinputmode.h" #include "katetemplatehandler.h" #if EDITORCONFIG_FOUND #include "editorconfig.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if LIBGIT2_FOUND #include #include #include #endif //END includes #if 0 #define EDIT_DEBUG qCDebug(LOG_KTE) #else #define EDIT_DEBUG if (0) qCDebug(LOG_KTE) #endif template static int indexOf(const std::initializer_list & list, const E& entry) { auto it = std::find(list.begin(), list.end(), entry); return it == list.end() ? -1 : std::distance(list.begin(), it); } template static bool contains(const std::initializer_list & list, const E& entry) { return indexOf(list, entry) >= 0; } static inline QChar matchingStartBracket(const QChar c) { switch (c.toLatin1()) { case '}': return QLatin1Char('{'); case ']': return QLatin1Char('['); case ')': return QLatin1Char('('); } return QChar(); } static inline QChar matchingEndBracket(const QChar c, bool withQuotes = true) { switch (c.toLatin1()) { case '{': return QLatin1Char('}'); case '[': return QLatin1Char(']'); case '(': return QLatin1Char(')'); case '\'': return withQuotes ? QLatin1Char('\'') : QChar(); case '"': return withQuotes ? QLatin1Char('"') : QChar(); } return QChar(); } static inline QChar matchingBracket(const QChar c) { QChar bracket = matchingStartBracket(c); if (bracket.isNull()) { bracket = matchingEndBracket(c, /*withQuotes=*/false); } return bracket; } static inline bool isStartBracket(const QChar c) { return ! matchingEndBracket(c, /*withQuotes=*/false).isNull(); } static inline bool isEndBracket(const QChar c) { return ! matchingStartBracket(c).isNull(); } static inline bool isBracket(const QChar c) { return isStartBracket(c) || isEndBracket(c); } /** * normalize given url * @param url input url * @return normalized url */ static QUrl normalizeUrl (const QUrl &url) { /** * only normalize local urls */ if (url.isEmpty() || !url.isLocalFile()) return url; /** * don't normalize if not existing! * canonicalFilePath won't work! */ const QString normalizedUrl(QFileInfo(url.toLocalFile()).canonicalFilePath()); if (normalizedUrl.isEmpty()) return url; /** * else: use canonicalFilePath to normalize */ return QUrl::fromLocalFile(normalizedUrl); } //BEGIN d'tor, c'tor // // KTextEditor::DocumentPrivate Constructor // KTextEditor::DocumentPrivate::DocumentPrivate(bool bSingleViewMode, bool bReadOnly, QWidget *parentWidget, QObject *parent) : KTextEditor::Document (this, parent), m_bSingleViewMode(bSingleViewMode), m_bReadOnly(bReadOnly), m_undoManager(new KateUndoManager(this)), m_buffer(new KateBuffer(this)), m_indenter(new KateAutoIndent(this)), m_docName(QStringLiteral("need init")), m_fileType(QStringLiteral("Normal")), m_config(new KateDocumentConfig(this)) { /** * no plugins from kparts here */ setPluginLoadingMode (DoNotLoadPlugins); /** * pass on our component data, do this after plugin loading is off */ setComponentData(KTextEditor::EditorPrivate::self()->aboutData()); /** * avoid spamming plasma and other window managers with progress dialogs * we show such stuff inline in the views! */ setProgressInfoEnabled(false); // register doc at factory KTextEditor::EditorPrivate::self()->registerDocument(this); // normal hl m_buffer->setHighlight(0); // swap file m_swapfile = (config()->swapFileMode() == KateDocumentConfig::DisableSwapFile) ? nullptr : new Kate::SwapFile(this); // some nice signals from the buffer connect(m_buffer, SIGNAL(tagLines(int,int)), this, SLOT(tagLines(int,int))); // if the user changes the highlight with the dialog, notify the doc connect(KateHlManager::self(), SIGNAL(changed()), SLOT(internalHlChanged())); // signals for mod on hd connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(dirty(QString)), this, SLOT(slotModOnHdDirty(QString))); connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(created(QString)), this, SLOT(slotModOnHdCreated(QString))); connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(deleted(QString)), this, SLOT(slotModOnHdDeleted(QString))); /** * singleshot timer to handle updates of mod on hd state delayed */ m_modOnHdTimer.setSingleShot(true); m_modOnHdTimer.setInterval(200); connect(&m_modOnHdTimer, SIGNAL(timeout()), this, SLOT(slotDelayedHandleModOnHd())); // Setup auto reload stuff m_autoReloadMode = new KToggleAction(i18n("Auto Reload Document"), this); m_autoReloadMode->setWhatsThis(i18n("Automatic reload the document when it was changed on disk")); connect(m_autoReloadMode, &KToggleAction::triggered, this, &DocumentPrivate::autoReloadToggled); // Prepare some reload amok protector... m_autoReloadThrottle.setSingleShot(true); //...but keep the value small in unit tests m_autoReloadThrottle.setInterval(KTextEditor::EditorPrivate::self()->unitTestMode() ? 50 : 3000); connect(&m_autoReloadThrottle, &QTimer::timeout, this, &DocumentPrivate::onModOnHdAutoReload); /** * load handling * this is needed to ensure we signal the user if a file ist still loading * and to disallow him to edit in that time */ connect(this, SIGNAL(started(KIO::Job*)), this, SLOT(slotStarted(KIO::Job*))); connect(this, SIGNAL(completed()), this, SLOT(slotCompleted())); connect(this, SIGNAL(canceled(QString)), this, SLOT(slotCanceled())); connect(this, SIGNAL(urlChanged(QUrl)), this, SLOT(slotUrlChanged(QUrl))); // update doc name updateDocName(); // if single view mode, like in the konqui embedding, create a default view ;) // be lazy, only create it now, if any parentWidget is given, otherwise widget() // will create it on demand... if (m_bSingleViewMode && parentWidget) { KTextEditor::View *view = (KTextEditor::View *)createView(parentWidget); insertChildClient(view); view->setContextMenu(view->defaultContextMenu()); setWidget(view); } connect(m_undoManager, SIGNAL(undoChanged()), this, SIGNAL(undoChanged())); connect(m_undoManager, SIGNAL(undoStart(KTextEditor::Document*)), this, SIGNAL(editingStarted(KTextEditor::Document*))); connect(m_undoManager, SIGNAL(undoEnd(KTextEditor::Document*)), this, SIGNAL(editingFinished(KTextEditor::Document*))); connect(m_undoManager, SIGNAL(redoStart(KTextEditor::Document*)), this, SIGNAL(editingStarted(KTextEditor::Document*))); connect(m_undoManager, SIGNAL(redoEnd(KTextEditor::Document*)), this, SIGNAL(editingFinished(KTextEditor::Document*))); connect(this, SIGNAL(sigQueryClose(bool*,bool*)), this, SLOT(slotQueryClose_save(bool*,bool*))); connect(this, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearEditingPosStack())); onTheFlySpellCheckingEnabled(config()->onTheFlySpellCheck()); // make sure correct defaults are set (indenter, ...) updateConfig(); } // // KTextEditor::DocumentPrivate Destructor // KTextEditor::DocumentPrivate::~DocumentPrivate() { // delete pending mod-on-hd message, if applicable delete m_modOnHdHandler; /** * we are about to delete cursors/ranges/... */ emit aboutToDeleteMovingInterfaceContent(this); // kill it early, it has ranges! delete m_onTheFlyChecker; m_onTheFlyChecker = nullptr; clearDictionaryRanges(); // Tell the world that we're about to close (== destruct) // Apps must receive this in a direct signal-slot connection, and prevent // any further use of interfaces once they return. emit aboutToClose(this); // remove file from dirwatch deactivateDirWatch(); // thanks for offering, KPart, but we're already self-destructing setAutoDeleteWidget(false); setAutoDeletePart(false); // clean up remaining views qDeleteAll (m_views.keys()); m_views.clear(); // cu marks for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { delete i.value(); } m_marks.clear(); delete m_config; KTextEditor::EditorPrivate::self()->deregisterDocument(this); } //END void KTextEditor::DocumentPrivate::saveEditingPositions(const KTextEditor::Cursor &cursor) { if (m_editingStackPosition != m_editingStack.size() - 1) { m_editingStack.resize(m_editingStackPosition); } // try to be clever: reuse existing cursors if possible QSharedPointer mc; // we might pop last one: reuse that if (!m_editingStack.isEmpty() && cursor.line() == m_editingStack.top()->line()) { mc = m_editingStack.pop(); } // we might expire oldest one, reuse that one, if not already one there // we prefer the other one for reuse, as already on the right line aka in the right block! const int editingStackSizeLimit = 32; if (m_editingStack.size() >= editingStackSizeLimit) { if (mc) { m_editingStack.removeFirst(); } else { mc = m_editingStack.takeFirst(); } } // new cursor needed? or adjust existing one? if (mc) { mc->setPosition(cursor); } else { mc = QSharedPointer (newMovingCursor(cursor)); } // add new one as top of stack m_editingStack.push(mc); m_editingStackPosition = m_editingStack.size() - 1; } KTextEditor::Cursor KTextEditor::DocumentPrivate::lastEditingPosition(EditingPositionKind nextOrPrev, KTextEditor::Cursor currentCursor) { if (m_editingStack.isEmpty()) { return KTextEditor::Cursor::invalid(); } auto targetPos = m_editingStack.at(m_editingStackPosition)->toCursor(); if (targetPos == currentCursor) { if (nextOrPrev == Previous) { m_editingStackPosition--; } else { m_editingStackPosition++; } m_editingStackPosition = qBound(0, m_editingStackPosition, m_editingStack.size() - 1); } return m_editingStack.at(m_editingStackPosition)->toCursor(); } void KTextEditor::DocumentPrivate::clearEditingPosStack() { m_editingStack.clear(); m_editingStackPosition = -1; } // on-demand view creation QWidget *KTextEditor::DocumentPrivate::widget() { // no singleViewMode -> no widget()... if (!singleViewMode()) { return nullptr; } // does a widget exist already? use it! if (KTextEditor::Document::widget()) { return KTextEditor::Document::widget(); } // create and return one... KTextEditor::View *view = (KTextEditor::View *)createView(nullptr); insertChildClient(view); view->setContextMenu(view->defaultContextMenu()); setWidget(view); return view; } //BEGIN KTextEditor::Document stuff KTextEditor::View *KTextEditor::DocumentPrivate::createView(QWidget *parent, KTextEditor::MainWindow *mainWindow) { KTextEditor::ViewPrivate *newView = new KTextEditor::ViewPrivate(this, parent, mainWindow); if (m_fileChangedDialogsActivated) { connect(newView, SIGNAL(focusIn(KTextEditor::View*)), this, SLOT(slotModifiedOnDisk())); } emit viewCreated(this, newView); // post existing messages to the new view, if no specific view is given foreach (KTextEditor::Message *message, m_messageHash.keys()) { if (!message->view()) { newView->postMessage(message, m_messageHash[message]); } } return newView; } KTextEditor::Range KTextEditor::DocumentPrivate::rangeOnLine(KTextEditor::Range range, int line) const { const int col1 = toVirtualColumn(range.start()); const int col2 = toVirtualColumn(range.end()); return KTextEditor::Range(line, fromVirtualColumn(line, col1), line, fromVirtualColumn(line, col2)); } //BEGIN KTextEditor::EditInterface stuff bool KTextEditor::DocumentPrivate::isEditingTransactionRunning() const { return editSessionNumber > 0; } QString KTextEditor::DocumentPrivate::text() const { return m_buffer->text(); } QString KTextEditor::DocumentPrivate::text(const KTextEditor::Range &range, bool blockwise) const { if (!range.isValid()) { qCWarning(LOG_KTE) << "Text requested for invalid range" << range; return QString(); } QString s; if (range.start().line() == range.end().line()) { if (range.start().column() > range.end().column()) { return QString(); } Kate::TextLine textLine = m_buffer->plainLine(range.start().line()); if (!textLine) { return QString(); } return textLine->string(range.start().column(), range.end().column() - range.start().column()); } else { for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i) { Kate::TextLine textLine = m_buffer->plainLine(i); if (!blockwise) { if (i == range.start().line()) { s.append(textLine->string(range.start().column(), textLine->length() - range.start().column())); } else if (i == range.end().line()) { s.append(textLine->string(0, range.end().column())); } else { s.append(textLine->string()); } } else { KTextEditor::Range subRange = rangeOnLine(range, i); s.append(textLine->string(subRange.start().column(), subRange.columnWidth())); } if (i < range.end().line()) { s.append(QLatin1Char('\n')); } } } return s; } QChar KTextEditor::DocumentPrivate::characterAt(const KTextEditor::Cursor &position) const { Kate::TextLine textLine = m_buffer->plainLine(position.line()); if (!textLine) { return QChar(); } return textLine->at(position.column()); } QString KTextEditor::DocumentPrivate::wordAt(const KTextEditor::Cursor &cursor) const { return text(wordRangeAt(cursor)); } KTextEditor::Range KTextEditor::DocumentPrivate::wordRangeAt(const KTextEditor::Cursor &cursor) const { // get text line const int line = cursor.line(); Kate::TextLine textLine = m_buffer->plainLine(line); if (!textLine) { return KTextEditor::Range::invalid(); } // make sure the cursor is const int lineLenth = textLine->length(); if (cursor.column() > lineLenth) { return KTextEditor::Range::invalid(); } int start = cursor.column(); int end = start; while (start > 0 && highlight()->isInWord(textLine->at(start - 1), textLine->attribute(start - 1))) { start--; } while (end < lineLenth && highlight()->isInWord(textLine->at(end), textLine->attribute(end))) { end++; } return KTextEditor::Range(line, start, line, end); } bool KTextEditor::DocumentPrivate::isValidTextPosition(const KTextEditor::Cursor& cursor) const { const int ln = cursor.line(); const int col = cursor.column(); // cursor in document range? if (ln < 0 || col < 0 || ln >= lines() || col > lineLength(ln)) { return false; } const QString str = line(ln); Q_ASSERT(str.length() >= col); // cursor at end of line? const int len = lineLength(ln); if (col == 0 || col == len) { return true; } // cursor in the middle of a valid utf32-surrogate? return (! str.at(col).isLowSurrogate()) || (! str.at(col-1).isHighSurrogate()); } QStringList KTextEditor::DocumentPrivate::textLines(const KTextEditor::Range &range, bool blockwise) const { QStringList ret; if (!range.isValid()) { qCWarning(LOG_KTE) << "Text requested for invalid range" << range; return ret; } if (blockwise && (range.start().column() > range.end().column())) { return ret; } if (range.start().line() == range.end().line()) { Q_ASSERT(range.start() <= range.end()); Kate::TextLine textLine = m_buffer->plainLine(range.start().line()); if (!textLine) { return ret; } ret << textLine->string(range.start().column(), range.end().column() - range.start().column()); } else { for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i) { Kate::TextLine textLine = m_buffer->plainLine(i); if (!blockwise) { if (i == range.start().line()) { ret << textLine->string(range.start().column(), textLine->length() - range.start().column()); } else if (i == range.end().line()) { ret << textLine->string(0, range.end().column()); } else { ret << textLine->string(); } } else { KTextEditor::Range subRange = rangeOnLine(range, i); ret << textLine->string(subRange.start().column(), subRange.columnWidth()); } } } return ret; } QString KTextEditor::DocumentPrivate::line(int line) const { Kate::TextLine l = m_buffer->plainLine(line); if (!l) { return QString(); } return l->string(); } bool KTextEditor::DocumentPrivate::setText(const QString &s) { if (!isReadWrite()) { return false; } QList msave; foreach (KTextEditor::Mark *mark, m_marks) { msave.append(*mark); } editStart(); // delete the text clear(); // insert the new text insertText(KTextEditor::Cursor(), s); editEnd(); foreach (KTextEditor::Mark mark, msave) { setMark(mark.line, mark.type); } return true; } bool KTextEditor::DocumentPrivate::setText(const QStringList &text) { if (!isReadWrite()) { return false; } QList msave; foreach (KTextEditor::Mark *mark, m_marks) { msave.append(*mark); } editStart(); // delete the text clear(); // insert the new text insertText(KTextEditor::Cursor::start(), text); editEnd(); foreach (KTextEditor::Mark mark, msave) { setMark(mark.line, mark.type); } return true; } bool KTextEditor::DocumentPrivate::clear() { if (!isReadWrite()) { return false; } foreach (KTextEditor::ViewPrivate *view, m_views) { view->clear(); view->tagAll(); view->update(); } clearMarks(); emit aboutToInvalidateMovingInterfaceContent(this); m_buffer->invalidateRanges(); emit aboutToRemoveText(documentRange()); return editRemoveLines(0, lastLine()); } bool KTextEditor::DocumentPrivate::insertText(const KTextEditor::Cursor &position, const QString &text, bool block) { if (!isReadWrite()) { return false; } if (text.isEmpty()) { return true; } editStart(); int currentLine = position.line(); int currentLineStart = 0; const int totalLength = text.length(); int insertColumn = position.column(); // pad with empty lines, if insert position is after last line if (position.line() > lines()) { int line = lines(); while (line <= position.line()) { editInsertLine(line, QString()); line++; } } // compute expanded column for block mode int positionColumnExpanded = insertColumn; const int tabWidth = config()->tabWidth(); if (block) { if (auto l = plainKateTextLine(currentLine)) { positionColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); } } int pos = 0; for (; pos < totalLength; pos++) { const QChar &ch = text.at(pos); if (ch == QLatin1Char('\n')) { // Only perform the text insert if there is text to insert if (currentLineStart < pos) { editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart)); } if (!block) { editWrapLine(currentLine, insertColumn + pos - currentLineStart); insertColumn = 0; } currentLine++; if (block) { auto l = plainKateTextLine(currentLine); if (currentLine == lastLine() + 1) { editInsertLine(currentLine, QString()); } insertColumn = positionColumnExpanded; if (l) { insertColumn = l->fromVirtualColumn(insertColumn, tabWidth); } } currentLineStart = pos + 1; } } // Only perform the text insert if there is text to insert if (currentLineStart < pos) { editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart)); } editEnd(); return true; } bool KTextEditor::DocumentPrivate::insertText(const KTextEditor::Cursor &position, const QStringList &textLines, bool block) { if (!isReadWrite()) { return false; } // just reuse normal function return insertText(position, textLines.join(QStringLiteral("\n")), block); } bool KTextEditor::DocumentPrivate::removeText(const KTextEditor::Range &_range, bool block) { KTextEditor::Range range = _range; if (!isReadWrite()) { return false; } // Should now be impossible to trigger with the new Range class Q_ASSERT(range.start().line() <= range.end().line()); if (range.start().line() > lastLine()) { return false; } if (!block) { emit aboutToRemoveText(range); } editStart(); if (!block) { if (range.end().line() > lastLine()) { range.setEnd(KTextEditor::Cursor(lastLine() + 1, 0)); } if (range.onSingleLine()) { editRemoveText(range.start().line(), range.start().column(), range.columnWidth()); } else { int from = range.start().line(); int to = range.end().line(); // remove last line if (to <= lastLine()) { editRemoveText(to, 0, range.end().column()); } // editRemoveLines() will be called on first line (to remove bookmark) if (range.start().column() == 0 && from > 0) { --from; } // remove middle lines editRemoveLines(from + 1, to - 1); // remove first line if not already removed by editRemoveLines() if (range.start().column() > 0 || range.start().line() == 0) { editRemoveText(from, range.start().column(), m_buffer->plainLine(from)->length() - range.start().column()); editUnWrapLine(from); } } } // if ( ! block ) else { int startLine = qMax(0, range.start().line()); int vc1 = toVirtualColumn(range.start()); int vc2 = toVirtualColumn(range.end()); for (int line = qMin(range.end().line(), lastLine()); line >= startLine; --line) { int col1 = fromVirtualColumn(line, vc1); int col2 = fromVirtualColumn(line, vc2); editRemoveText(line, qMin(col1, col2), qAbs(col2 - col1)); } } editEnd(); return true; } bool KTextEditor::DocumentPrivate::insertLine(int l, const QString &str) { if (!isReadWrite()) { return false; } if (l < 0 || l > lines()) { return false; } return editInsertLine(l, str); } bool KTextEditor::DocumentPrivate::insertLines(int line, const QStringList &text) { if (!isReadWrite()) { return false; } if (line < 0 || line > lines()) { return false; } bool success = true; foreach (const QString &string, text) { success &= editInsertLine(line++, string); } return success; } bool KTextEditor::DocumentPrivate::removeLine(int line) { if (!isReadWrite()) { return false; } if (line < 0 || line > lastLine()) { return false; } return editRemoveLine(line); } int KTextEditor::DocumentPrivate::totalCharacters() const { int l = 0; for (int i = 0; i < m_buffer->count(); ++i) { Kate::TextLine line = m_buffer->plainLine(i); if (line) { l += line->length(); } } return l; } int KTextEditor::DocumentPrivate::lines() const { return m_buffer->count(); } int KTextEditor::DocumentPrivate::lineLength(int line) const { if (line < 0 || line > lastLine()) { return -1; } Kate::TextLine l = m_buffer->plainLine(line); if (!l) { return -1; } return l->length(); } bool KTextEditor::DocumentPrivate::isLineModified(int line) const { if (line < 0 || line >= lines()) { return false; } Kate::TextLine l = m_buffer->plainLine(line); Q_ASSERT(l); return l->markedAsModified(); } bool KTextEditor::DocumentPrivate::isLineSaved(int line) const { if (line < 0 || line >= lines()) { return false; } Kate::TextLine l = m_buffer->plainLine(line); Q_ASSERT(l); return l->markedAsSavedOnDisk(); } bool KTextEditor::DocumentPrivate::isLineTouched(int line) const { if (line < 0 || line >= lines()) { return false; } Kate::TextLine l = m_buffer->plainLine(line); Q_ASSERT(l); return l->markedAsModified() || l->markedAsSavedOnDisk(); } //END //BEGIN KTextEditor::EditInterface internal stuff // // Starts an edit session with (or without) undo, update of view disabled during session // bool KTextEditor::DocumentPrivate::editStart() { editSessionNumber++; if (editSessionNumber > 1) { return false; } editIsRunning = true; // no last change cursor at start m_editLastChangeStartCursor = KTextEditor::Cursor::invalid(); m_undoManager->editStart(); foreach (KTextEditor::ViewPrivate *view, m_views) { view->editStart(); } m_buffer->editStart(); return true; } // // End edit session and update Views // bool KTextEditor::DocumentPrivate::editEnd() { if (editSessionNumber == 0) { Q_ASSERT(0); return false; } // wrap the new/changed text, if something really changed! if (m_buffer->editChanged() && (editSessionNumber == 1)) if (m_undoManager->isActive() && config()->wordWrap()) { wrapText(m_buffer->editTagStart(), m_buffer->editTagEnd()); } editSessionNumber--; if (editSessionNumber > 0) { return false; } // end buffer edit, will trigger hl update // this will cause some possible adjustment of tagline start/end m_buffer->editEnd(); m_undoManager->editEnd(); // edit end for all views !!!!!!!!! foreach (KTextEditor::ViewPrivate *view, m_views) { view->editEnd(m_buffer->editTagStart(), m_buffer->editTagEnd(), m_buffer->editTagFrom()); } if (m_buffer->editChanged()) { setModified(true); emit textChanged(this); } // remember last change position in the stack, if any // this avoid costly updates for longer editing transactions // before we did that on textInsert/Removed if (m_editLastChangeStartCursor.isValid()) saveEditingPositions(m_editLastChangeStartCursor); editIsRunning = false; return true; } void KTextEditor::DocumentPrivate::pushEditState() { editStateStack.push(editSessionNumber); } void KTextEditor::DocumentPrivate::popEditState() { if (editStateStack.isEmpty()) { return; } int count = editStateStack.pop() - editSessionNumber; while (count < 0) { ++count; editEnd(); } while (count > 0) { --count; editStart(); } } void KTextEditor::DocumentPrivate::inputMethodStart() { m_undoManager->inputMethodStart(); } void KTextEditor::DocumentPrivate::inputMethodEnd() { m_undoManager->inputMethodEnd(); } bool KTextEditor::DocumentPrivate::wrapText(int startLine, int endLine) { if (startLine < 0 || endLine < 0) { return false; } if (!isReadWrite()) { return false; } int col = config()->wordWrapAt(); if (col == 0) { return false; } editStart(); for (int line = startLine; (line <= endLine) && (line < lines()); line++) { Kate::TextLine l = kateTextLine(line); if (!l) { break; } //qCDebug(LOG_KTE) << "try wrap line: " << line; if (l->virtualLength(m_buffer->tabWidth()) > col) { Kate::TextLine nextl = kateTextLine(line + 1); //qCDebug(LOG_KTE) << "do wrap line: " << line; int eolPosition = l->length() - 1; // take tabs into account here, too int x = 0; const QString &t = l->string(); int z2 = 0; for (; z2 < l->length(); z2++) { static const QChar tabChar(QLatin1Char('\t')); if (t.at(z2) == tabChar) { x += m_buffer->tabWidth() - (x % m_buffer->tabWidth()); } else { x++; } if (x > col) { break; } } const int colInChars = qMin(z2, l->length() - 1); int searchStart = colInChars; // If where we are wrapping is an end of line and is a space we don't // want to wrap there if (searchStart == eolPosition && t.at(searchStart).isSpace()) { searchStart--; } // Scan backwards looking for a place to break the line // We are not interested in breaking at the first char // of the line (if it is a space), but we are at the second // anders: if we can't find a space, try breaking on a word // boundary, using KateHighlight::canBreakAt(). // This could be a priority (setting) in the hl/filetype/document int z = -1; int nw = -1; // alternative position, a non word character for (z = searchStart; z >= 0; z--) { if (t.at(z).isSpace()) { break; } if ((nw < 0) && highlight()->canBreakAt(t.at(z), l->attribute(z))) { nw = z; } } if (z >= 0) { // So why don't we just remove the trailing space right away? // Well, the (view's) cursor may be directly in front of that space // (user typing text before the last word on the line), and if that // happens, the cursor would be moved to the next line, which is not // what we want (bug #106261) z++; } else { // There was no space to break at so break at a nonword character if // found, or at the wrapcolumn ( that needs be configurable ) // Don't try and add any white space for the break if ((nw >= 0) && nw < colInChars) { nw++; // break on the right side of the character } z = (nw >= 0) ? nw : colInChars; } if (nextl && !nextl->isAutoWrapped()) { editWrapLine(line, z, true); editMarkLineAutoWrapped(line + 1, true); endLine++; } else { if (nextl && (nextl->length() > 0) && !nextl->at(0).isSpace() && ((l->length() < 1) || !l->at(l->length() - 1).isSpace())) { editInsertText(line + 1, 0, QLatin1String(" ")); } bool newLineAdded = false; editWrapLine(line, z, false, &newLineAdded); editMarkLineAutoWrapped(line + 1, true); endLine++; } } } editEnd(); return true; } bool KTextEditor::DocumentPrivate::wrapParagraph(int first, int last) { if (first == last) { return wrapText(first, last); } if (first < 0 || last < first) { return false; } if (last >= lines() || first > last) { return false; } if (!isReadWrite()) { return false; } editStart(); // Because we shrink and expand lines, we need to track the working set by powerful "MovingStuff" std::unique_ptr range(newMovingRange(KTextEditor::Range(first, 0, last, 0))); std::unique_ptr curr(newMovingCursor(KTextEditor::Cursor(range->start()))); // Scan the selected range for paragraphs, whereas each empty line trigger a new paragraph for (int line = first; line <= range->end().line(); ++line) { // Is our first line a somehow filled line? if(plainKateTextLine(first)->firstChar() < 0) { // Fast forward to first non empty line ++first; curr->setPosition(curr->line() + 1, 0); continue; } // Is our current line a somehow filled line? If not, wrap the paragraph if (plainKateTextLine(line)->firstChar() < 0) { curr->setPosition(line, 0); // Set on empty line joinLines(first, line - 1); // Don't wrap twice! That may cause a bad result if (!wordWrap()) { wrapText(first, first); } first = curr->line() + 1; line = first; } } // If there was no paragraph, we need to wrap now bool needWrap = (curr->line() != range->end().line()); if (needWrap && plainKateTextLine(first)->firstChar() != -1) { joinLines(first, range->end().line()); // Don't wrap twice! That may cause a bad result if (!wordWrap()) { wrapText(first, first); } } editEnd(); return true; } bool KTextEditor::DocumentPrivate::editInsertText(int line, int col, const QString &s) { // verbose debug EDIT_DEBUG << "editInsertText" << line << col << s; if (line < 0 || col < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } // nothing to do, do nothing! if (s.isEmpty()) { return true; } editStart(); QString s2 = s; int col2 = col; if (col2 > l->length()) { s2 = QString(col2 - l->length(), QLatin1Char(' ')) + s; col2 = l->length(); } m_undoManager->slotTextInserted(line, col2, s2); // remember last change cursor m_editLastChangeStartCursor = KTextEditor::Cursor(line, col2); // insert text into line m_buffer->insertText(m_editLastChangeStartCursor, s2); emit textInserted(this, KTextEditor::Range(line, col2, line, col2 + s2.length())); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editRemoveText(int line, int col, int len) { // verbose debug EDIT_DEBUG << "editRemoveText" << line << col << len; if (line < 0 || col < 0 || len < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } // nothing to do, do nothing! if (len == 0) { return true; } // wrong column if (col >= l->text().size()) { return false; } // don't try to remove what's not there len = qMin(len, l->text().size() - col); editStart(); QString oldText = l->string().mid(col, len); m_undoManager->slotTextRemoved(line, col, oldText); // remember last change cursor m_editLastChangeStartCursor = KTextEditor::Cursor(line, col); // remove text from line m_buffer->removeText(KTextEditor::Range(m_editLastChangeStartCursor, KTextEditor::Cursor(line, col + len))); emit textRemoved(this, KTextEditor::Range(line, col, line, col + len), oldText); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editMarkLineAutoWrapped(int line, bool autowrapped) { // verbose debug EDIT_DEBUG << "editMarkLineAutoWrapped" << line << autowrapped; if (line < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } editStart(); m_undoManager->slotMarkLineAutoWrapped(line, autowrapped); l->setAutoWrapped(autowrapped); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editWrapLine(int line, int col, bool newLine, bool *newLineAdded) { // verbose debug EDIT_DEBUG << "editWrapLine" << line << col << newLine; if (line < 0 || col < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } editStart(); Kate::TextLine nextLine = kateTextLine(line + 1); const int length = l->length(); m_undoManager->slotLineWrapped(line, col, length - col, (!nextLine || newLine)); if (!nextLine || newLine) { m_buffer->wrapLine(KTextEditor::Cursor(line, col)); QList list; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { if (i.value()->line >= line) { if ((col == 0) || (i.value()->line > line)) { list.append(i.value()); } } } for (int i = 0; i < list.size(); ++i) { m_marks.take(list.at(i)->line); } for (int i = 0; i < list.size(); ++i) { list.at(i)->line++; m_marks.insert(list.at(i)->line, list.at(i)); } if (!list.isEmpty()) { emit marksChanged(this); } // yes, we added a new line ! if (newLineAdded) { (*newLineAdded) = true; } } else { m_buffer->wrapLine(KTextEditor::Cursor(line, col)); m_buffer->unwrapLine(line + 2); // no, no new line added ! if (newLineAdded) { (*newLineAdded) = false; } } // remember last change cursor m_editLastChangeStartCursor = KTextEditor::Cursor(line, col); emit textInserted(this, KTextEditor::Range(line, col, line + 1, 0)); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editUnWrapLine(int line, bool removeLine, int length) { // verbose debug EDIT_DEBUG << "editUnWrapLine" << line << removeLine << length; if (line < 0 || length < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); Kate::TextLine nextLine = kateTextLine(line + 1); if (!l || !nextLine) { return false; } editStart(); int col = l->length(); m_undoManager->slotLineUnWrapped(line, col, length, removeLine); if (removeLine) { m_buffer->unwrapLine(line + 1); } else { m_buffer->wrapLine(KTextEditor::Cursor(line + 1, length)); m_buffer->unwrapLine(line + 1); } QList list; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { if (i.value()->line >= line + 1) { list.append(i.value()); } if (i.value()->line == line + 1) { KTextEditor::Mark *mark = m_marks.take(line); if (mark) { i.value()->type |= mark->type; } } } for (int i = 0; i < list.size(); ++i) { m_marks.take(list.at(i)->line); } for (int i = 0; i < list.size(); ++i) { list.at(i)->line--; m_marks.insert(list.at(i)->line, list.at(i)); } if (!list.isEmpty()) { emit marksChanged(this); } // remember last change cursor m_editLastChangeStartCursor = KTextEditor::Cursor(line, col); emit textRemoved(this, KTextEditor::Range(line, col, line + 1, 0), QStringLiteral("\n")); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editInsertLine(int line, const QString &s) { // verbose debug EDIT_DEBUG << "editInsertLine" << line << s; if (line < 0) { return false; } if (!isReadWrite()) { return false; } if (line > lines()) { return false; } editStart(); m_undoManager->slotLineInserted(line, s); // wrap line if (line > 0) { Kate::TextLine previousLine = m_buffer->line(line - 1); m_buffer->wrapLine(KTextEditor::Cursor(line - 1, previousLine->text().size())); } else { m_buffer->wrapLine(KTextEditor::Cursor(0, 0)); } // insert text m_buffer->insertText(KTextEditor::Cursor(line, 0), s); Kate::TextLine tl = m_buffer->line(line); QList list; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { if (i.value()->line >= line) { list.append(i.value()); } } for (int i = 0; i < list.size(); ++i) { m_marks.take(list.at(i)->line); } for (int i = 0; i < list.size(); ++i) { list.at(i)->line++; m_marks.insert(list.at(i)->line, list.at(i)); } if (!list.isEmpty()) { emit marksChanged(this); } KTextEditor::Range rangeInserted(line, 0, line, tl->length()); if (line) { Kate::TextLine prevLine = plainKateTextLine(line - 1); rangeInserted.setStart(KTextEditor::Cursor(line - 1, prevLine->length())); } else { rangeInserted.setEnd(KTextEditor::Cursor(line + 1, 0)); } // remember last change cursor m_editLastChangeStartCursor = rangeInserted.start(); emit textInserted(this, rangeInserted); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editRemoveLine(int line) { return editRemoveLines(line, line); } bool KTextEditor::DocumentPrivate::editRemoveLines(int from, int to) { // verbose debug EDIT_DEBUG << "editRemoveLines" << from << to; if (to < from || from < 0 || to > lastLine()) { return false; } if (!isReadWrite()) { return false; } if (lines() == 1) { return editRemoveText(0, 0, kateTextLine(0)->length()); } editStart(); QStringList oldText; /** * first remove text */ for (int line = to; line >= from; --line) { Kate::TextLine tl = m_buffer->line(line); oldText.prepend(this->line(line)); m_undoManager->slotLineRemoved(line, this->line(line)); m_buffer->removeText(KTextEditor::Range(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, tl->text().size()))); } /** * then collapse lines */ for (int line = to; line >= from; --line) { /** * unwrap all lines, prefer to unwrap line behind, skip to wrap line 0 */ if (line + 1 < m_buffer->lines()) { m_buffer->unwrapLine(line + 1); } else if (line) { m_buffer->unwrapLine(line); } } QList rmark; QList list; foreach (KTextEditor::Mark *mark, m_marks) { int line = mark->line; if (line > to) { list << line; } else if (line >= from) { rmark << line; } } foreach (int line, rmark) { delete m_marks.take(line); } foreach (int line, list) { KTextEditor::Mark *mark = m_marks.take(line); mark->line -= to - from + 1; m_marks.insert(mark->line, mark); } if (!list.isEmpty()) { emit marksChanged(this); } KTextEditor::Range rangeRemoved(from, 0, to + 1, 0); if (to == lastLine() + to - from + 1) { rangeRemoved.setEnd(KTextEditor::Cursor(to, oldText.last().length())); if (from > 0) { Kate::TextLine prevLine = plainKateTextLine(from - 1); rangeRemoved.setStart(KTextEditor::Cursor(from - 1, prevLine->length())); } } // remember last change cursor m_editLastChangeStartCursor = rangeRemoved.start(); emit textRemoved(this, rangeRemoved, oldText.join(QStringLiteral("\n")) + QLatin1Char('\n')); editEnd(); return true; } //END //BEGIN KTextEditor::UndoInterface stuff uint KTextEditor::DocumentPrivate::undoCount() const { return m_undoManager->undoCount(); } uint KTextEditor::DocumentPrivate::redoCount() const { return m_undoManager->redoCount(); } void KTextEditor::DocumentPrivate::undo() { m_undoManager->undo(); } void KTextEditor::DocumentPrivate::redo() { m_undoManager->redo(); } //END //BEGIN KTextEditor::SearchInterface stuff QVector KTextEditor::DocumentPrivate::searchText( const KTextEditor::Range &range, const QString &pattern, const KTextEditor::SearchOptions options) const { const bool escapeSequences = options.testFlag(KTextEditor::EscapeSequences); const bool regexMode = options.testFlag(KTextEditor::Regex); const bool backwards = options.testFlag(KTextEditor::Backwards); const bool wholeWords = options.testFlag(KTextEditor::WholeWords); const Qt::CaseSensitivity caseSensitivity = options.testFlag(KTextEditor::CaseInsensitive) ? Qt::CaseInsensitive : Qt::CaseSensitive; if (regexMode) { // regexp search // escape sequences are supported by definition KateRegExpSearch searcher(this, caseSensitivity); return searcher.search(pattern, range, backwards); } if (escapeSequences) { // escaped search KatePlainTextSearch searcher(this, caseSensitivity, wholeWords); KTextEditor::Range match = searcher.search(KateRegExpSearch::escapePlaintext(pattern), range, backwards); QVector result; result.append(match); return result; } // plaintext search KatePlainTextSearch searcher(this, caseSensitivity, wholeWords); KTextEditor::Range match = searcher.search(pattern, range, backwards); QVector result; result.append(match); return result; } //END QWidget *KTextEditor::DocumentPrivate::dialogParent() { QWidget *w = widget(); if (!w) { w = activeView(); if (!w) { w = QApplication::activeWindow(); } } return w; } //BEGIN KTextEditor::HighlightingInterface stuff bool KTextEditor::DocumentPrivate::setMode(const QString &name) { updateFileType(name); return true; } KTextEditor::DefaultStyle KTextEditor::DocumentPrivate::defaultStyleAt(const KTextEditor::Cursor &position) const { // TODO, FIXME KDE5: in surrogate, use 2 bytes before if (! isValidTextPosition(position)) { return dsNormal; } int ds = const_cast(this)-> defStyleNum(position.line(), position.column()); if (ds < 0 || ds > static_cast(dsError)) { return dsNormal; } return static_cast(ds); } QString KTextEditor::DocumentPrivate::mode() const { return m_fileType; } QStringList KTextEditor::DocumentPrivate::modes() const { QStringList m; const QList &modeList = KTextEditor::EditorPrivate::self()->modeManager()->list(); foreach (KateFileType *type, modeList) { m << type->name; } return m; } bool KTextEditor::DocumentPrivate::setHighlightingMode(const QString &name) { int mode = KateHlManager::self()->nameFind(name); if (mode == -1) { return false; } m_buffer->setHighlight(mode); return true; } QString KTextEditor::DocumentPrivate::highlightingMode() const { return highlight()->name(); } QStringList KTextEditor::DocumentPrivate::highlightingModes() const { QStringList hls; for (const auto &hl : KateHlManager::self()->modeList()) { hls << hl.name(); } return hls; } QString KTextEditor::DocumentPrivate::highlightingModeSection(int index) const { return KateHlManager::self()->modeList().at(index).section(); } QString KTextEditor::DocumentPrivate::modeSection(int index) const { return KTextEditor::EditorPrivate::self()->modeManager()->list().at(index)->section; } void KTextEditor::DocumentPrivate::bufferHlChanged() { // update all views makeAttribs(false); // deactivate indenter if necessary m_indenter->checkRequiredStyle(); emit highlightingModeChanged(this); } void KTextEditor::DocumentPrivate::setDontChangeHlOnSave() { m_hlSetByUser = true; } void KTextEditor::DocumentPrivate::bomSetByUser() { m_bomSetByUser = true; } //END //BEGIN KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff void KTextEditor::DocumentPrivate::readSessionConfig(const KConfigGroup &kconfig, const QSet &flags) { if (!flags.contains(QStringLiteral("SkipEncoding"))) { // get the encoding QString tmpenc = kconfig.readEntry("Encoding"); if (!tmpenc.isEmpty() && (tmpenc != encoding())) { setEncoding(tmpenc); } } if (!flags.contains(QStringLiteral("SkipUrl"))) { // restore the url QUrl url(kconfig.readEntry("URL")); // open the file if url valid if (!url.isEmpty() && url.isValid()) { openUrl(url); } else { completed(); //perhaps this should be emitted at the end of this function } } else { completed(); //perhaps this should be emitted at the end of this function } if (!flags.contains(QStringLiteral("SkipMode"))) { // restore the filetype if (kconfig.hasKey("Mode")) { updateFileType(kconfig.readEntry("Mode", fileType())); // restore if set by user, too! m_fileTypeSetByUser = kconfig.readEntry("Mode Set By User", false); } } if (!flags.contains(QStringLiteral("SkipHighlighting"))) { // restore the hl stuff if (kconfig.hasKey("Highlighting")) { const int mode = KateHlManager::self()->nameFind(kconfig.readEntry("Highlighting")); if (mode >= 0) { m_buffer->setHighlight(mode); // restore if set by user, too! see bug 332605, otherwise we loose the hl later again on save m_hlSetByUser = kconfig.readEntry("Highlighting Set By User", false); } } } // indent mode config()->setIndentationMode(kconfig.readEntry("Indentation Mode", config()->indentationMode())); // Restore Bookmarks const QList marks = kconfig.readEntry("Bookmarks", QList()); for (int i = 0; i < marks.count(); i++) { addMark(marks.at(i), KTextEditor::DocumentPrivate::markType01); } } void KTextEditor::DocumentPrivate::writeSessionConfig(KConfigGroup &kconfig, const QSet &flags) { if (this->url().isLocalFile()) { const QString path = this->url().toLocalFile(); if (path.startsWith(QDir::tempPath())) { return; // inside tmp resource, do not save } } if (!flags.contains(QStringLiteral("SkipUrl"))) { // save url kconfig.writeEntry("URL", this->url().toString()); } if (!flags.contains(QStringLiteral("SkipEncoding"))) { // save encoding kconfig.writeEntry("Encoding", encoding()); } if (!flags.contains(QStringLiteral("SkipMode"))) { // save file type kconfig.writeEntry("Mode", m_fileType); // save if set by user, too! kconfig.writeEntry("Mode Set By User", m_fileTypeSetByUser); } if (!flags.contains(QStringLiteral("SkipHighlighting"))) { // save hl kconfig.writeEntry("Highlighting", highlight()->name()); // save if set by user, too! see bug 332605, otherwise we loose the hl later again on save kconfig.writeEntry("Highlighting Set By User", m_hlSetByUser); } // indent mode kconfig.writeEntry("Indentation Mode", config()->indentationMode()); // Save Bookmarks QList marks; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) if (i.value()->type & KTextEditor::MarkInterface::markType01) { marks << i.value()->line; } kconfig.writeEntry("Bookmarks", marks); } //END KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff uint KTextEditor::DocumentPrivate::mark(int line) { KTextEditor::Mark *m = m_marks.value(line); if (!m) { return 0; } return m->type; } void KTextEditor::DocumentPrivate::setMark(int line, uint markType) { clearMark(line); addMark(line, markType); } void KTextEditor::DocumentPrivate::clearMark(int line) { if (line < 0 || line > lastLine()) { return; } if (!m_marks.value(line)) { return; } KTextEditor::Mark *mark = m_marks.take(line); emit markChanged(this, *mark, MarkRemoved); emit marksChanged(this); delete mark; tagLines(line, line); repaintViews(true); } void KTextEditor::DocumentPrivate::addMark(int line, uint markType) { KTextEditor::Mark *mark; if (line < 0 || line > lastLine()) { return; } if (markType == 0) { return; } if ((mark = m_marks.value(line))) { // Remove bits already set markType &= ~mark->type; if (markType == 0) { return; } // Add bits mark->type |= markType; } else { mark = new KTextEditor::Mark; mark->line = line; mark->type = markType; m_marks.insert(line, mark); } // Emit with a mark having only the types added. KTextEditor::Mark temp; temp.line = line; temp.type = markType; emit markChanged(this, temp, MarkAdded); emit marksChanged(this); tagLines(line, line); repaintViews(true); } void KTextEditor::DocumentPrivate::removeMark(int line, uint markType) { if (line < 0 || line > lastLine()) { return; } KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { return; } // Remove bits not set markType &= mark->type; if (markType == 0) { return; } // Subtract bits mark->type &= ~markType; // Emit with a mark having only the types removed. KTextEditor::Mark temp; temp.line = line; temp.type = markType; emit markChanged(this, temp, MarkRemoved); if (mark->type == 0) { m_marks.remove(line); delete mark; } emit marksChanged(this); tagLines(line, line); repaintViews(true); } const QHash &KTextEditor::DocumentPrivate::marks() { return m_marks; } void KTextEditor::DocumentPrivate::requestMarkTooltip(int line, QPoint position) { KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { return; } bool handled = false; emit markToolTipRequested(this, *mark, position, handled); } bool KTextEditor::DocumentPrivate::handleMarkClick(int line) { bool handled = false; KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { emit markClicked(this, KTextEditor::Mark{line, 0}, handled); } else { emit markClicked(this, *mark, handled); } return handled; } bool KTextEditor::DocumentPrivate::handleMarkContextMenu(int line, QPoint position) { bool handled = false; KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { emit markContextMenuRequested(this, KTextEditor::Mark{line, 0}, position, handled); } else { emit markContextMenuRequested(this, *mark, position, handled); } return handled; } void KTextEditor::DocumentPrivate::clearMarks() { while (!m_marks.isEmpty()) { QHash::iterator it = m_marks.begin(); KTextEditor::Mark mark = *it.value(); delete it.value(); m_marks.erase(it); emit markChanged(this, mark, MarkRemoved); tagLines(mark.line, mark.line); } m_marks.clear(); emit marksChanged(this); repaintViews(true); } void KTextEditor::DocumentPrivate::setMarkPixmap(MarkInterface::MarkTypes type, const QPixmap &pixmap) { m_markPixmaps.insert(type, pixmap); } void KTextEditor::DocumentPrivate::setMarkDescription(MarkInterface::MarkTypes type, const QString &description) { m_markDescriptions.insert(type, description); } QPixmap KTextEditor::DocumentPrivate::markPixmap(MarkInterface::MarkTypes type) const { return m_markPixmaps.value(type, QPixmap()); } QColor KTextEditor::DocumentPrivate::markColor(MarkInterface::MarkTypes type) const { uint reserved = (0x1 << KTextEditor::MarkInterface::reservedMarkersCount()) - 1; if ((uint)type >= (uint)markType01 && (uint)type <= reserved) { return KateRendererConfig::global()->lineMarkerColor(type); } else { return QColor(); } } QString KTextEditor::DocumentPrivate::markDescription(MarkInterface::MarkTypes type) const { return m_markDescriptions.value(type, QString()); } void KTextEditor::DocumentPrivate::setEditableMarks(uint markMask) { m_editableMarks = markMask; } uint KTextEditor::DocumentPrivate::editableMarks() const { return m_editableMarks; } //END //BEGIN KTextEditor::PrintInterface stuff bool KTextEditor::DocumentPrivate::print() { return KatePrinter::print(this); } void KTextEditor::DocumentPrivate::printPreview() { KatePrinter::printPreview(this); } //END KTextEditor::PrintInterface stuff //BEGIN KTextEditor::DocumentInfoInterface (### unfinished) QString KTextEditor::DocumentPrivate::mimeType() { /** * collect first 4k of text * only heuristic */ QByteArray buf; for (int i = 0; (i < lines()) && (buf.size() <= 4096); ++i) { buf.append(line(i).toUtf8()); buf.append('\n'); } // use path of url, too, if set if (!url().path().isEmpty()) { return QMimeDatabase().mimeTypeForFileNameAndData(url().path(), buf).name(); } // else only use the content return QMimeDatabase().mimeTypeForData(buf).name(); } //END KTextEditor::DocumentInfoInterface //BEGIN: error void KTextEditor::DocumentPrivate::showAndSetOpeningErrorAccess() { QPointer message = new KTextEditor::Message(i18n("The file %1 could not be loaded, as it was not possible to read from it.
Check if you have read access to this file.", this->url().toDisplayString(QUrl::PreferLocalFile)), KTextEditor::Message::Error); message->setWordWrap(true); QAction *tryAgainAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("translators: you can also translate 'Try Again' with 'Reload'", "Try Again"), nullptr); connect(tryAgainAction, SIGNAL(triggered()), SLOT(documentReload()), Qt::QueuedConnection); QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), nullptr); closeAction->setToolTip(i18n("Close message")); // add try again and close actions message->addAction(tryAgainAction); message->addAction(closeAction); // finally post message postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 could not be loaded, as it was not possible to read from it.\n\nCheck if you have read access to this file.", this->url().toDisplayString(QUrl::PreferLocalFile)); } //END: error void KTextEditor::DocumentPrivate::openWithLineLengthLimitOverride() { // raise line length limit to the next power of 2 const int longestLine = m_buffer->longestLineLoaded(); int newLimit = pow(2, ceil(log2(longestLine))); if (newLimit <= longestLine) { newLimit *= 2; } // do the raise config()->setLineLengthLimit(newLimit); // just reload m_buffer->clear(); openFile(); if (!m_openingError) { setReadWrite(true); m_readWriteStateBeforeLoading = true; } } int KTextEditor::DocumentPrivate::lineLengthLimit() const { return config()->lineLengthLimit(); } //BEGIN KParts::ReadWrite stuff bool KTextEditor::DocumentPrivate::openFile() { /** * we are about to invalidate all cursors/ranges/.. => m_buffer->openFile will do so */ emit aboutToInvalidateMovingInterfaceContent(this); // no open errors until now... m_openingError = false; m_openingErrorMessage.clear (); // add new m_file to dirwatch activateDirWatch(); // remember current encoding QString currentEncoding = encoding(); // // mime type magic to get encoding right // QString mimeType = arguments().mimeType(); int pos = mimeType.indexOf(QLatin1Char(';')); if (pos != -1 && !(m_reloading && m_userSetEncodingForNextReload)) { setEncoding(mimeType.mid(pos + 1)); } // update file type, we do this here PRE-LOAD, therefore pass file name for reading from updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, localFilePath())); // read dir config (if possible and wanted) // do this PRE-LOAD to get encoding info! readDirConfig(); // perhaps we need to re-set again the user encoding if (m_reloading && m_userSetEncodingForNextReload && (currentEncoding != encoding())) { setEncoding(currentEncoding); } bool success = m_buffer->openFile(localFilePath(), (m_reloading && m_userSetEncodingForNextReload)); // // yeah, success // read variables // if (success) { readVariables(); } // // update views // foreach (KTextEditor::ViewPrivate *view, m_views) { // This is needed here because inserting the text moves the view's start position (it is a MovingCursor) view->setCursorPosition(KTextEditor::Cursor()); view->updateView(true); } // Inform that the text has changed (required as we're not inside the usual editStart/End stuff) emit textChanged(this); emit loaded(this); // // to houston, we are not modified // if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // // display errors // if (!success) { showAndSetOpeningErrorAccess(); } // warn: broken encoding if (m_buffer->brokenEncoding()) { // this file can't be saved again without killing it setReadWrite(false); m_readWriteStateBeforeLoading=false; QPointer message = new KTextEditor::Message(i18n("The file %1 was opened with %2 encoding but contained invalid characters.
" "It is set to read-only mode, as saving might destroy its content.
" "Either reopen the file with the correct encoding chosen or enable the read-write mode again in the tools menu to be able to edit it.", this->url().toDisplayString(QUrl::PreferLocalFile), QString::fromLatin1(m_buffer->textCodec()->name())), KTextEditor::Message::Warning); message->setWordWrap(true); postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 was opened with %2 encoding but contained invalid characters." " It is set to read-only mode, as saving might destroy its content." " Either reopen the file with the correct encoding chosen or enable the read-write mode again in the tools menu to be able to edit it.", this->url().toDisplayString(QUrl::PreferLocalFile), QString::fromLatin1(m_buffer->textCodec()->name())); } // warn: too long lines if (m_buffer->tooLongLinesWrapped()) { // this file can't be saved again without modifications setReadWrite(false); m_readWriteStateBeforeLoading = false; QPointer message = new KTextEditor::Message(i18n("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).
" "The longest of those lines was %3 characters long
" "Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().toDisplayString(QUrl::PreferLocalFile), config()->lineLengthLimit(), m_buffer->longestLineLoaded()), KTextEditor::Message::Warning); QAction *increaseAndReload = new QAction(i18n("Temporarily raise limit and reload file"), message); connect(increaseAndReload, SIGNAL(triggered()), this, SLOT(openWithLineLengthLimitOverride())); message->addAction(increaseAndReload, true); message->addAction(new QAction(i18n("Close"), message), true); message->setWordWrap(true); postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).
" "The longest of those lines was %3 characters long
" "Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().toDisplayString(QUrl::PreferLocalFile), config()->lineLengthLimit(),m_buffer->longestLineLoaded()); } // // return the success // return success; } bool KTextEditor::DocumentPrivate::saveFile() { // delete pending mod-on-hd message if applicable. delete m_modOnHdHandler; // some warnings, if file was changed by the outside! if (!url().isEmpty()) { if (m_fileChangedDialogsActivated && m_modOnHd) { QString str = reasonedMOHString() + QLatin1String("\n\n"); if (!isModified()) { if (KMessageBox::warningContinueCancel(dialogParent(), str + i18n("Do you really want to save this unmodified file? You could overwrite changed data in the file on disk."), i18n("Trying to Save Unmodified File"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue) { return false; } } else { if (KMessageBox::warningContinueCancel(dialogParent(), str + i18n("Do you really want to save this file? Both your open file and the file on disk were changed. There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue) { return false; } } } } // // can we encode it if we want to save it ? // if (!m_buffer->canEncode() && (KMessageBox::warningContinueCancel(dialogParent(), i18n("The selected encoding cannot encode every Unicode character in this document. Do you really want to save it? There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue)) { return false; } /** * create a backup file or abort if that fails! * if no backup file wanted, this routine will just return true */ if (!createBackupFile()) return false; // update file type, pass no file path, read file type content from this document QString oldPath = m_dirWatchFile; // only update file type if path has changed so that variables are not overridden on normal save if (oldPath != localFilePath()) { updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, QString())); if (url().isLocalFile()) { // if file is local then read dir config for new path readDirConfig(); } } // read our vars readVariables(); // remove file from dirwatch deactivateDirWatch(); // remove all trailing spaces in the document (as edit actions) // NOTE: we need this as edit actions, since otherwise the edit actions // in the swap file recovery may happen at invalid cursor positions removeTrailingSpaces(); // // try to save // if (!m_buffer->saveFile(localFilePath())) { // add m_file again to dirwatch activateDirWatch(oldPath); KMessageBox::error(dialogParent(), i18n("The document could not be saved, as it was not possible to write to %1.\nCheck that you have write access to this file or that enough disk space is available.\nThe original file may be lost or damaged. Don't quit the application until the file is successfully written.", this->url().toDisplayString(QUrl::PreferLocalFile))); return false; } // update the checksum createDigest(); // add m_file again to dirwatch activateDirWatch(); // // we are not modified // if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // (dominik) mark last undo group as not mergeable, otherwise the next // edit action might be merged and undo will never stop at the saved state m_undoManager->undoSafePoint(); m_undoManager->updateLineModifications(); // // return success // return true; } bool KTextEditor::DocumentPrivate::createBackupFile() { /** * backup for local or remote files wanted? */ const bool backupLocalFiles = config()->backupOnSaveLocal(); const bool backupRemoteFiles = config()->backupOnSaveRemote(); /** * early out, before mount check: backup wanted at all? * => if not, all fine, just return */ if (!backupLocalFiles && !backupRemoteFiles) { return true; } /** * decide if we need backup based on locality * skip that, if we always want backups, as currentMountPoints is not that fast */ QUrl u(url()); bool needBackup = backupLocalFiles && backupRemoteFiles; if (!needBackup) { bool slowOrRemoteFile = !u.isLocalFile(); if (!slowOrRemoteFile) { // could be a mounted remote filesystem (e.g. nfs, sshfs, cifs) // we have the early out above to skip this, if we want no backup, which is the default KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByDevice(u.toLocalFile()); slowOrRemoteFile = (mountPoint && mountPoint->probablySlow()); } needBackup = (!slowOrRemoteFile && backupLocalFiles) || (slowOrRemoteFile && backupRemoteFiles); } /** * no backup needed? be done */ if (!needBackup) { return true; } /** * else: try to backup */ if (config()->backupPrefix().contains(QDir::separator())) { /** * replace complete path, as prefix is a path! */ u.setPath(config()->backupPrefix() + u.fileName() + config()->backupSuffix()); } else { /** * replace filename in url */ const QString fileName = u.fileName(); u = u.adjusted(QUrl::RemoveFilename); u.setPath(u.path() + config()->backupPrefix() + fileName + config()->backupSuffix()); } qCDebug(LOG_KTE) << "backup src file name: " << url(); qCDebug(LOG_KTE) << "backup dst file name: " << u; // handle the backup... bool backupSuccess = false; // local file mode, no kio if (u.isLocalFile()) { if (QFile::exists(url().toLocalFile())) { // first: check if backupFile is already there, if true, unlink it QFile backupFile(u.toLocalFile()); if (backupFile.exists()) { backupFile.remove(); } backupSuccess = QFile::copy(url().toLocalFile(), u.toLocalFile()); } else { backupSuccess = true; } } else { // remote file mode, kio // get the right permissions, start with safe default KIO::StatJob *statJob = KIO::stat(url(), KIO::StatJob::SourceSide, 2); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); if (statJob->exec()) { // do a evil copy which will overwrite target if possible KFileItem item(statJob->statResult(), url()); KIO::FileCopyJob *job = KIO::file_copy(url(), u, item.permissions(), KIO::Overwrite); KJobWidgets::setWindow(job, QApplication::activeWindow()); backupSuccess = job->exec(); } else { backupSuccess = true; } } // backup has failed, ask user how to proceed if (!backupSuccess && (KMessageBox::warningContinueCancel(dialogParent() , i18n("For file %1 no backup copy could be created before saving." " If an error occurs while saving, you might lose the data of this file." " A reason could be that the media you write to is full or the directory of the file is read-only for you.", url().toDisplayString(QUrl::PreferLocalFile)) , i18n("Failed to create backup copy.") , KGuiItem(i18n("Try to Save Nevertheless")) , KStandardGuiItem::cancel(), QStringLiteral("Backup Failed Warning")) != KMessageBox::Continue)) { return false; } return true; } void KTextEditor::DocumentPrivate::readDirConfig() { if (!url().isLocalFile()) { return; } /** * first search .kateconfig upwards * with recursion guard */ QSet seenDirectories; QDir dir (QFileInfo(localFilePath()).absolutePath()); while (!seenDirectories.contains (dir.absolutePath ())) { /** * fill recursion guard */ seenDirectories.insert (dir.absolutePath ()); // try to open config file in this dir QFile f(dir.absolutePath () + QLatin1String("/.kateconfig")); if (f.open(QIODevice::ReadOnly)) { QTextStream stream(&f); uint linesRead = 0; QString line = stream.readLine(); while ((linesRead < 32) && !line.isNull()) { readVariableLine(line); line = stream.readLine(); linesRead++; } return; } /** * else: cd up, if possible or abort */ if (!dir.cdUp()) { break; } } #if EDITORCONFIG_FOUND // if there wasn’t any .kateconfig file and KTextEditor was compiled with // EditorConfig support, try to load document config from a .editorconfig // file, if such is provided EditorConfig editorConfig(this); editorConfig.parse(); #endif } void KTextEditor::DocumentPrivate::activateDirWatch(const QString &useFileName) { QString fileToUse = useFileName; if (fileToUse.isEmpty()) { fileToUse = localFilePath(); } QFileInfo fileInfo = QFileInfo(fileToUse); if (fileInfo.isSymLink()) { // Monitor the actual data and not the symlink fileToUse = fileInfo.canonicalFilePath(); } // same file as we are monitoring, return if (fileToUse == m_dirWatchFile) { return; } // remove the old watched file deactivateDirWatch(); // add new file if needed if (url().isLocalFile() && !fileToUse.isEmpty()) { KTextEditor::EditorPrivate::self()->dirWatch()->addFile(fileToUse); m_dirWatchFile = fileToUse; } } void KTextEditor::DocumentPrivate::deactivateDirWatch() { if (!m_dirWatchFile.isEmpty()) { KTextEditor::EditorPrivate::self()->dirWatch()->removeFile(m_dirWatchFile); } m_dirWatchFile.clear(); } bool KTextEditor::DocumentPrivate::openUrl(const QUrl &url) { if (!m_reloading) { // Reset filetype when opening url m_fileTypeSetByUser = false; } bool res = KTextEditor::Document::openUrl(normalizeUrl(url)); updateDocName(); return res; } bool KTextEditor::DocumentPrivate::closeUrl() { // // file mod on hd // if (!m_reloading && !url().isEmpty()) { if (m_fileChangedDialogsActivated && m_modOnHd) { // make sure to not forget pending mod-on-hd handler delete m_modOnHdHandler; QWidget *parentWidget(dialogParent()); if (!(KMessageBox::warningContinueCancel( parentWidget, reasonedMOHString() + QLatin1String("\n\n") + i18n("Do you really want to continue to close this file? Data loss may occur."), i18n("Possible Data Loss"), KGuiItem(i18n("Close Nevertheless")), KStandardGuiItem::cancel(), QStringLiteral("kate_close_modonhd_%1").arg(m_modOnHdReason)) == KMessageBox::Continue)) { /** * reset reloading */ m_reloading = false; return false; } } } // // first call the normal kparts implementation // if (!KParts::ReadWritePart::closeUrl()) { /** * reset reloading */ m_reloading = false; return false; } // Tell the world that we're about to go ahead with the close if (!m_reloading) { emit aboutToClose(this); } /** * delete all KTE::Messages */ if (!m_messageHash.isEmpty()) { QList keys = m_messageHash.keys(); foreach (KTextEditor::Message *message, keys) { delete message; } } /** * we are about to invalidate all cursors/ranges/.. => m_buffer->clear will do so */ emit aboutToInvalidateMovingInterfaceContent(this); // remove file from dirwatch deactivateDirWatch(); // // empty url + fileName // setUrl(QUrl()); setLocalFilePath(QString()); // we are not modified if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // remove all marks clearMarks(); // clear the buffer m_buffer->clear(); // clear undo/redo history m_undoManager->clearUndo(); m_undoManager->clearRedo(); // no, we are no longer modified setModified(false); // we have no longer any hl m_buffer->setHighlight(0); // update all our views foreach (KTextEditor::ViewPrivate *view, m_views) { view->clearSelection(); // fix bug #118588 view->clear(); } // purge swap file if (m_swapfile) { m_swapfile->fileClosed(); } // success return true; } bool KTextEditor::DocumentPrivate::isDataRecoveryAvailable() const { return m_swapfile && m_swapfile->shouldRecover(); } void KTextEditor::DocumentPrivate::recoverData() { if (isDataRecoveryAvailable()) { m_swapfile->recover(); } } void KTextEditor::DocumentPrivate::discardDataRecovery() { if (isDataRecoveryAvailable()) { m_swapfile->discard(); } } void KTextEditor::DocumentPrivate::setReadWrite(bool rw) { if (isReadWrite() == rw) { return; } KParts::ReadWritePart::setReadWrite(rw); foreach (KTextEditor::ViewPrivate *view, m_views) { view->slotUpdateUndo(); view->slotReadWriteChanged(); } emit readWriteChanged(this); } void KTextEditor::DocumentPrivate::setModified(bool m) { if (isModified() != m) { KParts::ReadWritePart::setModified(m); foreach (KTextEditor::ViewPrivate *view, m_views) { view->slotUpdateUndo(); } emit modifiedChanged(this); } m_undoManager->setModified(m); } //END //BEGIN Kate specific stuff ;) void KTextEditor::DocumentPrivate::makeAttribs(bool needInvalidate) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->renderer()->updateAttributes(); } if (needInvalidate) { m_buffer->invalidateHighlighting(); } foreach (KTextEditor::ViewPrivate *view, m_views) { view->tagAll(); view->updateView(true); } } // the attributes of a hl have changed, update void KTextEditor::DocumentPrivate::internalHlChanged() { makeAttribs(); } void KTextEditor::DocumentPrivate::addView(KTextEditor::View *view) { Q_ASSERT (!m_views.contains(view)); m_views.insert(view, static_cast(view)); m_viewsCache.append(view); // apply the view & renderer vars from the file type if (!m_fileType.isEmpty()) { readVariableLine(KTextEditor::EditorPrivate::self()->modeManager()->fileType(m_fileType).varLine, true); } // apply the view & renderer vars from the file readVariables(true); setActiveView(view); } void KTextEditor::DocumentPrivate::removeView(KTextEditor::View *view) { Q_ASSERT (m_views.contains(view)); m_views.remove(view); m_viewsCache.removeAll(view); if (activeView() == view) { setActiveView(nullptr); } } void KTextEditor::DocumentPrivate::setActiveView(KTextEditor::View *view) { if (m_activeView == view) { return; } m_activeView = static_cast(view); } bool KTextEditor::DocumentPrivate::ownedView(KTextEditor::ViewPrivate *view) { // do we own the given view? return (m_views.contains(view)); } int KTextEditor::DocumentPrivate::toVirtualColumn(int line, int column) const { Kate::TextLine textLine = m_buffer->plainLine(line); if (textLine) { return textLine->toVirtualColumn(column, config()->tabWidth()); } else { return 0; } } int KTextEditor::DocumentPrivate::toVirtualColumn(const KTextEditor::Cursor &cursor) const { return toVirtualColumn(cursor.line(), cursor.column()); } int KTextEditor::DocumentPrivate::fromVirtualColumn(int line, int column) const { Kate::TextLine textLine = m_buffer->plainLine(line); if (textLine) { return textLine->fromVirtualColumn(column, config()->tabWidth()); } else { return 0; } } int KTextEditor::DocumentPrivate::fromVirtualColumn(const KTextEditor::Cursor &cursor) const { return fromVirtualColumn(cursor.line(), cursor.column()); } bool KTextEditor::DocumentPrivate::typeChars(KTextEditor::ViewPrivate *view, const QString &realChars) { /** * filter out non-printable chars (convert to utf-32 to support surrogate pairs) */ const auto realUcs4Chars = realChars.toUcs4(); QVector ucs4Chars; Q_FOREACH (auto c, realUcs4Chars) if (QChar::isPrint(c) || c == QChar::fromLatin1('\t') || c == QChar::fromLatin1('\n') || c == QChar::fromLatin1('\r')) { ucs4Chars.append(c); } /** * no printable chars => nothing to insert! */ QString chars = QString::fromUcs4(ucs4Chars.data(), ucs4Chars.size()); if (chars.isEmpty()) { return false; } // auto bracket handling QChar closingBracket; if (view->config()->autoBrackets()) { // Check if entered closing bracket is already balanced const QChar typedChar = chars.at(0); const QChar openBracket = matchingStartBracket(typedChar); if (!openBracket.isNull()) { KTextEditor::Cursor curPos = view->cursorPosition(); if ((characterAt(curPos) == typedChar) && findMatchingBracket(curPos, 123/*Which value may best?*/).isValid()) { // Do nothing view->cursorRight(); return true; } } // for newly inserted text: remember if we should auto add some bracket if (chars.size() == 1) { // we inserted a bracket? => remember the matching closing one closingBracket = matchingEndBracket(typedChar); // closing bracket for the autobracket we inserted earlier? if (m_currentAutobraceClosingChar == typedChar && m_currentAutobraceRange) { // do nothing m_currentAutobraceRange.reset(nullptr); view->cursorRight(); return true; } } } editStart(); /** * special handling if we want to add auto brackets to a selection */ if (view->selection() && !closingBracket.isNull()) { std::unique_ptr selectionRange(newMovingRange(view->selectionRange())); const int startLine = qMax(0, selectionRange->start().line()); const int endLine = qMin(selectionRange->end().line(), lastLine()); const bool blockMode = view->blockSelection() && (startLine != endLine); if (blockMode) { if (selectionRange->start().column() > selectionRange->end().column()) { // Selection was done from right->left, requires special setting to ensure the new // added brackets will not be part of the selection selectionRange->setInsertBehaviors(MovingRange::ExpandLeft | MovingRange::ExpandRight); } // Add brackets to each line of the block const int startColumn = qMin(selectionRange->start().column(), selectionRange->end().column()); const int endColumn = qMax(selectionRange->start().column(), selectionRange->end().column()); const KTextEditor::Range workingRange(startLine, startColumn, endLine, endColumn); for (int line = startLine; line <= endLine; ++line) { const KTextEditor::Range r(rangeOnLine(workingRange, line)); insertText(r.end(), QString(closingBracket)); view->slotTextInserted(view, r.end(), QString(closingBracket)); insertText(r.start(), chars); view->slotTextInserted(view, r.start(), chars); } } else { // No block, just add to start & end of selection insertText(selectionRange->end(), QString(closingBracket)); view->slotTextInserted(view, selectionRange->end(), QString(closingBracket)); insertText(selectionRange->start(), chars); view->slotTextInserted(view, selectionRange->start(), chars); } // Refesh selection view->setSelection(selectionRange->toRange()); view->setCursorPosition(selectionRange->end()); editEnd(); return true; } /** * normal handling */ if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); } const KTextEditor::Cursor oldCur(view->cursorPosition()); const bool multiLineBlockMode = view->blockSelection() && view->selection(); if (view->currentInputMode()->overwrite()) { // blockmode multiline selection case: remove chars in every line const KTextEditor::Range selectionRange = view->selectionRange(); const int startLine = multiLineBlockMode ? qMax(0, selectionRange.start().line()) : view->cursorPosition().line(); const int endLine = multiLineBlockMode ? qMin(selectionRange.end().line(), lastLine()) : startLine; const int virtualColumn = toVirtualColumn(multiLineBlockMode ? selectionRange.end() : view->cursorPosition()); for (int line = endLine; line >= startLine; --line) { Kate::TextLine textLine = m_buffer->plainLine(line); Q_ASSERT(textLine); const int column = fromVirtualColumn(line, virtualColumn); KTextEditor::Range r = KTextEditor::Range(KTextEditor::Cursor(line, column), qMin(chars.length(), textLine->length() - column)); // replace mode needs to know what was removed so it can be restored with backspace if (oldCur.column() < lineLength(line)) { QChar removed = characterAt(KTextEditor::Cursor(line, column)); view->currentInputMode()->overwrittenChar(removed); } removeText(r); } } chars = eventuallyReplaceTabs(view->cursorPosition(), chars); if (multiLineBlockMode) { KTextEditor::Range selectionRange = view->selectionRange(); const int startLine = qMax(0, selectionRange.start().line()); const int endLine = qMin(selectionRange.end().line(), lastLine()); const int column = toVirtualColumn(selectionRange.end()); for (int line = endLine; line >= startLine; --line) { editInsertText(line, fromVirtualColumn(line, column), chars); } int newSelectionColumn = toVirtualColumn(view->cursorPosition()); selectionRange.setRange(KTextEditor::Cursor(selectionRange.start().line(), fromVirtualColumn(selectionRange.start().line(), newSelectionColumn)) , KTextEditor::Cursor(selectionRange.end().line(), fromVirtualColumn(selectionRange.end().line(), newSelectionColumn))); view->setSelection(selectionRange); } else { insertText(view->cursorPosition(), chars); } /** * auto bracket handling for newly inserted text * we inserted a bracket? * => add the matching closing one to the view + input chars * try to preserve the cursor position */ bool skipAutobrace = closingBracket == QLatin1Char('\''); if (highlight() && skipAutobrace) { // skip adding ' in spellchecked areas, because those are text skipAutobrace = highlight()->spellCheckingRequiredForLocation(this, view->cursorPosition() - Cursor{0, 1}); } const auto cursorPos(view->cursorPosition()); if (!skipAutobrace && (closingBracket == QLatin1Char('\''))) { // skip auto quotes when these looks already balanced, bug 405089 Kate::TextLine textLine = m_buffer->plainLine(cursorPos.line()); // RegEx match quote, but not excaped quote, thanks to https://stackoverflow.com/a/11819111 const int count = textLine->text().left(cursorPos.column()).count(QRegularExpression(QStringLiteral("(?plainLine(cursorPos.line()); const int count = textLine->text().left(cursorPos.column()).count(QRegularExpression(QStringLiteral("(?document()->text({cursorPos, cursorPos + Cursor{0, 1}}).trimmed(); if (nextChar.isEmpty() || !nextChar.at(0).isLetterOrNumber()) { insertText(view->cursorPosition(), QString(closingBracket)); const auto insertedAt(view->cursorPosition()); view->setCursorPosition(cursorPos); m_currentAutobraceRange.reset(newMovingRange({cursorPos - Cursor{0, 1}, insertedAt}, KTextEditor::MovingRange::DoNotExpand)); connect(view, &View::cursorPositionChanged, this, &DocumentPrivate::checkCursorForAutobrace, Qt::UniqueConnection); // add bracket to chars inserted! needed for correct signals + indent chars.append(closingBracket); } m_currentAutobraceClosingChar = closingBracket; } // end edit session here, to have updated HL in userTypedChar! editEnd(); // trigger indentation KTextEditor::Cursor b(view->cursorPosition()); m_indenter->userTypedChar(view, b, chars.isEmpty() ? QChar() : chars.at(chars.length() - 1)); /** * inform the view about the original inserted chars */ view->slotTextInserted(view, oldCur, chars); return true; } void KTextEditor::DocumentPrivate::checkCursorForAutobrace(KTextEditor::View*, const KTextEditor::Cursor& newPos) { if ( m_currentAutobraceRange && ! m_currentAutobraceRange->toRange().contains(newPos) ) { m_currentAutobraceRange.clear(); } } -void KTextEditor::DocumentPrivate::newLine(KTextEditor::ViewPrivate *v) +void KTextEditor::DocumentPrivate::newLine(KTextEditor::ViewPrivate *v, + KTextEditor::DocumentPrivate::NewLineIndent indent) { editStart(); if (!v->config()->persistentSelection() && v->selection()) { v->removeSelectedText(); v->clearSelection(); } // query cursor position KTextEditor::Cursor c = v->cursorPosition(); if (c.line() > lastLine()) { c.setLine(lastLine()); } if (c.line() < 0) { c.setLine(0); } int ln = c.line(); Kate::TextLine textLine = plainKateTextLine(ln); if (c.column() > textLine->length()) { c.setColumn(textLine->length()); } // first: wrap line editWrapLine(c.line(), c.column()); // end edit session here, to have updated HL in userTypedChar! editEnd(); - // second: indent the new line, if needed... - m_indenter->userTypedChar(v, v->cursorPosition(), QLatin1Char('\n')); + // second: if "indent" is true, indent the new line, if needed... + if (indent == KTextEditor::DocumentPrivate::Indent) { + m_indenter->userTypedChar(v, v->cursorPosition(), QLatin1Char('\n')); + } } void KTextEditor::DocumentPrivate::transpose(const KTextEditor::Cursor &cursor) { Kate::TextLine textLine = m_buffer->plainLine(cursor.line()); if (!textLine || (textLine->length() < 2)) { return; } uint col = cursor.column(); if (col > 0) { col--; } if ((textLine->length() - col) < 2) { return; } uint line = cursor.line(); QString s; //clever swap code if first character on the line swap right&left //otherwise left & right s.append(textLine->at(col + 1)); s.append(textLine->at(col)); //do the swap // do it right, never ever manipulate a textline editStart(); editRemoveText(line, col, 2); editInsertText(line, col, s); editEnd(); } void KTextEditor::DocumentPrivate::backspace(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c) { if (!view->config()->persistentSelection() && view->selection()) { KTextEditor::Range range = view->selectionRange(); editStart(); // Avoid bad selection in case of undo if (view->blockSelection() && view->selection() && range.start().column() > 0 && toVirtualColumn(range.start()) == toVirtualColumn(range.end())) { // Remove one character before vertical selection line by expanding the selection range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1)); view->setSelection(range); } view->removeSelectedText(); editEnd(); return; } uint col = qMax(c.column(), 0); uint line = qMax(c.line(), 0); if ((col == 0) && (line == 0)) { return; } const Kate::TextLine textLine = m_buffer->plainLine(line); // don't forget this check!!!! really!!!! if (!textLine) { return; } if (col > 0) { bool useNextBlock = false; if (config()->backspaceIndents()) { // backspace indents: erase to next indent position int colX = textLine->toVirtualColumn(col, config()->tabWidth()); int pos = textLine->firstChar(); if (pos > 0) { pos = textLine->toVirtualColumn(pos, config()->tabWidth()); } if (pos < 0 || pos >= (int)colX) { // only spaces on left side of cursor indent(KTextEditor::Range(line, 0, line, 0), -1); } else { useNextBlock = true; } } if (!config()->backspaceIndents() || useNextBlock) { KTextEditor::Cursor beginCursor(line, 0); KTextEditor::Cursor endCursor(line, col); if (!view->config()->backspaceRemoveComposed()) { // Normal backspace behavior beginCursor.setColumn(col - 1); // move to left of surrogate pair if (!isValidTextPosition(beginCursor)) { Q_ASSERT(col >= 2); beginCursor.setColumn(col - 2); } } else { beginCursor.setColumn(view->textLayout(c)->previousCursorPosition(c.column())); } removeText(KTextEditor::Range(beginCursor, endCursor)); // in most cases cursor is moved by removeText, but we should do it manually // for past-end-of-line cursors in block mode view->setCursorPosition(beginCursor); } } else { // col == 0: wrap to previous line const Kate::TextLine textLine = m_buffer->plainLine(line - 1); if (line > 0 && textLine) { if (config()->wordWrap() && textLine->endsWith(QLatin1String(" "))) { // gg: in hard wordwrap mode, backspace must also eat the trailing space removeText(KTextEditor::Range(line - 1, textLine->length() - 1, line, 0)); } else { removeText(KTextEditor::Range(line - 1, textLine->length(), line, 0)); } } } if (m_currentAutobraceRange) { const auto r = m_currentAutobraceRange->toRange(); if (r.columnWidth() == 1 && view->cursorPosition() == r.start()) { // start parenthesis removed and range length is 1, remove end as well del(view, view->cursorPosition()); m_currentAutobraceRange.clear(); } } } void KTextEditor::DocumentPrivate::del(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c) { if (!view->config()->persistentSelection() && view->selection()) { KTextEditor::Range range = view->selectionRange(); editStart(); // Avoid bad selection in case of undo if (view->blockSelection() && toVirtualColumn(range.start()) == toVirtualColumn(range.end())) { // Remove one character after vertical selection line by expanding the selection range.setEnd(KTextEditor::Cursor(range.end().line(), range.end().column() + 1)); view->setSelection(range); } view->removeSelectedText(); editEnd(); return; } if (c.column() < (int) m_buffer->plainLine(c.line())->length()) { KTextEditor::Cursor endCursor(c.line(), view->textLayout(c)->nextCursorPosition(c.column())); removeText(KTextEditor::Range(c, endCursor)); } else if (c.line() < lastLine()) { removeText(KTextEditor::Range(c.line(), c.column(), c.line() + 1, 0)); } } void KTextEditor::DocumentPrivate::paste(KTextEditor::ViewPrivate *view, const QString &text) { static const QChar newLineChar(QLatin1Char('\n')); QString s = text; if (s.isEmpty()) { return; } int lines = s.count(newLineChar); m_undoManager->undoSafePoint(); editStart(); KTextEditor::Cursor pos = view->cursorPosition(); if (!view->config()->persistentSelection() && view->selection()) { pos = view->selectionRange().start(); if (view->blockSelection()) { pos = rangeOnLine(view->selectionRange(), pos.line()).start(); if (lines == 0) { s += newLineChar; s = s.repeated(view->selectionRange().numberOfLines() + 1); s.chop(1); } } view->removeSelectedText(); } if (config()->ovr()) { QStringList pasteLines = s.split(newLineChar); if (!view->blockSelection()) { int endColumn = (pasteLines.count() == 1 ? pos.column() : 0) + pasteLines.last().length(); removeText(KTextEditor::Range(pos, pos.line() + pasteLines.count() - 1, endColumn)); } else { int maxi = qMin(pos.line() + pasteLines.count(), this->lines()); for (int i = pos.line(); i < maxi; ++i) { int pasteLength = pasteLines.at(i - pos.line()).length(); removeText(KTextEditor::Range(i, pos.column(), i, qMin(pasteLength + pos.column(), lineLength(i)))); } } } insertText(pos, s, view->blockSelection()); editEnd(); // move cursor right for block select, as the user is moved right internal // even in that case, but user expects other behavior in block selection // mode ! // just let cursor stay, that was it before I changed to moving ranges! if (view->blockSelection()) { view->setCursorPositionInternal(pos); } if (config()->indentPastedText()) { KTextEditor::Range range = KTextEditor::Range(KTextEditor::Cursor(pos.line(), 0), KTextEditor::Cursor(pos.line() + lines, 0)); m_indenter->indent(view, range); } if (!view->blockSelection()) { emit charactersSemiInteractivelyInserted(pos, s); } m_undoManager->undoSafePoint(); } void KTextEditor::DocumentPrivate::indent(KTextEditor::Range range, int change) { if (!isReadWrite()) { return; } editStart(); m_indenter->changeIndent(range, change); editEnd(); } void KTextEditor::DocumentPrivate::align(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range) { m_indenter->indent(view, range); } void KTextEditor::DocumentPrivate::insertTab(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &) { if (!isReadWrite()) { return; } int lineLen = line(view->cursorPosition().line()).length(); KTextEditor::Cursor c = view->cursorPosition(); editStart(); if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); } else if (view->currentInputMode()->overwrite() && c.column() < lineLen) { KTextEditor::Range r = KTextEditor::Range(view->cursorPosition(), 1); // replace mode needs to know what was removed so it can be restored with backspace QChar removed = line(view->cursorPosition().line()).at(r.start().column()); view->currentInputMode()->overwrittenChar(removed); removeText(r); } c = view->cursorPosition(); editInsertText(c.line(), c.column(), QStringLiteral("\t")); editEnd(); } /* Remove a given string at the beginning of the current line. */ bool KTextEditor::DocumentPrivate::removeStringFromBeginning(int line, const QString &str) { Kate::TextLine textline = m_buffer->plainLine(line); KTextEditor::Cursor cursor(line, 0); bool there = textline->startsWith(str); if (!there) { cursor.setColumn(textline->firstChar()); there = textline->matchesAt(cursor.column(), str); } if (there) { // Remove some chars removeText(KTextEditor::Range(cursor, str.length())); } return there; } /* Remove a given string at the end of the current line. */ bool KTextEditor::DocumentPrivate::removeStringFromEnd(int line, const QString &str) { Kate::TextLine textline = m_buffer->plainLine(line); KTextEditor::Cursor cursor(line, 0); bool there = textline->endsWith(str); if (there) { cursor.setColumn(textline->length() - str.length()); } else { cursor.setColumn(textline->lastChar() - str.length() + 1); there = textline->matchesAt(cursor.column(), str); } if (there) { // Remove some chars removeText(KTextEditor::Range(cursor, str.length())); } return there; } /* Replace tabs by spaces in the given string, if enabled. */ QString KTextEditor::DocumentPrivate::eventuallyReplaceTabs(const KTextEditor::Cursor &cursorPos, const QString &str) const { const bool replacetabs = config()->replaceTabsDyn(); if ( ! replacetabs ) { return str; } const int indentWidth = config()->indentationWidth(); static const QLatin1Char tabChar('\t'); int column = cursorPos.column(); // The result will always be at least as long as the input QString result; result.reserve(str.size()); Q_FOREACH (const QChar ch, str) { if (ch == tabChar) { // Insert only enough spaces to align to the next indentWidth column // This fixes bug #340212 int spacesToInsert = indentWidth - (column % indentWidth); result += QStringLiteral(" ").repeated(spacesToInsert); column += spacesToInsert; } else { // Just keep all other typed characters as-is result += ch; ++column; } } return result; } /* Add to the current line a comment line mark at the beginning. */ void KTextEditor::DocumentPrivate::addStartLineCommentToSingleLine(int line, int attrib) { QString commentLineMark = highlight()->getCommentSingleLineStart(attrib); int pos = -1; if (highlight()->getCommentSingleLinePosition(attrib) == KSyntaxHighlighting::CommentPosition::StartOfLine) { pos = 0; commentLineMark += QLatin1Char(' '); } else { const Kate::TextLine l = kateTextLine(line); pos = l->firstChar(); } if (pos >= 0) { insertText(KTextEditor::Cursor(line, pos), commentLineMark); } } /* Remove from the current line a comment line mark at the beginning if there is one. */ bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSingleLine(int line, int attrib) { const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib); const QString longCommentMark = shortCommentMark + QLatin1Char(' '); editStart(); // Try to remove the long comment mark first bool removed = (removeStringFromBeginning(line, longCommentMark) || removeStringFromBeginning(line, shortCommentMark)); editEnd(); return removed; } /* Add to the current line a start comment mark at the beginning and a stop comment mark at the end. */ void KTextEditor::DocumentPrivate::addStartStopCommentToSingleLine(int line, int attrib) { const QString startCommentMark = highlight()->getCommentStart(attrib) + QLatin1Char(' '); const QString stopCommentMark = QLatin1Char(' ') + highlight()->getCommentEnd(attrib); editStart(); // Add the start comment mark insertText(KTextEditor::Cursor(line, 0), startCommentMark); // Go to the end of the line const int col = m_buffer->plainLine(line)->length(); // Add the stop comment mark insertText(KTextEditor::Cursor(line, col), stopCommentMark); editEnd(); } /* Remove from the current line a start comment mark at the beginning and a stop comment mark at the end. */ bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSingleLine(int line, int attrib) { const QString shortStartCommentMark = highlight()->getCommentStart(attrib); const QString longStartCommentMark = shortStartCommentMark + QLatin1Char(' '); const QString shortStopCommentMark = highlight()->getCommentEnd(attrib); const QString longStopCommentMark = QLatin1Char(' ') + shortStopCommentMark; editStart(); // Try to remove the long start comment mark first const bool removedStart = (removeStringFromBeginning(line, longStartCommentMark) || removeStringFromBeginning(line, shortStartCommentMark)); // Try to remove the long stop comment mark first const bool removedStop = removedStart && (removeStringFromEnd(line, longStopCommentMark) || removeStringFromEnd(line, shortStopCommentMark)); editEnd(); return (removedStart || removedStop); } /* Add to the current selection a start comment mark at the beginning and a stop comment mark at the end. */ void KTextEditor::DocumentPrivate::addStartStopCommentToSelection(KTextEditor::ViewPrivate *view, int attrib) { const QString startComment = highlight()->getCommentStart(attrib); const QString endComment = highlight()->getCommentEnd(attrib); KTextEditor::Range range = view->selectionRange(); if ((range.end().column() == 0) && (range.end().line() > 0)) { range.setEnd(KTextEditor::Cursor(range.end().line() - 1, lineLength(range.end().line() - 1))); } editStart(); if (!view->blockSelection()) { insertText(range.end(), endComment); insertText(range.start(), startComment); } else { for (int line = range.start().line(); line <= range.end().line(); line++) { KTextEditor::Range subRange = rangeOnLine(range, line); insertText(subRange.end(), endComment); insertText(subRange.start(), startComment); } } editEnd(); // selection automatically updated (MovingRange) } /* Add to the current selection a comment line mark at the beginning of each line. */ void KTextEditor::DocumentPrivate::addStartLineCommentToSelection(KTextEditor::ViewPrivate *view, int attrib) { //const QString commentLineMark = highlight()->getCommentSingleLineStart(attrib) + QLatin1Char(' '); int sl = view->selectionRange().start().line(); int el = view->selectionRange().end().line(); // if end of selection is in column 0 in last line, omit the last line if ((view->selectionRange().end().column() == 0) && (el > 0)) { el--; } editStart(); // For each line of the selection for (int z = el; z >= sl; z--) { //insertText (z, 0, commentLineMark); addStartLineCommentToSingleLine(z, attrib); } editEnd(); // selection automatically updated (MovingRange) } bool KTextEditor::DocumentPrivate::nextNonSpaceCharPos(int &line, int &col) { for (; line < (int)m_buffer->count(); line++) { Kate::TextLine textLine = m_buffer->plainLine(line); if (!textLine) { break; } col = textLine->nextNonSpaceChar(col); if (col != -1) { return true; // Next non-space char found } col = 0; } // No non-space char found line = -1; col = -1; return false; } bool KTextEditor::DocumentPrivate::previousNonSpaceCharPos(int &line, int &col) { while (true) { Kate::TextLine textLine = m_buffer->plainLine(line); if (!textLine) { break; } col = textLine->previousNonSpaceChar(col); if (col != -1) { return true; } if (line == 0) { return false; } --line; col = textLine->length(); } // No non-space char found line = -1; col = -1; return false; } /* Remove from the selection a start comment mark at the beginning and a stop comment mark at the end. */ bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib) { const QString startComment = highlight()->getCommentStart(attrib); const QString endComment = highlight()->getCommentEnd(attrib); int sl = qMax (0, view->selectionRange().start().line()); int el = qMin (view->selectionRange().end().line(), lastLine()); int sc = view->selectionRange().start().column(); int ec = view->selectionRange().end().column(); // The selection ends on the char before selectEnd if (ec != 0) { --ec; } else if (el > 0) { --el; ec = m_buffer->plainLine(el)->length() - 1; } const int startCommentLen = startComment.length(); const int endCommentLen = endComment.length(); // had this been perl or sed: s/^\s*$startComment(.+?)$endComment\s*/$2/ bool remove = nextNonSpaceCharPos(sl, sc) && m_buffer->plainLine(sl)->matchesAt(sc, startComment) && previousNonSpaceCharPos(el, ec) && ((ec - endCommentLen + 1) >= 0) && m_buffer->plainLine(el)->matchesAt(ec - endCommentLen + 1, endComment); if (remove) { editStart(); removeText(KTextEditor::Range(el, ec - endCommentLen + 1, el, ec + 1)); removeText(KTextEditor::Range(sl, sc, sl, sc + startCommentLen)); editEnd(); // selection automatically updated (MovingRange) } return remove; } bool KTextEditor::DocumentPrivate::removeStartStopCommentFromRegion(const KTextEditor::Cursor &start, const KTextEditor::Cursor &end, int attrib) { const QString startComment = highlight()->getCommentStart(attrib); const QString endComment = highlight()->getCommentEnd(attrib); const int startCommentLen = startComment.length(); const int endCommentLen = endComment.length(); const bool remove = m_buffer->plainLine(start.line())->matchesAt(start.column(), startComment) && m_buffer->plainLine(end.line())->matchesAt(end.column() - endCommentLen, endComment); if (remove) { editStart(); removeText(KTextEditor::Range(end.line(), end.column() - endCommentLen, end.line(), end.column())); removeText(KTextEditor::Range(start, startCommentLen)); editEnd(); } return remove; } /* Remove from the beginning of each line of the selection a start comment line mark. */ bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib) { const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib); const QString longCommentMark = shortCommentMark + QLatin1Char(' '); int sl = view->selectionRange().start().line(); int el = view->selectionRange().end().line(); if ((view->selectionRange().end().column() == 0) && (el > 0)) { el--; } bool removed = false; editStart(); // For each line of the selection for (int z = el; z >= sl; z--) { // Try to remove the long comment mark first removed = (removeStringFromBeginning(z, longCommentMark) || removeStringFromBeginning(z, shortCommentMark) || removed); } editEnd(); // selection automatically updated (MovingRange) return removed; } /* Comment or uncomment the selection or the current line if there is no selection. */ void KTextEditor::DocumentPrivate::comment(KTextEditor::ViewPrivate *v, uint line, uint column, int change) { // skip word wrap bug #105373 const bool skipWordWrap = wordWrap(); if (skipWordWrap) { setWordWrap(false); } bool hassel = v->selection(); int c = 0; if (hassel) { c = v->selectionRange().start().column(); } int startAttrib = 0; Kate::TextLine ln = kateTextLine(line); if (c < ln->length()) { startAttrib = ln->attribute(c); } else if (!ln->attributesList().isEmpty()) { startAttrib = ln->attributesList().back().attributeValue; } bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart(startAttrib).isEmpty()); bool hasStartStopCommentMark = (!(highlight()->getCommentStart(startAttrib).isEmpty()) && !(highlight()->getCommentEnd(startAttrib).isEmpty())); if (change > 0) { // comment if (!hassel) { if (hasStartLineCommentMark) { addStartLineCommentToSingleLine(line, startAttrib); } else if (hasStartStopCommentMark) { addStartStopCommentToSingleLine(line, startAttrib); } } else { // anders: prefer single line comment to avoid nesting probs // If the selection starts after first char in the first line // or ends before the last char of the last line, we may use // multiline comment markers. // TODO We should try to detect nesting. // - if selection ends at col 0, most likely she wanted that // line ignored const KTextEditor::Range sel = v->selectionRange(); if (hasStartStopCommentMark && (!hasStartLineCommentMark || ( (sel.start().column() > m_buffer->plainLine(sel.start().line())->firstChar()) || (sel.end().column() > 0 && sel.end().column() < (m_buffer->plainLine(sel.end().line())->length())) ))) { addStartStopCommentToSelection(v, startAttrib); } else if (hasStartLineCommentMark) { addStartLineCommentToSelection(v, startAttrib); } } } else { // uncomment bool removed = false; if (!hassel) { removed = (hasStartLineCommentMark && removeStartLineCommentFromSingleLine(line, startAttrib)) || (hasStartStopCommentMark && removeStartStopCommentFromSingleLine(line, startAttrib)); } else { // anders: this seems like it will work with above changes :) removed = (hasStartStopCommentMark && removeStartStopCommentFromSelection(v, startAttrib)) || (hasStartLineCommentMark && removeStartLineCommentFromSelection(v, startAttrib)); } // recursive call for toggle comment if (!removed && change == 0) { comment(v, line, column, 1); } } if (skipWordWrap) { setWordWrap(true); // see begin of function ::comment (bug #105373) } } void KTextEditor::DocumentPrivate::transform(KTextEditor::ViewPrivate *v, const KTextEditor::Cursor &c, KTextEditor::DocumentPrivate::TextTransform t) { if (v->selection()) { editStart(); // cache the selection and cursor, so we can be sure to restore. KTextEditor::Range selection = v->selectionRange(); KTextEditor::Range range(selection.start(), 0); while (range.start().line() <= selection.end().line()) { int start = 0; int end = lineLength(range.start().line()); if (range.start().line() == selection.start().line() || v->blockSelection()) { start = selection.start().column(); } if (range.start().line() == selection.end().line() || v->blockSelection()) { end = selection.end().column(); } if (start > end) { int swapCol = start; start = end; end = swapCol; } range.setStart(KTextEditor::Cursor(range.start().line(), start)); range.setEnd(KTextEditor::Cursor(range.end().line(), end)); QString s = text(range); QString old = s; if (t == Uppercase) { s = s.toUpper(); } else if (t == Lowercase) { s = s.toLower(); } else { // Capitalize Kate::TextLine l = m_buffer->plainLine(range.start().line()); int p(0); while (p < s.length()) { // If bol or the character before is not in a word, up this one: // 1. if both start and p is 0, upper char. // 2. if blockselect or first line, and p == 0 and start-1 is not in a word, upper // 3. if p-1 is not in a word, upper. if ((! range.start().column() && ! p) || ((range.start().line() == selection.start().line() || v->blockSelection()) && ! p && ! highlight()->isInWord(l->at(range.start().column() - 1))) || (p && ! highlight()->isInWord(s.at(p - 1))) ) { s[p] = s.at(p).toUpper(); } p++; } } if (s != old) { removeText(range); insertText(range.start(), s); } range.setBothLines(range.start().line() + 1); } editEnd(); // restore selection & cursor v->setSelection(selection); v->setCursorPosition(c); } else { // no selection editStart(); // get cursor KTextEditor::Cursor cursor = c; QString old = text(KTextEditor::Range(cursor, 1)); QString s; switch (t) { case Uppercase: s = old.toUpper(); break; case Lowercase: s = old.toLower(); break; case Capitalize: { Kate::TextLine l = m_buffer->plainLine(cursor.line()); while (cursor.column() > 0 && highlight()->isInWord(l->at(cursor.column() - 1), l->attribute(cursor.column() - 1))) { cursor.setColumn(cursor.column() - 1); } old = text(KTextEditor::Range(cursor, 1)); s = old.toUpper(); } break; default: break; } removeText(KTextEditor::Range(cursor, 1)); insertText(cursor, s); editEnd(); } } void KTextEditor::DocumentPrivate::joinLines(uint first, uint last) { // if ( first == last ) last += 1; editStart(); int line(first); while (first < last) { // Normalize the whitespace in the joined lines by making sure there's // always exactly one space between the joined lines // This cannot be done in editUnwrapLine, because we do NOT want this // behavior when deleting from the start of a line, just when explicitly // calling the join command Kate::TextLine l = kateTextLine(line); Kate::TextLine tl = kateTextLine(line + 1); if (!l || !tl) { editEnd(); return; } int pos = tl->firstChar(); if (pos >= 0) { if (pos != 0) { editRemoveText(line + 1, 0, pos); } if (!(l->length() == 0 || l->at(l->length() - 1).isSpace())) { editInsertText(line + 1, 0, QLatin1String(" ")); } } else { // Just remove the whitespace and let Kate handle the rest editRemoveText(line + 1, 0, tl->length()); } editUnWrapLine(line); first++; } editEnd(); } void KTextEditor::DocumentPrivate::tagLines(int start, int end) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->tagLines(start, end, true); } } void KTextEditor::DocumentPrivate::repaintViews(bool paintOnlyDirty) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->repaintText(paintOnlyDirty); } } /* Bracket matching uses the following algorithm: If in overwrite mode, match the bracket currently underneath the cursor. Otherwise, if the character to the left is a bracket, match it. Otherwise if the character to the right of the cursor is a bracket, match it. Otherwise, don't match anything. */ KTextEditor::Range KTextEditor::DocumentPrivate::findMatchingBracket(const KTextEditor::Cursor &start, int maxLines) { if (maxLines < 0) { return KTextEditor::Range::invalid(); } Kate::TextLine textLine = m_buffer->plainLine(start.line()); if (!textLine) { return KTextEditor::Range::invalid(); } KTextEditor::Range range(start, start); const QChar right = textLine->at(range.start().column()); const QChar left = textLine->at(range.start().column() - 1); QChar bracket; if (config()->ovr()) { if (isBracket(right)) { bracket = right; } else { return KTextEditor::Range::invalid(); } } else if (isBracket(right)) { bracket = right; } else if (isBracket(left)) { range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1)); bracket = left; } else { return KTextEditor::Range::invalid(); } const QChar opposite = matchingBracket(bracket); if (opposite.isNull()) { return KTextEditor::Range::invalid(); } const int searchDir = isStartBracket(bracket) ? 1 : -1; uint nesting = 0; const int minLine = qMax(range.start().line() - maxLines, 0); const int maxLine = qMin(range.start().line() + maxLines, documentEnd().line()); range.setEnd(range.start()); KTextEditor::DocumentCursor cursor(this); cursor.setPosition(range.start()); int validAttr = kateTextLine(cursor.line())->attribute(cursor.column()); while (cursor.line() >= minLine && cursor.line() <= maxLine) { if (!cursor.move(searchDir)) { return KTextEditor::Range::invalid(); } Kate::TextLine textLine = kateTextLine(cursor.line()); if (textLine->attribute(cursor.column()) == validAttr) { // Check for match QChar c = textLine->at(cursor.column()); if (c == opposite) { if (nesting == 0) { if (searchDir > 0) { // forward range.setEnd(cursor.toCursor()); } else { range.setStart(cursor.toCursor()); } return range; } nesting--; } else if (c == bracket) { nesting++; } } } return KTextEditor::Range::invalid(); } // helper: remove \r and \n from visible document name (bug #170876) inline static QString removeNewLines(const QString &str) { QString tmp(str); return tmp.replace(QLatin1String("\r\n"), QLatin1String(" ")) .replace(QLatin1Char('\r'), QLatin1Char(' ')) .replace(QLatin1Char('\n'), QLatin1Char(' ')); } void KTextEditor::DocumentPrivate::updateDocName() { // if the name is set, and starts with FILENAME, it should not be changed! if (! url().isEmpty() && (m_docName == removeNewLines(url().fileName()) || m_docName.startsWith(removeNewLines(url().fileName()) + QLatin1String(" (")))) { return; } int count = -1; foreach (KTextEditor::DocumentPrivate *doc, KTextEditor::EditorPrivate::self()->kateDocuments()) { if ((doc != this) && (doc->url().fileName() == url().fileName())) if (doc->m_docNameNumber > count) { count = doc->m_docNameNumber; } } m_docNameNumber = count + 1; QString oldName = m_docName; m_docName = removeNewLines(url().fileName()); m_isUntitled = m_docName.isEmpty(); if (m_isUntitled) { m_docName = i18n("Untitled"); } if (m_docNameNumber > 0) { m_docName = QString(m_docName + QLatin1String(" (%1)")).arg(m_docNameNumber + 1); } /** * avoid to emit this, if name doesn't change! */ if (oldName != m_docName) { emit documentNameChanged(this); } } void KTextEditor::DocumentPrivate::slotModifiedOnDisk(KTextEditor::View * /*v*/) { if (url().isEmpty() || !m_modOnHd) { return; } if (!isModified() && isAutoReload()) { onModOnHdAutoReload(); return; } if (!m_fileChangedDialogsActivated || m_modOnHdHandler) { return; } // don't ask the user again and again the same thing if (m_modOnHdReason == m_prevModOnHdReason) { return; } m_prevModOnHdReason = m_modOnHdReason; m_modOnHdHandler = new KateModOnHdPrompt(this, m_modOnHdReason, reasonedMOHString()); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::saveAsTriggered, this, &DocumentPrivate::onModOnHdSaveAs); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::closeTriggered, this, &DocumentPrivate::onModOnHdClose); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::reloadTriggered, this, &DocumentPrivate::onModOnHdReload); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::autoReloadTriggered, this, &DocumentPrivate::onModOnHdAutoReload); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::ignoreTriggered, this, &DocumentPrivate::onModOnHdIgnore); } void KTextEditor::DocumentPrivate::onModOnHdSaveAs() { m_modOnHd = false; QWidget *parentWidget(dialogParent()); const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File"), url()); if (!res.isEmpty()) { if (! saveAs(res)) { KMessageBox::error(parentWidget, i18n("Save failed")); m_modOnHd = true; } else { delete m_modOnHdHandler; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); } } else { // the save as dialog was canceled, we are still modified on disk m_modOnHd = true; } } void KTextEditor::DocumentPrivate::onModOnHdClose() { // avoid prompt in closeUrl() m_fileChangedDialogsActivated = false; // close the file without prompt confirmation closeUrl(); // Useful for kate only closeDocumentInApplication(); } void KTextEditor::DocumentPrivate::onModOnHdReload() { m_modOnHd = false; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); documentReload(); delete m_modOnHdHandler; } void KTextEditor::DocumentPrivate::autoReloadToggled(bool b) { m_autoReloadMode->setChecked(b); if (b) { connect(&m_modOnHdTimer, &QTimer::timeout, this, &DocumentPrivate::onModOnHdAutoReload); } else { disconnect(&m_modOnHdTimer, &QTimer::timeout, this, &DocumentPrivate::onModOnHdAutoReload); } } bool KTextEditor::DocumentPrivate::isAutoReload() { return m_autoReloadMode->isChecked(); } void KTextEditor::DocumentPrivate::delayAutoReload() { if (isAutoReload()) { m_autoReloadThrottle.start(); } } void KTextEditor::DocumentPrivate::onModOnHdAutoReload() { if (m_modOnHdHandler) { delete m_modOnHdHandler; autoReloadToggled(true); } if (!isAutoReload()) { return; } if (m_modOnHd && !m_reloading && !m_autoReloadThrottle.isActive()) { m_modOnHd = false; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); documentReload(); m_autoReloadThrottle.start(); } } void KTextEditor::DocumentPrivate::onModOnHdIgnore() { // ignore as long as m_prevModOnHdReason == m_modOnHdReason delete m_modOnHdHandler; } void KTextEditor::DocumentPrivate::setModifiedOnDisk(ModifiedOnDiskReason reason) { m_modOnHdReason = reason; m_modOnHd = (reason != OnDiskUnmodified); emit modifiedOnDisk(this, (reason != OnDiskUnmodified), reason); } class KateDocumentTmpMark { public: QString line; KTextEditor::Mark mark; }; void KTextEditor::DocumentPrivate::setModifiedOnDiskWarning(bool on) { m_fileChangedDialogsActivated = on; } bool KTextEditor::DocumentPrivate::documentReload() { if (url().isEmpty()) { return false; } // typically, the message for externally modified files is visible. Since it // does not make sense showing an additional dialog, just hide the message. delete m_modOnHdHandler; emit aboutToReload(this); QList tmp; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { KateDocumentTmpMark m; m.line = line(i.value()->line); m.mark = *i.value(); tmp.append(m); } const QString oldMode = mode(); const bool byUser = m_fileTypeSetByUser; const QString hl_mode = highlightingMode(); m_storedVariables.clear(); // save cursor positions for all views QHash cursorPositions; for (auto it = m_views.constBegin(); it != m_views.constEnd(); ++it) { auto v = it.value(); cursorPositions.insert(v, v->cursorPosition()); } m_reloading = true; KTextEditor::DocumentPrivate::openUrl(url()); // reset some flags only valid for one reload! m_userSetEncodingForNextReload = false; // restore cursor positions for all views for (auto it = m_views.constBegin(); it != m_views.constEnd(); ++it) { auto v = it.value(); setActiveView(v); v->setCursorPosition(cursorPositions.value(v)); if (v->isVisible()) { v->repaintText(false); } } for (int z = 0; z < tmp.size(); z++) { if (z < lines()) { if (line(tmp.at(z).mark.line) == tmp.at(z).line) { setMark(tmp.at(z).mark.line, tmp.at(z).mark.type); } } } if (byUser) { setMode(oldMode); } setHighlightingMode(hl_mode); emit reloaded(this); return true; } bool KTextEditor::DocumentPrivate::documentSave() { if (!url().isValid() || !isReadWrite()) { return documentSaveAs(); } return save(); } bool KTextEditor::DocumentPrivate::documentSaveAs() { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url()); if (saveUrl.isEmpty()) { return false; } return saveAs(saveUrl); } bool KTextEditor::DocumentPrivate::documentSaveAsWithEncoding(const QString &encoding) { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url()); if (saveUrl.isEmpty()) { return false; } setEncoding(encoding); return saveAs(saveUrl); } bool KTextEditor::DocumentPrivate::documentSaveCopyAs() { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save Copy of File"), url()); if (saveUrl.isEmpty()) { return false; } QTemporaryFile file; if (!file.open()) { return false; } if (!m_buffer->saveFile(file.fileName())) { KMessageBox::error(dialogParent(), i18n("The document could not be saved, as it was not possible to write to %1.\n\nCheck that you have write access to this file or that enough disk space is available.", this->url().toDisplayString(QUrl::PreferLocalFile))); return false; } // get the right permissions, start with safe default KIO::StatJob *statJob = KIO::stat(url(), KIO::StatJob::SourceSide, 2); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); int permissions = -1; if (statJob->exec()) { permissions = KFileItem(statJob->statResult(), url()).permissions(); } // KIO move, important: allow overwrite, we checked above! KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file.fileName()), saveUrl, permissions, KIO::Overwrite); KJobWidgets::setWindow(job, QApplication::activeWindow()); return job->exec(); } void KTextEditor::DocumentPrivate::setWordWrap(bool on) { config()->setWordWrap(on); } bool KTextEditor::DocumentPrivate::wordWrap() const { return config()->wordWrap(); } void KTextEditor::DocumentPrivate::setWordWrapAt(uint col) { config()->setWordWrapAt(col); } unsigned int KTextEditor::DocumentPrivate::wordWrapAt() const { return config()->wordWrapAt(); } void KTextEditor::DocumentPrivate::setPageUpDownMovesCursor(bool on) { config()->setPageUpDownMovesCursor(on); } bool KTextEditor::DocumentPrivate::pageUpDownMovesCursor() const { return config()->pageUpDownMovesCursor(); } //END bool KTextEditor::DocumentPrivate::setEncoding(const QString &e) { return m_config->setEncoding(e); } QString KTextEditor::DocumentPrivate::encoding() const { return m_config->encoding(); } void KTextEditor::DocumentPrivate::updateConfig() { m_undoManager->updateConfig(); // switch indenter if needed and update config.... m_indenter->setMode(m_config->indentationMode()); m_indenter->updateConfig(); // set tab width there, too m_buffer->setTabWidth(config()->tabWidth()); // update all views, does tagAll and updateView... foreach (KTextEditor::ViewPrivate *view, m_views) { view->updateDocumentConfig(); } // update on-the-fly spell checking as spell checking defaults might have changes if (m_onTheFlyChecker) { m_onTheFlyChecker->updateConfig(); } emit configChanged(); } //BEGIN Variable reader // "local variable" feature by anders, 2003 /* TODO add config options (how many lines to read, on/off) add interface for plugins/apps to set/get variables add view stuff */ void KTextEditor::DocumentPrivate::readVariables(bool onlyViewAndRenderer) { if (!onlyViewAndRenderer) { m_config->configStart(); } // views! KTextEditor::ViewPrivate *v; foreach (v, m_views) { v->config()->configStart(); v->renderer()->config()->configStart(); } // read a number of lines in the top/bottom of the document for (int i = 0; i < qMin(9, lines()); ++i) { readVariableLine(line(i), onlyViewAndRenderer); } if (lines() > 10) { for (int i = qMax(10, lines() - 10); i < lines(); i++) { readVariableLine(line(i), onlyViewAndRenderer); } } if (!onlyViewAndRenderer) { m_config->configEnd(); } foreach (v, m_views) { v->config()->configEnd(); v->renderer()->config()->configEnd(); } } void KTextEditor::DocumentPrivate::readVariableLine(QString t, bool onlyViewAndRenderer) { static const QRegularExpression kvLine(QStringLiteral("kate:(.*)")); static const QRegularExpression kvLineWildcard(QStringLiteral("kate-wildcard\\((.*)\\):(.*)")); static const QRegularExpression kvLineMime(QStringLiteral("kate-mimetype\\((.*)\\):(.*)")); static const QRegularExpression kvVar(QStringLiteral("([\\w\\-]+)\\s+([^;]+)")); // simple check first, no regex // no kate inside, no vars, simple... if (!t.contains(QLatin1String("kate"))) { return; } // found vars, if any QString s; // now, try first the normal ones auto match = kvLine.match(t); if (match.hasMatch()) { s = match.captured(1); //qCDebug(LOG_KTE) << "normal variable line kate: matched: " << s; } else if ((match = kvLineWildcard.match(t)).hasMatch()) { // regex given const QStringList wildcards(match.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); const QString nameOfFile = url().fileName(); bool found = false; foreach (const QString &pattern, wildcards) { QRegExp wildcard(pattern, Qt::CaseSensitive, QRegExp::Wildcard); found = wildcard.exactMatch(nameOfFile); if (found) { break; } } // nothing usable found... if (!found) { return; } s = match.captured(2); //qCDebug(LOG_KTE) << "guarded variable line kate-wildcard: matched: " << s; } else if ((match = kvLineMime.match(t)).hasMatch()) { // mime-type given const QStringList types(match.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); // no matching type found if (!types.contains(mimeType())) { return; } s = match.captured(2); //qCDebug(LOG_KTE) << "guarded variable line kate-mimetype: matched: " << s; } else { // nothing found return; } // view variable names static const auto vvl = { QLatin1String("dynamic-word-wrap") , QLatin1String("dynamic-word-wrap-indicators") , QLatin1String("line-numbers") , QLatin1String("icon-border") , QLatin1String("folding-markers") , QLatin1String("folding-preview") , QLatin1String("bookmark-sorting") , QLatin1String("auto-center-lines") , QLatin1String("icon-bar-color") , QLatin1String("scrollbar-minimap") , QLatin1String("scrollbar-preview") // renderer , QLatin1String("background-color") , QLatin1String("selection-color") , QLatin1String("current-line-color") , QLatin1String("bracket-highlight-color") , QLatin1String("word-wrap-marker-color") , QLatin1String("font") , QLatin1String("font-size") , QLatin1String("scheme") }; int spaceIndent = -1; // for backward compatibility; see below bool replaceTabsSet = false; int startPos(0); QString var, val; while ((match = kvVar.match(s, startPos)).hasMatch()) { startPos = match.capturedEnd(0); var = match.captured(1); val = match.captured(2).trimmed(); bool state; // store booleans here int n; // store ints here // only apply view & renderer config stuff if (onlyViewAndRenderer) { if (contains(vvl, var)) { // FIXME define above setViewVariable(var, val); } } else { // BOOL SETTINGS if (var == QLatin1String("word-wrap") && checkBoolValue(val, &state)) { setWordWrap(state); // ??? FIXME CHECK } // KateConfig::configFlags // FIXME should this be optimized to only a few calls? how? else if (var == QLatin1String("backspace-indents") && checkBoolValue(val, &state)) { m_config->setBackspaceIndents(state); } else if (var == QLatin1String("indent-pasted-text") && checkBoolValue(val, &state)) { m_config->setIndentPastedText(state); } else if (var == QLatin1String("replace-tabs") && checkBoolValue(val, &state)) { m_config->setReplaceTabsDyn(state); replaceTabsSet = true; // for backward compatibility; see below } else if (var == QLatin1String("remove-trailing-space") && checkBoolValue(val, &state)) { qCWarning(LOG_KTE) << i18n("Using deprecated modeline 'remove-trailing-space'. " "Please replace with 'remove-trailing-spaces modified;', see " "https://docs.kde.org/stable5/en/applications/katepart/config-variables.html#variable-remove-trailing-spaces"); m_config->setRemoveSpaces(state ? 1 : 0); } else if (var == QLatin1String("replace-trailing-space-save") && checkBoolValue(val, &state)) { qCWarning(LOG_KTE) << i18n("Using deprecated modeline 'replace-trailing-space-save'. " "Please replace with 'remove-trailing-spaces all;', see " "https://docs.kde.org/stable5/en/applications/katepart/config-variables.html#variable-remove-trailing-spaces"); m_config->setRemoveSpaces(state ? 2 : 0); } else if (var == QLatin1String("overwrite-mode") && checkBoolValue(val, &state)) { m_config->setOvr(state); } else if (var == QLatin1String("keep-extra-spaces") && checkBoolValue(val, &state)) { m_config->setKeepExtraSpaces(state); } else if (var == QLatin1String("tab-indents") && checkBoolValue(val, &state)) { m_config->setTabIndents(state); } else if (var == QLatin1String("show-tabs") && checkBoolValue(val, &state)) { m_config->setShowTabs(state); } else if (var == QLatin1String("show-trailing-spaces") && checkBoolValue(val, &state)) { m_config->setShowSpaces(state ? KateDocumentConfig::Trailing : KateDocumentConfig::None); } else if (var == QLatin1String("space-indent") && checkBoolValue(val, &state)) { // this is for backward compatibility; see below spaceIndent = state; } else if (var == QLatin1String("smart-home") && checkBoolValue(val, &state)) { m_config->setSmartHome(state); } else if (var == QLatin1String("newline-at-eof") && checkBoolValue(val, &state)) { m_config->setNewLineAtEof(state); } // INTEGER SETTINGS else if (var == QLatin1String("tab-width") && checkIntValue(val, &n)) { m_config->setTabWidth(n); } else if (var == QLatin1String("indent-width") && checkIntValue(val, &n)) { m_config->setIndentationWidth(n); } else if (var == QLatin1String("indent-mode")) { m_config->setIndentationMode(val); } else if (var == QLatin1String("word-wrap-column") && checkIntValue(val, &n) && n > 0) { // uint, but hard word wrap at 0 will be no fun ;) m_config->setWordWrapAt(n); } // STRING SETTINGS else if (var == QLatin1String("eol") || var == QLatin1String("end-of-line")) { const auto l = { QLatin1String("unix"), QLatin1String("dos"), QLatin1String("mac") }; if ((n = indexOf(l, val.toLower())) != -1) { /** * set eol + avoid that it is overwritten by auto-detection again! * this fixes e.g. .kateconfig files with // kate: eol dos; to work, bug 365705 */ m_config->setEol(n); m_config->setAllowEolDetection(false); } } else if (var == QLatin1String("bom") || var == QLatin1String("byte-order-mark") || var == QLatin1String("byte-order-marker")) { if (checkBoolValue(val, &state)) { m_config->setBom(state); } } else if (var == QLatin1String("remove-trailing-spaces")) { val = val.toLower(); if (val == QLatin1String("1") || val == QLatin1String("modified") || val == QLatin1String("mod") || val == QLatin1String("+")) { m_config->setRemoveSpaces(1); } else if (val == QLatin1String("2") || val == QLatin1String("all") || val == QLatin1String("*")) { m_config->setRemoveSpaces(2); } else { m_config->setRemoveSpaces(0); } } else if (var == QLatin1String("syntax") || var == QLatin1String("hl")) { setHighlightingMode(val); } else if (var == QLatin1String("mode")) { setMode(val); } else if (var == QLatin1String("encoding")) { setEncoding(val); } else if (var == QLatin1String("default-dictionary")) { setDefaultDictionary(val); } else if (var == QLatin1String("automatic-spell-checking") && checkBoolValue(val, &state)) { onTheFlySpellCheckingEnabled(state); } // VIEW SETTINGS else if (contains(vvl, var)) { setViewVariable(var, val); } else { m_storedVariables.insert(var, val); } } } // Backward compatibility // If space-indent was set, but replace-tabs was not set, we assume // that the user wants to replace tabulators and set that flag. // If both were set, replace-tabs has precedence. // At this point spaceIndent is -1 if it was never set, // 0 if it was set to off, and 1 if it was set to on. // Note that if onlyViewAndRenderer was requested, spaceIndent is -1. if (!replaceTabsSet && spaceIndent >= 0) { m_config->setReplaceTabsDyn(spaceIndent > 0); } } void KTextEditor::DocumentPrivate::setViewVariable(QString var, QString val) { KTextEditor::ViewPrivate *v; bool state; int n; QColor c; foreach (v, m_views) { // First, try the new config interface QVariant help(val); // Special treatment to catch "on"/"off" if (checkBoolValue(val, &state)) { help = state; } if (v->config()->setValue(var, help)) { } else if (v->renderer()->config()->setValue(var, help)) { // No success? Go the old way } else if (var == QLatin1String("dynamic-word-wrap") && checkBoolValue(val, &state)) { v->config()->setDynWordWrap(state); } else if (var == QLatin1String("block-selection") && checkBoolValue(val, &state)) { v->setBlockSelection(state); //else if ( var = "dynamic-word-wrap-indicators" ) } else if (var == QLatin1String("icon-bar-color") && checkColorValue(val, c)) { v->renderer()->config()->setIconBarColor(c); } // RENDERER else if (var == QLatin1String("background-color") && checkColorValue(val, c)) { v->renderer()->config()->setBackgroundColor(c); } else if (var == QLatin1String("selection-color") && checkColorValue(val, c)) { v->renderer()->config()->setSelectionColor(c); } else if (var == QLatin1String("current-line-color") && checkColorValue(val, c)) { v->renderer()->config()->setHighlightedLineColor(c); } else if (var == QLatin1String("bracket-highlight-color") && checkColorValue(val, c)) { v->renderer()->config()->setHighlightedBracketColor(c); } else if (var == QLatin1String("word-wrap-marker-color") && checkColorValue(val, c)) { v->renderer()->config()->setWordWrapMarkerColor(c); } else if (var == QLatin1String("font") || (checkIntValue(val, &n) && var == QLatin1String("font-size"))) { QFont _f(v->renderer()->config()->font()); if (var == QLatin1String("font")) { _f.setFamily(val); _f.setFixedPitch(QFont(val).fixedPitch()); } else { _f.setPointSize(n); } v->renderer()->config()->setFont(_f); } else if (var == QLatin1String("scheme")) { v->renderer()->config()->setSchema(val); } } } bool KTextEditor::DocumentPrivate::checkBoolValue(QString val, bool *result) { val = val.trimmed().toLower(); static const auto trueValues = { QLatin1String("1"), QLatin1String("on"), QLatin1String("true") }; if (contains(trueValues, val)) { *result = true; return true; } static const auto falseValues = { QLatin1String("0"), QLatin1String("off"), QLatin1String("false") }; if (contains(falseValues, val)) { *result = false; return true; } return false; } bool KTextEditor::DocumentPrivate::checkIntValue(QString val, int *result) { bool ret(false); *result = val.toInt(&ret); return ret; } bool KTextEditor::DocumentPrivate::checkColorValue(QString val, QColor &c) { c.setNamedColor(val); return c.isValid(); } // KTextEditor::variable QString KTextEditor::DocumentPrivate::variable(const QString &name) const { return m_storedVariables.value(name, QString()); } void KTextEditor::DocumentPrivate::setVariable(const QString &name, const QString &value) { QString s = QStringLiteral("kate: "); s.append(name); s.append(QLatin1Char(' ')); s.append(value); readVariableLine(s); } //END void KTextEditor::DocumentPrivate::slotModOnHdDirty(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskModified)) { m_modOnHd = true; m_modOnHdReason = OnDiskModified; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotModOnHdCreated(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskCreated)) { m_modOnHd = true; m_modOnHdReason = OnDiskCreated; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotModOnHdDeleted(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskDeleted)) { m_modOnHd = true; m_modOnHdReason = OnDiskDeleted; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotDelayedHandleModOnHd() { // compare git hash with the one we have (if we have one) const QByteArray oldDigest = checksum(); if (!oldDigest.isEmpty() && !url().isEmpty() && url().isLocalFile()) { /** * if current checksum == checksum of new file => unmodified */ if (m_modOnHdReason != OnDiskDeleted && createDigest() && oldDigest == checksum()) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; } #if LIBGIT2_FOUND /** * if still modified, try to take a look at git * skip that, if document is modified! * only do that, if the file is still there, else reload makes no sense! */ if (m_modOnHd && !isModified() && QFile::exists(url().toLocalFile())) { /** * try to discover the git repo of this file * libgit2 docs state that UTF-8 is the right encoding, even on windows * I hope that is correct! */ git_repository *repository = nullptr; const QByteArray utf8Path = url().toLocalFile().toUtf8(); if (git_repository_open_ext(&repository, utf8Path.constData(), 0, nullptr) == 0) { /** * if we have repo, convert the git hash to an OID */ git_oid oid; if (git_oid_fromstr(&oid, oldDigest.toHex().data()) == 0) { /** * finally: is there a blob for this git hash? */ git_blob *blob = nullptr; if (git_blob_lookup(&blob, repository, &oid) == 0) { /** * this hash exists still in git => just reload */ m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; documentReload(); } git_blob_free(blob); } } git_repository_free(repository); } #endif } /** * emit our signal to the outside! */ emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } QByteArray KTextEditor::DocumentPrivate::checksum() const { return m_buffer->digest(); } bool KTextEditor::DocumentPrivate::createDigest() { QByteArray digest; if (url().isLocalFile()) { QFile f(url().toLocalFile()); if (f.open(QIODevice::ReadOnly)) { // init the hash with the git header QCryptographicHash crypto(QCryptographicHash::Sha1); const QString header = QStringLiteral("blob %1").arg(f.size()); crypto.addData(header.toLatin1() + '\0'); while (!f.atEnd()) { crypto.addData(f.read(256 * 1024)); } digest = crypto.result(); } } /** * set new digest */ m_buffer->setDigest(digest); return !digest.isEmpty(); } QString KTextEditor::DocumentPrivate::reasonedMOHString() const { // squeeze path const QString str = KStringHandler::csqueeze(url().toDisplayString(QUrl::PreferLocalFile)); switch (m_modOnHdReason) { case OnDiskModified: return i18n("The file '%1' was modified by another program.", str); break; case OnDiskCreated: return i18n("The file '%1' was created by another program.", str); break; case OnDiskDeleted: return i18n("The file '%1' was deleted by another program.", str); break; default: return QString(); } Q_UNREACHABLE(); return QString(); } void KTextEditor::DocumentPrivate::removeTrailingSpaces() { const int remove = config()->removeSpaces(); if (remove == 0) { return; } // temporarily disable static word wrap (see bug #328900) const bool wordWrapEnabled = config()->wordWrap(); if (wordWrapEnabled) { setWordWrap(false); } editStart(); for (int line = 0; line < lines(); ++line) { Kate::TextLine textline = plainKateTextLine(line); // remove trailing spaces in entire document, remove = 2 // remove trailing spaces of touched lines, remove = 1 // remove trailing spaces of lines saved on disk, remove = 1 if (remove == 2 || textline->markedAsModified() || textline->markedAsSavedOnDisk()) { const int p = textline->lastChar() + 1; const int l = textline->length() - p; if (l > 0) { editRemoveText(line, p, l); } } } editEnd(); // enable word wrap again, if it was enabled (see bug #328900) if (wordWrapEnabled) { setWordWrap(true); // see begin of this function } } void KTextEditor::DocumentPrivate::updateFileType(const QString &newType, bool user) { if (user || !m_fileTypeSetByUser) { if (!newType.isEmpty()) { // remember that we got set by user m_fileTypeSetByUser = user; m_fileType = newType; m_config->configStart(); // NOTE: if the user changes the Mode, the Highlighting also changes. // m_hlSetByUser avoids resetting the highlight when saving the document, if // the current hl isn't stored (eg, in sftp:// or fish:// files) (see bug #407763) if ((user || !m_hlSetByUser) && !KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).hl.isEmpty()) { int hl(KateHlManager::self()->nameFind(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).hl)); if (hl >= 0) { m_buffer->setHighlight(hl); } } /** * set the indentation mode, if any in the mode... * and user did not set it before! * NOTE: KateBuffer::setHighlight() also sets the indentation. */ if (!m_indenterSetByUser && !KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).indenter.isEmpty()) { config()->setIndentationMode(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).indenter); } // views! KTextEditor::ViewPrivate *v; foreach (v, m_views) { v->config()->configStart(); v->renderer()->config()->configStart(); } bool bom_settings = false; if (m_bomSetByUser) { bom_settings = m_config->bom(); } readVariableLine(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).varLine); if (m_bomSetByUser) { m_config->setBom(bom_settings); } m_config->configEnd(); foreach (v, m_views) { v->config()->configEnd(); v->renderer()->config()->configEnd(); } } } // fixme, make this better... emit modeChanged(this); } void KTextEditor::DocumentPrivate::slotQueryClose_save(bool *handled, bool *abortClosing) { *handled = true; *abortClosing = true; if (this->url().isEmpty()) { QWidget *parentWidget(dialogParent()); const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File")); if (res.isEmpty()) { *abortClosing = true; return; } saveAs(res); *abortClosing = false; } else { save(); *abortClosing = false; } } //BEGIN KTextEditor::ConfigInterface // BEGIN ConfigInterface stff QStringList KTextEditor::DocumentPrivate::configKeys() const { /** * expose all internally registered keys of the KateDocumentConfig */ return m_config->configKeys(); } QVariant KTextEditor::DocumentPrivate::configValue(const QString &key) { /** * just dispatch to internal key => value lookup */ return m_config->value(key); } void KTextEditor::DocumentPrivate::setConfigValue(const QString &key, const QVariant &value) { /** * just dispatch to internal key + value set */ m_config->setValue(key, value); } //END KTextEditor::ConfigInterface KTextEditor::Cursor KTextEditor::DocumentPrivate::documentEnd() const { return KTextEditor::Cursor(lastLine(), lineLength(lastLine())); } bool KTextEditor::DocumentPrivate::replaceText(const KTextEditor::Range &range, const QString &s, bool block) { // TODO more efficient? editStart(); bool changed = removeText(range, block); changed |= insertText(range.start(), s, block); editEnd(); return changed; } KateHighlighting *KTextEditor::DocumentPrivate::highlight() const { return m_buffer->highlight(); } Kate::TextLine KTextEditor::DocumentPrivate::kateTextLine(int i) { m_buffer->ensureHighlighted(i); return m_buffer->plainLine(i); } Kate::TextLine KTextEditor::DocumentPrivate::plainKateTextLine(int i) { return m_buffer->plainLine(i); } bool KTextEditor::DocumentPrivate::isEditRunning() const { return editIsRunning; } void KTextEditor::DocumentPrivate::setUndoMergeAllEdits(bool merge) { if (merge && m_undoMergeAllEdits) { // Don't add another undo safe point: it will override our current one, // meaning we'll need two undo's to get back there - which defeats the object! return; } m_undoManager->undoSafePoint(); m_undoManager->setAllowComplexMerge(merge); m_undoMergeAllEdits = merge; } //BEGIN KTextEditor::MovingInterface KTextEditor::MovingCursor *KTextEditor::DocumentPrivate::newMovingCursor(const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior) { return new Kate::TextCursor(buffer(), position, insertBehavior); } KTextEditor::MovingRange *KTextEditor::DocumentPrivate::newMovingRange(const KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior) { return new Kate::TextRange(buffer(), range, insertBehaviors, emptyBehavior); } qint64 KTextEditor::DocumentPrivate::revision() const { return m_buffer->history().revision(); } qint64 KTextEditor::DocumentPrivate::lastSavedRevision() const { return m_buffer->history().lastSavedRevision(); } void KTextEditor::DocumentPrivate::lockRevision(qint64 revision) { m_buffer->history().lockRevision(revision); } void KTextEditor::DocumentPrivate::unlockRevision(qint64 revision) { m_buffer->history().unlockRevision(revision); } void KTextEditor::DocumentPrivate::transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision) { m_buffer->history().transformCursor(line, column, insertBehavior, fromRevision, toRevision); } void KTextEditor::DocumentPrivate::transformCursor(KTextEditor::Cursor &cursor, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision) { int line = cursor.line(), column = cursor.column(); m_buffer->history().transformCursor(line, column, insertBehavior, fromRevision, toRevision); cursor.setLine(line); cursor.setColumn(column); } void KTextEditor::DocumentPrivate::transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision) { m_buffer->history().transformRange(range, insertBehaviors, emptyBehavior, fromRevision, toRevision); } //END //BEGIN KTextEditor::AnnotationInterface void KTextEditor::DocumentPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) { KTextEditor::AnnotationModel *oldmodel = m_annotationModel; m_annotationModel = model; emit annotationModelChanged(oldmodel, m_annotationModel); } KTextEditor::AnnotationModel *KTextEditor::DocumentPrivate::annotationModel() const { return m_annotationModel; } //END KTextEditor::AnnotationInterface //TAKEN FROM kparts.h bool KTextEditor::DocumentPrivate::queryClose() { if (!isReadWrite() || !isModified()) { return true; } QString docName = documentName(); int res = KMessageBox::warningYesNoCancel(dialogParent(), i18n("The document \"%1\" has been modified.\n" "Do you want to save your changes or discard them?", docName), i18n("Close Document"), KStandardGuiItem::save(), KStandardGuiItem::discard()); bool abortClose = false; bool handled = false; switch (res) { case KMessageBox::Yes : sigQueryClose(&handled, &abortClose); if (!handled) { if (url().isEmpty()) { QUrl url = QFileDialog::getSaveFileUrl(dialogParent()); if (url.isEmpty()) { return false; } saveAs(url); } else { save(); } } else if (abortClose) { return false; } return waitSaveComplete(); case KMessageBox::No : return true; default : // case KMessageBox::Cancel : return false; } } void KTextEditor::DocumentPrivate::slotStarted(KIO::Job *job) { /** * if we are idle before, we are now loading! */ if (m_documentState == DocumentIdle) { m_documentState = DocumentLoading; } /** * if loading: * - remember pre loading read-write mode * if remote load: * - set to read-only * - trigger possible message */ if (m_documentState == DocumentLoading) { /** * remember state */ m_readWriteStateBeforeLoading = isReadWrite(); /** * perhaps show loading message, but wait one second */ if (job) { /** * only read only if really remote file! */ setReadWrite(false); /** * perhaps some message about loading in one second! * remember job pointer, we want to be able to kill it! */ m_loadingJob = job; QTimer::singleShot(1000, this, SLOT(slotTriggerLoadingMessage())); } } } void KTextEditor::DocumentPrivate::slotCompleted() { /** * if were loading, reset back to old read-write mode before loading * and kill the possible loading message */ if (m_documentState == DocumentLoading) { setReadWrite(m_readWriteStateBeforeLoading); delete m_loadingMessage; } /** * Emit signal that we saved the document, if needed */ if (m_documentState == DocumentSaving || m_documentState == DocumentSavingAs) { emit documentSavedOrUploaded(this, m_documentState == DocumentSavingAs); } /** * back to idle mode */ m_documentState = DocumentIdle; m_reloading = false; } void KTextEditor::DocumentPrivate::slotCanceled() { /** * if were loading, reset back to old read-write mode before loading * and kill the possible loading message */ if (m_documentState == DocumentLoading) { setReadWrite(m_readWriteStateBeforeLoading); delete m_loadingMessage; showAndSetOpeningErrorAccess(); updateDocName(); } /** * back to idle mode */ m_documentState = DocumentIdle; m_reloading = false; } void KTextEditor::DocumentPrivate::slotTriggerLoadingMessage() { /** * no longer loading? * no message needed! */ if (m_documentState != DocumentLoading) { return; } /** * create message about file loading in progress */ delete m_loadingMessage; m_loadingMessage = new KTextEditor::Message(i18n("The file %2 is still loading.", url().toDisplayString(QUrl::PreferLocalFile), url().fileName())); m_loadingMessage->setPosition(KTextEditor::Message::TopInView); /** * if around job: add cancel action */ if (m_loadingJob) { QAction *cancel = new QAction(i18n("&Abort Loading"), nullptr); connect(cancel, SIGNAL(triggered()), this, SLOT(slotAbortLoading())); m_loadingMessage->addAction(cancel); } /** * really post message */ postMessage(m_loadingMessage); } void KTextEditor::DocumentPrivate::slotAbortLoading() { /** * no job, no work */ if (!m_loadingJob) { return; } /** * abort loading if any job * signal results! */ m_loadingJob->kill(KJob::EmitResult); m_loadingJob = nullptr; } void KTextEditor::DocumentPrivate::slotUrlChanged(const QUrl &url) { if (m_reloading) { // the URL is temporarily unset and then reset to the previous URL during reload // we do not want to notify the outside about this return; } Q_UNUSED(url); updateDocName(); emit documentUrlChanged(this); } bool KTextEditor::DocumentPrivate::save() { /** * no double save/load * we need to allow DocumentPreSavingAs here as state, as save is called in saveAs! */ if ((m_documentState != DocumentIdle) && (m_documentState != DocumentPreSavingAs)) { return false; } /** * if we are idle, we are now saving */ if (m_documentState == DocumentIdle) { m_documentState = DocumentSaving; } else { m_documentState = DocumentSavingAs; } /** * call back implementation for real work */ return KTextEditor::Document::save(); } bool KTextEditor::DocumentPrivate::saveAs(const QUrl &url) { /** * abort on bad URL * that is done in saveAs below, too * but we must check it here already to avoid messing up * as no signals will be send, then */ if (!url.isValid()) { return false; } /** * no double save/load */ if (m_documentState != DocumentIdle) { return false; } /** * we enter the pre save as phase */ m_documentState = DocumentPreSavingAs; /** * call base implementation for real work */ return KTextEditor::Document::saveAs(normalizeUrl(url)); } QString KTextEditor::DocumentPrivate::defaultDictionary() const { return m_defaultDictionary; } QList > KTextEditor::DocumentPrivate::dictionaryRanges() const { return m_dictionaryRanges; } void KTextEditor::DocumentPrivate::clearDictionaryRanges() { for (QList >::iterator i = m_dictionaryRanges.begin(); i != m_dictionaryRanges.end(); ++i) { delete(*i).first; } m_dictionaryRanges.clear(); if (m_onTheFlyChecker) { m_onTheFlyChecker->refreshSpellCheck(); } emit dictionaryRangesPresent(false); } void KTextEditor::DocumentPrivate::setDictionary(const QString &newDictionary, const KTextEditor::Range &range, bool blockmode) { if (blockmode) { for (int i = range.start().line(); i <= range.end().line(); ++i) { setDictionary(newDictionary, rangeOnLine(range, i)); } } else { setDictionary(newDictionary, range); } emit dictionaryRangesPresent(!m_dictionaryRanges.isEmpty()); } void KTextEditor::DocumentPrivate::setDictionary(const QString &newDictionary, const KTextEditor::Range &range) { KTextEditor::Range newDictionaryRange = range; if (!newDictionaryRange.isValid() || newDictionaryRange.isEmpty()) { return; } QList > newRanges; // all ranges is 'm_dictionaryRanges' are assumed to be mutually disjoint for (QList >::iterator i = m_dictionaryRanges.begin(); i != m_dictionaryRanges.end();) { qCDebug(LOG_KTE) << "new iteration" << newDictionaryRange; if (newDictionaryRange.isEmpty()) { break; } QPair pair = *i; QString dictionarySet = pair.second; KTextEditor::MovingRange *dictionaryRange = pair.first; qCDebug(LOG_KTE) << *dictionaryRange << dictionarySet; if (dictionaryRange->contains(newDictionaryRange) && newDictionary == dictionarySet) { qCDebug(LOG_KTE) << "dictionaryRange contains newDictionaryRange"; return; } if (newDictionaryRange.contains(*dictionaryRange)) { delete dictionaryRange; i = m_dictionaryRanges.erase(i); qCDebug(LOG_KTE) << "newDictionaryRange contains dictionaryRange"; continue; } KTextEditor::Range intersection = dictionaryRange->toRange().intersect(newDictionaryRange); if (!intersection.isEmpty() && intersection.isValid()) { if (dictionarySet == newDictionary) { // we don't have to do anything for 'intersection' // except cut off the intersection QList remainingRanges = KateSpellCheckManager::rangeDifference(newDictionaryRange, intersection); Q_ASSERT(remainingRanges.size() == 1); newDictionaryRange = remainingRanges.first(); ++i; qCDebug(LOG_KTE) << "dictionarySet == newDictionary"; continue; } QList remainingRanges = KateSpellCheckManager::rangeDifference(*dictionaryRange, intersection); for (QList::iterator j = remainingRanges.begin(); j != remainingRanges.end(); ++j) { KTextEditor::MovingRange *remainingRange = newMovingRange(*j, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); remainingRange->setFeedback(this); newRanges.push_back(QPair(remainingRange, dictionarySet)); } i = m_dictionaryRanges.erase(i); delete dictionaryRange; } else { ++i; } } m_dictionaryRanges += newRanges; if (!newDictionaryRange.isEmpty() && !newDictionary.isEmpty()) { // we don't add anything for the default dictionary KTextEditor::MovingRange *newDictionaryMovingRange = newMovingRange(newDictionaryRange, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); newDictionaryMovingRange->setFeedback(this); m_dictionaryRanges.push_back(QPair(newDictionaryMovingRange, newDictionary)); } if (m_onTheFlyChecker && !newDictionaryRange.isEmpty()) { m_onTheFlyChecker->refreshSpellCheck(newDictionaryRange); } } void KTextEditor::DocumentPrivate::setDefaultDictionary(const QString &dict) { if (m_defaultDictionary == dict) { return; } m_defaultDictionary = dict; if (m_onTheFlyChecker) { m_onTheFlyChecker->updateConfig(); refreshOnTheFlyCheck(); } emit defaultDictionaryChanged(this); } void KTextEditor::DocumentPrivate::onTheFlySpellCheckingEnabled(bool enable) { if (isOnTheFlySpellCheckingEnabled() == enable) { return; } if (enable) { Q_ASSERT(m_onTheFlyChecker == nullptr); m_onTheFlyChecker = new KateOnTheFlyChecker(this); } else { delete m_onTheFlyChecker; m_onTheFlyChecker = nullptr; } foreach (KTextEditor::ViewPrivate *view, m_views) { view->reflectOnTheFlySpellCheckStatus(enable); } } bool KTextEditor::DocumentPrivate::isOnTheFlySpellCheckingEnabled() const { return m_onTheFlyChecker != nullptr; } QString KTextEditor::DocumentPrivate::dictionaryForMisspelledRange(const KTextEditor::Range &range) const { if (!m_onTheFlyChecker) { return QString(); } else { return m_onTheFlyChecker->dictionaryForMisspelledRange(range); } } void KTextEditor::DocumentPrivate::clearMisspellingForWord(const QString &word) { if (m_onTheFlyChecker) { m_onTheFlyChecker->clearMisspellingForWord(word); } } void KTextEditor::DocumentPrivate::refreshOnTheFlyCheck(const KTextEditor::Range &range) { if (m_onTheFlyChecker) { m_onTheFlyChecker->refreshSpellCheck(range); } } void KTextEditor::DocumentPrivate::rangeInvalid(KTextEditor::MovingRange *movingRange) { deleteDictionaryRange(movingRange); } void KTextEditor::DocumentPrivate::rangeEmpty(KTextEditor::MovingRange *movingRange) { deleteDictionaryRange(movingRange); } void KTextEditor::DocumentPrivate::deleteDictionaryRange(KTextEditor::MovingRange *movingRange) { qCDebug(LOG_KTE) << "deleting" << movingRange; auto finder = [=] (const QPair& item) -> bool { return item.first == movingRange; }; auto it = std::find_if(m_dictionaryRanges.begin(), m_dictionaryRanges.end(), finder); if (it != m_dictionaryRanges.end()) { m_dictionaryRanges.erase(it); delete movingRange; } Q_ASSERT(std::find_if(m_dictionaryRanges.begin(), m_dictionaryRanges.end(), finder) == m_dictionaryRanges.end()); } bool KTextEditor::DocumentPrivate::containsCharacterEncoding(const KTextEditor::Range &range) { KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn; ++col) { int attr = textLine->attribute(col); const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr); if (!prefixStore.findPrefix(textLine, col).isEmpty()) { return true; } } } return false; } int KTextEditor::DocumentPrivate::computePositionWrtOffsets(const OffsetList &offsetList, int pos) { int previousOffset = 0; for (OffsetList::const_iterator i = offsetList.begin(); i != offsetList.end(); ++i) { if ((*i).first > pos) { break; } previousOffset = (*i).second; } return pos + previousOffset; } QString KTextEditor::DocumentPrivate::decodeCharacters(const KTextEditor::Range &range, KTextEditor::DocumentPrivate::OffsetList &decToEncOffsetList, KTextEditor::DocumentPrivate::OffsetList &encToDecOffsetList) { QString toReturn; KTextEditor::Cursor previous = range.start(); int decToEncCurrentOffset = 0, encToDecCurrentOffset = 0; int i = 0; int newI = 0; KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn;) { int attr = textLine->attribute(col); const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr); const QHash &characterEncodingsHash = highlighting->getCharacterEncodings(attr); QString matchingPrefix = prefixStore.findPrefix(textLine, col); if (!matchingPrefix.isEmpty()) { toReturn += text(KTextEditor::Range(previous, KTextEditor::Cursor(line, col))); const QChar &c = characterEncodingsHash.value(matchingPrefix); const bool isNullChar = c.isNull(); if (!c.isNull()) { toReturn += c; } i += matchingPrefix.length(); col += matchingPrefix.length(); previous = KTextEditor::Cursor(line, col); decToEncCurrentOffset = decToEncCurrentOffset - (isNullChar ? 0 : 1) + matchingPrefix.length(); encToDecCurrentOffset = encToDecCurrentOffset - matchingPrefix.length() + (isNullChar ? 0 : 1); newI += (isNullChar ? 0 : 1); decToEncOffsetList.push_back(QPair(newI, decToEncCurrentOffset)); encToDecOffsetList.push_back(QPair(i, encToDecCurrentOffset)); continue; } ++col; ++i; ++newI; } ++i; ++newI; } if (previous < range.end()) { toReturn += text(KTextEditor::Range(previous, range.end())); } return toReturn; } void KTextEditor::DocumentPrivate::replaceCharactersByEncoding(const KTextEditor::Range &range) { KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn;) { int attr = textLine->attribute(col); const QHash &reverseCharacterEncodingsHash = highlighting->getReverseCharacterEncodings(attr); QHash::const_iterator it = reverseCharacterEncodingsHash.find(textLine->at(col)); if (it != reverseCharacterEncodingsHash.end()) { replaceText(KTextEditor::Range(line, col, line, col + 1), *it); col += (*it).length(); continue; } ++col; } } } // // Highlighting information // KTextEditor::Attribute::Ptr KTextEditor::DocumentPrivate::attributeAt(const KTextEditor::Cursor &position) { KTextEditor::Attribute::Ptr attrib(new KTextEditor::Attribute()); KTextEditor::ViewPrivate *view = m_views.empty() ? nullptr : m_views.begin().value(); if (!view) { qCWarning(LOG_KTE) << "ATTENTION: cannot access lineAttributes() without any View (will be fixed eventually)"; return attrib; } Kate::TextLine kateLine = kateTextLine(position.line()); if (!kateLine) { return attrib; } *attrib = *view->renderer()->attribute(kateLine->attribute(position.column())); return attrib; } QStringList KTextEditor::DocumentPrivate::embeddedHighlightingModes() const { return highlight()->getEmbeddedHighlightingModes(); } QString KTextEditor::DocumentPrivate::highlightingModeAt(const KTextEditor::Cursor &position) { return highlight()->higlightingModeForLocation(this, position); } Kate::SwapFile *KTextEditor::DocumentPrivate::swapFile() { return m_swapfile; } /** * \return \c -1 if \c line or \c column invalid, otherwise one of * standard style attribute number */ int KTextEditor::DocumentPrivate::defStyleNum(int line, int column) { // Validate parameters to prevent out of range access if (line < 0 || line >= lines() || column < 0) { return -1; } // get highlighted line Kate::TextLine tl = kateTextLine(line); // make sure the textline is a valid pointer if (!tl) { return -1; } /** * either get char attribute or attribute of context still active at end of line */ int attribute = 0; if (column < tl->length()) { attribute = tl->attribute(column); } else if (column == tl->length()) { if (!tl->attributesList().isEmpty()) { attribute = tl->attributesList().back().attributeValue; } else { return -1; } } else { return -1; } return highlight()->defaultStyleForAttribute(attribute); } bool KTextEditor::DocumentPrivate::isComment(int line, int column) { const int defaultStyle = defStyleNum(line, column); return defaultStyle == KTextEditor::dsComment; } int KTextEditor::DocumentPrivate::findTouchedLine(int startLine, bool down) { const int offset = down ? 1 : -1; const int lineCount = lines(); while (startLine >= 0 && startLine < lineCount) { Kate::TextLine tl = m_buffer->plainLine(startLine); if (tl && (tl->markedAsModified() || tl->markedAsSavedOnDisk())) { return startLine; } startLine += offset; } return -1; } void KTextEditor::DocumentPrivate::setActiveTemplateHandler(KateTemplateHandler* handler) { // delete any active template handler delete m_activeTemplateHandler.data(); m_activeTemplateHandler = handler; } //BEGIN KTextEditor::MessageInterface bool KTextEditor::DocumentPrivate::postMessage(KTextEditor::Message *message) { // no message -> cancel if (!message) { return false; } // make sure the desired view belongs to this document if (message->view() && message->view()->document() != this) { qCWarning(LOG_KTE) << "trying to post a message to a view of another document:" << message->text(); return false; } message->setParent(this); message->setDocument(this); // if there are no actions, add a close action by default if widget does not auto-hide if (message->actions().count() == 0 && message->autoHide() < 0) { QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), nullptr); closeAction->setToolTip(i18n("Close message")); message->addAction(closeAction); } // make sure the message is registered even if no actions and no views exist m_messageHash[message] = QList >(); // reparent actions, as we want full control over when they are deleted foreach (QAction *action, message->actions()) { action->setParent(nullptr); m_messageHash[message].append(QSharedPointer(action)); } // post message to requested view, or to all views if (KTextEditor::ViewPrivate *view = qobject_cast(message->view())) { view->postMessage(message, m_messageHash[message]); } else { foreach (KTextEditor::ViewPrivate *view, m_views) { view->postMessage(message, m_messageHash[message]); } } // also catch if the user manually calls delete message connect(message, SIGNAL(closed(KTextEditor::Message*)), SLOT(messageDestroyed(KTextEditor::Message*))); return true; } void KTextEditor::DocumentPrivate::messageDestroyed(KTextEditor::Message *message) { // KTE:Message is already in destructor Q_ASSERT(m_messageHash.contains(message)); m_messageHash.remove(message); } //END KTextEditor::MessageInterface void KTextEditor::DocumentPrivate::closeDocumentInApplication() { KTextEditor::EditorPrivate::self()->application()->closeDocument(this); } diff --git a/src/document/katedocument.h b/src/document/katedocument.h index 5303f526..5346a974 100644 --- a/src/document/katedocument.h +++ b/src/document/katedocument.h @@ -1,1439 +1,1441 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy Copyright (C) 2006 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KATE_DOCUMENT_H_ #define _KATE_DOCUMENT_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katetextline.h" class KateTemplateHandler; namespace KTextEditor { class Plugin; class Attribute; class TemplateScript; } namespace KIO { class TransferJob; } namespace Kate { class SwapFile; } class KateBuffer; namespace KTextEditor { class ViewPrivate; } class KateDocumentConfig; class KateHighlighting; class KateUndoManager; class KateOnTheFlyChecker; class KateDocumentTest; class KateAutoIndent; class KateModOnHdPrompt; class KToggleAction; /** * @brief Backend of KTextEditor::Document related public KTextEditor interfaces. * * @warning This file is @e private API and not part of the public * KTextEditor interfaces. */ class KTEXTEDITOR_EXPORT KTextEditor::DocumentPrivate : public KTextEditor::Document, public KTextEditor::MarkInterface, public KTextEditor::ModificationInterface, public KTextEditor::ConfigInterface, public KTextEditor::AnnotationInterface, public KTextEditor::MovingInterface, private KTextEditor::MovingRangeFeedback { Q_OBJECT Q_INTERFACES(KTextEditor::MarkInterface) Q_INTERFACES(KTextEditor::ModificationInterface) Q_INTERFACES(KTextEditor::AnnotationInterface) Q_INTERFACES(KTextEditor::ConfigInterface) Q_INTERFACES(KTextEditor::MovingInterface) friend class KTextEditor::Document; friend class ::KateDocumentTest; friend class ::KateBuffer; public: explicit DocumentPrivate(bool bSingleViewMode = false, bool bReadOnly = false, QWidget *parentWidget = nullptr, QObject * = nullptr); ~DocumentPrivate() override; using ReadWritePart::closeUrl; bool closeUrl() override; bool openUrl(const QUrl &url) override; KTextEditor::Range rangeOnLine(KTextEditor::Range range, int line) const; private: void showAndSetOpeningErrorAccess(); /* * Overload this to have on-demand view creation */ public: /** * @return The widget defined by this part, set by setWidget(). */ QWidget *widget() override; public: bool readOnly() const { return m_bReadOnly; } bool singleViewMode() const { return m_bSingleViewMode; } private: // only to make part work, don't change it ! const bool m_bSingleViewMode; const bool m_bReadOnly; // // KTextEditor::Document stuff // public: KTextEditor::View *createView(QWidget *parent, KTextEditor::MainWindow *mainWindow = nullptr) override; QList views() const override { return m_viewsCache; } virtual KTextEditor::View *activeView() const { return m_activeView; } private: QHash m_views; KTextEditor::View *m_activeView = nullptr; // // KTextEditor::EditInterface stuff // public Q_SLOTS: bool setText(const QString &) override; bool setText(const QStringList &text) override; bool clear() override; bool insertText(const KTextEditor::Cursor &position, const QString &s, bool block = false) override; bool insertText(const KTextEditor::Cursor &position, const QStringList &text, bool block = false) override; bool insertLine(int line, const QString &s) override; bool insertLines(int line, const QStringList &s) override; bool removeText(const KTextEditor::Range &range, bool block = false) override; bool removeLine(int line) override; bool replaceText(const KTextEditor::Range &range, const QString &s, bool block = false) override; // unhide method... bool replaceText(const KTextEditor::Range &r, const QStringList &l, bool b) override { return KTextEditor::Document::replaceText(r, l, b); } public: bool isEditingTransactionRunning() const override; QString text(const KTextEditor::Range &range, bool blockwise = false) const override; QStringList textLines(const KTextEditor::Range &range, bool block = false) const override; QString text() const override; QString line(int line) const override; QChar characterAt(const KTextEditor::Cursor &position) const override; QString wordAt(const KTextEditor::Cursor &cursor) const override; KTextEditor::Range wordRangeAt(const KTextEditor::Cursor &cursor) const override; bool isValidTextPosition(const KTextEditor::Cursor& cursor) const override; int lines() const override; bool isLineModified(int line) const override; bool isLineSaved(int line) const override; bool isLineTouched(int line) const override; KTextEditor::Cursor documentEnd() const override; int totalCharacters() const override; int lineLength(int line) const override; Q_SIGNALS: void charactersSemiInteractivelyInserted(const KTextEditor::Cursor &position, const QString &text); /** * The \p document emits this signal whenever text was inserted. The * insertion occurred at range.start(), and new text now occupies up to * range.end(). * \param document document which emitted this signal * \param range range that the newly inserted text occupies * \see insertText(), insertLine() */ void textInserted(KTextEditor::Document *document, const KTextEditor::Range &range); /** * The \p document emits this signal whenever \p range was removed, i.e. * text was removed. * \param document document which emitted this signal * \param range range that the removed text previously occupied * \param oldText the text that has been removed * \see removeText(), removeLine(), clear() */ void textRemoved(KTextEditor::Document *document, const KTextEditor::Range &range, const QString &oldText); public: //BEGIN editStart/editEnd (start, end, undo, cursor update, view update) /** * Enclose editor actions with @p editStart() and @p editEnd() to group * them. */ bool editStart(); /** * Alias for @p editStart() */ void editBegin() { editStart(); } /** * End a editor operation. * @see editStart() */ bool editEnd(); void pushEditState(); void popEditState(); virtual bool startEditing() { return editStart(); } virtual bool finishEditing() { return editEnd(); } //END editStart/editEnd void inputMethodStart(); void inputMethodEnd(); //BEGIN LINE BASED INSERT/REMOVE STUFF (editStart() and editEnd() included) /** * Add a string in the given line/column * @param line line number * @param col column * @param s string to be inserted * @return true on success */ bool editInsertText(int line, int col, const QString &s); /** * Remove a string in the given line/column * @param line line number * @param col column * @param len length of text to be removed * @return true on success */ bool editRemoveText(int line, int col, int len); /** * Mark @p line as @p autowrapped. This is necessary if static word warp is * enabled, because we have to know whether to insert a new line or add the * wrapped words to the following line. * @param line line number * @param autowrapped autowrapped? * @return true on success */ bool editMarkLineAutoWrapped(int line, bool autowrapped); /** * Wrap @p line. If @p newLine is true, ignore the textline's flag * KateTextLine::flagAutoWrapped and force a new line. Whether a new line * was needed/added you can grab with @p newLineAdded. * @param line line number * @param col column * @param newLine if true, force a new line * @param newLineAdded return value is true, if new line was added (may be 0) * @return true on success */ bool editWrapLine(int line, int col, bool newLine = true, bool *newLineAdded = nullptr); /** * Unwrap @p line. If @p removeLine is true, we force to join the lines. If * @p removeLine is true, @p length is ignored (eg not needed). * @param line line number * @param removeLine if true, force to remove the next line * @return true on success */ bool editUnWrapLine(int line, bool removeLine = true, int length = 0); /** * Insert a string at the given line. * @param line line number * @param s string to insert * @return true on success */ bool editInsertLine(int line, const QString &s); /** * Remove a line * @param line line number * @return true on success */ bool editRemoveLine(int line); bool editRemoveLines(int from, int to); /** * Warp a line * @param startLine line to begin wrapping * @param endLine line to stop wrapping * @return true on success */ bool wrapText(int startLine, int endLine); /** * Wrap lines touched by the selection with respect of existing paragraphs. * To do so will the paragraph prior to the wrap joined as one single line * which cause an almost perfect wrapped paragraph as long as there are no * unneeded spaces exist or some formatting like this comment block. * Without any selection the current line is wrapped. * Empty lines around each paragraph are untouched. * @param first line to begin wrapping * @param last line to stop wrapping * @return true on success */ bool wrapParagraph(int first, int last); //END LINE BASED INSERT/REMOVE STUFF Q_SIGNALS: /** * Emitted when text from @p line was wrapped at position pos onto line @p nextLine. */ void editLineWrapped(int line, int col, int len); /** * Emitted each time text from @p nextLine was upwrapped onto @p line. */ void editLineUnWrapped(int line, int col); public: bool isEditRunning() const; void setUndoMergeAllEdits(bool merge); enum EditingPositionKind { Previous, Next }; /** *Returns the next or previous position cursor in this document from the stack depending on the argument passed. *@return cursor invalid if m_editingStack empty */ KTextEditor::Cursor lastEditingPosition(EditingPositionKind nextOrPrevious, KTextEditor::Cursor); private: int editSessionNumber = 0; QStack editStateStack; bool editIsRunning = false; bool m_undoMergeAllEdits = false; KTextEditor::Cursor m_editLastChangeStartCursor = KTextEditor::Cursor::invalid(); QStack> m_editingStack; int m_editingStackPosition = -1; // // KTextEditor::UndoInterface stuff // public Q_SLOTS: void undo(); void redo(); /** * Removes all the elements in m_editingStack of the respective document. */ void clearEditingPosStack(); /** * Saves the editing positions into the stack. * If the consecutive editings happens in the same line, then remove * the previous and add the new one with updated column no. */ void saveEditingPositions(const KTextEditor::Cursor &cursor); public: uint undoCount() const; uint redoCount() const; KateUndoManager *undoManager() { return m_undoManager; } protected: KateUndoManager *const m_undoManager; Q_SIGNALS: void undoChanged(); public: QVector searchText( const KTextEditor::Range &range, const QString &pattern, const KTextEditor::SearchOptions options) const; private: /** * Return a widget suitable to be used as a dialog parent. */ QWidget *dialogParent(); /* * Access to the mode/highlighting subsystem */ public: /** * @copydoc KTextEditor::Document::defaultStyleAt() */ KTextEditor::DefaultStyle defaultStyleAt(const KTextEditor::Cursor &position) const override; /** * Return the name of the currently used mode * \return name of the used mode */ QString mode() const override; /** * Return the name of the currently used mode * \return name of the used mode */ QString highlightingMode() const override; /** * Return a list of the names of all possible modes * \return list of mode names */ QStringList modes() const override; /** * Return a list of the names of all possible modes * \return list of mode names */ QStringList highlightingModes() const override; /** * Set the current mode of the document by giving its name * \param name name of the mode to use for this document * \return \e true on success, otherwise \e false */ bool setMode(const QString &name) override; /** * Set the current mode of the document by giving its name * \param name name of the mode to use for this document * \return \e true on success, otherwise \e false */ bool setHighlightingMode(const QString &name) override; /** * Returns the name of the section for a highlight given its @p index in the highlight * list (as returned by highlightModes()). * You can use this function to build a tree of the highlight names, organized in sections. * \param index in the highlight list for which to find the section name. */ QString highlightingModeSection(int index) const override; /** * Returns the name of the section for a mode given its @p index in the highlight * list (as returned by modes()). * You can use this function to build a tree of the mode names, organized in sections. * \param index index in the highlight list for which to find the section name. */ QString modeSection(int index) const override; /* * Helpers.... */ public: void bufferHlChanged(); /** * allow to mark, that we changed hl on user wish and should not reset it * atm used for the user visible menu to select highlightings */ void setDontChangeHlOnSave(); /** * Set that the BOM marker is forced via the tool menu */ void bomSetByUser(); public: /** * Read session settings from the given \p config. * * Known flags: * "SkipUrl" => don't save/restore the file * "SkipMode" => don't save/restore the mode * "SkipHighlighting" => don't save/restore the highlighting * "SkipEncoding" => don't save/restore the encoding * * \param config read the session settings from this KConfigGroup * \param flags additional flags * \see writeSessionConfig() */ void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) override; /** * Write session settings to the \p config. * See readSessionConfig() for more details. * * \param config write the session settings to this KConfigGroup * \param flags additional flags * \see readSessionConfig() */ void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) override; Q_SIGNALS: void configChanged(); // // KTextEditor::MarkInterface // public Q_SLOTS: void setMark(int line, uint markType) override; void clearMark(int line) override; void addMark(int line, uint markType) override; void removeMark(int line, uint markType) override; void clearMarks() override; void requestMarkTooltip(int line, QPoint position); ///Returns true if the click on the mark should not be further processed bool handleMarkClick(int line); ///Returns true if the context-menu event should not further be processed bool handleMarkContextMenu(int line, QPoint position); void setMarkPixmap(MarkInterface::MarkTypes, const QPixmap &) override; void setMarkDescription(MarkInterface::MarkTypes, const QString &) override; void setEditableMarks(uint markMask) override; public: uint mark(int line) override; const QHash &marks() override; QPixmap markPixmap(MarkInterface::MarkTypes) const override; QString markDescription(MarkInterface::MarkTypes) const override; virtual QColor markColor(MarkInterface::MarkTypes) const; uint editableMarks() const override; Q_SIGNALS: void markToolTipRequested(KTextEditor::Document *document, KTextEditor::Mark mark, QPoint position, bool &handled); void markContextMenuRequested(KTextEditor::Document *document, KTextEditor::Mark mark, QPoint pos, bool &handled); void markClicked(KTextEditor::Document *document, KTextEditor::Mark mark, bool &handled); void marksChanged(KTextEditor::Document *) override; void markChanged(KTextEditor::Document *, KTextEditor::Mark, KTextEditor::MarkInterface::MarkChangeAction) override; private: QHash m_marks; QHash m_markPixmaps; QHash m_markDescriptions; uint m_editableMarks = markType01; // KTextEditor::PrintInterface // public Q_SLOTS: bool print() override; void printPreview() override; // // KTextEditor::DocumentInfoInterface ( ### unfinished ) // public: /** * Tries to detect mime-type based on file name and content of buffer. * * @return the name of the mimetype for the document. */ QString mimeType() override; // // once was KTextEditor::VariableInterface // public: /** * Returns the value for the variable @p name. * If the Document does not have a variable called @p name, * an empty QString() is returned. * * @param name variable to query * @return value of the variable @p name * @see setVariable() */ virtual QString variable(const QString &name) const; /** * Set the variable @p name to @p value. Setting and changing a variable * has immediate effect on the Document. For instance, setting the variable * @e indent-mode to @e cstyle will immediately cause the Document to load * the C Style indenter. * * @param name the variable name * @param value the value to be set * @see variable() */ virtual void setVariable(const QString &name, const QString &value); private: QMap m_storedVariables; // // MovingInterface API // public: /** * Create a new moving cursor for this document. * @param position position of the moving cursor to create * @param insertBehavior insertion behavior * @return new moving cursor for the document */ KTextEditor::MovingCursor *newMovingCursor(const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior = KTextEditor::MovingCursor::MoveOnInsert) override; /** * Create a new moving range for this document. * @param range range of the moving range to create * @param insertBehaviors insertion behaviors * @param emptyBehavior behavior on becoming empty * @return new moving range for the document */ KTextEditor::MovingRange *newMovingRange(const KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors = KTextEditor::MovingRange::DoNotExpand , KTextEditor::MovingRange::EmptyBehavior emptyBehavior = KTextEditor::MovingRange::AllowEmpty) override; /** * Current revision * @return current revision */ qint64 revision() const override; /** * Last revision the buffer got successful saved * @return last revision buffer got saved, -1 if none */ qint64 lastSavedRevision() const override; /** * Lock a revision, this will keep it around until released again. * But all revisions will always be cleared on buffer clear() (and therefor load()) * @param revision revision to lock */ void lockRevision(qint64 revision) override; /** * Release a revision. * @param revision revision to release */ void unlockRevision(qint64 revision) override; /** * Transform a cursor from one revision to an other. * @param cursor cursor to transform * @param insertBehavior behavior of this cursor on insert of text at its position * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ void transformCursor(KTextEditor::Cursor &cursor, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1) override; /** * Transform a cursor from one revision to an other. * @param line line number of the cursor to transform * @param column column number of the cursor to transform * @param insertBehavior behavior of this cursor on insert of text at its position * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ void transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1) override; /** * Transform a range from one revision to an other. * @param range range to transform * @param insertBehaviors behavior of this range on insert of text at its position * @param emptyBehavior behavior on becoming empty * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ void transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision = -1) override; // // MovingInterface Signals // Q_SIGNALS: /** * This signal is emitted before the cursors/ranges/revisions of a document are destroyed as the document is deleted. * @param document the document which the interface belongs too which is in the process of being deleted */ void aboutToDeleteMovingInterfaceContent(KTextEditor::Document *document); /** * This signal is emitted before the ranges of a document are invalidated and the revisions are deleted as the document is cleared (for example on load/reload). * While this signal is emitted, still the old document content is around before the clear. * @param document the document which the interface belongs too which will invalidate its data */ void aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *document); // // Annotation Interface // public: void setAnnotationModel(KTextEditor::AnnotationModel *model) override; KTextEditor::AnnotationModel *annotationModel() const override; Q_SIGNALS: void annotationModelChanged(KTextEditor::AnnotationModel *, KTextEditor::AnnotationModel *); private: KTextEditor::AnnotationModel *m_annotationModel = nullptr; // // KParts::ReadWrite stuff // public: /** * open the file obtained by the kparts framework * the framework abstracts the loading of remote files * @return success */ bool openFile() override; /** * save the file obtained by the kparts framework * the framework abstracts the uploading of remote files * @return success */ bool saveFile() override; void setReadWrite(bool rw = true) override; void setModified(bool m) override; bool isAutoReload(); KToggleAction* autoReloadToggleAction() { return m_autoReloadMode; }; void delayAutoReload(); private Q_SLOTS: void autoReloadToggled(bool b); private: void activateDirWatch(const QString &useFileName = QString()); void deactivateDirWatch(); QString m_dirWatchFile; /** * Make backup copy during saveFile, if configured that way. * @return success? else saveFile should return false and not write the file */ bool createBackupFile(); public: /** * Type chars in a view. * Will filter out non-printable chars from the realChars array before inserting. */ bool typeChars(KTextEditor::ViewPrivate *type, const QString &realChars); /** * gets the last line number (lines() - 1) */ inline int lastLine() const { return lines() - 1; } // Repaint all of all of the views void repaintViews(bool paintOnlyDirty = true); KateHighlighting *highlight() const; public Q_SLOTS: void tagLines(int start, int end); private Q_SLOTS: void internalHlChanged(); public: void addView(KTextEditor::View *); /** removes the view from the list of views. The view is *not* deleted. * That's your job. Or, easier, just delete the view in the first place. * It will remove itself. TODO: this could be converted to a private slot * connected to the view's destroyed() signal. It is not currently called * anywhere except from the KTextEditor::ViewPrivate destructor. */ void removeView(KTextEditor::View *); void setActiveView(KTextEditor::View *); bool ownedView(KTextEditor::ViewPrivate *); int toVirtualColumn(int line, int column) const; int toVirtualColumn(const KTextEditor::Cursor &) const; int fromVirtualColumn(int line, int column) const; int fromVirtualColumn(const KTextEditor::Cursor &) const; - void newLine(KTextEditor::ViewPrivate *view); // Changes input + enum NewLineIndent { Indent, NoIndent }; + + void newLine(KTextEditor::ViewPrivate *view, NewLineIndent indent = NewLineIndent::Indent); // Changes input void backspace(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &); void del(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &); void transpose(const KTextEditor::Cursor &); void paste(KTextEditor::ViewPrivate *view, const QString &text); public: void indent(KTextEditor::Range range, int change); void comment(KTextEditor::ViewPrivate *view, uint line, uint column, int change); void align(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range); void insertTab(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &); enum TextTransform { Uppercase, Lowercase, Capitalize }; /** Handling uppercase, lowercase and capitalize for the view. If there is a selection, that is transformed, otherwise for uppercase or lowercase the character right of the cursor is transformed, for capitalize the word under the cursor is transformed. */ void transform(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &, TextTransform); /** Unwrap a range of lines. */ void joinLines(uint first, uint last); private: bool removeStringFromBeginning(int line, const QString &str); bool removeStringFromEnd(int line, const QString &str); /** Expand tabs to spaces in typed text, if enabled. @param cursorPos The current cursor position for the inserted characters. @param str The typed characters to expand. */ QString eventuallyReplaceTabs(const KTextEditor::Cursor &cursorPos, const QString &str) const; /** Find the position (line and col) of the next char that is not a space. If found line and col point to the found character. Otherwise they have both the value -1. @param line Line of the character which is examined first. @param col Column of the character which is examined first. @return True if the specified or a following character is not a space Otherwise false. */ bool nextNonSpaceCharPos(int &line, int &col); /** Find the position (line and col) of the previous char that is not a space. If found line and col point to the found character. Otherwise they have both the value -1. @return True if the specified or a preceding character is not a space. Otherwise false. */ bool previousNonSpaceCharPos(int &line, int &col); /** * Sets a comment marker as defined by the language providing the attribute * @p attrib on the line @p line */ void addStartLineCommentToSingleLine(int line, int attrib = 0); /** * Removes a comment marker as defined by the language providing the attribute * @p attrib on the line @p line */ bool removeStartLineCommentFromSingleLine(int line, int attrib = 0); /** * @see addStartLineCommentToSingleLine. */ void addStartStopCommentToSingleLine(int line, int attrib = 0); /** *@see removeStartLineCommentFromSingleLine. */ bool removeStartStopCommentFromSingleLine(int line, int attrib = 0); /** *@see removeStartLineCommentFromSingleLine. */ bool removeStartStopCommentFromRegion(const KTextEditor::Cursor &start, const KTextEditor::Cursor &end, int attrib = 0); /** * Add a comment marker as defined by the language providing the attribute * @p attrib to each line in the selection. */ void addStartStopCommentToSelection(KTextEditor::ViewPrivate *view, int attrib = 0); /** * @see addStartStopCommentToSelection. */ void addStartLineCommentToSelection(KTextEditor::ViewPrivate *view, int attrib = 0); /** * Removes comment markers relevant to the language providing * the attribuge @p attrib from each line in the selection. * * @return whether the operation succeeded. */ bool removeStartStopCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib = 0); /** * @see removeStartStopCommentFromSelection. */ bool removeStartLineCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib = 0); public: KTextEditor::Range findMatchingBracket(const KTextEditor::Cursor & start, int maxLines); public: QString documentName() const override { return m_docName; } private: void updateDocName(); public: /** * @return whether the document is modified on disk since last saved */ bool isModifiedOnDisc() { return m_modOnHd; } void setModifiedOnDisk(ModifiedOnDiskReason reason) override; void setModifiedOnDiskWarning(bool on) override; public Q_SLOTS: /** * Ask the user what to do, if the file has been modified on disk. * Reimplemented from KTextEditor::Document. */ virtual void slotModifiedOnDisk(KTextEditor::View *v = nullptr); /** * Reloads the current document from disk if possible */ bool documentReload() override; bool documentSave() override; bool documentSaveAs() override; bool documentSaveAsWithEncoding(const QString &encoding); bool documentSaveCopyAs(); bool save() override; public: bool saveAs(const QUrl &url) override; Q_SIGNALS: /** * Indicate this file is modified on disk * @param doc the KTextEditor::Document object that represents the file on disk * @param isModified indicates the file was modified rather than created or deleted * @param reason the reason we are emitting the signal. */ void modifiedOnDisk(KTextEditor::Document *doc, bool isModified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) override; private: // helper to handle the embedded notification for externally modified files QPointer m_modOnHdHandler; private Q_SLOTS: void onModOnHdSaveAs(); void onModOnHdClose(); void onModOnHdReload(); void onModOnHdAutoReload(); void onModOnHdIgnore(); public: bool setEncoding(const QString &e) override; QString encoding() const override; public Q_SLOTS: void setWordWrap(bool on); void setWordWrapAt(uint col); public: bool wordWrap() const; uint wordWrapAt() const; public Q_SLOTS: void setPageUpDownMovesCursor(bool on); public: bool pageUpDownMovesCursor() const; // code folding public: /** * Same as plainKateTextLine(), except that it is made sure * the line is highlighted. */ Kate::TextLine kateTextLine(int i); //! @copydoc KateBuffer::plainLine() Kate::TextLine plainKateTextLine(int i); Q_SIGNALS: void aboutToRemoveText(const KTextEditor::Range &); private Q_SLOTS: void slotModOnHdDirty(const QString &path); void slotModOnHdCreated(const QString &path); void slotModOnHdDeleted(const QString &path); void slotDelayedHandleModOnHd(); private: /** * Create a git compatible sha1 checksum of the file, if it is a local file. * The result can be accessed through KateBuffer::digest(). * * @return whether the operation was attempted and succeeded. */ bool createDigest(); /** * create a string for the modonhd warnings, giving the reason. */ QString reasonedMOHString() const; /** * Removes all trailing whitespace in the document. */ void removeTrailingSpaces(); public: /** * Returns a git compatible sha1 checksum of this document on disk. * @return checksum for this document on disk */ QByteArray checksum() const override; void updateFileType(const QString &newType, bool user = false); QString fileType() const { return m_fileType; } /** * Get access to buffer of this document. * Is needed to create cursors and ranges for example. * @return document buffer */ KateBuffer &buffer() { return *m_buffer; } /** * set indentation mode by user * this will remember that a user did set it and will avoid reset on save */ void rememberUserDidSetIndentationMode() { m_indenterSetByUser = true; } /** * User did set encoding for next reload => enforce it! */ void userSetEncodingForNextReload() { m_userSetEncodingForNextReload = true; } // // REALLY internal data ;) // private: // text buffer KateBuffer *const m_buffer; // indenter KateAutoIndent *const m_indenter; bool m_hlSetByUser = false; bool m_bomSetByUser = false; bool m_indenterSetByUser = false; bool m_userSetEncodingForNextReload = false; bool m_modOnHd = false; KToggleAction* m_autoReloadMode; QTimer m_autoReloadThrottle; ModifiedOnDiskReason m_modOnHdReason = OnDiskUnmodified; ModifiedOnDiskReason m_prevModOnHdReason = OnDiskUnmodified; QString m_docName; int m_docNameNumber = 0; // file type !!! QString m_fileType; bool m_fileTypeSetByUser = false; /** * document is still reloading a file */ bool m_reloading = false; public Q_SLOTS: void slotQueryClose_save(bool *handled, bool *abortClosing); public: bool queryClose() override; /** * Configuration */ public: KateDocumentConfig *config() { return m_config; } KateDocumentConfig *config() const { return m_config; } void updateConfig(); private: void makeAttribs(bool needInvalidate = true); KateDocumentConfig *const m_config; /** * Variable Reader * TODO add register functionality/ktexteditor interface */ private: /** * read dir config file */ void readDirConfig(); /** Reads all the variables in the document. Called when opening/saving a document */ void readVariables(bool onlyViewAndRenderer = false); /** Reads and applies the variables in a single line TODO registered variables gets saved in a [map] */ void readVariableLine(QString t, bool onlyViewAndRenderer = false); /** Sets a view variable in all the views. */ void setViewVariable(QString var, QString val); /** @return weather a string value could be converted to a bool value as supported. The value is put in *result. */ static bool checkBoolValue(QString value, bool *result); /** @return weather a string value could be converted to a integer value. The value is put in *result. */ static bool checkIntValue(QString value, int *result); /** Feeds value into @p col using QColor::setNamedColor() and returns whether the color is valid */ static bool checkColorValue(QString value, QColor &col); bool m_fileChangedDialogsActivated = false; // // KTextEditor::ConfigInterface // public: QStringList configKeys() const override; QVariant configValue(const QString &key) override; void setConfigValue(const QString &key, const QVariant &value) override; // // KTextEditor::RecoveryInterface // public: bool isDataRecoveryAvailable() const override; void recoverData() override; void discardDataRecovery() override; // // Highlighting information // public: QStringList embeddedHighlightingModes() const override; QString highlightingModeAt(const KTextEditor::Cursor &position) override; // TODO KDE5: move to View virtual KTextEditor::Attribute::Ptr attributeAt(const KTextEditor::Cursor &position); // //BEGIN: KTextEditor::MessageInterface // public: bool postMessage(KTextEditor::Message *message) override; public Q_SLOTS: void messageDestroyed(KTextEditor::Message *message); private: QHash > > m_messageHash; //END KTextEditor::MessageInterface public: QString defaultDictionary() const; QList > dictionaryRanges() const; bool isOnTheFlySpellCheckingEnabled() const; QString dictionaryForMisspelledRange(const KTextEditor::Range &range) const; void clearMisspellingForWord(const QString &word); public Q_SLOTS: void clearDictionaryRanges(); void setDictionary(const QString &dict, const KTextEditor::Range &range, bool blockmode); void setDictionary(const QString &dict, const KTextEditor::Range &range); void setDefaultDictionary(const QString &dict); void onTheFlySpellCheckingEnabled(bool enable); void refreshOnTheFlyCheck(const KTextEditor::Range &range = KTextEditor::Range::invalid()); Q_SIGNALS: void dictionaryRangesPresent(bool yesNo); void defaultDictionaryChanged(KTextEditor::DocumentPrivate *document); public: bool containsCharacterEncoding(const KTextEditor::Range &range); typedef QList > OffsetList; int computePositionWrtOffsets(const OffsetList &offsetList, int pos); /** * The first OffsetList is from decoded to encoded, and the second OffsetList from * encoded to decoded. **/ QString decodeCharacters(const KTextEditor::Range &range, KTextEditor::DocumentPrivate::OffsetList &decToEncOffsetList, KTextEditor::DocumentPrivate::OffsetList &encToDecOffsetList); void replaceCharactersByEncoding(const KTextEditor::Range &range); protected: KateOnTheFlyChecker *m_onTheFlyChecker = nullptr; QString m_defaultDictionary; QList > m_dictionaryRanges; // from KTextEditor::MovingRangeFeedback void rangeInvalid(KTextEditor::MovingRange *movingRange) override; void rangeEmpty(KTextEditor::MovingRange *movingRange) override; void deleteDictionaryRange(KTextEditor::MovingRange *movingRange); private: Kate::SwapFile *m_swapfile; public: Kate::SwapFile *swapFile(); //helpers for scripting and codefolding int defStyleNum(int line, int column); bool isComment(int line, int column); public: /** * Find the next modified/saved line, starting at @p startLine. If @p down * is \e true, the search is performed downwards, otherwise upwards. * @return the touched line in the requested search direction, or -1 if not found */ int findTouchedLine(int startLine, bool down); private Q_SLOTS: /** * watch for all started io jobs to remember if file is perhaps loading atm * @param job started job */ void slotStarted(KIO::Job *job); void slotCompleted(); void slotCanceled(); /** * trigger display of loading message, after 1000 ms */ void slotTriggerLoadingMessage(); /** * Abort loading */ void slotAbortLoading(); void slotUrlChanged(const QUrl &url); private: /** * different possible states */ enum DocumentStates { /** * Idle */ DocumentIdle, /** * Loading */ DocumentLoading, /** * Saving */ DocumentSaving, /** * Pre Saving As, this is between ::saveAs is called and ::save */ DocumentPreSavingAs, /** * Saving As */ DocumentSavingAs }; /** * current state */ DocumentStates m_documentState = DocumentIdle; /** * read-write state before loading started */ bool m_readWriteStateBeforeLoading = false; /** * if the document is untitled */ bool m_isUntitled = true; /** * loading job, we want to cancel with cancel in the loading message */ QPointer m_loadingJob; /** * message to show during loading */ QPointer m_loadingMessage; /** * Was there any open error on last file loading? */ bool m_openingError = false; /** * Last open file error message */ QString m_openingErrorMessage; public: /** * reads the line length limit from config, if it is not overridden */ int lineLengthLimit() const; public Q_SLOTS: void openWithLineLengthLimitOverride(); private: /** * timer for delayed handling of mod on hd */ QTimer m_modOnHdTimer; private: /** * currently active template handler; there can be only one */ QPointer m_activeTemplateHandler; private: /** * current autobrace range */ QSharedPointer m_currentAutobraceRange; /** * current autobrace closing character (e.g. ']') */ QChar m_currentAutobraceClosingChar; private Q_SLOTS: void checkCursorForAutobrace(KTextEditor::View* view, const KTextEditor::Cursor& newPos); public: void setActiveTemplateHandler(KateTemplateHandler* handler); Q_SIGNALS: void loaded(KTextEditor::DocumentPrivate *document); private Q_SLOTS: /** * trigger a close of this document in the application */ void closeDocumentInApplication(); private: // To calculate a QHash.keys() is quite expensive, // better keep a copy of that list updated when a view is added or removed. QList m_viewsCache; }; #endif diff --git a/src/view/kateview.cpp b/src/view/kateview.cpp index ec4b1ff4..956b9555 100644 --- a/src/view/kateview.cpp +++ b/src/view/kateview.cpp @@ -1,3943 +1,3960 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Michel Ludwig Copyright (C) 2007 Mirko Stocker Copyright (C) 2003 Hamish Rodda Copyright (C) 2002 John Firebaugh Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001-2010 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN includes #include "kateview.h" #include "kateviewinternal.h" #include "kateviewhelpers.h" #include "katerenderer.h" #include "katedocument.h" #include "kateundomanager.h" #include "kateglobal.h" #include "katehighlight.h" #include "katehighlightmenu.h" #include "katedialogs.h" #include "katetextline.h" #include "kateschema.h" #include "katebookmarks.h" #include "kateconfig.h" #include "katemodemenu.h" #include "kateautoindent.h" #include "katecompletionwidget.h" #include "katewordcompletion.h" #include "katekeywordcompletion.h" #include "katelayoutcache.h" #include "spellcheck/spellcheck.h" #include "spellcheck/spellcheckdialog.h" #include "spellcheck/spellingmenu.h" #include "katebuffer.h" #include "script/katescriptmanager.h" #include "script/katescriptaction.h" #include "export/exporter.h" #include "katemessagewidget.h" #include "katetemplatehandler.h" #include "katepartdebug.h" #include "printing/kateprinter.h" #include "katestatusbar.h" #include "kateabstractinputmode.h" #include "inlinenotedata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define VIEW_RANGE_DEBUG //END includes namespace { bool hasCommentInFirstLine(KTextEditor::DocumentPrivate* doc) { const Kate::TextLine& line = doc->kateTextLine(0); Q_ASSERT(line); return doc->isComment(0, line->firstChar()); } } void KTextEditor::ViewPrivate::blockFix(KTextEditor::Range &range) { if (range.start().column() > range.end().column()) { int tmp = range.start().column(); range.setStart(KTextEditor::Cursor(range.start().line(), range.end().column())); range.setEnd(KTextEditor::Cursor(range.end().line(), tmp)); } } KTextEditor::ViewPrivate::ViewPrivate(KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow) : KTextEditor::View (this, parent) , m_completionWidget(nullptr) , m_annotationModel(nullptr) , m_hasWrap(false) , m_doc(doc) , m_textFolding(doc->buffer()) , m_config(new KateViewConfig(this)) , m_renderer(new KateRenderer(doc, m_textFolding, this)) , m_viewInternal(new KateViewInternal(this)) , m_spell(new KateSpellCheckDialog(this)) , m_bookmarks(new KateBookmarks(this)) , m_topSpacer(new QSpacerItem(0,0)) , m_leftSpacer(new QSpacerItem(0,0)) , m_rightSpacer(new QSpacerItem(0,0)) , m_bottomSpacer(new QSpacerItem(0,0)) , m_startingUp(true) , m_updatingDocumentConfig(false) , m_selection(m_doc->buffer(), KTextEditor::Range::invalid(), Kate::TextRange::ExpandLeft, Kate::TextRange::AllowEmpty) , blockSelect(false) , m_bottomViewBar(nullptr) , m_gotoBar(nullptr) , m_dictionaryBar(nullptr) , m_spellingMenu(new KateSpellingMenu(this)) , m_userContextMenuSet(false) , m_delayedUpdateTriggered(false) , m_lineToUpdateMin(-1) , m_lineToUpdateMax(-1) , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there! , m_statusBar(nullptr) , m_temporaryAutomaticInvocationDisabled(false) , m_autoFoldedFirstLine(false) { // queued connect to collapse view updates for range changes, INIT THIS EARLY ENOUGH! connect(this, SIGNAL(delayedUpdateOfView()), this, SLOT(slotDelayedUpdateOfView()), Qt::QueuedConnection); KXMLGUIClient::setComponentName(KTextEditor::EditorPrivate::self()->aboutData().componentName(), KTextEditor::EditorPrivate::self()->aboutData().displayName()); // selection if for this view only and will invalidate if becoming empty m_selection.setView(this); // use z depth defined in moving ranges interface m_selection.setZDepth(-100000.0); KTextEditor::EditorPrivate::self()->registerView(this); /** * try to let the main window, if any, create a view bar for this view */ QWidget *bottomBarParent = m_mainWindow->createViewBar(this); m_bottomViewBar = new KateViewBar(bottomBarParent != nullptr, bottomBarParent ? bottomBarParent : this, this); // ugly workaround: // Force the layout to be left-to-right even on RTL desktop, as discussed // on the mailing list. This will cause the lines and icons panel to be on // the left, even for Arabic/Hebrew/Farsi/whatever users. setLayoutDirection(Qt::LeftToRight); m_bottomViewBar->installEventFilter(m_viewInternal); // add KateMessageWidget for KTE::MessageInterface immediately above view m_messageWidgets[KTextEditor::Message::AboveView] = new KateMessageWidget(this); m_messageWidgets[KTextEditor::Message::AboveView]->hide(); // add KateMessageWidget for KTE::MessageInterface immediately above view m_messageWidgets[KTextEditor::Message::BelowView] = new KateMessageWidget(this); m_messageWidgets[KTextEditor::Message::BelowView]->hide(); // add bottom viewbar... if (bottomBarParent) { m_mainWindow->addWidgetToViewBar(this, m_bottomViewBar); } // add layout for floating message widgets to KateViewInternal m_notificationLayout = new KateMessageLayout(m_viewInternal); m_notificationLayout->setContentsMargins(20, 20, 20, 20); m_viewInternal->setLayout(m_notificationLayout); // this really is needed :) m_viewInternal->updateView(); doc->addView(this); setFocusProxy(m_viewInternal); setFocusPolicy(Qt::StrongFocus); setXMLFile(QStringLiteral("katepart5ui.rc")); setupConnections(); setupActions(); // auto word completion new KateWordCompletionView(this, actionCollection()); // update the enabled state of the undo/redo actions... slotUpdateUndo(); /** * create the status bar of this view * do this after action creation, we use some of them! */ toggleStatusBar(); m_startingUp = false; updateConfig(); slotHlChanged(); KCursor::setAutoHideCursor(m_viewInternal, true); for (auto messageWidget : m_messageWidgets) { if (messageWidget) { // user interaction (scrolling) starts notification auto-hide timer connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), messageWidget, SLOT(startAutoHideTimer())); // user interaction (cursor navigation) starts notification auto-hide timer connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), messageWidget, SLOT(startAutoHideTimer())); } } // folding restoration on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(saveFoldingState())); connect(m_doc, SIGNAL(reloaded(KTextEditor::Document*)), SLOT(applyFoldingState())); connect(m_doc, &KTextEditor::DocumentPrivate::reloaded, this, &KTextEditor::ViewPrivate::slotDocumentReloaded); connect(m_doc, &KTextEditor::DocumentPrivate::aboutToReload, this, &KTextEditor::ViewPrivate::slotDocumentAboutToReload); // update highlights on scrolling and co connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(createHighlights())); // clear highlights on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(clearHighlights())); // setup layout setupLayout(); } KTextEditor::ViewPrivate::~ViewPrivate() { // invalidate update signal m_delayedUpdateTriggered = false; // remove from xmlgui factory, to be safe if (factory()) { factory()->removeClient(this); } // delete internal view before view bar! delete m_viewInternal; /** * remove view bar again, if needed */ m_mainWindow->deleteViewBar(this); m_bottomViewBar = nullptr; doc()->removeView(this); delete m_renderer; delete m_config; KTextEditor::EditorPrivate::self()->deregisterView(this); } void KTextEditor::ViewPrivate::toggleStatusBar() { /** * if there, delete it */ if (m_statusBar) { bottomViewBar()->removePermanentBarWidget(m_statusBar); delete m_statusBar; m_statusBar = nullptr; emit statusBarEnabledChanged(this, false); return; } /** * else: create it */ m_statusBar = new KateStatusBar(this); bottomViewBar()->addPermanentBarWidget(m_statusBar); emit statusBarEnabledChanged(this, true); } void KTextEditor::ViewPrivate::setupLayout() { // delete old layout if any if (layout()) { delete layout(); /** * need to recreate spacers because they are deleted with * their belonging layout */ m_topSpacer = new QSpacerItem(0,0); m_leftSpacer = new QSpacerItem(0,0); m_rightSpacer = new QSpacerItem(0,0); m_bottomSpacer = new QSpacerItem(0,0); } // set margins QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); m_topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); m_leftSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_rightSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); // define layout QGridLayout *layout=new QGridLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); const bool frameAroundContents = style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, this); if (frameAroundContents) { // top message widget layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 4); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 3, 0, 1, 4); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 1, 4, 3, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 4, 0, 1, 4); // dummy layout->addWidget(m_viewInternal->m_dummy, 4, 4, 1, 1); // bottom message layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_lineScroll->setAutoFillBackground(false); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_columnScroll->setAutoFillBackground(false); } else { // top message widget layout->addWidget(m_messageWidgets[KTextEditor::Message::AboveView], 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 5); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 2, 3, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 4, 1, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 3, 1, 1, 2); // dummy layout->addWidget(m_viewInternal->m_dummy, 3, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 4, 0, 1, 5); // bottom message layout->addWidget(m_messageWidgets[KTextEditor::Message::BelowView], 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_lineScroll->setAutoFillBackground(true); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_columnScroll->setAutoFillBackground(true); } } void KTextEditor::ViewPrivate::setupConnections() { connect(m_doc, SIGNAL(undoChanged()), this, SLOT(slotUpdateUndo())); connect(m_doc, SIGNAL(highlightingModeChanged(KTextEditor::Document*)), this, SLOT(slotHlChanged())); connect(m_doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); connect(m_viewInternal, SIGNAL(dropEventPass(QDropEvent*)), this, SIGNAL(dropEventPass(QDropEvent*))); connect(m_doc, SIGNAL(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*)), m_viewInternal->m_leftBorder, SLOT(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*))); } void KTextEditor::ViewPrivate::goToPreviousEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Previous, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::goToNextEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Next, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::setupActions() { KActionCollection *ac = actionCollection(); QAction *a; m_toggleWriteLock = nullptr; m_cut = a = ac->addAction(KStandardAction::Cut, this, SLOT(cut())); a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard")); m_paste = a = ac->addAction(KStandardAction::Paste, this, SLOT(paste())); a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents")); m_copy = a = ac->addAction(KStandardAction::Copy, this, SLOT(copy())); a->setWhatsThis(i18n("Use this command to copy the currently selected text to the system clipboard.")); m_pasteMenu = ac->addAction(QStringLiteral("edit_paste_menu"), new KatePasteMenu(i18n("Clipboard &History"), this)); connect(KTextEditor::EditorPrivate::self(), SIGNAL(clipboardHistoryChanged()), this, SLOT(slotClipboardHistoryChanged())); if (!doc()->readOnly()) { a = ac->addAction(KStandardAction::Save, m_doc, SLOT(documentSave())); a->setWhatsThis(i18n("Save the current document")); a = m_editUndo = ac->addAction(KStandardAction::Undo, m_doc, SLOT(undo())); a->setWhatsThis(i18n("Revert the most recent editing actions")); a = m_editRedo = ac->addAction(KStandardAction::Redo, m_doc, SLOT(redo())); a->setWhatsThis(i18n("Revert the most recent undo operation")); // Tools > Scripts // stored inside scoped pointer to ensure we destruct it early enough as it does internal cleanups of other child objects m_scriptActionMenu.reset(new KateScriptActionMenu(this, i18n("&Scripts"))); ac->addAction(QStringLiteral("tools_scripts"), m_scriptActionMenu.data()); a = ac->addAction(QStringLiteral("tools_apply_wordwrap")); a->setText(i18n("Apply &Word Wrap")); a->setWhatsThis(i18n("Use this to wrap the current line, or to reformat the selected lines as paragraph, " "to fit the 'Wrap words at' setting in the configuration dialog.

" "This is a static word wrap, meaning the document is changed.")); connect(a, SIGNAL(triggered(bool)), SLOT(applyWordWrap())); a = ac->addAction(QStringLiteral("tools_cleanIndent")); a->setText(i18n("&Clean Indentation")); a->setWhatsThis(i18n("Use this to clean the indentation of a selected block of text (only tabs/only spaces).

" "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); connect(a, SIGNAL(triggered(bool)), SLOT(cleanIndent())); a = ac->addAction(QStringLiteral("tools_align")); a->setText(i18n("&Align")); a->setWhatsThis(i18n("Use this to align the current line or block of text to its proper indent level.")); connect(a, SIGNAL(triggered(bool)), SLOT(align())); a = ac->addAction(QStringLiteral("tools_comment")); a->setText(i18n("C&omment")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_D)); a->setWhatsThis(i18n("This command comments out the current line or a selected block of text.

" "The characters for single/multiple line comments are defined within the language's highlighting.")); connect(a, SIGNAL(triggered(bool)), SLOT(comment())); a = ac->addAction(QStringLiteral("Previous Editing Line")); a->setText(i18n("Go to previous editing line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_E)); connect(a, SIGNAL(triggered(bool)), SLOT(goToPreviousEditingPosition())); a = ac->addAction(QStringLiteral("Next Editing Line")); a->setText(i18n("Go to next editing line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_E)); connect(a, SIGNAL(triggered(bool)), SLOT(goToNextEditingPosition())); a = ac->addAction(QStringLiteral("tools_uncomment")); a->setText(i18n("Unco&mment")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D)); a->setWhatsThis(i18n("This command removes comments from the current line or a selected block of text.

" "The characters for single/multiple line comments are defined within the language's highlighting.")); connect(a, SIGNAL(triggered(bool)), SLOT(uncomment())); a = ac->addAction(QStringLiteral("tools_toggle_comment")); a->setText(i18n("Toggle Comment")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Slash)); connect(a, SIGNAL(triggered(bool)), SLOT(toggleComment())); a = m_toggleWriteLock = new KToggleAction(i18n("&Read Only Mode"), this); a->setWhatsThis(i18n("Lock/unlock the document for writing")); a->setChecked(!doc()->isReadWrite()); connect(a, SIGNAL(triggered(bool)), SLOT(toggleWriteLock())); ac->addAction(QStringLiteral("tools_toggle_write_lock"), a); a = ac->addAction(QStringLiteral("tools_uppercase")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-uppercase"))); a->setText(i18n("Uppercase")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_U)); a->setWhatsThis(i18n("Convert the selection to uppercase, or the character to the " "right of the cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(uppercase())); a = ac->addAction(QStringLiteral("tools_lowercase")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-lowercase"))); a->setText(i18n("Lowercase")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U)); a->setWhatsThis(i18n("Convert the selection to lowercase, or the character to the " "right of the cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(lowercase())); a = ac->addAction(QStringLiteral("tools_capitalize")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-text-capitalize"))); a->setText(i18n("Capitalize")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_U)); a->setWhatsThis(i18n("Capitalize the selection, or the word under the " "cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(capitalize())); a = ac->addAction(QStringLiteral("tools_join_lines")); a->setText(i18n("Join Lines")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_J)); connect(a, SIGNAL(triggered(bool)), SLOT(joinLines())); a = ac->addAction(QStringLiteral("tools_invoke_code_completion")); a->setText(i18n("Invoke Code Completion")); a->setWhatsThis(i18n("Manually invoke command completion, usually by using a shortcut bound to this action.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Space)); connect(a, SIGNAL(triggered(bool)), SLOT(userInvokedCompletion())); } else { m_cut->setEnabled(false); m_paste->setEnabled(false); m_pasteMenu->setEnabled(false); m_editUndo = nullptr; m_editRedo = nullptr; } a = ac->addAction(KStandardAction::Print, this, SLOT(print())); a->setWhatsThis(i18n("Print the current document.")); a = ac->addAction(KStandardAction::PrintPreview, this, SLOT(printPreview())); a->setWhatsThis(i18n("Show print preview of current document")); a = ac->addAction(QStringLiteral("file_reload")); a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); a->setText(i18n("Reloa&d")); ac->setDefaultShortcuts(a, KStandardShortcut::reload()); a->setWhatsThis(i18n("Reload the current document from disk.")); connect(a, SIGNAL(triggered(bool)), SLOT(reloadFile())); a = ac->addAction(KStandardAction::SaveAs, m_doc, SLOT(documentSaveAs())); a->setWhatsThis(i18n("Save the current document to disk, with a name of your choice.")); a = new KateViewEncodingAction(m_doc, this, i18n("Save As with Encoding..."), this, true /* special mode for save as */); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); ac->addAction(QStringLiteral("file_save_as_with_encoding"), a); a = ac->addAction(QStringLiteral("file_save_copy_as")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); a->setText(i18n("Save &Copy As...")); a->setWhatsThis(i18n("Save a copy of the current document to disk.")); connect(a, SIGNAL(triggered(bool)), m_doc, SLOT(documentSaveCopyAs())); a = ac->addAction(KStandardAction::GotoLine, this, SLOT(gotoLine())); a->setWhatsThis(i18n("This command opens a dialog and lets you choose a line that you want the cursor to move to.")); a = ac->addAction(QStringLiteral("modified_line_up")); a->setText(i18n("Move to Previous Modified Line")); a->setWhatsThis(i18n("Move upwards to the previous modified line.")); connect(a, SIGNAL(triggered(bool)), SLOT(toPrevModifiedLine())); a = ac->addAction(QStringLiteral("modified_line_down")); a->setText(i18n("Move to Next Modified Line")); a->setWhatsThis(i18n("Move downwards to the next modified line.")); connect(a, SIGNAL(triggered(bool)), SLOT(toNextModifiedLine())); a = ac->addAction(QStringLiteral("set_confdlg")); a->setText(i18n("&Configure Editor...")); a->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); a->setWhatsThis(i18n("Configure various aspects of this editor.")); connect(a, SIGNAL(triggered(bool)), SLOT(slotConfigDialog())); m_modeAction = new KateModeMenu(i18n("&Mode"), this); ac->addAction(QStringLiteral("tools_mode"), m_modeAction); m_modeAction->setWhatsThis(i18n("Here you can choose which mode should be used for the current document. This will influence the highlighting and folding being used, for example.")); m_modeAction->updateMenu(m_doc); KateHighlightingMenu *menu = new KateHighlightingMenu(i18n("&Highlighting"), this); ac->addAction(QStringLiteral("tools_highlighting"), menu); menu->setWhatsThis(i18n("Here you can choose how the current document should be highlighted.")); menu->updateMenu(m_doc); KateViewSchemaAction *schemaMenu = new KateViewSchemaAction(i18n("&Schema"), this); ac->addAction(QStringLiteral("view_schemas"), schemaMenu); schemaMenu->updateMenu(this); // indentation menu KateViewIndentationAction *indentMenu = new KateViewIndentationAction(m_doc, i18n("&Indentation"), this); ac->addAction(QStringLiteral("tools_indentation"), indentMenu); m_selectAll = a = ac->addAction(KStandardAction::SelectAll, this, SLOT(selectAll())); a->setWhatsThis(i18n("Select the entire text of the current document.")); m_deSelect = a = ac->addAction(KStandardAction::Deselect, this, SLOT(clearSelection())); a->setWhatsThis(i18n("If you have selected something within the current document, this will no longer be selected.")); a = ac->addAction(QStringLiteral("view_inc_font_sizes")); a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); a->setText(i18n("Enlarge Font")); ac->setDefaultShortcuts(a, KStandardShortcut::zoomIn()); a->setWhatsThis(i18n("This increases the display font size.")); connect(a, SIGNAL(triggered(bool)), m_viewInternal, SLOT(slotIncFontSizes())); a = ac->addAction(QStringLiteral("view_dec_font_sizes")); a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); a->setText(i18n("Shrink Font")); ac->setDefaultShortcuts(a, KStandardShortcut::zoomOut()); a->setWhatsThis(i18n("This decreases the display font size.")); connect(a, SIGNAL(triggered(bool)), m_viewInternal, SLOT(slotDecFontSizes())); a = ac->addAction(QStringLiteral("view_reset_font_sizes")); a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original"))); a->setText(i18n("Reset Font Size")); ac->setDefaultShortcuts(a, KStandardShortcut::shortcut(KStandardShortcut::ActualSize)); a->setWhatsThis(i18n("This resets the display font size.")); connect(a, SIGNAL(triggered(bool)), m_viewInternal, SLOT(slotResetFontSizes())); a = m_toggleBlockSelection = new KToggleAction(i18n("Bl&ock Selection Mode"), this); ac->addAction(QStringLiteral("set_verticalSelect"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B)); a->setWhatsThis(i18n("This command allows switching between the normal (line based) selection mode and the block selection mode.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleBlockSelection())); a = ac->addAction(QStringLiteral("switch_next_input_mode")); a->setText(i18n("Switch to Next Input Mode")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_V)); a->setWhatsThis(i18n("Switch to the next input mode.")); connect(a, SIGNAL(triggered(bool)), SLOT(cycleInputMode())); a = m_toggleInsert = new KToggleAction(i18n("Overwr&ite Mode"), this); ac->addAction(QStringLiteral("set_insert"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Insert)); a->setWhatsThis(i18n("Choose whether you want the text you type to be inserted or to overwrite existing text.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleInsert())); KToggleAction *toggleAction; a = m_toggleDynWrap = toggleAction = new KToggleAction(i18n("&Dynamic Word Wrap"), this); ac->addAction(QStringLiteral("view_dynamic_word_wrap"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F10)); a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the view border on the screen.

" "This is only a view option, meaning the document will not changed.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleDynWordWrap())); a = m_setDynWrapIndicators = new KSelectAction(i18n("Dynamic Word Wrap Indicators"), this); ac->addAction(QStringLiteral("dynamic_word_wrap_indicators"), a); a->setWhatsThis(i18n("Choose when the Dynamic Word Wrap Indicators should be displayed")); connect(m_setDynWrapIndicators, SIGNAL(triggered(int)), this, SLOT(setDynWrapIndicators(int))); const QStringList list2{ i18n("&Off"), i18n("Follow &Line Numbers"), i18n("&Always On") }; m_setDynWrapIndicators->setItems(list2); m_setDynWrapIndicators->setEnabled(m_toggleDynWrap->isChecked()); // only synced on real change, later a = toggleAction = new KToggleAction(i18n("Static Word Wrap"), this); ac->addAction(QStringLiteral("view_static_word_wrap"), a); a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the column defined in the editing properties.")); connect(a, &KToggleAction::triggered, [=] { if (m_doc) { m_doc->setWordWrap(!m_doc->wordWrap()); }}); a = toggleAction = m_toggleWWMarker = new KToggleAction(i18n("Show Static &Word Wrap Marker"), this); ac->addAction(QStringLiteral("view_word_wrap_marker"), a); a->setWhatsThis(i18n("Show/hide the Word Wrap Marker, a vertical line drawn at the word " "wrap column as defined in the editing properties")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleWWMarker())); a = toggleAction = m_toggleFoldingMarkers = new KToggleAction(i18n("Show Folding &Markers"), this); ac->addAction(QStringLiteral("view_folding_markers"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F9)); a->setWhatsThis(i18n("You can choose if the codefolding marks should be shown, if codefolding is possible.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleFoldingMarkers())); a = m_toggleIconBar = toggleAction = new KToggleAction(i18n("Show &Icon Border"), this); ac->addAction(QStringLiteral("view_border"), a); a->setWhatsThis(i18n("Show/hide the icon border.

The icon border shows bookmark symbols, for instance.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleIconBorder())); a = toggleAction = m_toggleLineNumbers = new KToggleAction(i18n("Show &Line Numbers"), this); ac->addAction(QStringLiteral("view_line_numbers"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F11)); a->setWhatsThis(i18n("Show/hide the line numbers on the left hand side of the view.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleLineNumbersOn())); a = m_toggleScrollBarMarks = toggleAction = new KToggleAction(i18n("Show Scroll&bar Marks"), this); ac->addAction(QStringLiteral("view_scrollbar_marks"), a); a->setWhatsThis(i18n("Show/hide the marks on the vertical scrollbar.

The marks show bookmarks, for instance.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMarks())); a = m_toggleScrollBarMiniMap = toggleAction = new KToggleAction(i18n("Show Scrollbar Mini-Map"), this); ac->addAction(QStringLiteral("view_scrollbar_minimap"), a); a->setWhatsThis(i18n("Show/hide the mini-map on the vertical scrollbar.

The mini-map shows an overview of the whole document.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMap())); a = m_doc->autoReloadToggleAction(); ac->addAction(QStringLiteral("view_auto_reload"), a); // a = m_toggleScrollBarMiniMapAll = toggleAction = new KToggleAction(i18n("Show the whole document in the Mini-Map"), this); // ac->addAction(QLatin1String("view_scrollbar_minimap_all"), a); // a->setWhatsThis(i18n("Display the whole document in the mini-map.

With this option set the whole document will be visible in the mini-map.")); // connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMapAll())); // connect(m_toggleScrollBarMiniMap, SIGNAL(triggered(bool)), m_toggleScrollBarMiniMapAll, SLOT(setEnabled(bool))); a = m_toggleNPSpaces = new KToggleAction(i18n("Show Non-Printable Spaces"), this); ac->addAction(QStringLiteral("view_non_printable_spaces"), a); a->setWhatsThis(i18n("Show/hide bounding box around non-printable spaces")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleNPSpaces())); a = m_switchCmdLine = ac->addAction(QStringLiteral("switch_to_cmd_line")); a->setText(i18n("Switch to Command Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F7)); a->setWhatsThis(i18n("Show/hide the command line on the bottom of the view.")); connect(a, SIGNAL(triggered(bool)), SLOT(switchToCmdLine())); KActionMenu *am = new KActionMenu(i18n("Input Modes"), this); m_inputModeActions = new QActionGroup(am); ac->addAction(QStringLiteral("view_input_modes"), am); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { a = new QAction(mode->viewInputModeHuman(), m_inputModeActions); am->addAction(a); a->setWhatsThis(i18n("Activate/deactivate %1", mode->viewInputModeHuman())); const InputMode im = mode->viewInputMode(); a->setData(static_cast(im)); a->setCheckable(true); if (im == m_config->inputMode()) a->setChecked(true); connect(a, SIGNAL(triggered()), SLOT(toggleInputMode())); } a = m_setEndOfLine = new KSelectAction(i18n("&End of Line"), this); ac->addAction(QStringLiteral("set_eol"), a); a->setWhatsThis(i18n("Choose which line endings should be used, when you save the document")); const QStringList list { i18nc("@item:inmenu End of Line", "&UNIX") , i18nc("@item:inmenu End of Line", "&Windows/DOS") , i18nc("@item:inmenu End of Line", "&Macintosh") }; m_setEndOfLine->setItems(list); m_setEndOfLine->setCurrentItem(doc()->config()->eol()); connect(m_setEndOfLine, SIGNAL(triggered(int)), this, SLOT(setEol(int))); a = m_addBom = new KToggleAction(i18n("Add &Byte Order Mark (BOM)"), this); m_addBom->setChecked(doc()->config()->bom()); ac->addAction(QStringLiteral("add_bom"), a); a->setWhatsThis(i18n("Enable/disable adding of byte order marks for UTF-8/UTF-16 encoded files while saving")); connect(m_addBom, SIGNAL(triggered(bool)), this, SLOT(setAddBom(bool))); // encoding menu m_encodingAction = new KateViewEncodingAction(m_doc, this, i18n("E&ncoding"), this); ac->addAction(QStringLiteral("set_encoding"), m_encodingAction); a = ac->addAction(KStandardAction::Find, this, SLOT(find())); a->setWhatsThis(i18n("Look up the first occurrence of a piece of text or regular expression.")); addAction(a); a = ac->addAction(QStringLiteral("edit_find_selected")); a->setText(i18n("Find Selected")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_H)); a->setWhatsThis(i18n("Finds next occurrence of selected text.")); connect(a, SIGNAL(triggered(bool)), SLOT(findSelectedForwards())); a = ac->addAction(QStringLiteral("edit_find_selected_backwards")); a->setText(i18n("Find Selected Backwards")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_H)); a->setWhatsThis(i18n("Finds previous occurrence of selected text.")); connect(a, SIGNAL(triggered(bool)), SLOT(findSelectedBackwards())); a = ac->addAction(KStandardAction::FindNext, this, SLOT(findNext())); a->setWhatsThis(i18n("Look up the next occurrence of the search phrase.")); addAction(a); a = ac->addAction(KStandardAction::FindPrev, QStringLiteral("edit_find_prev"), this, SLOT(findPrevious())); a->setWhatsThis(i18n("Look up the previous occurrence of the search phrase.")); addAction(a); a = ac->addAction(KStandardAction::Replace, this, SLOT(replace())); a->setWhatsThis(i18n("Look up a piece of text or regular expression and replace the result with some given text.")); m_spell->createActions(ac); m_toggleOnTheFlySpellCheck = new KToggleAction(i18n("Automatic Spell Checking"), this); m_toggleOnTheFlySpellCheck->setWhatsThis(i18n("Enable/disable automatic spell checking")); connect(m_toggleOnTheFlySpellCheck, SIGNAL(triggered(bool)), SLOT(toggleOnTheFlySpellCheck(bool))); ac->addAction(QStringLiteral("tools_toggle_automatic_spell_checking"), m_toggleOnTheFlySpellCheck); ac->setDefaultShortcut(m_toggleOnTheFlySpellCheck, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_O)); a = ac->addAction(QStringLiteral("tools_change_dictionary")); a->setText(i18n("Change Dictionary...")); a->setWhatsThis(i18n("Change the dictionary that is used for spell checking.")); connect(a, SIGNAL(triggered()), SLOT(changeDictionary())); a = ac->addAction(QStringLiteral("tools_clear_dictionary_ranges")); a->setText(i18n("Clear Dictionary Ranges")); a->setEnabled(false); a->setWhatsThis(i18n("Remove all the separate dictionary ranges that were set for spell checking.")); connect(a, SIGNAL(triggered()), m_doc, SLOT(clearDictionaryRanges())); connect(m_doc, SIGNAL(dictionaryRangesPresent(bool)), a, SLOT(setEnabled(bool))); m_copyHtmlAction = ac->addAction(QStringLiteral("edit_copy_html"), this, SLOT(exportHtmlToClipboard())); m_copyHtmlAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); m_copyHtmlAction->setText(i18n("Copy as &HTML")); m_copyHtmlAction->setWhatsThis(i18n("Use this command to copy the currently selected text as HTML to the system clipboard.")); a = ac->addAction(QStringLiteral("file_export_html"), this, SLOT(exportHtmlToFile())); a->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); a->setText(i18n("E&xport as HTML...")); a->setWhatsThis(i18n("This command allows you to export the current document" " with all highlighting information into a HTML document.")); m_spellingMenu->createActions(ac); m_bookmarks->createActions(ac); slotSelectionChanged(); //Now setup the editing actions before adding the associated //widget and setting the shortcut context setupEditActions(); setupCodeFolding(); slotClipboardHistoryChanged(); ac->addAssociatedWidget(m_viewInternal); foreach (QAction *action, ac->actions()) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } connect(this, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(slotSelectionChanged())); } void KTextEditor::ViewPrivate::slotConfigDialog() { // invoke config dialog, will auto-save configuration to katepartrc KTextEditor::EditorPrivate::self()->configDialog(this); } void KTextEditor::ViewPrivate::setupEditActions() { //If you add an editing action to this //function make sure to include the line //m_editActions << a after creating the action KActionCollection *ac = actionCollection(); QAction *a = ac->addAction(QStringLiteral("word_left")); a->setText(i18n("Move Word Left")); ac->setDefaultShortcuts(a, KStandardShortcut::backwardWord()); connect(a, SIGNAL(triggered(bool)), SLOT(wordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_char_left")); a->setText(i18n("Select Character Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftCursorLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_word_left")); a->setText(i18n("Select Word Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftWordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("word_right")); a->setText(i18n("Move Word Right")); ac->setDefaultShortcuts(a, KStandardShortcut::forwardWord()); connect(a, SIGNAL(triggered(bool)), SLOT(wordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("select_char_right")); a->setText(i18n("Select Character Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftCursorRight())); m_editActions << a; a = ac->addAction(QStringLiteral("select_word_right")); a->setText(i18n("Select Word Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftWordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("beginning_of_line")); a->setText(i18n("Move to Beginning of Line")); ac->setDefaultShortcuts(a, KStandardShortcut::beginningOfLine()); connect(a, SIGNAL(triggered(bool)), SLOT(home())); m_editActions << a; a = ac->addAction(QStringLiteral("beginning_of_document")); a->setText(i18n("Move to Beginning of Document")); ac->setDefaultShortcuts(a, KStandardShortcut::begin()); connect(a, SIGNAL(triggered(bool)), SLOT(top())); m_editActions << a; a = ac->addAction(QStringLiteral("select_beginning_of_line")); a->setText(i18n("Select to Beginning of Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftHome())); m_editActions << a; a = ac->addAction(QStringLiteral("select_beginning_of_document")); a->setText(i18n("Select to Beginning of Document")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftTop())); m_editActions << a; a = ac->addAction(QStringLiteral("end_of_line")); a->setText(i18n("Move to End of Line")); ac->setDefaultShortcuts(a, KStandardShortcut::endOfLine()); connect(a, SIGNAL(triggered(bool)), SLOT(end())); m_editActions << a; a = ac->addAction(QStringLiteral("end_of_document")); a->setText(i18n("Move to End of Document")); ac->setDefaultShortcuts(a, KStandardShortcut::end()); connect(a, SIGNAL(triggered(bool)), SLOT(bottom())); m_editActions << a; a = ac->addAction(QStringLiteral("select_end_of_line")); a->setText(i18n("Select to End of Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftEnd())); m_editActions << a; a = ac->addAction(QStringLiteral("select_end_of_document")); a->setText(i18n("Select to End of Document")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftBottom())); m_editActions << a; a = ac->addAction(QStringLiteral("select_line_up")); a->setText(i18n("Select to Previous Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftUp())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_line_up")); a->setText(i18n("Scroll Line Up")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(scrollUp())); m_editActions << a; a = ac->addAction(QStringLiteral("move_line_down")); a->setText(i18n("Move to Next Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(down())); m_editActions << a; a = ac->addAction(QStringLiteral("move_line_up")); a->setText(i18n("Move to Previous Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(up())); m_editActions << a; a = ac->addAction(QStringLiteral("move_cursor_right")); a->setText(i18n("Move Cursor Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(cursorRight())); m_editActions << a; a = ac->addAction(QStringLiteral("move_cursor_left")); a->setText(i18n("Move Cursor Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(cursorLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_line_down")); a->setText(i18n("Select to Next Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftDown())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_line_down")); a->setText(i18n("Scroll Line Down")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(scrollDown())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_page_up")); a->setText(i18n("Scroll Page Up")); ac->setDefaultShortcuts(a, KStandardShortcut::prior()); connect(a, SIGNAL(triggered(bool)), SLOT(pageUp())); m_editActions << a; a = ac->addAction(QStringLiteral("select_page_up")); a->setText(i18n("Select Page Up")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_PageUp)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftPageUp())); m_editActions << a; a = ac->addAction(QStringLiteral("move_top_of_view")); a->setText(i18n("Move to Top of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::ALT + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(topOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("select_top_of_view")); a->setText(i18n("Select to Top of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftTopOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_page_down")); a->setText(i18n("Scroll Page Down")); ac->setDefaultShortcuts(a, KStandardShortcut::next()); connect(a, SIGNAL(triggered(bool)), SLOT(pageDown())); m_editActions << a; a = ac->addAction(QStringLiteral("select_page_down")); a->setText(i18n("Select Page Down")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_PageDown)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftPageDown())); m_editActions << a; a = ac->addAction(QStringLiteral("move_bottom_of_view")); a->setText(i18n("Move to Bottom of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::ALT + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(bottomOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("select_bottom_of_view")); a->setText(i18n("Select to Bottom of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::ALT + Qt::SHIFT + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftBottomOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("to_matching_bracket")); a->setText(i18n("Move to Matching Bracket")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_6)); connect(a, SIGNAL(triggered(bool)), SLOT(toMatchingBracket())); //m_editActions << a; a = ac->addAction(QStringLiteral("select_matching_bracket")); a->setText(i18n("Select to Matching Bracket")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_6)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftToMatchingBracket())); //m_editActions << a; // anders: shortcuts doing any changes should not be created in read-only mode if (!doc()->readOnly()) { a = ac->addAction(QStringLiteral("transpose_char")); a->setText(i18n("Transpose Characters")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_T)); connect(a, SIGNAL(triggered(bool)), SLOT(transpose())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_line")); a->setText(i18n("Delete Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_K)); connect(a, SIGNAL(triggered(bool)), SLOT(killLine())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_word_left")); a->setText(i18n("Delete Word Left")); ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordBack()); connect(a, SIGNAL(triggered(bool)), SLOT(deleteWordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_word_right")); a->setText(i18n("Delete Word Right")); ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordForward()); connect(a, SIGNAL(triggered(bool)), SLOT(deleteWordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_next_character")); a->setText(i18n("Delete Next Character")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Delete)); connect(a, SIGNAL(triggered(bool)), SLOT(keyDelete())); m_editActions << a; a = ac->addAction(QStringLiteral("backspace")); a->setText(i18n("Backspace")); QList scuts; scuts << QKeySequence(Qt::Key_Backspace) << QKeySequence(Qt::SHIFT + Qt::Key_Backspace); ac->setDefaultShortcuts(a, scuts); connect(a, SIGNAL(triggered(bool)), SLOT(backspace())); m_editActions << a; a = ac->addAction(QStringLiteral("insert_tabulator")); a->setText(i18n("Insert Tab")); connect(a, SIGNAL(triggered(bool)), SLOT(insertTab())); m_editActions << a; a = ac->addAction(QStringLiteral("smart_newline")); a->setText(i18n("Insert Smart Newline")); a->setWhatsThis(i18n("Insert newline including leading characters of the current line which are not letters or numbers.")); scuts.clear(); scuts << QKeySequence(Qt::SHIFT + Qt::Key_Return) << QKeySequence(Qt::SHIFT + Qt::Key_Enter); ac->setDefaultShortcuts(a, scuts); connect(a, SIGNAL(triggered(bool)), SLOT(smartNewline())); m_editActions << a; + a = ac->addAction(QStringLiteral("no_indent_newline")); + a->setText(i18n("Insert a non-indented Newline")); + a->setWhatsThis(i18n("Insert a new line without indentation, regardless of indentation settings.")); + scuts.clear(); + scuts << QKeySequence(Qt::CTRL + Qt::Key_Return) + << QKeySequence(Qt::CTRL + Qt::Key_Enter); + ac->setDefaultShortcuts(a, scuts); + connect(a, SIGNAL(triggered(bool)), SLOT(noIndentNewline())); + m_editActions << a; + a = ac->addAction(QStringLiteral("tools_indent")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-more"))); a->setText(i18n("&Indent")); a->setWhatsThis(i18n("Use this to indent a selected block of text.

" "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(indent())); a = ac->addAction(QStringLiteral("tools_unindent")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-less"))); a->setText(i18n("&Unindent")); a->setWhatsThis(i18n("Use this to unindent a selected block of text.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(unIndent())); } if (hasFocus()) { slotGotFocus(); } else { slotLostFocus(); } } void KTextEditor::ViewPrivate::setupCodeFolding() { KActionCollection *ac = this->actionCollection(); QAction *a; a = ac->addAction(QStringLiteral("folding_toplevel")); a->setText(i18n("Fold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotFoldToplevelNodes())); a = ac->addAction(QLatin1String("folding_expandtoplevel")); a->setText(i18n("Unfold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Plus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandToplevelNodes())); /*a = ac->addAction(QLatin1String("folding_expandall")); a->setText(i18n("Unfold All Nodes")); connect(a, SIGNAL(triggered(bool)), doc()->foldingTree(), SLOT(expandAll())); a = ac->addAction(QLatin1String("folding_collapse_dsComment")); a->setText(i18n("Fold Multiline Comments")); connect(a, SIGNAL(triggered(bool)), doc()->foldingTree(), SLOT(collapseAll_dsComments())); */ a = ac->addAction(QStringLiteral("folding_toggle_current")); a->setText(i18n("Toggle Current Node")); connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotToggleFolding); a = ac->addAction(QStringLiteral("folding_toggle_in_current")); a->setText(i18n("Toggle Contained Nodes")); connect(a, &QAction::triggered, this, &KTextEditor::ViewPrivate::slotToggleFoldingsInRange); } void KTextEditor::ViewPrivate::slotFoldToplevelNodes() { for (int line = 0; line < doc()->lines(); ++line) { if (textFolding().isLineVisible(line)) { foldLine(line); } } } void KTextEditor::ViewPrivate::slotExpandToplevelNodes() { const auto topLevelRanges(textFolding().foldingRangesForParentRange()); for (const auto &range : topLevelRanges) { textFolding().unfoldRange(range.first); } } void KTextEditor::ViewPrivate::slotToggleFolding() { int line = cursorPosition().line(); bool actionDone = false; while (!actionDone && (line > -1)) { actionDone = unfoldLine(line); if (!actionDone) { actionDone = foldLine(line--).isValid(); } } } void KTextEditor::ViewPrivate::slotToggleFoldingsInRange() { int line = cursorPosition().line(); while (!toggleFoldingsInRange(line) && (line > -1)) { --line; } } KTextEditor::Range KTextEditor::ViewPrivate::foldLine(int line) { KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line); if (!foldingRange.isValid()) { return foldingRange; } // Ensure not to fold the end marker to avoid a deceptive look, but only on token based folding Kate::TextLine startTextLine = doc()->buffer().plainLine(line); if (!startTextLine->markedAsFoldingStartIndentation()) { const int adjustedLine = foldingRange.end().line() - 1; foldingRange.setEnd(KTextEditor::Cursor(adjustedLine, doc()->buffer().plainLine(adjustedLine)->length())); } // Don't try to fold a single line, which can happens due to adjustment above // FIXME Avoid to offer such a folding marker if (!foldingRange.onSingleLine()) { textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); } return foldingRange; } bool KTextEditor::ViewPrivate::unfoldLine(int line) { bool actionDone = false; const KTextEditor::Cursor currentCursor = cursorPosition(); // ask the folding info for this line, if any folds are around! // auto = QVector> auto startingRanges = textFolding().foldingRangesStartingOnLine(line); for (int i = 0; i < startingRanges.size() && !actionDone; ++i) { // Avoid jumping view in case of a big unfold and ensure nice highlight of folding marker setCursorPosition(textFolding().foldingRange(startingRanges[i].first).start()); actionDone |= textFolding().unfoldRange(startingRanges[i].first); } if (!actionDone) { // Nothing unfolded? Restore old cursor position! setCursorPosition(currentCursor); } return actionDone; } bool KTextEditor::ViewPrivate::toggleFoldingOfLine(int line) { bool actionDone = unfoldLine(line); if (!actionDone) { actionDone = foldLine(line).isValid(); } return actionDone; } bool KTextEditor::ViewPrivate::toggleFoldingsInRange(int line) { KTextEditor::Range foldingRange = doc()->buffer().computeFoldingRangeForStartLine(line); if (!foldingRange.isValid()) { // Either line is not valid or there is no start range return false; } bool actionDone = false; // Track success const KTextEditor::Cursor currentCursor = cursorPosition(); // Don't be too eager but obliging! Only toggle containing ranges which are // visible -> Be done when the range is folded actionDone |= unfoldLine(line); if (!actionDone) { // Unfold all in range, but not the range itself for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) { actionDone |= unfoldLine(ln); } if (actionDone) { // In most cases we want now a not moved cursor setCursorPosition(currentCursor); } } if (!actionDone) { // Fold all in range, but not the range itself for (int ln = foldingRange.start().line() + 1; ln < foldingRange.end().line(); ++ln) { KTextEditor::Range fr = foldLine(ln); if (fr.isValid()) { // qMax to avoid infinite loop in case of range without content ln = qMax(ln, fr.end().line() - 1); actionDone = true; } } } if (!actionDone) { // At this point was an unfolded range clicked which contains no "childs" // We assume the user want to fold it by the wrong button, be obliging! actionDone |= foldLine(line).isValid(); } // At this point we should be always true return actionDone; } KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const { return currentInputMode()->viewMode(); } QString KTextEditor::ViewPrivate::viewModeHuman() const { QString currentMode = currentInputMode()->viewModeHuman(); /** * append read-only if needed */ if (!doc()->isReadWrite()) { currentMode = i18n("(R/O) %1", currentMode); } /** * return full mode */ return currentMode; } KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const { return currentInputMode()->viewInputMode(); } QString KTextEditor::ViewPrivate::viewInputModeHuman() const { return currentInputMode()->viewInputModeHuman(); } void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode) { if (currentInputMode()->viewInputMode() == mode) { return; } if (!m_viewInternal->m_inputModes.contains(mode)) { return; } m_viewInternal->m_currentInputMode->deactivate(); m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode]; m_viewInternal->m_currentInputMode->activate(); config()->setValue(KateViewConfig::InputMode, mode); // TODO: this could be called from read config procedure, so it's not a good idea to set a specific view mode here /* small duplication, but need to do this if not toggled by action */ Q_FOREACH(QAction *action, m_inputModeActions->actions()) { if (static_cast(action->data().toInt()) == mode) { action->setChecked(true); break; } } /* inform the rest of the system about the change */ emit viewInputModeChanged(this, mode); emit viewModeChanged(this, viewMode()); } void KTextEditor::ViewPrivate::slotDocumentAboutToReload() { if (doc()->isAutoReload()) { const int lastVisibleLine = m_viewInternal->endLine(); const int currentLine = cursorPosition().line(); m_gotoBottomAfterReload = (lastVisibleLine == currentLine) && (currentLine == doc()->lastLine()); if (!m_gotoBottomAfterReload) { // Ensure the view jumps not back when user scrolls around const int firstVisibleLine = 1 + lastVisibleLine - m_viewInternal->linesDisplayed(); const int newLine = qBound(firstVisibleLine, currentLine, lastVisibleLine); setCursorPositionVisual(KTextEditor::Cursor(newLine, cursorPosition().column())); } } else { m_gotoBottomAfterReload = false; } } void KTextEditor::ViewPrivate::slotDocumentReloaded() { if (m_gotoBottomAfterReload) { bottom(); } } void KTextEditor::ViewPrivate::slotGotFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus"; currentInputMode()->gotFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusIn(this); } void KTextEditor::ViewPrivate::slotLostFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus"; currentInputMode()->lostFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusOut(this); } void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode) { config()->setValue(KateViewConfig::DynWordWrapIndicators, mode); } bool KTextEditor::ViewPrivate::isOverwriteMode() const { return doc()->config()->ovr(); } void KTextEditor::ViewPrivate::reloadFile() { // bookmarks and cursor positions are temporarily saved by the document doc()->documentReload(); } void KTextEditor::ViewPrivate::slotReadWriteChanged() { if (m_toggleWriteLock) { m_toggleWriteLock->setChecked(! doc()->isReadWrite()); } m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut())); m_paste->setEnabled(doc()->isReadWrite()); m_pasteMenu->setEnabled(doc()->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); m_setEndOfLine->setEnabled(doc()->isReadWrite()); static const auto l = { QStringLiteral("edit_replace") , QStringLiteral("tools_spelling") , QStringLiteral("tools_indent") , QStringLiteral("tools_unindent") , QStringLiteral("tools_cleanIndent") , QStringLiteral("tools_align") , QStringLiteral("tools_comment") , QStringLiteral("tools_uncomment") , QStringLiteral("tools_toggle_comment") , QStringLiteral("tools_uppercase") , QStringLiteral("tools_lowercase") , QStringLiteral("tools_capitalize") , QStringLiteral("tools_join_lines") , QStringLiteral("tools_apply_wordwrap") , QStringLiteral("tools_spelling_from_cursor") , QStringLiteral("tools_spelling_selection") }; foreach (const auto &action, l) { QAction *a = actionCollection()->action(action); if (a) { a->setEnabled(doc()->isReadWrite()); } } slotUpdateUndo(); currentInputMode()->readWriteChanged(doc()->isReadWrite()); // => view mode changed emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotClipboardHistoryChanged() { m_pasteMenu->setEnabled(doc()->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); } void KTextEditor::ViewPrivate::slotUpdateUndo() { if (doc()->readOnly()) { return; } m_editUndo->setEnabled(doc()->isReadWrite() && doc()->undoCount() > 0); m_editRedo->setEnabled(doc()->isReadWrite() && doc()->redoCount() > 0); } bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor &position, uint tabwidth, bool calledExternally) { Kate::TextLine l = doc()->kateTextLine(position.line()); if (!l) { return false; } QString line_str = doc()->line(position.line()); int x = 0; int z = 0; for (; z < line_str.length() && z < position.column(); z++) { if (line_str[z] == QLatin1Char('\t')) { x += tabwidth - (x % tabwidth); } else { x++; } } if (blockSelection()) if (z < position.column()) { x += position.column() - z; } m_viewInternal->updateCursor(KTextEditor::Cursor(position.line(), x), false, calledExternally /* force center for external calls, see bug 408418 */, calledExternally); return true; } void KTextEditor::ViewPrivate::toggleInsert() { doc()->config()->setOvr(!doc()->config()->ovr()); m_toggleInsert->setChecked(isOverwriteMode()); emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error) { if (!error.isEmpty()) { // happens when canceling a job KMessageBox::error(this, error); } } void KTextEditor::ViewPrivate::gotoLine() { gotoBar()->updateData(); bottomViewBar()->showBarWidget(gotoBar()); } void KTextEditor::ViewPrivate::changeDictionary() { dictionaryBar()->updateData(); bottomViewBar()->showBarWidget(dictionaryBar()); } void KTextEditor::ViewPrivate::joinLines() { int first = selectionRange().start().line(); int last = selectionRange().end().line(); //int left = doc()->line( last ).length() - doc()->selEndCol(); if (first == last) { first = cursorPosition().line(); last = first + 1; } doc()->joinLines(first, last); } void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position setCursorPositionInternal(KTextEditor::Cursor(config.readEntry("CursorLine", 0), config.readEntry("CursorColumn", 0))); m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false)); // restore text folding m_savedFoldingState = QJsonDocument::fromJson(config.readEntry("TextFolding", QByteArray())); applyFoldingState(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->readSessionConfig(config); } } void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position config.writeEntry("CursorLine", cursorPosition().line()); config.writeEntry("CursorColumn", cursorPosition().column()); config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap()); // save text folding state saveFoldingState(); config.writeEntry("TextFolding", m_savedFoldingState.toJson(QJsonDocument::Compact)); m_savedFoldingState = QJsonDocument(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->writeSessionConfig(config); } } int KTextEditor::ViewPrivate::getEol() const { return doc()->config()->eol(); } void KTextEditor::ViewPrivate::setEol(int eol) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } if (eol != doc()->config()->eol()) { doc()->setModified(true); // mark modified (bug #143120) doc()->config()->setEol(eol); } } void KTextEditor::ViewPrivate::setAddBom(bool enabled) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } doc()->config()->setBom(enabled); doc()->bomSetByUser(); } void KTextEditor::ViewPrivate::setIconBorder(bool enable) { config()->setValue(KateViewConfig::ShowIconBar, enable); } void KTextEditor::ViewPrivate::toggleIconBorder() { config()->setValue(KateViewConfig::ShowIconBar, !config()->iconBar()); } void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable) { config()->setValue(KateViewConfig::ShowLineNumbers, enable); } void KTextEditor::ViewPrivate::toggleLineNumbersOn() { config()->setValue(KateViewConfig::ShowLineNumbers, !config()->lineNumbers()); } void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable) { config()->setValue(KateViewConfig::ShowScrollBarMarks, enable); } void KTextEditor::ViewPrivate::toggleScrollBarMarks() { config()->setValue(KateViewConfig::ShowScrollBarMarks, !config()->scrollBarMarks()); } void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable) { config()->setValue(KateViewConfig::ShowScrollBarMiniMap, enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMap() { config()->setValue(KateViewConfig::ShowScrollBarMiniMap, !config()->scrollBarMiniMap()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable) { config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll() { config()->setValue(KateViewConfig::ShowScrollBarMiniMapAll, !config()->scrollBarMiniMapAll()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width) { config()->setValue(KateViewConfig::ScrollBarMiniMapWidth, width); } void KTextEditor::ViewPrivate::toggleDynWordWrap() { config()->setDynWordWrap(!config()->dynWordWrap()); } void KTextEditor::ViewPrivate::toggleWWMarker() { m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker()); } void KTextEditor::ViewPrivate::toggleNPSpaces() { m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces()); m_viewInternal->update(); // force redraw } void KTextEditor::ViewPrivate::toggleWordCount(bool on) { config()->setShowWordCount(on); } void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable) { config()->setValue(KateViewConfig::ShowFoldingBar, enable); } void KTextEditor::ViewPrivate::toggleFoldingMarkers() { config()->setValue(KateViewConfig::ShowFoldingBar, !config()->foldingBar()); } bool KTextEditor::ViewPrivate::iconBorder() { return m_viewInternal->m_leftBorder->iconBorderOn(); } bool KTextEditor::ViewPrivate::lineNumbersOn() { return m_viewInternal->m_leftBorder->lineNumbersOn(); } bool KTextEditor::ViewPrivate::scrollBarMarks() { return m_viewInternal->m_lineScroll->showMarks(); } bool KTextEditor::ViewPrivate::scrollBarMiniMap() { return m_viewInternal->m_lineScroll->showMiniMap(); } int KTextEditor::ViewPrivate::dynWrapIndicators() { return m_viewInternal->m_leftBorder->dynWrapIndicators(); } bool KTextEditor::ViewPrivate::foldingMarkersOn() { return m_viewInternal->m_leftBorder->foldingMarkersOn(); } void KTextEditor::ViewPrivate::toggleWriteLock() { doc()->setReadWrite(! doc()->isReadWrite()); } void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->registerTextHintProvider(provider); } void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->unregisterTextHintProvider(provider); } void KTextEditor::ViewPrivate::setTextHintDelay(int delay) { m_viewInternal->setTextHintDelay(delay); } int KTextEditor::ViewPrivate::textHintDelay() const { return m_viewInternal->textHintDelay(); } void KTextEditor::ViewPrivate::find() { currentInputMode()->find(); } void KTextEditor::ViewPrivate::findSelectedForwards() { currentInputMode()->findSelectedForwards(); } void KTextEditor::ViewPrivate::findSelectedBackwards() { currentInputMode()->findSelectedBackwards(); } void KTextEditor::ViewPrivate::replace() { currentInputMode()->findReplace(); } void KTextEditor::ViewPrivate::findNext() { currentInputMode()->findNext(); } void KTextEditor::ViewPrivate::findPrevious() { currentInputMode()->findPrevious(); } void KTextEditor::ViewPrivate::slotSelectionChanged() { m_copy->setEnabled(selection() || m_config->smartCopyCut()); m_deSelect->setEnabled(selection()); m_copyHtmlAction->setEnabled (selection()); // update highlighting of current selected word selectionChangedForHighlights (); if (doc()->readOnly()) { return; } m_cut->setEnabled(selection() || m_config->smartCopyCut()); } void KTextEditor::ViewPrivate::switchToCmdLine() { currentInputMode()->activateCommandLine(); } KateRenderer *KTextEditor::ViewPrivate::renderer() { return m_renderer; } void KTextEditor::ViewPrivate::updateConfig() { if (m_startingUp) { return; } // dyn. word wrap & markers if (m_hasWrap != config()->dynWordWrap()) { m_viewInternal->prepareForDynWrapChange(); m_hasWrap = config()->dynWordWrap(); m_viewInternal->dynWrapChanged(); m_setDynWrapIndicators->setEnabled(config()->dynWordWrap()); m_toggleDynWrap->setChecked(config()->dynWordWrap()); } m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators()); m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators()); // line numbers m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers()); m_toggleLineNumbers->setChecked(config()->lineNumbers()); // icon bar m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar()); m_toggleIconBar->setChecked(config()->iconBar()); // scrollbar marks m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks()); m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks()); // scrollbar mini-map m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap()); m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap()); // scrollbar mini-map - (whole document) m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll()); //m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() ); // scrollbar mini-map.width m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth()); // misc edit m_toggleBlockSelection->setChecked(blockSelection()); m_toggleInsert->setChecked(isOverwriteMode()); updateFoldingConfig(); // bookmark m_bookmarks->setSorting((KateBookmarks::Sorting) config()->bookmarkSort()); m_viewInternal->setAutoCenterLines(config()->autoCenterLines()); Q_FOREACH(KateAbstractInputMode *input, m_viewInternal->m_inputModes) { input->updateConfig(); } setInputMode(config()->inputMode()); reflectOnTheFlySpellCheckStatus(doc()->isOnTheFlySpellCheckingEnabled()); // register/unregister word completion... bool wc = config()->wordCompletion(); if (wc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->wordCompletionModel())) { if (wc) registerCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); else unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); } bool kc = config()->keywordCompletion(); if (kc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->keywordCompletionModel())) { if (kc) registerCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel()); else unregisterCompletionModel (KTextEditor::EditorPrivate::self()->keywordCompletionModel()); } m_cut->setEnabled(doc()->isReadWrite() && (selection() || m_config->smartCopyCut())); m_copy->setEnabled(selection() || m_config->smartCopyCut()); // if not disabled, update status bar if (m_statusBar) { m_statusBar->updateStatus(); } // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); emit configChanged(); } void KTextEditor::ViewPrivate::updateDocumentConfig() { if (m_startingUp) { return; } m_updatingDocumentConfig = true; m_setEndOfLine->setCurrentItem(doc()->config()->eol()); m_addBom->setChecked(doc()->config()->bom()); m_updatingDocumentConfig = false; // maybe block selection or wrap-cursor mode changed ensureCursorColumnValid(); // first change this m_renderer->setTabWidth(doc()->config()->tabWidth()); m_renderer->setIndentWidth(doc()->config()->indentationWidth()); // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); } void KTextEditor::ViewPrivate::updateRendererConfig() { if (m_startingUp) { return; } m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker()); m_viewInternal->updateBracketMarkAttributes(); m_viewInternal->updateBracketMarks(); // now redraw... m_viewInternal->cache()->clear(); tagAll(); m_viewInternal->updateView(true); // update the left border right, for example linenumbers m_viewInternal->m_leftBorder->updateFont(); m_viewInternal->m_leftBorder->repaint(); m_viewInternal->m_lineScroll->queuePixmapUpdate(); currentInputMode()->updateRendererConfig(); // @@ showIndentLines is not cached anymore. // m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines()); emit configChanged(); } void KTextEditor::ViewPrivate::updateFoldingConfig() { // folding bar m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar()); m_toggleFoldingMarkers->setChecked(config()->foldingBar()); if (hasCommentInFirstLine(m_doc)) { if (config()->foldFirstLine() && !m_autoFoldedFirstLine) { foldLine(0); m_autoFoldedFirstLine = true; } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) { unfoldLine(0); m_autoFoldedFirstLine = false; } } else { m_autoFoldedFirstLine = false; } #if 0 // FIXME: FOLDING const QStringList l = { QStringLiteral("folding_toplevel") , QStringLiteral("folding_expandtoplevel") , QStringLiteral("folding_toggle_current") , QStringLiteral("folding_toggle_in_current") }; QAction *a = 0; for (int z = 0; z < l.size(); z++) if ((a = actionCollection()->action(l[z].toAscii().constData()))) { a->setEnabled(doc()->highlight() && doc()->highlight()->allowsFolding()); } #endif } void KTextEditor::ViewPrivate::ensureCursorColumnValid() { KTextEditor::Cursor c = m_viewInternal->cursorPosition(); // make sure the cursor is valid: // - in block selection mode or if wrap cursor is off, the column is arbitrary // - otherwise: it's bounded by the line length if (!blockSelection() && wrapCursor() && (!c.isValid() || c.column() > doc()->lineLength(c.line()))) { c.setColumn(doc()->kateTextLine(cursorPosition().line())->length()); setCursorPosition(c); } } //BEGIN EDIT STUFF void KTextEditor::ViewPrivate::editStart() { m_viewInternal->editStart(); } void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom); } void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor &cursor) { m_viewInternal->editSetCursor(cursor); } //END //BEGIN TAG & CLEAR bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor &virtualCursor) { return m_viewInternal->tagLine(virtualCursor); } bool KTextEditor::ViewPrivate::tagRange(const KTextEditor::Range &range, bool realLines) { return m_viewInternal->tagRange(range, realLines); } bool KTextEditor::ViewPrivate::tagLines(int start, int end, bool realLines) { return m_viewInternal->tagLines(start, end, realLines); } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) { return m_viewInternal->tagLines(start, end, realCursors); } void KTextEditor::ViewPrivate::tagAll() { m_viewInternal->tagAll(); } void KTextEditor::ViewPrivate::clear() { m_viewInternal->clear(); } void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty) { if (paintOnlyDirty) { m_viewInternal->updateDirty(); } else { m_viewInternal->update(); } } void KTextEditor::ViewPrivate::updateView(bool changed) { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView"; m_viewInternal->updateView(changed); m_viewInternal->m_leftBorder->update(); } //END void KTextEditor::ViewPrivate::slotHlChanged() { KateHighlighting *hl = doc()->highlight(); bool ok(!hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty()); if (actionCollection()->action(QStringLiteral("tools_comment"))) { actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_uncomment"))) { actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) { actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok); } // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry updateFoldingConfig(); } int KTextEditor::ViewPrivate::virtualCursorColumn() const { return doc()->toVirtualColumn(m_viewInternal->cursorPosition()); } void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor &newPosition) { emit mousePositionChanged(this, newPosition); } //BEGIN KTextEditor::SelectionInterface stuff bool KTextEditor::ViewPrivate::setSelection(const KTextEditor::Range &selection) { /** * anything to do? */ if (selection == m_selection) { return true; } /** * backup old range */ KTextEditor::Range oldSelection = m_selection; /** * set new range */ m_selection.setRange(selection.isEmpty() ? KTextEditor::Range::invalid() : selection); /** * trigger update of correct area */ tagSelection(oldSelection); repaintText(true); /** * emit holy signal */ emit selectionChanged(this); /** * be done */ return true; } bool KTextEditor::ViewPrivate::clearSelection() { return clearSelection(true); } bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection) { /** * no selection, nothing to do... */ if (!selection()) { return false; } /** * backup old range */ KTextEditor::Range oldSelection = m_selection; /** * invalidate current selection */ m_selection.setRange(KTextEditor::Range::invalid()); /** * trigger update of correct area */ tagSelection(oldSelection); if (redraw) { repaintText(true); } /** * emit holy signal */ if (finishedChangingSelection) { emit selectionChanged(this); } /** * be done */ return true; } bool KTextEditor::ViewPrivate::selection() const { if (!wrapCursor()) { return m_selection != KTextEditor::Range::invalid(); } else { return m_selection.toRange().isValid(); } } QString KTextEditor::ViewPrivate::selectionText() const { return doc()->text(m_selection, blockSelect); } bool KTextEditor::ViewPrivate::removeSelectedText() { if (!selection()) { return false; } doc()->editStart(); // Optimization: clear selection before removing text KTextEditor::Range selection = m_selection; doc()->removeText(selection, blockSelect); // don't redraw the cleared selection - that's done in editEnd(). if (blockSelect) { int selectionColumn = qMin(doc()->toVirtualColumn(selection.start()), doc()->toVirtualColumn(selection.end())); KTextEditor::Range newSelection = selection; newSelection.setStart(KTextEditor::Cursor(newSelection.start().line(), doc()->fromVirtualColumn(newSelection.start().line(), selectionColumn))); newSelection.setEnd(KTextEditor::Cursor(newSelection.end().line(), doc()->fromVirtualColumn(newSelection.end().line(), selectionColumn))); setSelection(newSelection); setCursorPositionInternal(newSelection.start()); } else { clearSelection(false); } doc()->editEnd(); return true; } bool KTextEditor::ViewPrivate::selectAll() { setBlockSelection(false); top(); shiftBottom(); return true; } bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor &cursor) { KTextEditor::Cursor ret = cursor; if ((!blockSelect) && (ret.column() < 0)) { ret.setColumn(0); } if (blockSelect) return cursor.line() >= m_selection.start().line() && ret.line() <= m_selection.end().line() && ret.column() >= m_selection.start().column() && ret.column() <= m_selection.end().column(); else { return m_selection.toRange().contains(cursor) || m_selection.end() == cursor; } } bool KTextEditor::ViewPrivate::lineSelected(int line) { return !blockSelect && m_selection.toRange().containsLine(line); } bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor &lineEndPos) { return (!blockSelect) && (lineEndPos.line() > m_selection.start().line() || (lineEndPos.line() == m_selection.start().line() && (m_selection.start().column() < lineEndPos.column() || lineEndPos.column() == -1))) && (lineEndPos.line() < m_selection.end().line() || (lineEndPos.line() == m_selection.end().line() && (lineEndPos.column() <= m_selection.end().column() && lineEndPos.column() != -1))); } bool KTextEditor::ViewPrivate::lineHasSelected(int line) { return selection() && m_selection.toRange().containsLine(line); } bool KTextEditor::ViewPrivate::lineIsSelection(int line) { return (line == m_selection.start().line() && line == m_selection.end().line()); } void KTextEditor::ViewPrivate::tagSelection(const KTextEditor::Range &oldSelection) { if (selection()) { if (oldSelection.start().line() == -1) { // We have to tag the whole lot if // 1) we have a selection, and: // a) it's new; or tagLines(m_selection, true); } else if (blockSelection() && (oldSelection.start().column() != m_selection.start().column() || oldSelection.end().column() != m_selection.end().column())) { // b) we're in block selection mode and the columns have changed tagLines(m_selection, true); tagLines(oldSelection, true); } else { if (oldSelection.start() != m_selection.start()) { if (oldSelection.start() < m_selection.start()) { tagLines(oldSelection.start(), m_selection.start(), true); } else { tagLines(m_selection.start(), oldSelection.start(), true); } } if (oldSelection.end() != m_selection.end()) { if (oldSelection.end() < m_selection.end()) { tagLines(oldSelection.end(), m_selection.end(), true); } else { tagLines(m_selection.end(), oldSelection.end(), true); } } } } else { // No more selection, clean up tagLines(oldSelection, true); } } void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor &cursor) { setSelection(doc()->wordRangeAt(cursor)); } void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor &cursor) { int line = cursor.line(); if (line + 1 >= doc()->lines()) { setSelection(KTextEditor::Range(line, 0, line, doc()->lineLength(line))); } else { setSelection(KTextEditor::Range(line, 0, line + 1, 0)); } } void KTextEditor::ViewPrivate::cut() { if (!selection() && !m_config->smartCopyCut()) { return; } copy(); if (!selection()) { selectLine(cursorPosition()); } removeSelectedText(); } void KTextEditor::ViewPrivate::copy() const { QString text; if (!selection()) { if (!m_config->smartCopyCut()) { return; } text = doc()->line(cursorPosition().line()) + QLatin1Char('\n'); m_viewInternal->moveEdge(KateViewInternal::left, false); } else { text = selectionText(); } // copy to clipboard and our history! KTextEditor::EditorPrivate::self()->copyToClipboard(text); } void KTextEditor::ViewPrivate::applyWordWrap() { int first = selectionRange().start().line(); int last = selectionRange().end().line(); if (first == last) { // Either no selection or only one line selected, wrap only the current line first = cursorPosition().line(); last = first; } doc()->wrapParagraph(first, last); } //END //BEGIN KTextEditor::BlockSelectionInterface stuff bool KTextEditor::ViewPrivate::blockSelection() const { return blockSelect; } bool KTextEditor::ViewPrivate::setBlockSelection(bool on) { if (on != blockSelect) { blockSelect = on; KTextEditor::Range oldSelection = m_selection; const bool hadSelection = clearSelection(false, false); setSelection(oldSelection); m_toggleBlockSelection->setChecked(blockSelection()); // when leaving block selection mode, if cursor is at an invalid position or past the end of the // line, move the cursor to the last column of the current line unless cursor wrapping is off ensureCursorColumnValid(); if (!hadSelection) { // emit selectionChanged() according to the KTextEditor::View api // documentation also if there is no selection around. This is needed, // as e.g. the Kate App status bar uses this signal to update the state // of the selection mode (block selection, line based selection) emit selectionChanged(this); } } return true; } bool KTextEditor::ViewPrivate::toggleBlockSelection() { m_toggleBlockSelection->setChecked(!blockSelect); return setBlockSelection(!blockSelect); } bool KTextEditor::ViewPrivate::wrapCursor() const { return !blockSelection(); } //END void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text) { emit textInserted(view, position, text); } bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor& c, const QString& templateString, const QString& script) { /** * no empty templates */ if (templateString.isEmpty()) { return false; } /** * not for read-only docs */ if (!doc()->isReadWrite()) { return false; } /** * only one handler maybe active at a time; store it in the document. * Clear it first to make sure at no time two handlers are active at once */ doc()->setActiveTemplateHandler(nullptr); doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, doc()->undoManager())); return true; } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange) { return tagLines(range.start(), range.end(), realRange); } void KTextEditor::ViewPrivate::deactivateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(false); } } void KTextEditor::ViewPrivate::activateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(true); } } bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const { // FIXME support return true; } bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool) { // FIXME support return true; } bool KTextEditor::ViewPrivate::isCompletionActive() const { return completionWidget()->isCompletionActive(); } KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const { if (!m_completionWidget) { m_completionWidget = new KateCompletionWidget(const_cast(this)); } return m_completionWidget; } void KTextEditor::ViewPrivate::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) { completionWidget()->startCompletion(word, model); } void KTextEditor::ViewPrivate::abortCompletion() { completionWidget()->abortCompletion(); } void KTextEditor::ViewPrivate::forceCompletion() { completionWidget()->execute(); } void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->registerCompletionModel(model); } void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->unregisterCompletionModel(model); } bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const { return completionWidget()->isCompletionModelRegistered(model); } bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const { return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation(); } void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled) { config()->setValue(KateViewConfig::AutomaticCompletionInvocation, enabled); } void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index) { emit completionExecuted(this, position, model, index); } void KTextEditor::ViewPrivate::sendCompletionAborted() { emit completionAborted(this); } void KTextEditor::ViewPrivate::paste(const QString *textToPaste) { m_temporaryAutomaticInvocationDisabled = true; doc()->paste(this, textToPaste ? *textToPaste : QApplication::clipboard()->text(QClipboard::Clipboard)); m_temporaryAutomaticInvocationDisabled = false; } bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position) { return setCursorPositionInternal(position, 1, true); } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const { return m_viewInternal->cursorPosition(); } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const { return KTextEditor::Cursor(m_viewInternal->cursorPosition().line(), virtualCursorColumn()); } QPoint KTextEditor::ViewPrivate::cursorToCoordinate(const KTextEditor::Cursor &cursor) const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, true, false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const { // map from View to ViewInternal coordinates return m_viewInternal->coordinatesToCursor(m_viewInternal->mapFromParent(coords), false); } QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorCoordinates(false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor &cursor) { m_viewInternal->scrollPos(cursor, false, true, false); } void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x) { m_viewInternal->scrollColumns(x); } KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const { return m_viewInternal->maxStartPos(true); } int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const { if (lineType == RealLine) { return m_textFolding.visibleLineToLine(m_viewInternal->startLine()); } else { return m_viewInternal->startLine(); } } int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const { if (lineType == RealLine) { return m_textFolding.visibleLineToLine(m_viewInternal->endLine()); } else { return m_viewInternal->endLine(); } } QRect KTextEditor::ViewPrivate::textAreaRectInternal() const { const auto sourceRect = m_viewInternal->rect(); const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft()); const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight()); return {topLeft, bottomRight}; } bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor &position) { return setCursorPositionInternal(position, doc()->config()->tabWidth(), true); } QString KTextEditor::ViewPrivate::currentTextLine() { return doc()->line(cursorPosition().line()); } QTextLayout * KTextEditor::ViewPrivate::textLayout(int line) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(line); return thisLine->isValid() ? thisLine->layout() : nullptr; } QTextLayout * KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor &pos) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(pos); return thisLine->isValid() ? thisLine->layout() : nullptr; } void KTextEditor::ViewPrivate::indent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); doc()->indent(r, 1); } void KTextEditor::ViewPrivate::unIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); doc()->indent(r, -1); } void KTextEditor::ViewPrivate::cleanIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); doc()->indent(r, 0); } void KTextEditor::ViewPrivate::align() { // no selection: align current line; selection: use selection range const int line = cursorPosition().line(); KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0)); if (selection()) { alignRange = selectionRange(); } doc()->align(this, alignRange); } void KTextEditor::ViewPrivate::comment() { m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); doc()->comment(this, cursorPosition().line(), cursorPosition().column(), 1); m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uncomment() { doc()->comment(this, cursorPosition().line(), cursorPosition().column(), -1); } void KTextEditor::ViewPrivate::toggleComment() { m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); doc()->comment(this, cursorPosition().line(), cursorPosition().column(), 0); m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uppercase() { doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Uppercase); } void KTextEditor::ViewPrivate::killLine() { if (m_selection.isEmpty()) { doc()->removeLine(cursorPosition().line()); } else { doc()->editStart(); // cache endline, else that moves and we might delete complete document if last line is selected! for (int line = m_selection.end().line(), endLine = m_selection.start().line(); line >= endLine; line--) { doc()->removeLine(line); } doc()->editEnd(); } } void KTextEditor::ViewPrivate::lowercase() { doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase); } void KTextEditor::ViewPrivate::capitalize() { doc()->editStart(); doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Lowercase); doc()->transform(this, cursorPosition(), KTextEditor::DocumentPrivate::Capitalize); doc()->editEnd(); } void KTextEditor::ViewPrivate::keyReturn() { doc()->newLine(this); m_viewInternal->iconBorder()->updateForCursorLineChange(); m_viewInternal->updateView(); } void KTextEditor::ViewPrivate::smartNewline() { const KTextEditor::Cursor cursor = cursorPosition(); const int ln = cursor.line(); Kate::TextLine line = doc()->kateTextLine(ln); int col = qMin(cursor.column(), line->firstChar()); if (col != -1) { while (line->length() > col && !(line->at(col).isLetterOrNumber() || line->at(col) == QLatin1Char('_')) && col < cursor.column()) { ++col; } } else { col = line->length(); // stay indented } doc()->editStart(); doc()->editWrapLine(ln, cursor.column()); doc()->insertText(KTextEditor::Cursor(ln + 1, 0), line->string(0, col)); doc()->editEnd(); m_viewInternal->updateView(); } +void KTextEditor::ViewPrivate::noIndentNewline() +{ + doc()->newLine(this, KTextEditor::DocumentPrivate::NoIndent); + m_viewInternal->iconBorder()->updateForCursorLineChange(); + m_viewInternal->updateView(); +} + void KTextEditor::ViewPrivate::backspace() { doc()->backspace(this, cursorPosition()); } void KTextEditor::ViewPrivate::insertTab() { doc()->insertTab(this, cursorPosition()); } void KTextEditor::ViewPrivate::deleteWordLeft() { doc()->editStart(); m_viewInternal->wordPrev(true); KTextEditor::Range selection = selectionRange(); removeSelectedText(); doc()->editEnd(); m_viewInternal->tagRange(selection, true); m_viewInternal->updateDirty(); } void KTextEditor::ViewPrivate::keyDelete() { doc()->del(this, cursorPosition()); } void KTextEditor::ViewPrivate::deleteWordRight() { doc()->editStart(); m_viewInternal->wordNext(true); KTextEditor::Range selection = selectionRange(); removeSelectedText(); doc()->editEnd(); m_viewInternal->tagRange(selection, true); m_viewInternal->updateDirty(); } void KTextEditor::ViewPrivate::transpose() { doc()->transpose(cursorPosition()); } void KTextEditor::ViewPrivate::cursorLeft() { if (selection() && !config()->persistentSelection()) { if (currentTextLine().isRightToLeft()) { m_viewInternal->updateCursor(selectionRange().end()); setSelection(KTextEditor::Range::invalid()); } else { m_viewInternal->updateCursor(selectionRange().start()); setSelection(KTextEditor::Range::invalid()); } } else { if (currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(); } else { m_viewInternal->cursorPrevChar(); } } } void KTextEditor::ViewPrivate::shiftCursorLeft() { if (currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(true); } else { m_viewInternal->cursorPrevChar(true); } } void KTextEditor::ViewPrivate::cursorRight() { if (selection() && !config()->persistentSelection()) { if (currentTextLine().isRightToLeft()) { m_viewInternal->updateCursor(selectionRange().start()); setSelection(KTextEditor::Range::invalid()); } else { m_viewInternal->updateCursor(selectionRange().end()); setSelection(KTextEditor::Range::invalid()); } } else { if (currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(); } else { m_viewInternal->cursorNextChar(); } } } void KTextEditor::ViewPrivate::shiftCursorRight() { if (currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(true); } else { m_viewInternal->cursorNextChar(true); } } void KTextEditor::ViewPrivate::wordLeft() { if (currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(); } else { m_viewInternal->wordPrev(); } } void KTextEditor::ViewPrivate::shiftWordLeft() { if (currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(true); } else { m_viewInternal->wordPrev(true); } } void KTextEditor::ViewPrivate::wordRight() { if (currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(); } else { m_viewInternal->wordNext(); } } void KTextEditor::ViewPrivate::shiftWordRight() { if (currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(true); } else { m_viewInternal->wordNext(true); } } void KTextEditor::ViewPrivate::home() { m_viewInternal->home(); } void KTextEditor::ViewPrivate::shiftHome() { m_viewInternal->home(true); } void KTextEditor::ViewPrivate::end() { m_viewInternal->end(); } void KTextEditor::ViewPrivate::shiftEnd() { m_viewInternal->end(true); } void KTextEditor::ViewPrivate::up() { m_viewInternal->cursorUp(); } void KTextEditor::ViewPrivate::shiftUp() { m_viewInternal->cursorUp(true); } void KTextEditor::ViewPrivate::down() { m_viewInternal->cursorDown(); } void KTextEditor::ViewPrivate::shiftDown() { m_viewInternal->cursorDown(true); } void KTextEditor::ViewPrivate::scrollUp() { m_viewInternal->scrollUp(); } void KTextEditor::ViewPrivate::scrollDown() { m_viewInternal->scrollDown(); } void KTextEditor::ViewPrivate::topOfView() { m_viewInternal->topOfView(); } void KTextEditor::ViewPrivate::shiftTopOfView() { m_viewInternal->topOfView(true); } void KTextEditor::ViewPrivate::bottomOfView() { m_viewInternal->bottomOfView(); } void KTextEditor::ViewPrivate::shiftBottomOfView() { m_viewInternal->bottomOfView(true); } void KTextEditor::ViewPrivate::pageUp() { m_viewInternal->pageUp(); } void KTextEditor::ViewPrivate::shiftPageUp() { m_viewInternal->pageUp(true); } void KTextEditor::ViewPrivate::pageDown() { m_viewInternal->pageDown(); } void KTextEditor::ViewPrivate::shiftPageDown() { m_viewInternal->pageDown(true); } void KTextEditor::ViewPrivate::top() { m_viewInternal->top_home(); } void KTextEditor::ViewPrivate::shiftTop() { m_viewInternal->top_home(true); } void KTextEditor::ViewPrivate::bottom() { m_viewInternal->bottom_end(); } void KTextEditor::ViewPrivate::shiftBottom() { m_viewInternal->bottom_end(true); } void KTextEditor::ViewPrivate::toMatchingBracket() { m_viewInternal->cursorToMatchingBracket(); } void KTextEditor::ViewPrivate::shiftToMatchingBracket() { m_viewInternal->cursorToMatchingBracket(true); } void KTextEditor::ViewPrivate::toPrevModifiedLine() { const int startLine = cursorPosition().line() - 1; const int line = doc()->findTouchedLine(startLine, false); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->updateCursor(c); } } void KTextEditor::ViewPrivate::toNextModifiedLine() { const int startLine = cursorPosition().line() + 1; const int line = doc()->findTouchedLine(startLine, true); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->updateCursor(c); } } KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const { return m_selection; } KTextEditor::Document *KTextEditor::ViewPrivate::document() const { return m_doc; } void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu) { if (m_contextMenu) { disconnect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); disconnect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } m_contextMenu = menu; m_userContextMenuSet = true; if (m_contextMenu) { connect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } } QMenu *KTextEditor::ViewPrivate::contextMenu() const { if (m_userContextMenuSet) { return m_contextMenu; } else { KXMLGUIClient *client = const_cast(this); while (client->parentClient()) { client = client->parentClient(); } //qCDebug(LOG_KTE) << "looking up all menu containers"; if (client->factory()) { QList conts = client->factory()->containers(QStringLiteral("menu")); foreach (QWidget *w, conts) { if (w->objectName() == QLatin1String("ktexteditor_popup")) { //perhaps optimize this block QMenu *menu = (QMenu *)w; // menu is a reusable instance shared among all views. Therefore, // disconnect the current receiver(s) from the menu show/hide signals // before connecting `this` view. This ensures that only the current // view gets a signal when the menu is about to be shown or hidden, // and not also the view(s) that previously had the menu open. disconnect(menu, SIGNAL(aboutToShow()), nullptr, nullptr); disconnect(menu, SIGNAL(aboutToHide()), nullptr, nullptr); connect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); return menu; } } } } return nullptr; } QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const { if (!menu) { menu = new QMenu(const_cast(this)); } menu->addAction(m_editUndo); menu->addAction(m_editRedo); menu->addSeparator(); menu->addAction(m_cut); menu->addAction(m_copy); menu->addAction(m_paste); menu->addSeparator(); menu->addAction(m_selectAll); menu->addAction(m_deSelect); if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) { menu->addSeparator(); menu->addAction(spellingSuggestions); } if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) { menu->addSeparator(); menu->addAction(bookmark); } return menu; } void KTextEditor::ViewPrivate::aboutToShowContextMenu() { QMenu *menu = qobject_cast(sender()); if (menu) { emit contextMenuAboutToShow(this, menu); } } void KTextEditor::ViewPrivate::aboutToHideContextMenu() { m_spellingMenu->setUseMouseForMisspelledRange(false); } // BEGIN ConfigInterface stff QStringList KTextEditor::ViewPrivate::configKeys() const { static const QStringList keys = { QStringLiteral("icon-bar"), QStringLiteral("line-numbers"), QStringLiteral("dynamic-word-wrap"), QStringLiteral("background-color"), QStringLiteral("selection-color"), QStringLiteral("search-highlight-color"), QStringLiteral("replace-highlight-color"), QStringLiteral("default-mark-type"), QStringLiteral("allow-mark-menu"), QStringLiteral("folding-bar"), QStringLiteral("folding-preview"), QStringLiteral("icon-border-color"), QStringLiteral("folding-marker-color"), QStringLiteral("line-number-color"), QStringLiteral("current-line-number-color"), QStringLiteral("modification-markers"), QStringLiteral("keyword-completion"), QStringLiteral("word-count"), QStringLiteral("scrollbar-minimap"), QStringLiteral("scrollbar-preview"), QStringLiteral("font") }; return keys; } QVariant KTextEditor::ViewPrivate::configValue(const QString &key) { if (key == QLatin1String("icon-bar")) { return config()->iconBar(); } else if (key == QLatin1String("line-numbers")) { return config()->lineNumbers(); } else if (key == QLatin1String("dynamic-word-wrap")) { return config()->dynWordWrap(); } else if (key == QLatin1String("background-color")) { return renderer()->config()->backgroundColor(); } else if (key == QLatin1String("selection-color")) { return renderer()->config()->selectionColor(); } else if (key == QLatin1String("search-highlight-color")) { return renderer()->config()->searchHighlightColor(); } else if (key == QLatin1String("replace-highlight-color")) { return renderer()->config()->replaceHighlightColor(); } else if (key == QLatin1String("default-mark-type")) { return config()->defaultMarkType(); } else if (key == QLatin1String("allow-mark-menu")) { return config()->allowMarkMenu(); } else if (key == QLatin1String("folding-bar")) { return config()->foldingBar(); } else if (key == QLatin1String("folding-preview")) { return config()->foldingPreview(); } else if (key == QLatin1String("icon-border-color")) { return renderer()->config()->iconBarColor(); } else if (key == QLatin1String("folding-marker-color")) { return renderer()->config()->foldingColor(); } else if (key == QLatin1String("line-number-color")) { return renderer()->config()->lineNumberColor(); } else if (key == QLatin1String("current-line-number-color")) { return renderer()->config()->currentLineNumberColor(); } else if (key == QLatin1String("modification-markers")) { return config()->lineModification(); } else if (key == QLatin1String("keyword-completion")) { return config()->keywordCompletion(); } else if (key == QLatin1String("word-count")) { return config()->showWordCount(); } else if (key == QLatin1String("scrollbar-minimap")) { return config()->scrollBarMiniMap(); } else if (key == QLatin1String("scrollbar-preview")) { return config()->scrollBarPreview(); } else if (key == QLatin1String("font")) { return renderer()->config()->font(); } // return invalid variant return QVariant(); } void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value) { // First, try the new config interface if (config()->setValue(key, value)) { return; } else if (renderer()->config()->setValue(key, value)) { return; } // No success? Go the old way if (value.canConvert(QVariant::Color)) { if (key == QLatin1String("background-color")) { renderer()->config()->setBackgroundColor(value.value()); } else if (key == QLatin1String("selection-color")) { renderer()->config()->setSelectionColor(value.value()); } else if (key == QLatin1String("search-highlight-color")) { renderer()->config()->setSearchHighlightColor(value.value()); } else if (key == QLatin1String("replace-highlight-color")) { renderer()->config()->setReplaceHighlightColor(value.value()); } else if (key == QLatin1String("icon-border-color")) { renderer()->config()->setIconBarColor(value.value()); } else if (key == QLatin1String("folding-marker-color")) { renderer()->config()->setFoldingColor(value.value()); } else if (key == QLatin1String("line-number-color")) { renderer()->config()->setLineNumberColor(value.value()); } else if (key == QLatin1String("current-line-number-color")) { renderer()->config()->setCurrentLineNumberColor(value.value()); } } else if (value.type() == QVariant::Bool) { // Note explicit type check above. If we used canConvert, then // values of type UInt will be trapped here. if (key == QLatin1String("dynamic-word-wrap")) { config()->setDynWordWrap(value.toBool()); } else if (key == QLatin1String("word-count")) { config()->setShowWordCount(value.toBool()); } } else if (value.canConvert(QVariant::Font)) { if (key == QLatin1String("font")) { renderer()->config()->setFont(value.value()); } } } // END ConfigInterface void KTextEditor::ViewPrivate::userInvokedCompletion() { completionWidget()->userInvokedCompletion(); } KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const { return m_bottomViewBar; } KateGotoBar *KTextEditor::ViewPrivate::gotoBar() { if (!m_gotoBar) { m_gotoBar = new KateGotoBar(this); bottomViewBar()->addBarWidget(m_gotoBar); } return m_gotoBar; } KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar() { if (!m_dictionaryBar) { m_dictionaryBar = new KateDictionaryBar(this); bottomViewBar()->addBarWidget(m_dictionaryBar); } return m_dictionaryBar; } void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) { KTextEditor::AnnotationModel *oldmodel = m_annotationModel; m_annotationModel = model; m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, m_annotationModel); } KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const { return m_annotationModel; } void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible) { m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible); } bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const { return m_viewInternal->m_leftBorder->annotationBorderOn(); } KTextEditor::AbstractAnnotationItemDelegate* KTextEditor::ViewPrivate::annotationItemDelegate() const { return m_viewInternal->m_leftBorder->annotationItemDelegate(); } void KTextEditor::ViewPrivate::setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate) { m_viewInternal->m_leftBorder->setAnnotationItemDelegate(delegate); } bool KTextEditor::ViewPrivate::uniformAnnotationItemSizes() const { return m_viewInternal->m_leftBorder->uniformAnnotationItemSizes(); } void KTextEditor::ViewPrivate::setAnnotationUniformItemSizes(bool enable) { m_viewInternal->m_leftBorder->setAnnotationUniformItemSizes(enable); } KTextEditor::Range KTextEditor::ViewPrivate::visibleRange() { //ensure that the view is up-to-date, otherwise 'endPos()' might fail! if (!m_viewInternal->endPos().isValid()) { m_viewInternal->updateView(); } return KTextEditor::Range(m_viewInternal->toRealCursor(m_viewInternal->startPos()), m_viewInternal->toRealCursor(m_viewInternal->endPos())); } bool KTextEditor::ViewPrivate::event(QEvent *e) { switch (e->type()) { case QEvent::StyleChange: setupLayout(); return true; default: return KTextEditor::View::event(e); } } void KTextEditor::ViewPrivate::paintEvent(QPaintEvent *e) { //base class KTextEditor::View::paintEvent(e); const QRect contentsRect = m_topSpacer->geometry()| m_bottomSpacer->geometry()| m_leftSpacer->geometry()| m_rightSpacer->geometry(); if (contentsRect.isValid()) { QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; // clear mouseOver and focus state // update from relevant widgets opt.state &= ~(QStyle::State_HasFocus|QStyle::State_MouseOver); const QList widgets = QList() << m_viewInternal << m_viewInternal->m_leftBorder << m_viewInternal->m_lineScroll << m_viewInternal->m_columnScroll; foreach (const QWidget *w, widgets) { if (w->hasFocus()) opt.state |= QStyle::State_HasFocus; if (w->underMouse()) opt.state |= QStyle::State_MouseOver; } // update rect opt.rect=contentsRect; // render QPainter paint(this); paint.setClipRegion(e->region()); paint.setRenderHints(QPainter::Antialiasing); style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this); } } void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b) { doc()->onTheFlySpellCheckingEnabled(b); } void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled) { m_spellingMenu->setVisible(enabled); m_toggleOnTheFlySpellCheck->setChecked(enabled); } KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu() { return m_spellingMenu; } void KTextEditor::ViewPrivate::notifyAboutRangeChange(int startLine, int endLine, bool rangeWithAttribute) { #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "trigger attribute changed from" << startLine << "to" << endLine << "rangeWithAttribute" << rangeWithAttribute; #endif // first call: if (!m_delayedUpdateTriggered) { m_delayedUpdateTriggered = true; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; // only set initial line range, if range with attribute! if (rangeWithAttribute) { m_lineToUpdateMin = startLine; m_lineToUpdateMax = endLine; } // emit queued signal and be done emit delayedUpdateOfView(); return; } // ignore lines if no attribute if (!rangeWithAttribute) { return; } // update line range if (startLine != -1 && (m_lineToUpdateMin == -1 || startLine < m_lineToUpdateMin)) { m_lineToUpdateMin = startLine; } if (endLine != -1 && endLine > m_lineToUpdateMax) { m_lineToUpdateMax = endLine; } } void KTextEditor::ViewPrivate::slotDelayedUpdateOfView() { if (!m_delayedUpdateTriggered) { return; } #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "delayed attribute changed from" << m_lineToUpdateMin << "to" << m_lineToUpdateMax; #endif // update ranges in updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); // update view, if valid line range, else only feedback update wanted anyway if (m_lineToUpdateMin != -1 && m_lineToUpdateMax != -1) { tagLines(m_lineToUpdateMin, m_lineToUpdateMax, true); updateView(true); } // reset flags m_delayedUpdateTriggered = false; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; } void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType) { // new ranges with cursor in, default none QSet newRangesIn; // on which range set we work? QSet &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn; // which cursor position to honor? KTextEditor::Cursor currentCursor = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->mousePosition() : m_viewInternal->cursorPosition(); // first: validate the remembered ranges QSet validRanges; foreach (Kate::TextRange *range, oldSet) if (doc()->buffer().rangePointerValid(range)) { validRanges.insert(range); } // cursor valid? else no new ranges can be found if (currentCursor.isValid() && currentCursor.line() < doc()->buffer().lines()) { // now: get current ranges for the line of cursor with an attribute QList rangesForCurrentCursor = doc()->buffer().rangesForLine(currentCursor.line(), this, false); // match which ranges really fit the given cursor foreach (Kate::TextRange *range, rangesForCurrentCursor) { // range has no dynamic attribute of right type and no feedback object if ((!range->attribute() || !range->attribute()->dynamicAttribute(activationType)) && !range->feedback()) { continue; } // range doesn't contain cursor, not interesting if ((range->start().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->start().toCursor()) : (currentCursor <= range->start().toCursor())) { continue; } if ((range->end().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->end().toCursor() <= currentCursor) : (range->end().toCursor() < currentCursor)) { continue; } // range contains cursor, was it already in old set? if (validRanges.contains(range)) { // insert in new, remove from old, be done with it newRangesIn.insert(range); validRanges.remove(range); continue; } // oh, new range, trigger update and insert into new set newRangesIn.insert(range); if (range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseEnteredRange(range, this); } else { range->feedback()->caretEnteredRange(range, this); emit caretChangedRange(this); } } #ifdef VIEW_RANGE_DEBUG // found new range for activation qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType; #endif } } // now: notify for left ranges! foreach (Kate::TextRange *range, validRanges) { // range valid + right dynamic attribute, trigger update if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseExitedRange(range, this); } else { range->feedback()->caretExitedRange(range, this); emit caretChangedRange(this); } } } // set new ranges oldSet = newRangesIn; } void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList > actions) { // just forward to KateMessageWidget :-) auto messageWidget = m_messageWidgets[message->position()]; if (!messageWidget) { // this branch is used for: TopInView, CenterInView, and BottomInView messageWidget = new KateMessageWidget(m_viewInternal, true); m_messageWidgets[message->position()] = messageWidget; m_notificationLayout->addWidget(messageWidget, message->position()); connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), messageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), messageWidget, SLOT(startAutoHideTimer())); } messageWidget->postMessage(message, actions); } KateMessageWidget *KTextEditor::ViewPrivate::messageWidget() { return m_messageWidgets[KTextEditor::Message::TopInView]; } void KTextEditor::ViewPrivate::saveFoldingState() { m_savedFoldingState = m_textFolding.exportFoldingRanges(); } void KTextEditor::ViewPrivate::applyFoldingState() { m_textFolding.importFoldingRanges(m_savedFoldingState); m_savedFoldingState = QJsonDocument(); } void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file) { KateExporter(this).exportToFile(file); } void KTextEditor::ViewPrivate::exportHtmlToClipboard () { KateExporter(this).exportToClipboard(); } void KTextEditor::ViewPrivate::exportHtmlToFile () { const QString file = QFileDialog::getSaveFileName(this, i18n("Export File as HTML"), doc()->documentName()); if (!file.isEmpty()) { KateExporter(this).exportToFile(file); } } void KTextEditor::ViewPrivate::clearHighlights() { qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); m_currentTextForHighlights.clear(); } void KTextEditor::ViewPrivate::selectionChangedForHighlights() { QString text; // if text of selection is still the same, abort if (selection() && selectionRange().onSingleLine()) { text = selectionText(); if (text == m_currentTextForHighlights) return; } // text changed: remove all highlights + create new ones // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); // do not highlight strings with leading and trailing spaces if (!text.isEmpty() && (text.at(0).isSpace() || text.at(text.length()-1).isSpace())) return; // trigger creation of ranges for current view range m_currentTextForHighlights = text; createHighlights(); } void KTextEditor::ViewPrivate::createHighlights() { // do nothing if no text to highlight if (m_currentTextForHighlights.isEmpty()) { return; } KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); attr->setBackground(Qt::yellow); // set correct highlight color from Kate's color schema QColor fgColor = defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); QColor bgColor = renderer()->config()->searchHighlightColor(); attr->setForeground(fgColor); attr->setBackground(bgColor); KTextEditor::Cursor start(visibleRange().start()); KTextEditor::Range searchRange; /** * only add word boundary if we can find the text then * fixes $lala hl */ QString regex = QRegExp::escape (m_currentTextForHighlights); if (QRegExp (QStringLiteral("\\b%1").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("\\b%1").arg(regex); if (QRegExp (QStringLiteral("%1\\b").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("%1\\b").arg(regex); QVector matches; do { searchRange.setRange(start, visibleRange().end()); matches = doc()->searchText(searchRange, regex, KTextEditor::Regex); if (matches.first().isValid()) { KTextEditor::MovingRange* mr = doc()->newMovingRange(matches.first()); mr->setAttribute(attr); mr->setView(this); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); m_rangesForHighlights.append(mr); start = matches.first().end(); } } while (matches.first().isValid()); } KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const { return m_viewInternal->m_currentInputMode; } void KTextEditor::ViewPrivate::toggleInputMode() { if (QAction *a = dynamic_cast(sender())) { setInputMode(static_cast(a->data().toInt())); } } void KTextEditor::ViewPrivate::cycleInputMode() { InputMode current = currentInputMode()->viewInputMode(); InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode; setInputMode(to); } //BEGIN KTextEditor::PrintInterface stuff bool KTextEditor::ViewPrivate::print() { return KatePrinter::print(this); } void KTextEditor::ViewPrivate::printPreview() { KatePrinter::printPreview(this); } //END //BEGIN KTextEditor::InlineNoteInterface void KTextEditor::ViewPrivate::registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) { if (! m_inlineNoteProviders.contains(provider)) { m_inlineNoteProviders.append(provider); connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesReset, this, &ViewPrivate::inlineNotesReset); connect(provider, &KTextEditor::InlineNoteProvider::inlineNotesChanged, this, &ViewPrivate::inlineNotesLineChanged); inlineNotesReset(); } } void KTextEditor::ViewPrivate::unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) { const int index = m_inlineNoteProviders.indexOf(provider); if (index >= 0) { m_inlineNoteProviders.removeAt(index); provider->disconnect(this); inlineNotesReset(); } } QVarLengthArray KTextEditor::ViewPrivate::inlineNotes(int line) const { QVarLengthArray allInlineNotes; for (KTextEditor::InlineNoteProvider *provider: m_inlineNoteProviders) { int index = 0; for (auto column: provider->inlineNotes(line)) { KateInlineNoteData note = { provider, this, {line, column}, index, m_viewInternal->m_activeInlineNote.m_underMouse, m_viewInternal->renderer()->currentFont(), m_viewInternal->renderer()->lineHeight() }; allInlineNotes.append(note); index++; } } return allInlineNotes; } QRect KTextEditor::ViewPrivate::inlineNoteRect(const KateInlineNoteData& note) const { return m_viewInternal->inlineNoteRect(note); } void KTextEditor::ViewPrivate::inlineNotesReset() { m_viewInternal->m_activeInlineNote = {}; tagLines(0, doc()->lastLine(), true); } void KTextEditor::ViewPrivate::inlineNotesLineChanged(int line) { if ( line == m_viewInternal->m_activeInlineNote.m_position.line() ) { m_viewInternal->m_activeInlineNote = {}; } tagLines(line, line, true); } //END KTextEditor::InlineNoteInterface KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const { KateRendererConfig * renderConfig = const_cast(this)->renderer()->config(); KTextEditor::Attribute::Ptr style = doc()->highlight()->attributes(renderConfig->schema()).at(defaultStyle); if (!style->hasProperty(QTextFormat::BackgroundBrush)) { // make sure the returned style has the default background color set style = new KTextEditor::Attribute(*style); style->setBackground(QBrush(renderConfig->backgroundColor())); } return style; } QList KTextEditor::ViewPrivate::lineAttributes(int line) { QList attribs; if (line < 0 || line >= doc()->lines()) return attribs; Kate::TextLine kateLine = doc()->kateTextLine(line); if (!kateLine) { return attribs; } const QVector &intAttrs = kateLine->attributesList(); for (int i = 0; i < intAttrs.size(); ++i) { if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) { attribs << KTextEditor::AttributeBlock( intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(intAttrs.at(i).attributeValue) ); } } return attribs; } diff --git a/src/view/kateview.h b/src/view/kateview.h index 64be10f5..4abb3a97 100644 --- a/src/view/kateview.h +++ b/src/view/kateview.h @@ -1,1097 +1,1105 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001-2010 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef kate_view_h #define kate_view_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katetextrange.h" #include "katetextfolding.h" #include "katerenderer.h" namespace KTextEditor { class AnnotationModel; class Message; class InlineNoteProvider; } namespace KTextEditor { class DocumentPrivate; } class KateBookmarks; class KateViewConfig; class KateRenderer; class KateSpellCheckDialog; class KateCompletionWidget; class KateViewInternal; class KateViewBar; class KateTextPreview; class KateGotoBar; class KateDictionaryBar; class KateSpellingMenu; class KateMessageWidget; class KateIconBorder; class KateStatusBar; class KateViewEncodingAction; class KateModeMenu; class KateAbstractInputMode; class KateScriptActionMenu; class KateMessageLayout; class KateInlineNoteData; class KToggleAction; class KSelectAction; class QAction; namespace KTextEditor { // // Kate KTextEditor::View class ;) // class KTEXTEDITOR_EXPORT ViewPrivate : public KTextEditor::View, public KTextEditor::TextHintInterface, public KTextEditor::CodeCompletionInterface, public KTextEditor::ConfigInterface, public KTextEditor::InlineNoteInterface, public KTextEditor::AnnotationViewInterfaceV2 { Q_OBJECT Q_INTERFACES(KTextEditor::TextHintInterface) Q_INTERFACES(KTextEditor::ConfigInterface) Q_INTERFACES(KTextEditor::CodeCompletionInterface) Q_INTERFACES(KTextEditor::AnnotationViewInterface) Q_INTERFACES(KTextEditor::AnnotationViewInterfaceV2) Q_INTERFACES(KTextEditor::InlineNoteInterface) friend class KTextEditor::View; friend class ::KateViewInternal; friend class ::KateIconBorder; friend class ::KateTextPreview; public: ViewPrivate (KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow = nullptr); ~ViewPrivate() override; /** * Get the view's main window, if any * \return the view's main window */ KTextEditor::MainWindow *mainWindow() const override { return m_mainWindow; } KTextEditor::Document *document() const override; ViewMode viewMode() const override; QString viewModeHuman() const override; InputMode viewInputMode() const override; QString viewInputModeHuman() const override; void setInputMode(InputMode mode); // // KTextEditor::ClipboardInterface // public Q_SLOTS: void paste(const QString *textToPaste = nullptr); void cut(); void copy() const; private Q_SLOTS: /** * Wrap lines touched by the selection with respect of existing paragraphs. * Work is done by KTextEditor::DocumentPrivate::wrapParagraph */ void applyWordWrap(); // // KTextEditor::PopupMenuInterface // public: void setContextMenu(QMenu *menu) override; QMenu *contextMenu() const override; QMenu *defaultContextMenu(QMenu *menu = nullptr) const override; private Q_SLOTS: void aboutToShowContextMenu(); void aboutToHideContextMenu(); private: QPointer m_contextMenu; // // KTextEditor::ViewCursorInterface // public: bool setCursorPosition(KTextEditor::Cursor position) override; KTextEditor::Cursor cursorPosition() const override; KTextEditor::Cursor cursorPositionVirtual() const override; QPoint cursorToCoordinate(const KTextEditor::Cursor &cursor) const override; KTextEditor::Cursor coordinatesToCursor(const QPoint &coord) const override; QPoint cursorPositionCoordinates() const override; bool setCursorPositionVisual(const KTextEditor::Cursor &position); /** * Return the virtual cursor column, each tab is expanded into the * document's tabWidth characters. If word wrap is off, the cursor may be * behind the line's length. */ int virtualCursorColumn() const; bool mouseTrackingEnabled() const override; bool setMouseTrackingEnabled(bool enable) override; private: void notifyMousePositionChanged(const KTextEditor::Cursor &newPosition); // Internal public: bool setCursorPositionInternal(const KTextEditor::Cursor &position, uint tabwidth = 1, bool calledExternally = false); // // KTextEditor::ConfigInterface // public: QStringList configKeys() const override; QVariant configValue(const QString &key) override; void setConfigValue(const QString &key, const QVariant &value) override; Q_SIGNALS: void configChanged(); public: /** * Try to fold an unfolded range starting at @p line * @return the new folded range on success, otherwise an unvalid range */ KTextEditor::Range foldLine(int line); /** * Try to unfold a folded range starting at @p line * @return true when a range was unfolded, otherwise false */ bool unfoldLine(int line); /** * Try to toggle the folding state of a range starting at line @p line * @return true when the line was toggled, false when not */ bool toggleFoldingOfLine(int line); /** * Try to change all the foldings inside a folding range starting at @p line * but not the range itself starting there. * However, should the range itself be folded, will only the range unfolded * and the containing ranges kept untouched. * Should the range not contain other ranges will the range itself folded, * @return true when any range was folded or unfolded, otherwise false */ bool toggleFoldingsInRange(int line); // // KTextEditor::CodeCompletionInterface2 // public: bool isCompletionActive() const override; void startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) override; void abortCompletion() override; void forceCompletion() override; void registerCompletionModel(KTextEditor::CodeCompletionModel *model) override; void unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) override; bool isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const; bool isAutomaticInvocationEnabled() const override; void setAutomaticInvocationEnabled(bool enabled = true) override; Q_SIGNALS: void completionExecuted(KTextEditor::View *view, const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &); void completionAborted(KTextEditor::View *view); public Q_SLOTS: void userInvokedCompletion(); public: KateCompletionWidget *completionWidget() const; mutable KateCompletionWidget *m_completionWidget; void sendCompletionExecuted(const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index); void sendCompletionAborted(); // // KTextEditor::TextHintInterface // public: void registerTextHintProvider(KTextEditor::TextHintProvider *provider) override; void unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) override; void setTextHintDelay(int delay) override; int textHintDelay() const override; public: bool dynWordWrap() const { return m_hasWrap; } // // Inline Notes Interface // public: void registerInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) override; void unregisterInlineNoteProvider(KTextEditor::InlineNoteProvider *provider) override; QRect inlineNoteRect(const KateInlineNoteData& note) const; QVarLengthArray inlineNotes(int line) const; private: QVector m_inlineNoteProviders; private Q_SLOTS: void inlineNotesReset(); void inlineNotesLineChanged(int line); // // KTextEditor::SelectionInterface stuff // public Q_SLOTS: bool setSelection(const KTextEditor::Range &selection) override; bool removeSelection() override { return clearSelection(); } bool removeSelectionText() override { return removeSelectedText(); } bool setBlockSelection(bool on) override; bool toggleBlockSelection(); bool clearSelection(); bool clearSelection(bool redraw, bool finishedChangingSelection = true); bool removeSelectedText(); bool selectAll(); public: bool selection() const override; QString selectionText() const override; bool blockSelection() const override; KTextEditor::Range selectionRange() const override; static void blockFix(KTextEditor::Range &range); // // Arbitrary Syntax HL + Action extensions // public: // Action association extension void deactivateEditActions(); void activateEditActions(); // // internal helper stuff, for katerenderer and so on // public: // should cursor be wrapped ? take config + blockselection state in account bool wrapCursor() const; // some internal functions to get selection state of a line/col bool cursorSelected(const KTextEditor::Cursor &cursor); bool lineSelected(int line); bool lineEndSelected(const KTextEditor::Cursor &lineEndPos); bool lineHasSelected(int line); bool lineIsSelection(int line); void ensureCursorColumnValid(); void tagSelection(const KTextEditor::Range &oldSelection); void selectWord(const KTextEditor::Cursor &cursor); void selectLine(const KTextEditor::Cursor &cursor); //BEGIN EDIT STUFF public: void editStart(); void editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom); void editSetCursor(const KTextEditor::Cursor &cursor); //END //BEGIN TAG & CLEAR public: bool tagLine(const KTextEditor::Cursor &virtualCursor); bool tagRange(const KTextEditor::Range &range, bool realLines = false); bool tagLines(int start, int end, bool realLines = false); bool tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors = false); bool tagLines(KTextEditor::Range range, bool realRange = false); void tagAll(); void clear(); void repaintText(bool paintOnlyDirty = false); void updateView(bool changed = false); //END // // KTextEditor::AnnotationView // public: void setAnnotationModel(KTextEditor::AnnotationModel *model) override; KTextEditor::AnnotationModel *annotationModel() const override; void setAnnotationBorderVisible(bool visible) override; bool isAnnotationBorderVisible() const override; void setAnnotationItemDelegate(KTextEditor::AbstractAnnotationItemDelegate *delegate) override; KTextEditor::AbstractAnnotationItemDelegate* annotationItemDelegate() const override; void setAnnotationUniformItemSizes(bool enable) override; bool uniformAnnotationItemSizes() const override; Q_SIGNALS: void annotationContextMenuAboutToShow(KTextEditor::View *view, QMenu *menu, int line) override; void annotationActivated(KTextEditor::View *view, int line) override; // KF6: fix View -> KTextEditor::View void annotationBorderVisibilityChanged(View *view, bool visible) override; void navigateLeft(); void navigateRight(); void navigateUp(); void navigateDown(); void navigateAccept(); void navigateBack(); private: KTextEditor::AnnotationModel *m_annotationModel; // // KTextEditor::View // public: void emitNavigateLeft() { emit navigateLeft(); } void emitNavigateRight() { emit navigateRight(); } void emitNavigateUp() { emit navigateUp(); } void emitNavigateDown() { emit navigateDown(); } void emitNavigateAccept() { emit navigateAccept(); } void emitNavigateBack() { emit navigateBack(); } /** Return values for "save" related commands. */ bool isOverwriteMode() const; QString currentTextLine(); QTextLayout * textLayout(int line) const; QTextLayout * textLayout(const KTextEditor::Cursor &pos) const; public Q_SLOTS: void indent(); void unIndent(); void cleanIndent(); void align(); void comment(); void uncomment(); void toggleComment(); void killLine(); /** * Sets the cursor to the previous editing position in this document */ void goToPreviousEditingPosition(); /** * Sets the cursor to the next editing position in this document. */ void goToNextEditingPosition(); /** * Uppercases selected text, or an alphabetic character next to the cursor. */ void uppercase(); /** * Lowercases selected text, or an alphabetic character next to the cursor. */ void lowercase(); /** * Capitalizes the selection (makes each word start with an uppercase) or * the word under the cursor. */ void capitalize(); /** * Joins lines touched by the selection. */ void joinLines(); /** * Performs a line break (insert a new line char) at current cursor position * and indent the new line. * * Most work is done by @c KTextEditor::DocumentPrivate::newLine and * @c KateAutoIndent::userTypedChar * @see KTextEditor::DocumentPrivate::newLine, KateAutoIndent */ void keyReturn(); /** * Performs a line break (insert a new line char) at current cursor position * but keep all leading non word char as indent for the new line. */ void smartNewline(); + /** + * Inserts a new line character at the current cursor position, with + * the newly inserted line having no indentation regardless of indentation + * settings. Useful e.g. for inserting a new, empty, line to separate + * blocks of code inside a function. + */ + void noIndentNewline(); + /** * Insert a tabulator char at current cursor position. */ void backspace(); /** * Remove the word left from the current cursor position including all leading * space. * @see KateViewInternal::wordPrev */ void deleteWordLeft(); /** * Remove the current selection. When nothing is selected the char right * from the current cursor position is removed. * @see KTextEditor::DocumentPrivate::del */ void keyDelete(); /** * When the char right from the current cursor position is a space is all * space to the right removed. Otherwise is the word to the right including * all trialling space removed. * @see KateViewInternal::wordNext */ void deleteWordRight(); /** * Transpose the characters left and right from the current cursor position * and move the cursor one position to the right. If the char right to the * current cursor position is a new line char, nothing is done. * @see KTextEditor::DocumentPrivate::transpose */ void transpose(); void cursorLeft(); void shiftCursorLeft(); void cursorRight(); void shiftCursorRight(); void wordLeft(); void shiftWordLeft(); void wordRight(); void shiftWordRight(); void home(); void shiftHome(); void end(); void shiftEnd(); void up(); void shiftUp(); void down(); void shiftDown(); void scrollUp(); void scrollDown(); void topOfView(); void shiftTopOfView(); void bottomOfView(); void shiftBottomOfView(); void pageUp(); void shiftPageUp(); void pageDown(); void shiftPageDown(); void top(); void shiftTop(); void bottom(); void shiftBottom(); void toMatchingBracket(); void shiftToMatchingBracket(); void toPrevModifiedLine(); void toNextModifiedLine(); /** * Insert a tabulator char at current cursor position. */ void insertTab(); void gotoLine(); // config file / session management functions public: /** * Read session settings from the given \p config. * * Known flags: * "SkipUrl" => don't save/restore the file * "SkipMode" => don't save/restore the mode * "SkipHighlighting" => don't save/restore the highlighting * "SkipEncoding" => don't save/restore the encoding * * \param config read the session settings from this KConfigGroup * \param flags additional flags * \see writeSessionConfig() */ void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) override; /** * Write session settings to the \p config. * See readSessionConfig() for more details. * * \param config write the session settings to this KConfigGroup * \param flags additional flags * \see readSessionConfig() */ void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) override; public Q_SLOTS: void setEol(int eol); void setAddBom(bool enabled); void find(); void findSelectedForwards(); void findSelectedBackwards(); void replace(); void findNext(); void findPrevious(); void setFoldingMarkersOn(bool enable); // Not in KTextEditor::View, but should be void setIconBorder(bool enable); void setLineNumbersOn(bool enable); void setScrollBarMarks(bool enable); void setScrollBarMiniMap(bool enable); void setScrollBarMiniMapAll(bool enable); void setScrollBarMiniMapWidth(int width); void toggleFoldingMarkers(); void toggleIconBorder(); void toggleLineNumbersOn(); void toggleScrollBarMarks(); void toggleScrollBarMiniMap(); void toggleScrollBarMiniMapAll(); void toggleDynWordWrap(); void setDynWrapIndicators(int mode); public: int getEol() const; public: KateRenderer *renderer(); bool iconBorder(); bool lineNumbersOn(); bool scrollBarMarks(); bool scrollBarMiniMap(); bool scrollBarMiniMapAll(); int dynWrapIndicators(); bool foldingMarkersOn(); private Q_SLOTS: /** * used to update actions after selection changed */ void slotSelectionChanged(); void toggleInputMode(); void cycleInputMode(); public: /** * accessor to katedocument pointer * @return pointer to document */ KTextEditor::DocumentPrivate *doc() { return m_doc; } const KTextEditor::DocumentPrivate *doc() const { return m_doc; } public Q_SLOTS: void slotUpdateUndo(); void toggleInsert(); void reloadFile(); void toggleWWMarker(); void toggleNPSpaces(); void toggleWordCount(bool on); void toggleWriteLock(); void switchToCmdLine(); void slotReadWriteChanged(); void slotClipboardHistoryChanged(); Q_SIGNALS: void dropEventPass(QDropEvent *); public: /** * Folding handler for this view. * @return folding handler */ Kate::TextFolding &textFolding() { return m_textFolding; } public: void slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text); void exportHtmlToFile(const QString &file); private Q_SLOTS: void slotDocumentReloaded(); void slotDocumentAboutToReload(); void slotGotFocus(); void slotLostFocus(); void slotSaveCanceled(const QString &error); void slotConfigDialog(); void exportHtmlToClipboard(); void exportHtmlToFile(); public Q_SLOTS: void slotFoldToplevelNodes(); void slotExpandToplevelNodes(); void slotToggleFolding(); void slotToggleFoldingsInRange(); private: void setupLayout(); void setupConnections(); void setupActions(); void setupEditActions(); void setupCodeFolding(); QList m_editActions; QAction *m_editUndo; QAction *m_editRedo; QAction *m_pasteMenu; bool m_gotoBottomAfterReload; KToggleAction *m_toggleFoldingMarkers; KToggleAction *m_toggleIconBar; KToggleAction *m_toggleLineNumbers; KToggleAction *m_toggleScrollBarMarks; KToggleAction *m_toggleScrollBarMiniMap; KToggleAction *m_toggleScrollBarMiniMapAll; KToggleAction *m_toggleDynWrap; KSelectAction *m_setDynWrapIndicators; KToggleAction *m_toggleWWMarker; KToggleAction *m_toggleNPSpaces; KToggleAction *m_toggleWordCount; QAction *m_switchCmdLine; KToggleAction *m_viInputModeAction; KSelectAction *m_setEndOfLine; KToggleAction *m_addBom; QAction *m_cut; QAction *m_copy; QAction *m_copyHtmlAction; QAction *m_paste; QAction *m_selectAll; QAction *m_deSelect; QActionGroup *m_inputModeActions; KToggleAction *m_toggleBlockSelection; KToggleAction *m_toggleInsert; KToggleAction *m_toggleWriteLock; bool m_hasWrap; KTextEditor::DocumentPrivate *const m_doc; Kate::TextFolding m_textFolding; KateViewConfig *const m_config; KateRenderer *const m_renderer; KateViewInternal *const m_viewInternal; KateSpellCheckDialog *m_spell; KateBookmarks *const m_bookmarks; //* margins QSpacerItem *m_topSpacer; QSpacerItem *m_leftSpacer; QSpacerItem *m_rightSpacer; QSpacerItem *m_bottomSpacer; private Q_SLOTS: void slotHlChanged(); /** * Configuration */ public: inline KateViewConfig *config() { return m_config; } void updateConfig(); void updateDocumentConfig(); void updateRendererConfig(); private Q_SLOTS: void updateFoldingConfig(); private: bool m_startingUp; bool m_updatingDocumentConfig; // stores the current selection Kate::TextRange m_selection; // do we select normal or blockwise ? bool blockSelect; // templates public: bool insertTemplateInternal(const KTextEditor::Cursor& insertPosition, const QString& templateString, const QString& script = QString()); /** * Accessors to the bars... */ public: KateViewBar *bottomViewBar() const; KateDictionaryBar *dictionaryBar(); private: KateGotoBar *gotoBar(); /** * viewbar + its widgets * they are created on demand... */ private: // created in constructor of the view KateViewBar *m_bottomViewBar; // created on demand..., only access them through the above accessors.... KateGotoBar *m_gotoBar; KateDictionaryBar *m_dictionaryBar; // input modes public: KateAbstractInputMode *currentInputMode() const; public: KTextEditor::Range visibleRange(); Q_SIGNALS: void displayRangeChanged(KTextEditor::ViewPrivate *view); protected: bool event(QEvent *e) override; void paintEvent(QPaintEvent *e) override; KToggleAction *m_toggleOnTheFlySpellCheck; KateSpellingMenu *m_spellingMenu; protected Q_SLOTS: void toggleOnTheFlySpellCheck(bool b); public Q_SLOTS: void changeDictionary(); void reflectOnTheFlySpellCheckStatus(bool enabled); public: KateSpellingMenu *spellingMenu(); private: bool m_userContextMenuSet; private Q_SLOTS: /** * save folding state before document reload */ void saveFoldingState(); /** * restore folding state after document reload */ void applyFoldingState(); void clearHighlights(); void createHighlights(); private: void selectionChangedForHighlights(); /** * saved folding state */ QJsonDocument m_savedFoldingState; QString m_currentTextForHighlights; QList m_rangesForHighlights; public: /** * Attribute of a range changed or range with attribute changed in given line range. * @param startLine start line of change * @param endLine end line of change * @param rangeWithAttribute attribute changed or is active, this will perhaps lead to repaints */ void notifyAboutRangeChange(int startLine, int endLine, bool rangeWithAttribute); private Q_SLOTS: /** * Delayed update for view after text ranges changed */ void slotDelayedUpdateOfView(); Q_SIGNALS: /** * Delayed update for view after text ranges changed */ void delayedUpdateOfView(); /** * Emitted whenever the caret enter or leave a range. * ATM only used by KateStatusBar to update the dict button */ void caretChangedRange(KTextEditor::View *); public: /** * set of ranges which had the mouse inside last time, used for rendering * @return set of ranges which had the mouse inside last time checked */ const QSet *rangesMouseIn() const { return &m_rangesMouseIn; } /** * set of ranges which had the caret inside last time, used for rendering * @return set of ranges which had the caret inside last time checked */ const QSet *rangesCaretIn() const { return &m_rangesCaretIn; } /** * check if ranges changed for mouse in and caret in * @param activationType type of activation to check */ void updateRangesIn(KTextEditor::Attribute::ActivationType activationType); // // helpers for delayed view update after ranges changes // private: /** * update already inited? */ bool m_delayedUpdateTriggered; /** * minimal line to update */ int m_lineToUpdateMin; /** * maximal line to update */ int m_lineToUpdateMax; /** * set of ranges which had the mouse inside last time */ QSet m_rangesMouseIn; /** * set of ranges which had the caret inside last time */ QSet m_rangesCaretIn; // // forward impl for KTextEditor::MessageInterface // public: /** * Used by Document::postMessage(). */ void postMessage(KTextEditor::Message *message, QList > actions); private: /** * Message widgets showing KTextEditor::Messages. * The index of the array maps to the enum KTextEditor::Message::MessagePosition. */ std::array m_messageWidgets{{nullptr}}; /** Layout for floating notifications */ KateMessageLayout *m_notificationLayout = nullptr; // for unit test 'tests/messagetest.cpp' public: KateMessageWidget *messageWidget(); private: /** * The main window responsible for this view, if any */ QPointer m_mainWindow; // // KTextEditor::PrintInterface // public Q_SLOTS: bool print() override; void printPreview() override; public: /** * Get the view status bar * @return status bar, in enabled */ KateStatusBar *statusBar () const { return m_statusBar; } /** * Toogle status bar, e.g. create or remove it */ void toggleStatusBar (); /** * Get the encoding menu * @return the encoding menu */ KateViewEncodingAction *encodingAction () const { return m_encodingAction; } /** * Get the mode menu * @return the mode menu */ KateModeMenu *modeAction () const { return m_modeAction; } private: /** * the status bar of this view */ KateStatusBar *m_statusBar; /** * the encoding selection menu, used by view + status bar */ KateViewEncodingAction *m_encodingAction; /** * the mode selection menu, used by view */ KateModeMenu *m_modeAction; /** * is automatic invocation of completion disabled temporarily? */ bool m_temporaryAutomaticInvocationDisabled; public: /** * Returns the attribute for the default style \p defaultStyle. */ Attribute::Ptr defaultStyleAttribute(DefaultStyle defaultStyle) const override; /** * Get the list of AttributeBlocks for a given \p line in the document. * * \return list of AttributeBlocks for given \p line. */ QList lineAttributes(int line) override; private: // remember folding state to prevent refolding the first line if it was manually unfolded, // e.g. when saving a file or changing other config vars bool m_autoFoldedFirstLine; public: void setScrollPositionInternal(KTextEditor::Cursor &cursor); void setHorizontalScrollPositionInternal(int x); KTextEditor::Cursor maxScrollPositionInternal() const; int firstDisplayedLineInternal(LineType lineType) const; int lastDisplayedLineInternal(LineType lineType) const; QRect textAreaRectInternal() const; private: /** * script action menu, stored in scoped pointer to ensure * destruction before other QObject auto-cleanup as it * manage sub objects on its own that have this view as parent */ QScopedPointer m_scriptActionMenu; }; } #endif