diff --git a/src/completion/katekeywordcompletion.cpp b/src/completion/katekeywordcompletion.cpp index bdb8c137..ad40535d 100644 --- a/src/completion/katekeywordcompletion.cpp +++ b/src/completion/katekeywordcompletion.cpp @@ -1,211 +1,189 @@ /* This file is part of the KDE libraries Copyright (C) 2014 Sven Brauch 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, or any later version, 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. */ #include "katekeywordcompletion.h" #include "katehighlight.h" #include "katehighlighthelpers.h" #include "katedocument.h" #include "katetextline.h" #include #include #include KateKeywordCompletionModel::KateKeywordCompletionModel(QObject* parent) : CodeCompletionModel(parent) { setHasGroups(false); } void KateKeywordCompletionModel::completionInvoked(KTextEditor::View* view, const KTextEditor::Range& range, KTextEditor::CodeCompletionModel::InvocationType /*invocationType*/) { KTextEditor::DocumentPrivate* doc = static_cast(view->document()); - if ( ! doc->highlight() || doc->highlight()->noHighlighting() ) { - // no highlighting -- nothing to do + if ( !doc->highlight() || doc->highlight()->noHighlighting() ) { return; } - - Kate::TextLine line = doc->kateTextLine(range.end().line()); - Kate::TextLine previousLine = doc->kateTextLine(range.end().line() - 1); - Kate::TextLine nextLine = doc->kateTextLine(range.end().line() + 1); - bool contextChanged; - QVector contextChanges; - // Ask the highlighting engine to re-calcualte the highlighting for the line - // where completion was invoked, and store all the context changes in the process. - doc->highlight()->doHighlight(previousLine.data(), line.data(), nextLine.data(), - contextChanged, 0, &contextChanges); - - // From the list of context changes, find the highlighting context which is - // active at the position where completion was invoked. - KateHlContext* context = 0; - foreach ( KateHighlighting::ContextChange change, contextChanges ) { - if ( change.pos == 0 || change.pos <= range.end().column() ) { - context = change.toContext; - } - if ( change.pos > range.end().column() ) { - break; - } - } + auto context = doc->highlight()->contextForLocation(doc, range.end()); // Find all keyword items which exist for that context, // and suggest them as completion items. QSet items; if ( ! context ) { // default context context = doc->highlight()->contextNum(0); } foreach ( const KateHlItem* item, context->items ) { if ( const KateHlKeyword* kw = dynamic_cast(item) ) { items.unite(kw->allKeywords()); } } m_items = items.toList(); qSort(m_items); } QModelIndex KateKeywordCompletionModel::parent(const QModelIndex& index) const { if ( index.internalId() ) return createIndex(0, 0); else return QModelIndex(); } QModelIndex KateKeywordCompletionModel::index(int row, int column, const QModelIndex& parent) const { if ( !parent.isValid() ) { if ( row == 0 ) return createIndex(row, column); else return QModelIndex(); } else if ( parent.parent().isValid() ) { return QModelIndex(); } if ( row < 0 || row >= m_items.count() || column < 0 || column >= ColumnCount ) { return QModelIndex(); } return createIndex(row, column, 1); } int KateKeywordCompletionModel::rowCount(const QModelIndex& parent) const { if( !parent.isValid() && !m_items.isEmpty() ) return 1; //One root node to define the custom group else if(parent.parent().isValid()) return 0; //Completion-items have no children else return m_items.count(); } static bool isInWord(const KTextEditor::View* view, const KTextEditor::Cursor& position, QChar c) { KTextEditor::DocumentPrivate* document = static_cast(view->document()); KateHighlighting* highlight = document->highlight(); Kate::TextLine line = document->kateTextLine(position.line()); return highlight->isInWord(c, line->attribute(position.column()-1)); } KTextEditor::Range KateKeywordCompletionModel::completionRange(KTextEditor::View* view, const KTextEditor::Cursor& position) { const QString& text = view->document()->text(KTextEditor::Range(position, KTextEditor::Cursor(position.line(), 0))); int pos; for ( pos = text.size() - 1; pos >= 0; pos-- ) { if ( isInWord(view, position, text.at(pos)) ) { // This needs to be aware of what characters are word-characters in the // active language, so that languages which prefix commands with e.g. @ // or \ have properly working completion. continue; } break; } return KTextEditor::Range(KTextEditor::Cursor(position.line(), pos + 1), position); } bool KateKeywordCompletionModel::shouldAbortCompletion(KTextEditor::View* view, const KTextEditor::Range& range, const QString& currentCompletion) { if ( view->cursorPosition() < range.start() || view->cursorPosition() > range.end() ) return true; // Always abort when the completion-range has been left // Do not abort completions when the text has been empty already before and a newline has been entered foreach ( QChar c, currentCompletion ) { if ( ! isInWord(view, range.start(), c) ) { return true; } } return false; } bool KateKeywordCompletionModel::shouldStartCompletion(KTextEditor::View* /*view*/, const QString& insertedText, bool userInsertion, const KTextEditor::Cursor& /*position*/) { if ( userInsertion && insertedText.size() > 3 && ! insertedText.contains(QLatin1Char(' ')) && insertedText.at(insertedText.size()-1).isLetter() ) { return true; } return false; } bool KateKeywordCompletionModel::shouldHideItemsWithEqualNames() const { return true; } QVariant KateKeywordCompletionModel::data(const QModelIndex& index, int role) const { if ( role == UnimportantItemRole ) return QVariant(true); if ( role == InheritanceDepth ) return 9000; if ( !index.parent().isValid() ) { // group header switch ( role ) { case Qt::DisplayRole: return i18n("Language keywords"); case GroupRole: return Qt::DisplayRole; } } if ( index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole ) return m_items.at(index.row()); if ( index.column() == KTextEditor::CodeCompletionModel::Icon && role == Qt::DecorationRole ) { static const QIcon icon(QIcon::fromTheme(QStringLiteral("code-variable")).pixmap(QSize(16, 16))); return icon; } return QVariant(); } KTextEditor::CodeCompletionModelControllerInterface::MatchReaction KateKeywordCompletionModel::matchingItem( const QModelIndex& /*matched*/) { return KTextEditor::CodeCompletionModelControllerInterface::None; } // kate: indent-width 4; replace-tabs on diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp index 41926473..d046d319 100644 --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -1,5932 +1,5935 @@ /* 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 "katedocument.h" #include "kateglobal.h" #include "katedialogs.h" #include "katehighlight.h" #include "kateview.h" #include "kateautoindent.h" #include "katetextline.h" #include "katehighlighthelpers.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" #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 #ifdef 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 static inline QChar matchingStartBracket(QChar c, bool withQuotes) { 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 matchingEndBracket(QChar c, bool withQuotes) { 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(QChar c, bool withQuotes) { QChar bracket = matchingStartBracket(c, withQuotes); if (bracket.isNull()) { bracket = matchingEndBracket(c, false); } return bracket; } static inline bool isStartBracket(const QChar &c) { return ! matchingEndBracket(c, false).isNull(); } static inline bool isEndBracket(const QChar &c) { return ! matchingStartBracket(c, false).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_activeView(0), editSessionNumber(0), editIsRunning(false), m_undoMergeAllEdits(false), m_undoManager(new KateUndoManager(this)), m_editableMarks(markType01), m_annotationModel(0), m_isasking(0), m_buffer(new KateBuffer(this)), m_indenter(new KateAutoIndent(this)), m_hlSetByUser(false), m_bomSetByUser(false), m_indenterSetByUser(false), m_userSetEncodingForNextReload(false), m_modOnHd(false), m_modOnHdReason(OnDiskUnmodified), m_docName(QStringLiteral("need init")), m_docNameNumber(0), m_fileType(QStringLiteral("Normal")), m_fileTypeSetByUser(false), m_reloading(false), m_config(new KateDocumentConfig(this)), m_fileChangedDialogsActivated(false), m_onTheFlyChecker(0), m_documentState(DocumentIdle), m_readWriteStateBeforeLoading(false), m_isUntitled(true), m_openingError(false), m_lineLengthLimitOverride(0) { /** * 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) ? 0L : new Kate::SwapFile(this); // important, fill in the config into the indenter we use... m_indenter->updateConfig(); // 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())); /** * 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, &KTextEditor::DocumentPrivate::textRemoved, this, &KTextEditor::DocumentPrivate::saveEditingPositions); connect(this, &KTextEditor::DocumentPrivate::textInserted, this, &KTextEditor::DocumentPrivate::saveEditingPositions); connect(this, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearEditingPosStack())); onTheFlySpellCheckingEnabled(config()->onTheFlySpellCheck()); } // // KTextEditor::DocumentPrivate Destructor // KTextEditor::DocumentPrivate::~DocumentPrivate() { /** * we are about to delete cursors/ranges/... */ emit aboutToDeleteMovingInterfaceContent(this); // kill it early, it has ranges! delete m_onTheFlyChecker; m_onTheFlyChecker = NULL; 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(KTextEditor::Document *, const KTextEditor::Range &range) { if (m_editingStackPosition != m_editingStack.size() - 1) { m_editingStack.resize(m_editingStackPosition); } KTextEditor::MovingInterface *moving = qobject_cast(this); const auto c = range.start(); QSharedPointer mc (moving->newMovingCursor(c)); if (!m_editingStack.isEmpty() && c.line() == m_editingStack.top()->line()) { m_editingStack.pop(); } m_editingStack.push(mc); if (m_editingStack.size() > s_editingStackSizeLimit) { m_editingStack.removeFirst(); } 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 0; } // 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(0); 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; int totalLength = text.length(); int insertColumn = position.column(); if (position.line() > lines()) { int line = lines(); while (line != position.line() + totalLength + 1) { editInsertLine(line, QString()); line++; } } int tabWidth = config()->tabWidth(); static const QChar newLineChar(QLatin1Char('\n')); int insertColumnExpanded = insertColumn; Kate::TextLine l = plainKateTextLine(currentLine); if (l) { insertColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); } int positionColumnExpanded = insertColumnExpanded; int pos = 0; for (; pos < totalLength; pos++) { const QChar &ch = text.at(pos); if (ch == newLineChar) { // 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++; l = plainKateTextLine(currentLine); if (block) { if (currentLine == lastLine() + 1) { editInsertLine(currentLine, QString()); } insertColumn = positionColumnExpanded; if (l) { insertColumn = l->fromVirtualColumn(insertColumn, tabWidth); } } currentLineStart = pos + 1; if (l) { insertColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); } } } // 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; 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); } 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::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); // insert text into line m_buffer->insertText(KTextEditor::Cursor(line, col2), 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); // remove text from line m_buffer->removeText(KTextEditor::Range(KTextEditor::Cursor(line, col), 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; } } 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); } 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)); } 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())); } } 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 (int i = 0; i < KateHlManager::self()->highlights(); ++i) { hls << KateHlManager::self()->hlName(i); } return hls; } QString KTextEditor::DocumentPrivate::highlightingModeSection(int index) const { return KateHlManager::self()->hlSection(index); } 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())); } } 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); } 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) { KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { return false; } bool handled = false; emit markClicked(this, *mark, handled); return handled; } bool KTextEditor::DocumentPrivate::handleMarkContextMenu(int line, QPoint position) { KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { return false; } bool handled = false; 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() { QMimeType mt; if (!this->url().isEmpty()) { mt = QMimeDatabase().mimeTypeForUrl(this->url()); } else { mt = mimeTypeForContent(); } return mt.name(); } QMimeType KTextEditor::DocumentPrivate::mimeTypeForContent() { QByteArray buf(1024, '\0'); uint bufpos = 0; for (int i = 0; i < lines(); ++i) { QString line = this->line(i); uint len = line.length() + 1; if (bufpos + len > 1024) { len = 1024 - bufpos; } QString ld(line + QLatin1Char('\n')); buf.replace(bufpos, len, ld.toLatin1()); //memcpy(buf.data() + bufpos, ld.toLatin1().constData(), len); bufpos += len; if (bufpos >= 1024) { break; } } buf.resize(bufpos); QMimeDatabase db; return db.mimeTypeForData(buf); } //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"), 0); connect(tryAgainAction, SIGNAL(triggered()), SLOT(documentReload()), Qt::QueuedConnection); QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), 0); 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() { m_lineLengthLimitOverride=m_buffer->longestLineLoaded()+1; m_buffer->clear(); openFile(); if (!m_openingError) { setReadWrite(true); m_readWriteStateBeforeLoading=true; } m_lineLengthLimitOverride=0; } int KTextEditor::DocumentPrivate::lineLengthLimit() { int result; if (m_lineLengthLimitOverride>0) { result=m_lineLengthLimitOverride; } else { result=config()->lineLengthLimit(); } return result; } //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; 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() { QWidget *parentWidget(dialogParent()); // 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(parentWidget, 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(parentWidget, 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(parentWidget, 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; } // // try to create backup file.. // // local file or not is here the question bool l(url().isLocalFile()); // does the user want any backup, if not, not our problem? if ((l && config()->backupFlags() & KateDocumentConfig::LocalFiles) || (! l && config()->backupFlags() & KateDocumentConfig::RemoteFiles)) { QUrl u(url()); if (config()->backupPrefix().contains(QDir::separator())) { /** * replace complete path, as prefix is a path! */ u.setPath(config()->backupPrefix() + url().fileName() + config()->backupSuffix()); } else { /** * replace filename in url */ const QString fileName = url().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()); job->exec(); backupSuccess = !job->error(); } else { backupSuccess = true; } } // backup has failed, ask user how to proceed if (!backupSuccess && (KMessageBox::warningContinueCancel(parentWidget , 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; } } // update file type, pass no file path, read file type content from this document updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, QString())); // remember the oldpath... QString oldPath = m_dirWatchFile; // read dir config (if possible and wanted) if (url().isLocalFile()) { QFileInfo fo(oldPath), fn(localFilePath()); if (fo.path() != fn.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(parentWidget, 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; } // update the checksum createDigest(); // add m_file again to dirwatch activateDirWatch(); // // we are not modified // if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = 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; } void KTextEditor::DocumentPrivate::readDirConfig() { if (!url().isLocalFile()) { return; } /** * 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++; } break; } /** * else: cd up, if possible or abort */ if (!dir.cdUp()) { break; } } } 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) { 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) { 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; 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)); // 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); if (activeView() == view) { setActiveView(0L); } } 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); } QString chars = QString::fromUcs4(ucs4Chars.data(), ucs4Chars.size()); /** * no printable chars => nothing to insert! */ if (chars.isEmpty()) { return false; } /** * auto bracket handling for newly inserted text * remember if we should auto add some */ QChar closingBracket; if (view->config()->autoBrackets() && chars.size() == 1) { /** * we inserted a bracket? * => remember the matching closing one */ closingBracket = matchingEndBracket(chars[0], true); /** * closing bracket for the autobracket we inserted earlier? */ if ( m_currentAutobraceClosingChar == chars[0] && m_currentAutobraceRange ) { // do nothing m_currentAutobraceRange.reset(nullptr); view->cursorRight(); return true; } - - if ( ! closingBracket.isNull() ) { - m_currentAutobraceClosingChar = closingBracket; - } } /** * selection around => special handling if we want to add auto brackets */ if (view->selection() && !closingBracket.isNull()) { /** * add bracket at start + end of the selection */ KTextEditor::Cursor oldCur = view->cursorPosition(); insertText(view->selectionRange().start(), chars); view->slotTextInserted(view, oldCur, chars); view->setCursorPosition(view->selectionRange().end()); oldCur = view->cursorPosition(); insertText(view->selectionRange().end(), QString(closingBracket)); view->slotTextInserted(view, oldCur, QString(closingBracket)); /** * expand selection */ view->setSelection(KTextEditor::Range(view->selectionRange().start() + Cursor{0, 1}, view->cursorPosition() - Cursor{0, 1})); view->setCursorPosition(view->selectionRange().start()); } /** * else normal handling */ else { editStart(); 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); } } 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 { chars = eventuallyReplaceTabs(view->cursorPosition(), chars); 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 */ - if (!closingBracket.isNull()) { + bool skipAutobrace = closingBracket == QLatin1Char('\''); + if ( highlight() && skipAutobrace ) { + auto context = highlight()->contextForLocation(this, view->cursorPosition()); + // skip adding ' in spellchecked areas, because those are text + skipAutobrace = !context || highlight()->attributeRequiresSpellchecking(context->attr); + } + if (!closingBracket.isNull() && !skipAutobrace ) { // add bracket to the view const auto cursorPos(view->cursorPosition()); const auto nextChar = view->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); } /** * be done */ 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) { editStart(); if (!v->config()->persistentSelection() && v->selection()) { v->removeSelectedText(); v->clearSelection(); } // query cursor position KTextEditor::Cursor c = v->cursorPosition(); if (c.line() > (int)lastLine()) { c.setLine(lastLine()); } if (c.line() < 0) { c.setLine(0); } uint ln = c.line(); Kate::TextLine textLine = plainKateTextLine(ln); if (c.column() > (int)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')); } 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()) { if (view->blockSelection() && view->selection() && toVirtualColumn(view->selectionRange().start()) == toVirtualColumn(view->selectionRange().end())) { // Remove one character after selection line KTextEditor::Range range = view->selectionRange(); range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1)); view->setSelection(range); } view->removeSelectedText(); return; } uint col = qMax(c.column(), 0); uint line = qMax(c.line(), 0); if ((col == 0) && (line == 0)) { return; } if (col > 0) { if (!(config()->backspaceIndents())) { // ordinary backspace KTextEditor::Cursor beginCursor(line, view->textLayout(c)->previousCursorPosition(c.column())); KTextEditor::Cursor endCursor(line, col); 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 { // backspace indents: erase to next indent position Kate::TextLine textLine = m_buffer->plainLine(line); // don't forget this check!!!! really!!!! if (!textLine) { return; } 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 { KTextEditor::Cursor beginCursor(line, view->textLayout(c)->previousCursorPosition(c.column())); KTextEditor::Cursor endCursor(line, col); 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 if (line >= 1) { Kate::TextLine textLine = m_buffer->plainLine(line - 1); // don't forget this check!!!! really!!!! if (!textLine) { return; } 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()) { if (view->blockSelection() && view->selection() && toVirtualColumn(view->selectionRange().start()) == toVirtualColumn(view->selectionRange().end())) { // Remove one character after selection line KTextEditor::Range range = view->selectionRange(); range.setEnd(KTextEditor::Cursor(range.end().line(), range.end().column() + 1)); view->setSelection(range); } view->removeSelectedText(); 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) == KateHighlighting::CSLPosColumn0) { 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->contextStack().isEmpty()) { startAttrib = highlight()->attribute(ln->contextStack().last()); } bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart(startAttrib).isEmpty()); bool hasStartStopCommentMark = (!(highlight()->getCommentStart(startAttrib).isEmpty()) && !(highlight()->getCommentEnd(startAttrib).isEmpty())); bool removed = false; 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 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, false); 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 (m_isasking < 0) { m_isasking = 0; return; } if (!m_fileChangedDialogsActivated || m_isasking) { return; } if (m_modOnHd && !url().isEmpty()) { m_isasking = 1; QWidget *parentWidget(dialogParent()); KateModOnHdPrompt p(this, m_modOnHdReason, reasonedMOHString(), parentWidget); switch (p.exec()) { case KateModOnHdPrompt::Save: { m_modOnHd = false; const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File"), url()); if (!res.isEmpty() && checkOverwrite(res, parentWidget)) { if (! saveAs(res)) { KMessageBox::error(parentWidget, i18n("Save failed")); m_modOnHd = true; } else { emit modifiedOnDisk(this, false, OnDiskUnmodified); } } else { // the save as dialog was canceled, we are still modified on disk m_modOnHd = true; } m_isasking = 0; break; } case KateModOnHdPrompt::Reload: m_modOnHd = false; emit modifiedOnDisk(this, false, OnDiskUnmodified); documentReload(); m_isasking = 0; break; case KateModOnHdPrompt::Ignore: m_modOnHd = false; emit modifiedOnDisk(this, false, OnDiskUnmodified); m_isasking = 0; break; case KateModOnHdPrompt::Overwrite: m_modOnHd = false; emit modifiedOnDisk(this, false, OnDiskUnmodified); m_isasking = 0; save(); break; case KateModOnHdPrompt::Close: m_modOnHd = false; emit modifiedOnDisk(this, false, OnDiskUnmodified); m_isasking = 0; // delay close, else we delete ourself in bad situations QTimer::singleShot(0, this, SLOT(closeDocumentInApplication())); break; default: // Delay/cancel: ignore next focus event m_isasking = -1; } } } 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; } if (m_modOnHd && m_fileChangedDialogsActivated) { QWidget *parentWidget(dialogParent()); int i = KMessageBox::warningYesNoCancel (parentWidget, reasonedMOHString() + QLatin1String("\n\n") + i18n("What do you want to do?"), i18n("File Was Changed on Disk"), KGuiItem(i18n("&Reload File"), QStringLiteral("view-refresh")), KGuiItem(i18n("&Ignore Changes"), QStringLiteral("dialog-warning"))); if (i != KMessageBox::Yes) { if (i == KMessageBox::No) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // reset some flags only valid for one reload! m_userSetEncodingForNextReload = false; return false; } } 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 < (int)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() || !checkOverwrite(saveUrl, dialogParent())) { 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() || !checkOverwrite(saveUrl, dialogParent())) { 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() || !checkOverwrite(saveUrl, dialogParent())) { 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; } // KIO move KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file.fileName()), saveUrl); 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 QRegExp kvLine(QStringLiteral("kate:(.*)")); static const QRegExp kvLineWildcard(QStringLiteral("kate-wildcard\\((.*)\\):(.*)")); static const QRegExp kvLineMime(QStringLiteral("kate-mimetype\\((.*)\\):(.*)")); static const QRegExp 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 if (kvLine.indexIn(t) > -1) { s = kvLine.cap(1); //qCDebug(LOG_KTE) << "normal variable line kate: matched: " << s; } else if (kvLineWildcard.indexIn(t) > -1) { // regex given const QStringList wildcards(kvLineWildcard.cap(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); } // nothing usable found... if (!found) { return; } s = kvLineWildcard.cap(2); //qCDebug(LOG_KTE) << "guarded variable line kate-wildcard: matched: " << s; } else if (kvLineMime.indexIn(t) > -1) { // mime-type given const QStringList types(kvLineMime.cap(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); // no matching type found if (!types.contains(mimeType())) { return; } s = kvLineMime.cap(2); //qCDebug(LOG_KTE) << "guarded variable line kate-mimetype: matched: " << s; } else { // nothing found return; } QStringList vvl; // view variable names vvl << QStringLiteral("dynamic-word-wrap") << QStringLiteral("dynamic-word-wrap-indicators") << QStringLiteral("line-numbers") << QStringLiteral("icon-border") << QStringLiteral("folding-markers") << QStringLiteral("bookmark-sorting") << QStringLiteral("auto-center-lines") << QStringLiteral("icon-bar-color") // renderer << QStringLiteral("background-color") << QStringLiteral("selection-color") << QStringLiteral("current-line-color") << QStringLiteral("bracket-highlight-color") << QStringLiteral("word-wrap-marker-color") << QStringLiteral("font") << QStringLiteral("font-size") << QStringLiteral("scheme"); int spaceIndent = -1; // for backward compatibility; see below bool replaceTabsSet = false; int p(0); QString var, val; while ((p = kvVar.indexIn(s, p)) > -1) { p += kvVar.matchedLength(); var = kvVar.cap(1); val = kvVar.cap(2).trimmed(); bool state; // store booleans here int n; // store ints here // only apply view & renderer config stuff if (onlyViewAndRenderer) { if (vvl.contains(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 " "http://docs.kde.org/stable/en/applications/kate/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 " "http://docs.kde.org/stable/en/applications/kate/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); } 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")) { QStringList l; l << QStringLiteral("unix") << QStringLiteral("dos") << QStringLiteral("mac"); if ((n = l.indexOf(val.toLower())) != -1) { m_config->setEol(n); } } else if (var == QLatin1String("bom") || 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 (vvl.contains(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) { if (var == QLatin1String("dynamic-word-wrap") && checkBoolValue(val, &state)) { v->config()->setDynWordWrap(state); } else if (var == QLatin1String("persistent-selection") && checkBoolValue(val, &state)) { v->config()->setPersistentSelection(state); } else if (var == QLatin1String("block-selection") && checkBoolValue(val, &state)) { v->setBlockSelection(state); } //else if ( var = "dynamic-word-wrap-indicators" ) else if (var == QLatin1String("line-numbers") && checkBoolValue(val, &state)) { v->config()->setLineNumbers(state); } else if (var == QLatin1String("icon-border") && checkBoolValue(val, &state)) { v->config()->setIconBar(state); } else if (var == QLatin1String("folding-markers") && checkBoolValue(val, &state)) { v->config()->setFoldingBar(state); } else if (var == QLatin1String("auto-center-lines") && checkIntValue(val, &n)) { v->config()->setAutoCenterLines(n); } 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 QStringList trueValues = QStringList() << QStringLiteral("1") << QStringLiteral("on") << QStringLiteral("true"); if (trueValues.contains(val)) { *result = true; return true; } static const QStringList falseValues = QStringList() << QStringLiteral("0") << QStringLiteral("off") << QStringLiteral("false"); if (falseValues.contains(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; } #ifdef 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 = Q_NULLPTR; const QByteArray utf8Path = url().toLocalFile().toUtf8(); if (git_repository_open_ext(&repository, utf8Path.constData(), 0, Q_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 = Q_NULLPTR; if (git_blob_lookup(&blob, repository, &oid) == 0) { /** * this hash exists still in git => just reload */ m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; documentReload(); } git_blob_free(blob); } } git_repository_free(repository); } #endif } /** * reenable dialog if not running atm */ if (m_isasking == -1) { m_isasking = false; } /** * 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(); if (!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! */ 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() || !checkOverwrite(res, parentWidget)) { *abortClosing = true; return; } saveAs(res); *abortClosing = false; } else { save(); *abortClosing = false; } } bool KTextEditor::DocumentPrivate::checkOverwrite(QUrl u, QWidget *parent) { if (!u.isLocalFile()) { return true; } QFileInfo info(u.path()); if (!info.exists()) { return true; } return KMessageBox::Cancel != KMessageBox::warningContinueCancel(parent, i18n("A file named \"%1\" already exists. " "Are you sure you want to overwrite it?", info.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel(), QString(), KMessageBox::Options(KMessageBox::Notify | KMessageBox::Dangerous)); } //BEGIN KTextEditor::ConfigInterface // BEGIN ConfigInterface stff QStringList KTextEditor::DocumentPrivate::configKeys() const { static const QStringList keys = { QStringLiteral("backup-on-save-local"), QStringLiteral("backup-on-save-suffix"), QStringLiteral("backup-on-save-prefix"), QStringLiteral("replace-tabs"), QStringLiteral("indent-pasted-text"), QStringLiteral("tab-width"), QStringLiteral("indent-width"), }; return keys; } QVariant KTextEditor::DocumentPrivate::configValue(const QString &key) { if (key == QLatin1String("backup-on-save-local")) { return m_config->backupFlags() & KateDocumentConfig::LocalFiles; } else if (key == QLatin1String("backup-on-save-remote")) { return m_config->backupFlags() & KateDocumentConfig::RemoteFiles; } else if (key == QLatin1String("backup-on-save-suffix")) { return m_config->backupSuffix(); } else if (key == QLatin1String("backup-on-save-prefix")) { return m_config->backupPrefix(); } else if (key == QLatin1String("replace-tabs")) { return m_config->replaceTabsDyn(); } else if (key == QLatin1String("indent-pasted-text")) { return m_config->indentPastedText(); } else if (key == QLatin1String("tab-width")) { return m_config->tabWidth(); } else if (key == QLatin1String("indent-width")) { return m_config->indentationWidth(); } // return invalid variant return QVariant(); } void KTextEditor::DocumentPrivate::setConfigValue(const QString &key, const QVariant &value) { if (value.type() == QVariant::String) { if (key == QLatin1String("backup-on-save-suffix")) { m_config->setBackupSuffix(value.toString()); } else if (key == QLatin1String("backup-on-save-prefix")) { m_config->setBackupPrefix(value.toString()); } } else if (value.canConvert(QVariant::Bool)) { const bool bValue = value.toBool(); if (key == QLatin1String("backup-on-save-local") && value.type() == QVariant::String) { uint f = m_config->backupFlags(); if (bValue) { f |= KateDocumentConfig::LocalFiles; } else { f ^= KateDocumentConfig::LocalFiles; } m_config->setBackupFlags(f); } else if (key == QLatin1String("backup-on-save-remote")) { uint f = m_config->backupFlags(); if (bValue) { f |= KateDocumentConfig::RemoteFiles; } else { f ^= KateDocumentConfig::RemoteFiles; } m_config->setBackupFlags(f); } else if (key == QLatin1String("replace-tabs")) { m_config->setReplaceTabsDyn(bValue); } else if (key == QLatin1String("indent-pasted-text")) { m_config->setIndentPastedText(bValue); } } else if (value.canConvert(QVariant::Int)) { if (key == QLatin1String("tab-width")) { config()->setTabWidth(value.toInt()); } else if (key == QLatin1String("indent-width")) { config()->setIndentationWidth(value.toInt()); } } } //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; } void KTextEditor::DocumentPrivate::ignoreModifiedOnDiskOnce() { m_isasking = -1; } 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"), 0); 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 = 0; } 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) { 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); } emit dictionaryRangesPresent(!m_dictionaryRanges.isEmpty()); } void KTextEditor::DocumentPrivate::revertToDefaultDictionary(const KTextEditor::Range &range) { setDictionary(QString(), range); } 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 == 0); m_onTheFlyChecker = new KateOnTheFlyChecker(this); } else { delete m_onTheFlyChecker; m_onTheFlyChecker = 0; } foreach (KTextEditor::ViewPrivate *view, m_views) { view->reflectOnTheFlySpellCheckStatus(enable); } } bool KTextEditor::DocumentPrivate::isOnTheFlySpellCheckingEnabled() const { return m_onTheFlyChecker != 0; } 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() ? Q_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) { Kate::TextLine kateLine = kateTextLine(position.line()); // const QVector< short >& attrs = kateLine->ctxArray(); // qCDebug(LOG_KTE) << "----------------------------------------------------------------------"; // foreach( short a, attrs ) { // qCDebug(LOG_KTE) << a; // } // qCDebug(LOG_KTE) << "----------------------------------------------------------------------"; // qCDebug(LOG_KTE) << "col: " << position.column() << " lastchar:" << kateLine->lastChar() << " length:" << kateLine->length() << "global mode:" << highlightingMode(); int len = kateLine->length(); int pos = position.column(); if (pos >= len) { const Kate::TextLineData::ContextStack &ctxs = kateLine->contextStack(); int ctxcnt = ctxs.count(); if (ctxcnt == 0) { return highlightingMode(); } int ctx = ctxs.at(ctxcnt - 1); if (ctx == 0) { return highlightingMode(); } return KateHlManager::self()->nameForIdentifier(highlight()->hlKeyForContext(ctx)); } int attr = kateLine->attribute(pos); if (attr == 0) { return mode(); } return KateHlManager::self()->nameForIdentifier(highlight()->hlKeyForAttrib(attr)); } 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()) { KateHlContext *context = tl->contextStack().isEmpty() ? highlight()->contextNum(0) : highlight()->contextNum(tl->contextStack().back()); attribute = context->attr; } 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"), 0); 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(0); 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/syntax/katehighlight.cpp b/src/syntax/katehighlight.cpp index 67401b4a..524b9f98 100644 --- a/src/syntax/katehighlight.cpp +++ b/src/syntax/katehighlight.cpp @@ -1,2261 +1,2293 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Mirko Stocker Copyright (C) 2007 Matthew Woehlke Copyright (C) 2003, 2004 Anders Lund Copyright (C) 2003 Hamish Rodda Copyright (C) 2001,2002 Joseph Wenninger Copyright (C) 2001 Christoph Cullmann 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 "katehighlight.h" #include "katehighlighthelpers.h" #include "katetextline.h" #include "katedocument.h" #include "katesyntaxdocument.h" #include "katerenderer.h" #include "kateglobal.h" #include "kateschema.h" #include "kateconfig.h" #include "kateextendedattribute.h" #include "katepartdebug.h" #include "katedefaultcolors.h" #include #include #include #include #include #include #include #include #include //END //BEGIN STATICS namespace { inline const QString stdDeliminator() { return QStringLiteral(" \t.():!+,-<=>%&*/;?[]^{|}~\\"); } // @return true if x is "true" or "1" bool isTrue(const QString& x) { return (x.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0) || x.toInt() == 1; } } //END //BEGIN KateHighlighting KateHighlighting::KateHighlighting(const KateSyntaxModeListItem *def) : refCount(0) { errorsAndWarnings = QString(); building = false; noHl = false; m_foldingIndentationSensitive = false; folding = false; if (def == 0) { noHl = true; iName = QStringLiteral("None"); // not translated internal name (for config and more) iNameTranslated = i18nc("Syntax highlighting", "None"); // user visible name iSection = QString(); } else { iName = def->name; iNameTranslated = def->nameTranslated; iSection = def->section; iHidden = def->hidden; identifier = def->identifier; iVersion = def->version; iStyle = def->style; iAuthor = def->author; iLicense = def->license; } deliminator = stdDeliminator(); } KateHighlighting::~KateHighlighting() { // cleanup ;) cleanup(); qDeleteAll(m_additionalData); } void KateHighlighting::cleanup() { qDeleteAll(m_contexts); m_contexts.clear(); qDeleteAll(m_hlItemCleanupList); m_hlItemCleanupList.clear(); m_attributeArrays.clear(); internalIDList.clear(); } KateHlContext *KateHighlighting::generateContextStack(Kate::TextLineData::ContextStack &contextStack, KateHlContextModification modification, int &indexLastContextPreviousLine) { while (true) { switch (modification.type) { /** * stay, do nothing, just return the last context * in the stack or 0 */ case KateHlContextModification::doNothing: return contextNum(contextStack.isEmpty() ? 0 : contextStack.last()); /** * just add a new context to the stack * and return this one */ case KateHlContextModification::doPush: contextStack.append(modification.newContext); return contextNum(modification.newContext); /** * pop some contexts + add a new one afterwards, immediate.... */ case KateHlContextModification::doPopsAndPush: // resize stack contextStack.resize((modification.pops >= contextStack.size()) ? 0 : (contextStack.size() - modification.pops)); // push imediate the new context.... // don't handle the previous line stuff at all.... // ### TODO ### think about this contextStack.append(modification.newContext); return contextNum(modification.newContext); /** * do only pops... */ default: { // resize stack contextStack.resize((modification.pops >= contextStack.size()) ? 0 : (contextStack.size() - modification.pops)); // handling of context of previous line.... if (indexLastContextPreviousLine >= (contextStack.size() - 1)) { // set new index, if stack is empty, this is -1, done for eternity... indexLastContextPreviousLine = contextStack.size() - 1; // stack already empty, nothing to do... if (contextStack.isEmpty()) { return contextNum(0); } KateHlContext *c = contextNum(contextStack.last()); // this must be a valid context, or our context stack is borked.... Q_ASSERT(c); // handle line end context as new modificationContext modification = c->lineEndContext; continue; } return contextNum(contextStack.isEmpty() ? 0 : contextStack.last()); } } } // should never be reached Q_ASSERT(false); return contextNum(0); } /** * Creates a new dynamic context or reuse an old one if it has already been created. */ int KateHighlighting::makeDynamicContext(KateHlContext *model, const QStringList *args) { QPair key(model, args->front()); short value; if (dynamicCtxs.contains(key)) { value = dynamicCtxs[key]; } else { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "new stuff: " << startctx; #endif KateHlContext *newctx = model->clone(args); m_contexts.push_back(newctx); value = startctx++; dynamicCtxs[key] = value; KateHlManager::self()->incDynamicCtxs(); } // qCDebug(LOG_KTE) << "Dynamic context: using context #" << value << " (for model " << model << " with args " << *args << ")"; return value; } /** * Drop all dynamic contexts. Shall be called with extreme care, and shall be immediately * followed by a full HL invalidation. */ void KateHighlighting::dropDynamicContexts() { if (refCount == 0) { // unused highlighting - nothing to drop return; } if (noHl) { // "normal texts" highlighting - no context list return; } qDeleteAll(m_contexts.begin() + base_startctx, m_contexts.end()); // delete dynamic contexts (after base_startctx) m_contexts.resize(base_startctx); dynamicCtxs.clear(); startctx = base_startctx; } void KateHighlighting::doHighlight(const Kate::TextLineData *_prevLine, Kate::TextLineData *textLine, const Kate::TextLineData *nextLine, bool &ctxChanged, int tabWidth, QVector* contextChanges) { if (!textLine) { return; } // in all cases, remove old hl, or we will grow to infinite ;) textLine->clearAttributes(); // reset folding start textLine->clearMarkedAsFoldingStart(); // no hl set, nothing to do more than the above cleaning ;) if (noHl) { return; } const bool firstLine = (_prevLine == 0); const Kate::TextLine dummy = Kate::TextLine(new Kate::TextLineData()); const Kate::TextLineData *prevLine = firstLine ? dummy.data() : _prevLine; int previousLine = -1; KateHlContext *context; // duplicate the ctx stack, only once ! Kate::TextLineData::ContextStack ctx(prevLine->contextStack()); if (ctx.isEmpty()) { // If the stack is empty, we assume to be in Context 0 (Normal) if (firstLine) { context = contextNum(0); } else { context = generateContextStack(ctx, contextNum(0)->lineEndContext, previousLine); //get stack ID to use } } else { //qCDebug(LOG_KTE) << "\t\tctxNum = " << ctxNum << " contextList[ctxNum] = " << contextList[ctxNum]; // ellis //if (lineContinue) qCDebug(LOG_KTE)<hlLineContinue()) { previousLine--; } else { context = generateContextStack(ctx, context->lineEndContext, previousLine); //get stack ID to use } //qCDebug(LOG_KTE)<<"test1-2-1-text4"; //if (lineContinue) qCDebug(LOG_KTE)<string(); const int len = textLine->length(); // calc at which char the first char occurs, set it to length of line if never const int firstChar = textLine->firstChar(); const int startNonSpace = (firstChar == -1) ? len : firstChar; // last found item KateHlItem *item = 0; // loop over the line, offset gives current offset int offset = 0; KateHighlighting::HighlightPropertyBag *additionalData = m_additionalData[context->hlId]; KateHlContext *oldContext = context; // optimization: list of highlighting items that need their cache reset static QVarLengthArray cachingItems; // catch empty lines if (len == 0) { // regenerate context stack if needed if (context->emptyLineContext) { context = generateContextStack(ctx, context->emptyLineContextModification, previousLine); } } else { /** * check if the folding begin/ends are balanced! * constructed on demand! */ QHash *foldingStartToCount = 0; /** * loop over line content! */ QChar lastDelimChar = 0; int lastOffset = offset; int infiniteLoopDetectionCounter = 0; KateHlContext* previous = context; while (offset < len) { // If requested (happens from completion), return where context changes occur. if (contextChanges && ( offset == 0 || context != previous )) { previous = context; const ContextChange change = {context, offset}; contextChanges->append(change); } /** * infinite loop check */ if (lastOffset < offset) { /** * we did advance a bit, reset counter */ lastOffset = offset; infiniteLoopDetectionCounter = 0; } else { /** * we did not advance, inc counter */ ++infiniteLoopDetectionCounter; /** * not more than four times as many rounds as contexts known * break out of this loop and issue message */ if (infiniteLoopDetectionCounter > (4 * m_contexts.size())) { qCDebug(LOG_KTE) << "potential infinite loop found during highlighting, hl: " << iName; break; } } bool anItemMatched = false; bool customStartEnableDetermined = false; foreach (item, context->items) { // does we only match if we are firstNonSpace? if (item->firstNonSpace && (offset > startNonSpace)) { continue; } // have we a column specified? if yes, only match at this column if ((item->column != -1) && (item->column != offset)) { continue; } if (!item->alwaysStartEnable) { if (item->customStartEnable) { if (oldContext != context) { oldContext = context; additionalData = m_additionalData[oldContext->hlId]; } if (customStartEnableDetermined || additionalData->deliminator.contains(lastChar)) { customStartEnableDetermined = true; } else { continue; } } else { if (lastDelimChar == lastChar) { } else if (stdDeliminator().contains(lastChar)) { lastDelimChar = lastChar; } else { continue; } } } int offset2 = item->checkHgl(text, offset, len - offset); if (item->haveCache && !item->cachingHandled) { cachingItems.append(item); item->cachingHandled = true; } if (offset2 <= offset) { continue; } // dominik: on lookAhead, do not preocess any data by fixing offset2 if (item->lookAhead) { offset2 = offset; } else { // make sure the rule does not violate the text line length if (offset2 > len) { offset2 = len; } } // BUG 144599: Ignore a context change that would push the same context // without eating anything... this would be an infinite loop! if (item->lookAhead && (item->ctx.pops < 2 && item->ctx.newContext == (ctx.isEmpty() ? 0 : ctx.last()))) { continue; } // regenerate context stack if needed context = generateContextStack(ctx, item->ctx, previousLine); // dynamic context: substitute the model with an 'instance' if (context->dynamic) { // try to retrieve captures from regexp QStringList captures; item->capturedTexts(captures); if (!captures.empty()) { // Replace the top of the stack and the current context int newctx = makeDynamicContext(context, &captures); if (ctx.size() > 0) { ctx[ctx.size() - 1] = newctx; } context = contextNum(newctx); } } // handle folding end or begin if (item->region || item->region2) { /** * for each end region, decrement counter for that type, erase if count reaches 0! */ if (item->region2 && foldingStartToCount) { QHash::iterator end = foldingStartToCount->find(-item->region2); if (end != foldingStartToCount->end()) { if (end.value() > 1) { --(end.value()); } else { foldingStartToCount->erase(end); } } } /** * increment counter for each begin region! */ if (item->region) { // construct on demand! if (!foldingStartToCount) { foldingStartToCount = new QHash (); } ++(*foldingStartToCount)[item->region]; } } // even set attributes or end of region! ;) int attribute = item->onlyConsume ? context->attr : item->attr; if ((attribute > 0 && !item->lookAhead) || item->region2) { textLine->addAttribute(Kate::TextLineData::Attribute(offset, offset2 - offset, attribute, item->region2)); } // create 0 length attribute for begin of region, if any! if (item->region) { textLine->addAttribute(Kate::TextLineData::Attribute(offset2, 0, attribute, item->region)); } // only process, if lookAhead is false if (!item->lookAhead) { offset = offset2; lastChar = text[offset - 1]; } anItemMatched = true; break; } // something matched, continue loop if (anItemMatched) { continue; } item = 0; // nothing found: set attribute of one char // anders: unless this context does not want that! if (context->fallthrough) { // set context to context->ftctx. context = generateContextStack(ctx, context->ftctx, previousLine); //regenerate context stack //qCDebug(LOG_KTE)<<"context num after fallthrough at col "<attr > 0) { textLine->addAttribute(Kate::TextLineData::Attribute(offset, 1, context->attr, 0)); } lastChar = text[offset]; offset++; } } /** * check if folding is not balanced and we have more starts then ends * then this line is a possible folding start! */ if (foldingStartToCount) { /** * possible folding start, if imbalanced, aka hash not empty! */ if (!foldingStartToCount->isEmpty()) { textLine->markAsFoldingStartAttribute(); } /** * kill hash */ delete foldingStartToCount; foldingStartToCount = 0; } } /** * has the context stack changed? */ if ((ctxChanged = (ctx != textLine->contextStack()))) { /** * try to share the simple stack that contains only 0 */ static const Kate::TextLineData::ContextStack onlyDefaulContext(1, 0); if (ctx == onlyDefaulContext) { textLine->setContextStack(onlyDefaulContext); } /** * next try: try to share data with last line */ else if (ctx == prevLine->contextStack()) { textLine->setContextStack(prevLine->contextStack()); } /** * ok, really use newly constructed stack! */ else { textLine->setContextStack(ctx); } } // write hl continue flag textLine->setHlLineContinue(item && item->lineContinue()); // check for indentation based folding if (m_foldingIndentationSensitive && (tabWidth > 0) && !textLine->markedAsFoldingStartAttribute()) { bool skipIndentationBasedFolding = false; for (int i = ctx.size() - 1; i >= 0; --i) { if (contextNum(ctx[i])->noIndentationBasedFolding) { skipIndentationBasedFolding = true; break; } } /** * compute if we increase indentation in next line */ if (!skipIndentationBasedFolding && !isEmptyLine(textLine) && !isEmptyLine(nextLine) && (textLine->indentDepth(tabWidth) < nextLine->indentDepth(tabWidth))) { textLine->markAsFoldingStartIndentation(); } } // invalidate caches for (int i = 0; i < cachingItems.size(); ++i) { cachingItems[i]->cachingHandled = false; cachingItems[i]->haveCache = false; } cachingItems.clear(); } void KateHighlighting::getKateExtendedAttributeList(const QString &schema, QList &list, KConfig *cfg) { KConfigGroup config(cfg ? cfg : KateHlManager::self()->getKConfig(), QLatin1String("Highlighting ") + iName + QLatin1String(" - Schema ") + schema); list.clear(); createKateExtendedAttribute(list); foreach (KTextEditor::Attribute::Ptr p, list) { Q_ASSERT(p); QStringList s = config.readEntry(p->name(), QStringList()); // qCDebug(LOG_KTE)<name< 0) { while (s.count() < 10) { s << QString(); } QString name = p->name(); bool spellCheck = !p->skipSpellChecking(); p->clear(); p->setName(name); p->setSkipSpellChecking(!spellCheck); QString tmp = s[0]; if (!tmp.isEmpty()) { p->setDefaultStyle(static_cast (tmp.toInt())); } QRgb col; tmp = s[1]; if (!tmp.isEmpty()) { col = tmp.toUInt(0, 16); p->setForeground(QColor(col)); } tmp = s[2]; if (!tmp.isEmpty()) { col = tmp.toUInt(0, 16); p->setSelectedForeground(QColor(col)); } tmp = s[3]; if (!tmp.isEmpty()) { p->setFontBold(tmp != QLatin1String("0")); } tmp = s[4]; if (!tmp.isEmpty()) { p->setFontItalic(tmp != QLatin1String("0")); } tmp = s[5]; if (!tmp.isEmpty()) { p->setFontStrikeOut(tmp != QLatin1String("0")); } tmp = s[6]; if (!tmp.isEmpty()) { p->setFontUnderline(tmp != QLatin1String("0")); } tmp = s[7]; if (!tmp.isEmpty()) { col = tmp.toUInt(0, 16); p->setBackground(QColor(col)); } tmp = s[8]; if (!tmp.isEmpty()) { col = tmp.toUInt(0, 16); p->setSelectedBackground(QColor(col)); } tmp = s[9]; if (!tmp.isEmpty() && tmp != QLatin1String("---")) { p->setFontFamily(tmp); } } } } void KateHighlighting::getKateExtendedAttributeListCopy(const QString &schema, QList< KTextEditor::Attribute::Ptr > &list, KConfig *cfg) { QList attributes; getKateExtendedAttributeList(schema, attributes, cfg); list.clear(); foreach (const KTextEditor::Attribute::Ptr &attribute, attributes) { list.append(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute(*attribute.data()))); } } void KateHighlighting::setKateExtendedAttributeList(const QString &schema, QList &list, KConfig *cfg, bool writeDefaultsToo) { KConfigGroup config(cfg ? cfg : KateHlManager::self()->getKConfig(), QLatin1String("Highlighting ") + iName + QLatin1String(" - Schema ") + schema); QStringList settings; KateAttributeList defList; KateHlManager::self()->getDefaults(schema, defList); foreach (const KTextEditor::Attribute::Ptr &p, list) { Q_ASSERT(p); settings.clear(); KTextEditor::DefaultStyle defStyle = p->defaultStyle(); KTextEditor::Attribute::Ptr a(defList[defStyle]); settings << QString::number(p->defaultStyle(), 10); settings << (p->hasProperty(QTextFormat::ForegroundBrush) ? QString::number(p->foreground().color().rgb(), 16) : (writeDefaultsToo ? QString::number(a->foreground().color().rgb(), 16) : QString())); settings << (p->hasProperty(SelectedForeground) ? QString::number(p->selectedForeground().color().rgb(), 16) : (writeDefaultsToo ? QString::number(a->selectedForeground().color().rgb(), 16) : QString())); settings << (p->hasProperty(QTextFormat::FontWeight) ? (p->fontBold() ? QStringLiteral("1") : QStringLiteral("0")) : (writeDefaultsToo ? (a->fontBold() ? QStringLiteral("1") : QStringLiteral("0")) : QString())); settings << (p->hasProperty(QTextFormat::FontItalic) ? (p->fontItalic() ? QStringLiteral("1") : QStringLiteral("0")) : (writeDefaultsToo ? (a->fontItalic() ? QStringLiteral("1") : QStringLiteral("0")) : QString())); settings << (p->hasProperty(QTextFormat::FontStrikeOut) ? (p->fontStrikeOut() ? QStringLiteral("1") : QStringLiteral("0")) : (writeDefaultsToo ? (a->fontStrikeOut() ? QStringLiteral("1") : QStringLiteral("0")) : QString())); settings << (p->hasProperty(QTextFormat::FontUnderline) ? (p->fontUnderline() ? QStringLiteral("1") : QStringLiteral("0")) : (writeDefaultsToo ? (a->fontUnderline() ? QStringLiteral("1") : QStringLiteral("0")) : QString())); settings << (p->hasProperty(QTextFormat::BackgroundBrush) ? QString::number(p->background().color().rgb(), 16) : ((writeDefaultsToo && a->hasProperty(QTextFormat::BackgroundBrush)) ? QString::number(a->background().color().rgb(), 16) : QString())); settings << (p->hasProperty(SelectedBackground) ? QString::number(p->selectedBackground().color().rgb(), 16) : ((writeDefaultsToo && a->hasProperty(SelectedBackground)) ? QString::number(a->selectedBackground().color().rgb(), 16) : QString())); settings << (p->hasProperty(QTextFormat::FontFamily) ? (p->fontFamily()) : (writeDefaultsToo ? a->fontFamily() : QString())); settings << QStringLiteral("---"); config.writeEntry(p->name(), settings); } } const QHash &KateHighlighting::getCharacterEncodings(int attrib) const { return m_additionalData[ hlKeyForAttrib(attrib) ]->characterEncodings; } const KatePrefixStore &KateHighlighting::getCharacterEncodingsPrefixStore(int attrib) const { return m_additionalData[ hlKeyForAttrib(attrib) ]->characterEncodingsPrefixStore; } const QHash &KateHighlighting::getReverseCharacterEncodings(int attrib) const { return m_additionalData[ hlKeyForAttrib(attrib) ]->reverseCharacterEncodings; } int KateHighlighting::getEncodedCharactersInsertionPolicy(int attrib) const { return m_additionalData[ hlKeyForAttrib(attrib) ]->encodedCharactersInsertionPolicy; } void KateHighlighting::addCharacterEncoding(const QString &key, const QString &encoding, const QChar &c) { m_additionalData[ key ]->characterEncodingsPrefixStore.addPrefix(encoding); m_additionalData[ key ]->characterEncodings[ encoding ] = c; m_additionalData[ key ]->reverseCharacterEncodings[ c ] = encoding; } /** * Increase the usage count, and trigger initialization if needed. */ void KateHighlighting::use() { if (refCount == 0) { init(); } refCount = 1; } /** * Reload the highlighting. */ void KateHighlighting::reload() { // nop if not referenced if (refCount == 0) { return; } cleanup(); init(); } /** * Initialize a context for the first time. */ void KateHighlighting::init() { // shall be only called if clean! Q_ASSERT(m_contexts.empty()); // try to create contexts makeContextList(); // fixup internal id list, if empty if (internalIDList.isEmpty()) { internalIDList.append(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute(i18n("Normal Text"), KTextEditor::dsNormal))); } // something went wrong or no hl, fill something in if (noHl) { iHidden = false; m_additionalData.insert(QStringLiteral("none"), new HighlightPropertyBag); m_additionalData[QStringLiteral("none")]->deliminator = stdDeliminator(); m_additionalData[QStringLiteral("none")]->wordWrapDeliminator = stdDeliminator(); m_hlIndex[0] = QStringLiteral("none"); m_ctxIndex[0] = QStringLiteral("none"); // create one dummy context! m_contexts.push_back(new KateHlContext(identifier, 0, KateHlContextModification(), false, KateHlContextModification(), false, false, false, KateHlContextModification())); } // clear domdocument cache KateHlManager::self()->syntax.clearCache(); } /** * KateHighlighting - createKateExtendedAttribute * This function reads the itemData entries from the config file, which specifies the * default attribute styles for matched items/contexts. * * @param list A reference to the internal list containing the parsed default config */ void KateHighlighting::createKateExtendedAttribute(QList &list) { // trigger hl load, if needed use(); // return internal list, never empty! Q_ASSERT(!internalIDList.empty()); list = internalIDList; } /** * Adds the styles of the currently parsed highlight to the itemdata list */ void KateHighlighting::addToKateExtendedAttributeList() { //Tell the syntax document class which file we want to parse and which data group KateHlManager::self()->syntax.setIdentifier(buildIdentifier); KateSyntaxContextData *data = KateHlManager::self()->syntax.getGroupInfo(QStringLiteral("highlighting"), QStringLiteral("itemData")); // use global color instance, creation is expensive! const KateDefaultColors &colors(KTextEditor::EditorPrivate::self()->defaultColors()); //begin with the real parsing while (KateHlManager::self()->syntax.nextGroup(data)) { // read all attributes const QString color = KateHlManager::self()->syntax.groupData(data, QStringLiteral("color")); const QString selColor = KateHlManager::self()->syntax.groupData(data, QStringLiteral("selColor")); const QString bold = KateHlManager::self()->syntax.groupData(data, QStringLiteral("bold")); const QString italic = KateHlManager::self()->syntax.groupData(data, QStringLiteral("italic")); const QString underline = KateHlManager::self()->syntax.groupData(data, QStringLiteral("underline")); const QString strikeOut = KateHlManager::self()->syntax.groupData(data, QStringLiteral("strikeOut")); const QString bgColor = KateHlManager::self()->syntax.groupData(data, QStringLiteral("backgroundColor")); const QString selBgColor = KateHlManager::self()->syntax.groupData(data, QStringLiteral("selBackgroundColor")); const QString spellChecking = KateHlManager::self()->syntax.groupData(data, QStringLiteral("spellChecking")); const QString fontFamily = KateHlManager::self()->syntax.groupData(data, QStringLiteral("fontFamily")); const QString itemDataName = KateHlManager::self()->syntax.groupData(data, QStringLiteral("name")).simplified(); const QString defStyleName = KateHlManager::self()->syntax.groupData(data, QStringLiteral("defStyleNum")); KTextEditor::Attribute::Ptr newData(new KTextEditor::Attribute( buildPrefix + itemDataName, static_cast (KateHlManager::defaultStyleNameToIndex(defStyleName)))); /* here the custom style overrides are specified, if needed */ if (!color.isEmpty()) { newData->setForeground(colors.adaptToScheme(QColor(color), KateDefaultColors::ForegroundColor)); } if (!selColor.isEmpty()) { newData->setSelectedForeground(colors.adaptToScheme(QColor(selColor), KateDefaultColors::ForegroundColor)); } if (!bold.isEmpty()) { newData->setFontBold(isTrue(bold)); } if (!italic.isEmpty()) { newData->setFontItalic(isTrue(italic)); } // new attributes for the new rendering view if (!underline.isEmpty()) { newData->setFontUnderline(isTrue(underline)); } if (!strikeOut.isEmpty()) { newData->setFontStrikeOut(isTrue(strikeOut)); } if (!bgColor.isEmpty()) { newData->setBackground(colors.adaptToScheme(QColor(bgColor), KateDefaultColors::BackgroundColor)); } if (!selBgColor.isEmpty()) { newData->setSelectedBackground(colors.adaptToScheme(QColor(selBgColor), KateDefaultColors::BackgroundColor)); } // is spellchecking desired? if (!spellChecking.isEmpty()) { newData->setSkipSpellChecking(!(isTrue(spellChecking))); } if (!fontFamily.isEmpty()) { newData->setFontFamily(fontFamily); } internalIDList.append(newData); } //clean up if (data) { KateHlManager::self()->syntax.freeGroupInfo(data); } } /** * KateHighlighting - lookupAttrName * This function is a helper for makeContextList and createKateHlItem. It looks the given * attribute name in the itemData list up and returns its index * * @param name the attribute name to lookup * @param iDl the list containing all available attributes * * @return The index of the attribute, or 0 if the attribute isn't found */ int KateHighlighting::lookupAttrName(const QString &name, QList &iDl) { const QString needle = buildPrefix + name; for (int i = 0; i < iDl.count(); i++) if (iDl.at(i)->name() == needle) { return i; } #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Couldn't resolve itemDataName:" << name; #endif return 0; } /** * KateHighlighting - createKateHlItem * This function is a helper for makeContextList. It parses the xml file for * information. * * @param data Data about the item read from the xml file * @param iDl List of all available itemData entries. * Needed for attribute name->index translation * @param RegionList list of code folding region names * @param ContextNameList list of context names * * @return A pointer to the newly created item object */ KateHlItem *KateHighlighting::createKateHlItem(KateSyntaxContextData *data, QList &iDl, QStringList *RegionList, QStringList *ContextNameList) { // No highlighting -> exit if (noHl) { return 0; } // get the (tagname) itemd type const QString dataname = KateHlManager::self()->syntax.groupItemData(data, QString()); // code folding region handling: const QString beginRegionStr = KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("beginRegion")); const QString endRegionStr = KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("endRegion")); signed char regionId = 0; signed char regionId2 = 0; if (!beginRegionStr.isEmpty()) { regionId = RegionList->indexOf(beginRegionStr); if (regionId == -1) { // if the region name doesn't already exist, add it to the list (*RegionList) << beginRegionStr; regionId = RegionList->indexOf(beginRegionStr); } regionId++; #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "########### BEG REG: " << beginRegionStr << " NUM: " << regionId; #endif } if (!endRegionStr.isEmpty()) { regionId2 = RegionList->indexOf(endRegionStr); if (regionId2 == -1) { // if the region name doesn't already exist, add it to the list (*RegionList) << endRegionStr; regionId2 = RegionList->indexOf(endRegionStr); } regionId2 = -regionId2 - 1; #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "########### END REG: " << endRegionStr << " NUM: " << regionId2; #endif } int attr = 0; const QString tmpAttr = KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("attribute")).simplified(); bool onlyConsume = tmpAttr.isEmpty(); // only relevant for non consumer if (!onlyConsume) { if (QStringLiteral("%1").arg(tmpAttr.toInt()) == tmpAttr) { errorsAndWarnings += i18n( "%1: Deprecated syntax. Attribute (%2) not addressed by symbolic name
", buildIdentifier, tmpAttr); attr = tmpAttr.toInt(); } else { attr = lookupAttrName(tmpAttr, iDl); } } // Info about context switch KateHlContextModification context = -1; QString unresolvedContext; const QString tmpcontext = KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("context")); if (!tmpcontext.isEmpty()) { context = getContextModificationFromString(ContextNameList, tmpcontext, unresolvedContext); } // Get the char parameter (eg DetectChar) QChar chr; if (! KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("char")).isEmpty()) { chr = (KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("char"))).at(0); } // Get the String parameter (eg. StringDetect) const QString stringdata = KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("String")); // Get a second char parameter (char1) (eg Detect2Chars) QChar chr1; if (! KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("char1")).isEmpty()) { chr1 = (KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("char1"))).at(0); } // Will be removed eventually. Atm used for StringDetect, WordDetect, keyword and RegExp const QString &insensitive_str = KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("insensitive")); bool insensitive = isTrue(insensitive_str); // for regexp only bool minimal = isTrue(KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("minimal"))); // dominik: look ahead and do not change offset. so we can change contexts w/o changing offset1. bool lookAhead = isTrue(KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("lookAhead"))); bool dynamic = isTrue(KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("dynamic"))); bool firstNonSpace = isTrue(KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("firstNonSpace"))); int column = -1; QString colStr = KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("column")); if (!colStr.isEmpty()) { column = colStr.toInt(); } // Create the item corresponding to its type and set its parameters KateHlItem *tmpItem; if (dataname == QLatin1String("keyword")) { bool keywordInsensitive = insensitive_str.isEmpty() ? !casesensitive : insensitive; KateHlKeyword *keyword = new KateHlKeyword(attr, context, regionId, regionId2, keywordInsensitive, m_additionalData[ buildIdentifier ]->deliminator); //Get the entries for the keyword lookup list keyword->addList(KateHlManager::self()->syntax.finddata(QStringLiteral("highlighting"), stringdata)); tmpItem = keyword; } else if (dataname == QLatin1String("Float")) { tmpItem = (new KateHlFloat(attr, context, regionId, regionId2)); } else if (dataname == QLatin1String("Int")) { tmpItem = (new KateHlInt(attr, context, regionId, regionId2)); } else if (dataname == QLatin1String("DetectChar")) { tmpItem = (new KateHlCharDetect(attr, context, regionId, regionId2, chr)); } else if (dataname == QLatin1String("Detect2Chars")) { tmpItem = (new KateHl2CharDetect(attr, context, regionId, regionId2, chr, chr1)); } else if (dataname == QLatin1String("RangeDetect")) { tmpItem = (new KateHlRangeDetect(attr, context, regionId, regionId2, chr, chr1)); } else if (dataname == QLatin1String("LineContinue")) { tmpItem = (new KateHlLineContinue(attr, context, regionId, regionId2, chr)); } else if (dataname == QLatin1String("StringDetect")) { tmpItem = (new KateHlStringDetect(attr, context, regionId, regionId2, stringdata, insensitive)); } else if (dataname == QLatin1String("WordDetect")) { tmpItem = (new KateHlWordDetect(attr, context, regionId, regionId2, stringdata, insensitive)); } else if (dataname == QLatin1String("AnyChar")) { tmpItem = (new KateHlAnyChar(attr, context, regionId, regionId2, stringdata)); } else if (dataname == QLatin1String("RegExpr")) { tmpItem = (new KateHlRegExpr(attr, context, regionId, regionId2, stringdata, insensitive, minimal)); } else if (dataname == QLatin1String("HlCChar")) { tmpItem = (new KateHlCChar(attr, context, regionId, regionId2)); } else if (dataname == QLatin1String("HlCHex")) { tmpItem = (new KateHlCHex(attr, context, regionId, regionId2)); } else if (dataname == QLatin1String("HlCOct")) { tmpItem = (new KateHlCOct(attr, context, regionId, regionId2)); } else if (dataname == QLatin1String("HlCFloat")) { tmpItem = (new KateHlCFloat(attr, context, regionId, regionId2)); } else if (dataname == QLatin1String("HlCStringChar")) { tmpItem = (new KateHlCStringChar(attr, context, regionId, regionId2)); } else if (dataname == QLatin1String("DetectSpaces")) { tmpItem = (new KateHlDetectSpaces(attr, context, regionId, regionId2)); } else if (dataname == QLatin1String("DetectIdentifier")) { tmpItem = (new KateHlDetectIdentifier(attr, context, regionId, regionId2)); } else { // oops, unknown type. Perhaps a spelling error in the xml file return 0; } // set lookAhead & dynamic properties tmpItem->lookAhead = lookAhead; tmpItem->dynamic = dynamic; tmpItem->firstNonSpace = firstNonSpace; tmpItem->column = column; tmpItem->onlyConsume = onlyConsume; if (!unresolvedContext.isEmpty()) { unresolvedContextReferences.insert(&(tmpItem->ctx), unresolvedContext); } // remember all to delete them m_hlItemCleanupList.append(tmpItem); return tmpItem; } int KateHighlighting::attribute(int ctx) const { return m_contexts[ctx]->attr; } bool KateHighlighting::attributeRequiresSpellchecking(int attr) { if (attr >= 0 && attr < internalIDList.size() && internalIDList[attr]->hasProperty(Spellchecking)) { return !internalIDList[attr]->boolProperty(Spellchecking); } return true; } KTextEditor::DefaultStyle KateHighlighting::defaultStyleForAttribute(int attr) const { if (attr >= 0 && attr < internalIDList.size()) { return internalIDList[attr]->defaultStyle(); } return KTextEditor::dsNormal; } QString KateHighlighting::hlKeyForContext(int i) const { int k = 0; QMap::const_iterator it = m_ctxIndex.constEnd(); while (it != m_ctxIndex.constBegin()) { --it; k = it.key(); if (i >= k) { break; } } return it.value(); } QString KateHighlighting::hlKeyForAttrib(int i) const { // find entry. This is faster than QMap::find. m_hlIndex always has an entry // for key '0' (it is "none"), so the result is always valid. int k = 0; QMap::const_iterator it = m_hlIndex.constEnd(); while (it != m_hlIndex.constBegin()) { --it; k = it.key(); if (i >= k) { break; } } return it.value(); } bool KateHighlighting::isInWord(QChar c, int attrib) const { return m_additionalData[ hlKeyForAttrib(attrib) ]->deliminator.indexOf(c) < 0 && !c.isSpace() && c != QLatin1Char('"') && c != QLatin1Char('\'') && c != QLatin1Char('`'); } bool KateHighlighting::canBreakAt(QChar c, int attrib) const { static const QString &sq = QStringLiteral("\"'"); return (m_additionalData[ hlKeyForAttrib(attrib) ]->wordWrapDeliminator.indexOf(c) != -1) && (sq.indexOf(c) == -1); } QLinkedList KateHighlighting::emptyLines(int attrib) const { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "hlKeyForAttrib: " << hlKeyForAttrib(attrib); #endif return m_additionalData[hlKeyForAttrib(attrib)]->emptyLines; } signed char KateHighlighting::commentRegion(int attr) const { QString commentRegion = m_additionalData[ hlKeyForAttrib(attr) ]->multiLineRegion; return (commentRegion.isEmpty() ? 0 : (commentRegion.toShort())); } bool KateHighlighting::canComment(int startAttrib, int endAttrib) const { QString k = hlKeyForAttrib(startAttrib); return (k == hlKeyForAttrib(endAttrib) && ((!m_additionalData[k]->multiLineCommentStart.isEmpty() && !m_additionalData[k]->multiLineCommentEnd.isEmpty()) || ! m_additionalData[k]->singleLineCommentMarker.isEmpty())); } QString KateHighlighting::getCommentStart(int attrib) const { return m_additionalData[ hlKeyForAttrib(attrib) ]->multiLineCommentStart; } QString KateHighlighting::getCommentEnd(int attrib) const { return m_additionalData[ hlKeyForAttrib(attrib) ]->multiLineCommentEnd; } QString KateHighlighting::getCommentSingleLineStart(int attrib) const { return m_additionalData[ hlKeyForAttrib(attrib) ]->singleLineCommentMarker; } KateHighlighting::CSLPos KateHighlighting::getCommentSingleLinePosition(int attrib) const { return m_additionalData[ hlKeyForAttrib(attrib) ]->singleLineCommentPosition; } const QHash &KateHighlighting::characterEncodings(int attrib) const { return m_additionalData[ hlKeyForAttrib(attrib) ]->characterEncodings; } /** * Helper for makeContextList. It parses the xml file for * information, how single or multi line comments are marked */ void KateHighlighting::readCommentConfig() { KateHlManager::self()->syntax.setIdentifier(buildIdentifier); KateSyntaxContextData *data = KateHlManager::self()->syntax.getGroupInfo(QStringLiteral("general"), QStringLiteral("comment")); QString cmlStart, cmlEnd, cmlRegion, cslStart; CSLPos cslPosition = CSLPosColumn0; if (data) { while (KateHlManager::self()->syntax.nextGroup(data)) { if (KateHlManager::self()->syntax.groupData(data, QStringLiteral("name")) == QLatin1String("singleLine")) { cslStart = KateHlManager::self()->syntax.groupData(data, QStringLiteral("start")); QString cslpos = KateHlManager::self()->syntax.groupData(data, QStringLiteral("position")); if (cslpos == QLatin1String("afterwhitespace")) { cslPosition = CSLPosAfterWhitespace; } else { cslPosition = CSLPosColumn0; } } else if (KateHlManager::self()->syntax.groupData(data, QStringLiteral("name")) == QLatin1String("multiLine")) { cmlStart = KateHlManager::self()->syntax.groupData(data, QStringLiteral("start")); cmlEnd = KateHlManager::self()->syntax.groupData(data, QStringLiteral("end")); cmlRegion = KateHlManager::self()->syntax.groupData(data, QStringLiteral("region")); } } KateHlManager::self()->syntax.freeGroupInfo(data); } m_additionalData[buildIdentifier]->singleLineCommentMarker = cslStart; m_additionalData[buildIdentifier]->singleLineCommentPosition = cslPosition; m_additionalData[buildIdentifier]->multiLineCommentStart = cmlStart; m_additionalData[buildIdentifier]->multiLineCommentEnd = cmlEnd; m_additionalData[buildIdentifier]->multiLineRegion = cmlRegion; } void KateHighlighting::readEmptyLineConfig() { KateHlManager::self()->syntax.setIdentifier(buildIdentifier); KateSyntaxContextData *data = KateHlManager::self()->syntax.getGroupInfo(QStringLiteral("general"), QStringLiteral("emptyLine")); QLinkedList exprList; if (data) { while (KateHlManager::self()->syntax.nextGroup(data)) { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "creating an empty line regular expression"; #endif QString regexprline = KateHlManager::self()->syntax.groupData(data, QStringLiteral("regexpr")); bool regexprcase = isTrue(KateHlManager::self()->syntax.groupData(data, QStringLiteral("casesensitive"))); exprList.append(QRegularExpression(regexprline, !regexprcase ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption)); } KateHlManager::self()->syntax.freeGroupInfo(data); } m_additionalData[buildIdentifier]->emptyLines = exprList; } /** * Helper for makeContextList. It parses the xml file for information, * if keywords should be treated case(in)sensitive and creates the keyword * delimiter list. Which is the default list, without any given weak deliminiators */ void KateHighlighting::readGlobalKeywordConfig() { deliminator = stdDeliminator(); #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "readGlobalKeywordConfig:BEGIN"; #endif // Tell the syntax document class which file we want to parse KateHlManager::self()->syntax.setIdentifier(buildIdentifier); KateSyntaxContextData *data = KateHlManager::self()->syntax.getConfig(QStringLiteral("general"), QStringLiteral("keywords")); if (data) { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Found global keyword config"; #endif casesensitive = isTrue(KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("casesensitive"))); //get the weak deliminators weakDeliminator = (KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("weakDeliminator"))); #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "weak delimiters are: " << weakDeliminator; #endif // remove any weakDelimitars (if any) from the default list and store this list. for (int s = 0; s < weakDeliminator.length(); s++) { int f = deliminator.indexOf(weakDeliminator[s]); if (f > -1) { deliminator.remove(f, 1); } } QString addDelim = (KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("additionalDeliminator"))); if (!addDelim.isEmpty()) { deliminator = deliminator + addDelim; } KateHlManager::self()->syntax.freeGroupInfo(data); } else { //Default values casesensitive = true; weakDeliminator = QString(); } #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "readGlobalKeywordConfig:END"; qCDebug(LOG_KTE) << "delimiterCharacters are: " << deliminator; #endif m_additionalData[buildIdentifier]->deliminator = deliminator; } /** * Helper for makeContextList. It parses the xml file for any wordwrap * deliminators, characters * at which line can be broken. In case no keyword * tag is found in the xml file, the wordwrap deliminators list defaults to the * standard denominators. In case a keyword tag is defined, but no * wordWrapDeliminator attribute is specified, the deliminator list as computed * in readGlobalKeywordConfig is used. * * @return the computed delimiter string. */ void KateHighlighting::readWordWrapConfig() { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "readWordWrapConfig:BEGIN"; #endif // Tell the syntax document class which file we want to parse KateHlManager::self()->syntax.setIdentifier(buildIdentifier); KateSyntaxContextData *data = KateHlManager::self()->syntax.getConfig(QStringLiteral("general"), QStringLiteral("keywords")); QString wordWrapDeliminator = stdDeliminator(); if (data) { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Found global keyword config"; #endif wordWrapDeliminator = (KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("wordWrapDeliminator"))); //when no wordWrapDeliminator is defined use the deliminator list if (wordWrapDeliminator.length() == 0) { wordWrapDeliminator = deliminator; } #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "word wrap deliminators are " << wordWrapDeliminator; #endif KateHlManager::self()->syntax.freeGroupInfo(data); } #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "readWordWrapConfig:END"; #endif m_additionalData[buildIdentifier]->wordWrapDeliminator = wordWrapDeliminator; } void KateHighlighting::readIndentationConfig() { m_indentation = QString(); KateHlManager::self()->syntax.setIdentifier(buildIdentifier); KateSyntaxContextData *data = KateHlManager::self()->syntax.getConfig(QStringLiteral("general"), QStringLiteral("indentation")); if (data) { m_indentation = (KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("mode"))); KateHlManager::self()->syntax.freeGroupInfo(data); } } void KateHighlighting::readFoldingConfig() { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "readfoldignConfig:BEGIN"; #endif // Tell the syntax document class which file we want to parse KateHlManager::self()->syntax.setIdentifier(buildIdentifier); KateSyntaxContextData *data = KateHlManager::self()->syntax.getConfig(QStringLiteral("general"), QStringLiteral("folding")); if (data) { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Found global keyword config"; #endif m_foldingIndentationSensitive = isTrue(KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("indentationsensitive"))); KateHlManager::self()->syntax.freeGroupInfo(data); } else { //Default values m_foldingIndentationSensitive = false; } #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "readfoldingConfig:END"; qCDebug(LOG_KTE) << "############################ use indent for fold are: " << m_foldingIndentationSensitive; #endif } void KateHighlighting::readSpellCheckingConfig() { KateHlManager::self()->syntax.setIdentifier(buildIdentifier); KateSyntaxContextData *data = KateHlManager::self()->syntax.getGroupInfo(QStringLiteral("spellchecking"), QStringLiteral("encoding")); if (data) { while (KateHlManager::self()->syntax.nextGroup(data)) { QString encoding = KateHlManager::self()->syntax.groupData(data, QStringLiteral("string")); QString character = KateHlManager::self()->syntax.groupData(data, QStringLiteral("char")); QString ignored = KateHlManager::self()->syntax.groupData(data, QStringLiteral("ignored")); const bool ignoredIsTrue = isTrue(ignored); if (encoding.isEmpty() || (character.isEmpty() && !ignoredIsTrue)) { continue; } QRegularExpression newLineRegExp(QStringLiteral("\\r|\\n")); if (encoding.indexOf(newLineRegExp) >= 0) { encoding.replace(newLineRegExp, QStringLiteral("<\\n|\\r>")); #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Encoding" << encoding << "contains new-line characters. Ignored."; #endif } QChar c = (character.isEmpty() || ignoredIsTrue) ? QChar() : character[0]; addCharacterEncoding(buildIdentifier, encoding, c); } KateHlManager::self()->syntax.freeGroupInfo(data); } data = KateHlManager::self()->syntax.getConfig(QStringLiteral("spellchecking"), QStringLiteral("configuration")); if (data) { QString policy = KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("encodingReplacementPolicy")); QString policyLowerCase = policy.toLower(); int p; if (policyLowerCase == QLatin1String("encodewhenpresent")) { p = KTextEditor::DocumentPrivate::EncodeWhenPresent; } else if (policyLowerCase == QLatin1String("encodealways")) { p = KTextEditor::DocumentPrivate::EncodeAlways; } else { p = KTextEditor::DocumentPrivate::EncodeNever; } m_additionalData[buildIdentifier]->encodedCharactersInsertionPolicy = p; KateHlManager::self()->syntax.freeGroupInfo(data); } } void KateHighlighting::createContextNameList(QStringList *ContextNameList, int ctx0) { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "creatingContextNameList:BEGIN"; #endif if (ctx0 == 0) { ContextNameList->clear(); } KateHlManager::self()->syntax.setIdentifier(buildIdentifier); KateSyntaxContextData *data = KateHlManager::self()->syntax.getGroupInfo(QStringLiteral("highlighting"), QStringLiteral("context")); int id = ctx0; if (data) { while (KateHlManager::self()->syntax.nextGroup(data)) { QString tmpAttr = KateHlManager::self()->syntax.groupData(data, QStringLiteral("name")).simplified(); if (tmpAttr.isEmpty()) { tmpAttr = QStringLiteral("!KATE_INTERNAL_DUMMY! %1").arg(id); errorsAndWarnings += i18n("%1: Deprecated syntax. Context %2 has no symbolic name
", buildIdentifier, id - ctx0); } else { tmpAttr = buildPrefix + tmpAttr; } (*ContextNameList) << tmpAttr; id++; } KateHlManager::self()->syntax.freeGroupInfo(data); } #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "creatingContextNameList:END"; #endif } KateHlContextModification KateHighlighting::getContextModificationFromString(QStringList *ContextNameList, QString tmpLineEndContext, /*NO CONST*/ QString &unres) { // nothing unresolved unres = QString(); // context to push on stack int context = -1; // number of contexts to pop int pops = 0; // we allow arbitrary #stay and #pop at the start bool anyFound = false; while (tmpLineEndContext.startsWith(QLatin1String("#stay")) || tmpLineEndContext.startsWith(QLatin1String("#pop"))) { // ignore stay if (tmpLineEndContext.startsWith(QLatin1String("#stay"))) { tmpLineEndContext.remove(0, 5); } else { // count the pops ++pops; tmpLineEndContext.remove(0, 4); } anyFound = true; } /** * we want a ! if we have found any pop or push and still have stuff in the string... */ if (anyFound && !tmpLineEndContext.isEmpty()) { if (tmpLineEndContext.startsWith(QLatin1Char('!'))) { tmpLineEndContext.remove(0, 1); } } /** * empty string, done */ if (tmpLineEndContext.isEmpty()) { return KateHlContextModification(context, pops); } /** * handle the remaining string, this might be a ##contextname * or a normal contextname.... */ if (tmpLineEndContext.contains(QLatin1String("##"))) { int o = tmpLineEndContext.indexOf(QLatin1String("##")); // FIXME at least with 'foo##bar'-style contexts the rules are picked up // but the default attribute is not QString tmp = tmpLineEndContext.mid(o + 2); if (!embeddedHls.contains(tmp)) { embeddedHls.insert(tmp, KateEmbeddedHlInfo()); } unres = tmp + QLatin1Char(':') + tmpLineEndContext.left(o); #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "unres = " << unres; #endif context = 0; } else { context = ContextNameList->indexOf(buildPrefix + tmpLineEndContext); if (context == -1) { context = tmpLineEndContext.toInt(); errorsAndWarnings += i18n( "%1:Deprecated syntax. Context %2 not addressed by a symbolic name" , buildIdentifier, tmpLineEndContext); } //#warning restructure this the name list storage. // context=context+buildContext0Offset; } return KateHlContextModification(context, pops); } /** * The most important initialization function for each highlighting. It's called * each time a document gets a highlighting style assigned. parses the xml file * and creates a corresponding internal structure */ void KateHighlighting::makeContextList() { if (noHl) { // if this a highlighting for "normal texts" only, tere is no need for a context list creation return; } embeddedHls.clear(); embeddedHighlightingModes.clear(); unresolvedContextReferences.clear(); RegionList.clear(); ContextNameList.clear(); // prepare list creation. To reuse as much code as possible handle this // highlighting the same way as embedded onces embeddedHls.insert(iName, KateEmbeddedHlInfo()); bool something_changed; // the context "0" id is 0 for this hl, all embedded context "0"s have offsets startctx = base_startctx = 0; // inform everybody that we are building the highlighting contexts and itemlists building = true; do { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "**************** Outer loop in make ContextList"; qCDebug(LOG_KTE) << "**************** Hl List count:" << embeddedHls.count(); #endif something_changed = false; //assume all "embedded" hls have already been loaded for (KateEmbeddedHlInfos::iterator it = embeddedHls.begin(); it != embeddedHls.end(); ++it) { if (!it.value().loaded) { // we found one, we still have to load #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "**************** Inner loop in make ContextList"; #endif QString identifierToUse; #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Trying to open highlighting definition file: " << it.key(); #endif if (iName == it.key()) { // the own identifier is known identifierToUse = identifier; } else { // all others have to be looked up identifierToUse = KateHlManager::self()->identifierForName(it.key()); } #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Location is:" << identifierToUse; #endif if (identifierToUse.isEmpty()) { qCWarning(LOG_KTE) << "Unknown highlighting description referenced:" << it.key() << "in" << identifier; } buildPrefix = it.key() + QLatin1Char(':'); // attribute names get prefixed by the names // of the highlighting definitions they belong to #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "setting (" << it.key() << ") to loaded"; #endif //mark hl as loaded it = embeddedHls.insert(it.key(), KateEmbeddedHlInfo(true, startctx)); //set class member for context 0 offset, so we don't need to pass it around buildContext0Offset = startctx; //parse one hl definition file startctx = addToContextList(identifierToUse, startctx); if (noHl) { return; // an error occurred } base_startctx = startctx; something_changed = true; // something has been loaded } } } while (something_changed); // as long as there has been another file parsed // repeat everything, there could be newly added embedded hls. #ifdef HIGHLIGHTING_DEBUG // at this point all needed highlighing (sub)definitions are loaded. It's time // to resolve cross file references (if there are any)# qCDebug(LOG_KTE) << "Unresolved contexts, which need attention: " << unresolvedContextReferences.count(); #endif //optimize this a littlebit for (KateHlUnresolvedCtxRefs::iterator unresIt = unresolvedContextReferences.begin(); unresIt != unresolvedContextReferences.end(); ++unresIt) { QString incCtx = unresIt.value(); #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Context " << incCtx << " is unresolved"; #endif // only resolve '##Name' contexts here; handleKateHlIncludeRules() can figure // out 'Name##Name'-style inclusions, but we screw it up if (incCtx.endsWith(QLatin1Char(':'))) { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Looking up context0 for ruleset " << incCtx; #endif incCtx = incCtx.left(incCtx.length() - 1); //try to find the context0 id for a given unresolvedReference KateEmbeddedHlInfos::const_iterator hlIt = embeddedHls.constFind(incCtx); if (hlIt != embeddedHls.constEnd()) { *(unresIt.key()) = hlIt.value().context0; } } } // eventually handle KateHlIncludeRules items, if they exist. // This has to be done after the cross file references, because it is allowed // to include the context0 from a different definition, than the one the rule // belongs to handleKateHlIncludeRules(); embeddedHighlightingModes = embeddedHls.keys(); embeddedHighlightingModes.removeOne(iName); embeddedHls.clear(); //save some memory. unresolvedContextReferences.clear(); //save some memory RegionList.clear(); // I think you get the idea ;) ContextNameList.clear(); // if there have been errors show them if (!errorsAndWarnings.isEmpty()) KMessageBox::detailedSorry(QApplication::activeWindow(), i18n( "There were warning(s) and/or error(s) while parsing the syntax " "highlighting configuration."), errorsAndWarnings, i18n("Kate Syntax Highlighting Parser")); // we have finished building = false; Q_ASSERT(m_contexts.size() > 0); } void KateHighlighting::handleKateHlIncludeRules() { // if there are noe include rules to take care of, just return #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "KateHlIncludeRules, which need attention: " << includeRules.size(); #endif if (includeRules.isEmpty()) { return; } buildPrefix = QString(); QString dummy; // By now the context0 references are resolved, now more or less only inner // file references are resolved. If we decide that arbitrary inclusion is // needed, this doesn't need to be changed, only the addToContextList // method. //resolove context names for (KateHlIncludeRules::iterator it = includeRules.begin(); it != includeRules.end();) { if ((*it)->incCtx.newContext == -1) { // context unresolved ? if ((*it)->incCtxN.isEmpty()) { // no context name given, and no valid context id set, so this item is // going to be removed KateHlIncludeRules::iterator it1 = it; ++it1; delete(*it); includeRules.erase(it); it = it1; } else { // resolve name to id (*it)->incCtx = getContextModificationFromString(&ContextNameList, (*it)->incCtxN, dummy).newContext; #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Resolved " << (*it)->incCtxN << " to " << (*it)->incCtx.newContext << " for include rule"; #endif // It would be good to look here somehow, if the result is valid } } else { ++it; //nothing to do, already resolved (by the cross definition reference resolver) } } // now that all KateHlIncludeRule items should be valid and completely resolved, // do the real inclusion of the rules. // recursiveness is needed, because context 0 could include context 1, which // itself includes context 2 and so on. // In that case we have to handle context 2 first, then 1, 0 //TODO: catch circular references: eg 0->1->2->3->1 for (int i = 0; i < includeRules.count(); i++) { handleKateHlIncludeRulesRecursive(i, &includeRules); } qDeleteAll(includeRules); includeRules.clear(); } void KateHighlighting::handleKateHlIncludeRulesRecursive(int index, KateHlIncludeRules *list) { if (index < 0 || index >= list->count()) { return; //invalid iterator, shouldn't happen, but better have a rule prepared ;) } int index1 = index; int ctx = list->at(index1)->ctx; if (ctx == -1) { return; // skip already processed entries } // find the last entry for the given context in the KateHlIncludeRules list // this is need if one context includes more than one. This saves us from // updating all insert positions: // eg: context 0: // pos 3 - include context 2 // pos 5 - include context 3 // During the building of the includeRules list the items are inserted in // ascending order, now we need it descending to make our life easier. while (index < list->count() && list->at(index)->ctx == ctx) { index1 = index; ++index; } // iterate over each include rule for the context the function has been called for. while (index1 >= 0 && index1 < list->count() && list->at(index1)->ctx == ctx) { KateHlContextModification ctx1 = list->at(index1)->incCtx; //let's see, if the included context includes other contexts for (int index2 = 0; index2 < list->count(); ++index2) { if (list->at(index2)->ctx == ctx1.newContext) { if (index2 == index1) { // prevent accidental infinite recursion qCWarning(LOG_KTE) << "infinite recursion in IncludeRules in language file for " << iName << "in context" << list->at(index1)->incCtxN; continue; } //yes it does, so first handle that include rules, since we want to // include those subincludes too handleKateHlIncludeRulesRecursive(index2, list); break; } } // if the context we want to include had sub includes, they are already inserted there. KateHlContext *dest = m_contexts[ctx]; KateHlContext *src = m_contexts[ctx1.newContext]; // qCDebug(LOG_KTE)<<"linking included rules from "<at(index1)->includeAttrib) { dest->attr = src->attr; } // insert the included context's rules starting at position p int p = list->at(index1)->pos; // remember some stuff int oldLen = dest->items.size(); uint itemsToInsert = src->items.size(); // resize target dest->items.resize(oldLen + itemsToInsert); // move old elements for (int i = oldLen - 1; i >= p; --i) { dest->items[i + itemsToInsert] = dest->items[i]; } // insert new stuff for (uint i = 0; i < itemsToInsert; ++i) { dest->items[p + i] = src->items[i]; } index = index1; //backup the iterator --index1; //move to the next entry, which has to be take care of list->at(index)->ctx = -1; // set ctx to -1 to mark already processed entries } } /** * Add one highlight to the contextlist. * * @return the number of contexts after this is added. */ int KateHighlighting::addToContextList(const QString &ident, int ctx0) { //qCDebug(LOG_KTE)<<"=== Adding hl with ident '"< iDl = internalIDList; createContextNameList(&ContextNameList, ctx0); #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Parsing Context structure"; #endif //start the real work uint i = buildContext0Offset; KateSyntaxContextData *data = KateHlManager::self()->syntax.getGroupInfo(QStringLiteral("highlighting"), QStringLiteral("context")); if (data) { while (KateHlManager::self()->syntax.nextGroup(data)) { #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Found a context in file, building structure now"; #endif //BEGIN - Translation of the attribute parameter QString tmpAttr = KateHlManager::self()->syntax.groupData(data, QStringLiteral("attribute")).simplified(); int attr; if (QStringLiteral("%1").arg(tmpAttr.toInt()) == tmpAttr) { attr = tmpAttr.toInt(); } else { attr = lookupAttrName(tmpAttr, iDl); } //END - Translation of the attribute parameter QString tmpLineEndContext = KateHlManager::self()->syntax.groupData(data, QStringLiteral("lineEndContext")).simplified(); KateHlContextModification context; context = getContextModificationFromString(&ContextNameList, tmpLineEndContext, dummy); QString tmpNIBF = KateHlManager::self()->syntax.groupData(data, QStringLiteral("noIndentationBasedFolding")); bool noIndentationBasedFolding = isTrue(tmpNIBF); //BEGIN get fallthrough props KateHlContextModification ftc = 0; // fallthrough context QString tmpFt = KateHlManager::self()->syntax.groupData(data, QStringLiteral("fallthrough")); const bool ft = isTrue(tmpFt); if (ft) { QString tmpFtc = KateHlManager::self()->syntax.groupData(data, QStringLiteral("fallthroughContext")); ftc = getContextModificationFromString(&ContextNameList, tmpFtc, dummy); // stay is not allowed, we need to #pop or push some context... if (ftc.type == KateHlContextModification::doNothing) { ftc = 0; } #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Setting fall through context (context " << i << "): " << ftc.newContext; #endif } //END fallthrough props // empty line context QString emptyLineContext = KateHlManager::self()->syntax.groupData(data, QStringLiteral("lineEmptyContext")); KateHlContextModification emptyLineContextModification; if (!emptyLineContext.isEmpty()) { emptyLineContextModification = getContextModificationFromString(&ContextNameList, emptyLineContext, dummy); } QString tmpDynamic = KateHlManager::self()->syntax.groupData(data, QStringLiteral("dynamic")); bool dynamic = isTrue(tmpDynamic); KateHlContext *ctxNew = new KateHlContext( ident, attr, context, ft, ftc, dynamic, noIndentationBasedFolding, !emptyLineContext.isEmpty(), emptyLineContextModification); m_contexts.push_back(ctxNew); #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "INDEX: " << i << " LENGTH " << m_contexts.size() - 1; #endif //Let's create all items for the context while (KateHlManager::self()->syntax.nextItem(data)) { // qCDebug(LOG_KTE)<< "In make Contextlist: Item:"; // KateHlIncludeRules : add a pointer to each item in that context // TODO add a attrib includeAttrib QString tag = KateHlManager::self()->syntax.groupItemData(data, QString()); if (tag == QLatin1String("IncludeRules")) { //if the new item is an Include rule, we have to take special care QString incCtx = KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("context")); QString incAttrib = KateHlManager::self()->syntax.groupItemData(data, QStringLiteral("includeAttrib")); bool includeAttrib = isTrue(incAttrib); // only context refernces of type Name, ##Name, and Subname##Name are allowed if (incCtx.startsWith(QLatin1String("##")) || (!incCtx.startsWith(QLatin1Char('#')))) { int incCtxi = incCtx.indexOf(QLatin1String("##")); //#stay, #pop is not interesting here if (incCtxi >= 0) { QString incSet = incCtx.mid(incCtxi + 2); QString incCtxN = incSet + QLatin1Char(':') + incCtx.left(incCtxi); //a cross highlighting reference #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "Cross highlight reference , context " << incCtxN; #endif KateHlIncludeRule *ir = new KateHlIncludeRule(i, m_contexts[i]->items.count(), incCtxN, includeAttrib); //use the same way to determine cross hl file references as other items do if (!embeddedHls.contains(incSet)) { embeddedHls.insert(incSet, KateEmbeddedHlInfo()); } #ifdef HIGHLIGHTING_DEBUG else { qCDebug(LOG_KTE) << "Skipping embeddedHls.insert for " << incCtxN; } #endif unresolvedContextReferences.insert(&(ir->incCtx), incCtxN); includeRules.append(ir); } else { // a local reference -> just initialize the include rule structure incCtx = buildPrefix + incCtx.simplified(); includeRules.append(new KateHlIncludeRule(i, m_contexts[i]->items.count(), incCtx, includeAttrib)); } } continue; } // TODO -- can we remove the block below?? #if 0 QString tag = KateHlManager::self()->syntax.groupKateExtendedAttribute(data, QString()); if (tag == "IncludeRules") { // attrib context: the index (jowenn, i think using names here // would be a cool feat, goes for mentioning the context in // any item. a map or dict?) int ctxId = getIdFromString(&ContextNameList, KateHlManager::self()->syntax.groupKateExtendedAttribute(data, QString("context")), dummy); // the index is *required* if (ctxId > -1) { // we can even reuse rules of 0 if we want to:) qCDebug(LOG_KTE) << "makeContextList[" << i << "]: including all items of context " << ctxId; if (ctxId < (int) i) { // must be defined for (c = m_contexts[ctxId]->items.first(); c; c = m_contexts[ctxId]->items.next()) { m_contexts[i]->items.append(c); } } else { qCDebug(LOG_KTE) << "Context " << ctxId << "not defined. You can not include the rules of an undefined context"; } } continue; // while nextItem } #endif KateHlItem *c = createKateHlItem(data, iDl, &RegionList, &ContextNameList); if (c) { m_contexts[i]->items.append(c); // Not supported completely atm and only one level. Subitems.(all have // to be matched to at once) KateSyntaxContextData *datasub = KateHlManager::self()->syntax.getSubItems(data); for (bool tmpbool = KateHlManager::self()->syntax.nextItem(datasub); tmpbool; tmpbool = KateHlManager::self()->syntax.nextItem(datasub)) { c->subItems.resize(c->subItems.size() + 1); c->subItems[c->subItems.size() - 1] = createKateHlItem(datasub, iDl, &RegionList, &ContextNameList); } KateHlManager::self()->syntax.freeGroupInfo(datasub); } } i++; } KateHlManager::self()->syntax.freeGroupInfo(data); } else { // error handling: no "context" element at all in the xml file noHl = true; qCWarning(LOG_KTE) << "There is no \"context\" in the highlighting file:" << buildIdentifier; } if (RegionList.count() != 1) { folding = true; } folding = folding || m_foldingIndentationSensitive; //BEGIN Resolve multiline region if possible if (!m_additionalData[ ident ]->multiLineRegion.isEmpty()) { long commentregionid = RegionList.indexOf(m_additionalData[ ident ]->multiLineRegion); if (-1 == commentregionid) { errorsAndWarnings += i18n( "%1: Specified multiline comment region (%2) could not be resolved
" , buildIdentifier, m_additionalData[ ident ]->multiLineRegion); m_additionalData[ ident ]->multiLineRegion.clear(); #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "ERROR comment region attribute could not be resolved"; #endif } else { m_additionalData[ ident ]->multiLineRegion = QString::number(commentregionid + 1); #ifdef HIGHLIGHTING_DEBUG qCDebug(LOG_KTE) << "comment region resolved to:" << m_additionalData[ ident ]->multiLineRegion; #endif } } //END Resolve multiline region if possible return i; } void KateHighlighting::clearAttributeArrays() { QMutableHashIterator< QString, QList > it = m_attributeArrays; while (it.hasNext()) { it.next(); // k, schema correct, let create the data KateAttributeList defaultStyleList; KateHlManager::self()->getDefaults(it.key(), defaultStyleList); QList itemDataList; getKateExtendedAttributeList(it.key(), itemDataList); uint nAttribs = itemDataList.count(); QList &array = it.value(); array.clear(); for (uint z = 0; z < nAttribs; z++) { KTextEditor::Attribute::Ptr itemData = itemDataList.at(z); KTextEditor::Attribute::Ptr newAttribute(new KTextEditor::Attribute(*defaultStyleList.at(itemData->defaultStyle()))); if (itemData && itemData->hasAnyProperty()) { *newAttribute += *itemData; } array.append(newAttribute); } } } QList KateHighlighting::attributes(const QString &schema) { // found it, already floating around if (m_attributeArrays.contains(schema)) { return m_attributeArrays[schema]; } // k, schema correct, let create the data QList array; KateAttributeList defaultStyleList; KateHlManager::self()->getDefaults(schema, defaultStyleList); QList itemDataList; getKateExtendedAttributeList(schema, itemDataList); uint nAttribs = itemDataList.count(); for (uint z = 0; z < nAttribs; z++) { KTextEditor::Attribute::Ptr itemData = itemDataList.at(z); KTextEditor::Attribute::Ptr newAttribute(new KTextEditor::Attribute(*defaultStyleList.at(itemData->defaultStyle()))); if (itemData && itemData->hasAnyProperty()) { *newAttribute += *itemData; } array.append(newAttribute); } m_attributeArrays.insert(schema, array); return array; } KateHlContext *KateHighlighting::contextNum(int n) const { if (n >= 0 && n < m_contexts.size()) { return m_contexts[n]; } Q_ASSERT(false); return 0; } QStringList KateHighlighting::getEmbeddedHighlightingModes() const { return embeddedHighlightingModes; } bool KateHighlighting::isEmptyLine(const Kate::TextLineData *textline) const { const QString &txt = textline->string(); if (txt.isEmpty()) { return true; } const QLinkedList l = emptyLines(textline->attribute(0)); if (l.isEmpty()) { return false; } foreach (const QRegularExpression &re, l) { const QRegularExpressionMatch match = re.match (txt, 0, QRegularExpression::NormalMatch, QRegularExpression::AnchoredMatchOption); if (match.hasMatch() && match.capturedLength() == txt.length()) { return true; } } return false; } +KateHlContext* KateHighlighting::contextForLocation(KTextEditor::DocumentPrivate* doc, const KTextEditor::Cursor& cursor) +{ + if ( noHighlighting() ) { + // no highlighting -- nothing to do + return nullptr; + } + + Kate::TextLine line = doc->kateTextLine(cursor.line()); + Kate::TextLine previousLine = doc->kateTextLine(cursor.line() - 1); + Kate::TextLine nextLine = doc->kateTextLine(cursor.line() + 1); + bool contextChanged; + QVector contextChanges; + // Ask the highlighting engine to re-calcualte the highlighting for the line + // where completion was invoked, and store all the context changes in the process. + doHighlight(previousLine.data(), line.data(), nextLine.data(), + contextChanged, 0, &contextChanges); + + // From the list of context changes, find the highlighting context which is + // active at the position where completion was invoked. + KateHlContext* context = nullptr; + foreach ( KateHighlighting::ContextChange change, contextChanges ) { + if ( change.pos == 0 || change.pos <= cursor.column() ) { + context = change.toContext; + } + if ( change.pos > cursor.column() ) { + break; + } + } + return context; +} + + //END diff --git a/src/syntax/katehighlight.h b/src/syntax/katehighlight.h index 547f76f2..e4014e87 100644 --- a/src/syntax/katehighlight.h +++ b/src/syntax/katehighlight.h @@ -1,463 +1,469 @@ /* This file is part of the KDE libraries Copyright (C) 2001,2002 Joseph Wenninger Copyright (C) 2001 Christoph Cullmann 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 as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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_HIGHLIGHT_H__ #define __KATE_HIGHLIGHT_H__ #include "katetextline.h" #include "kateextendedattribute.h" #include "katesyntaxmanager.h" #include "spellcheck/prefixstore.h" +#include "range.h" #include #include #include #include #include #include #include #include #include #include class KConfig; class KateHlContext; class KateHlItem; class KateHlIncludeRule; class KateSyntaxModeListItem; class KateSyntaxContextData; +namespace KTextEditor { + class DocumentPrivate; +} // same as in kmimemagic, no need to feed more data #define KATE_HL_HOWMANY 1024 // min. x seconds between two dynamic contexts reset #define KATE_DYNAMIC_CONTEXTS_RESET_DELAY (30 * 1000) /** * describe a modification of the context stack */ class KateHlContextModification { public: enum modType { doNothing = 0, doPush = 1, doPops = 2, doPopsAndPush = 3 }; /** * Constructor * @param _newContext new context to push on stack * @param _pops number of contexts to remove from stack in advance */ KateHlContextModification(int _newContext = -1, int _pops = 0) : type(doNothing), newContext(_newContext), pops(_pops) //krazy:exclude=explicit { if (newContext >= 0 && pops == 0) { type = doPush; } else if (newContext < 0 && pops > 0) { type = doPops; } else if (newContext >= 0 && pops > 0) { type = doPopsAndPush; } else { type = doNothing; } } public: /** * indicates what this modification does, for speed */ char type; /** * new context to push on the stack * if this is < 0, push nothing on the stack */ int newContext; /** * number of contexts to pop from the stack * before pushing a new context on it */ int pops; }; class KateEmbeddedHlInfo { public: KateEmbeddedHlInfo() { loaded = false; context0 = -1; } KateEmbeddedHlInfo(bool l, int ctx0) { loaded = l; context0 = ctx0; } public: bool loaded; int context0; }; // some typedefs typedef QList KateHlIncludeRules; typedef QMap KateEmbeddedHlInfos; typedef QMap KateHlUnresolvedCtxRefs; class KateHighlighting { public: KateHighlighting(const KateSyntaxModeListItem *def); ~KateHighlighting(); private: /** * this method frees mem ;) * used by the destructor and done(), therefor * not only delete elements but also reset the array * sizes, as we will reuse this object later and refill ;) */ void cleanup(); public: struct ContextChange { KateHlContext* toContext; int pos; }; /** * Parse the text and fill in the context array and folding list array * * @param prevLine The previous line, the context array is picked up from that if present. * @param textLine The text line to parse * @param nextLine The next line, to check if indentation changed for indentation based folding. * @param ctxChanged will be set to reflect if the context changed * @param tabWidth tab width for indentation based folding, if wanted, else 0 */ void doHighlight(const Kate::TextLineData *prevLine, Kate::TextLineData *textLine, const Kate::TextLineData *nextLine, bool &ctxChanged, int tabWidth = 0, QVector* contextChanges = 0); /** * Saves the attribute definitions to the config file. * * @param schema The id of the schema group to save * @param list QList containing the data to be used */ void setKateExtendedAttributeList(const QString &schema, QList &list, KConfig *cfg = 0 /*if 0 standard kate config*/, bool writeDefaultsToo = false); const QString &name() const { return iName; } const QString &nameTranslated() const { return iNameTranslated; } const QString §ion() const { return iSection; } bool hidden() const { return iHidden; } const QString &version() const { return iVersion; } const QString &style() const { return iStyle; } const QString &author() const { return iAuthor; } const QString &license() const { return iLicense; } const QString &getIdentifier() const { return identifier; } void use(); void reload(); /** * @return true if the character @p c is not a deliminator character * for the corresponding highlight. */ bool isInWord(QChar c, int attrib = 0) const; /** * @return true if the character @p c is a wordwrap deliminator as specified * in the general keyword section of the xml file. */ bool canBreakAt(QChar c, int attrib = 0) const; /** * */ QLinkedList emptyLines(int attribute = 0) const; bool isEmptyLine(const Kate::TextLineData *textline) const; /** * @return true if @p beginAttr and @p endAttr are members of the same * highlight, and there are comment markers of either type in that. */ bool canComment(int startAttr, int endAttr) const; /** * @return 0 if highlighting which attr is a member of does not * define a comment region, otherwise the region is returned */ signed char commentRegion(int attr) const; /** * @return the mulitiline comment start marker for the highlight * corresponding to @p attrib. */ QString getCommentStart(int attrib = 0) const; /** * @return the muiltiline comment end marker for the highlight corresponding * to @p attrib. */ QString getCommentEnd(int attrib = 0) const; /** * @return the single comment marker for the highlight corresponding * to @p attrib. */ QString getCommentSingleLineStart(int attrib = 0) const; const QHash &characterEncodings(int attrib = 0) const; /** * This enum is used for storing the information where a single line comment marker should be inserted */ enum CSLPos { CSLPosColumn0 = 0, CSLPosAfterWhitespace = 1}; /** * @return the single comment marker position for the highlight corresponding * to @p attrib. */ CSLPos getCommentSingleLinePosition(int attrib = 0) const; /** * @return the attribute for @p context. */ int attribute(int context) const; bool attributeRequiresSpellchecking(int attr); /** * map attribute to its highlighting file. * the returned string is used as key for m_additionalData. */ QString hlKeyForAttrib(int attrib) const; QString hlKeyForContext(int attrib) const; + KateHlContext* contextForLocation(KTextEditor::DocumentPrivate* doc, const KTextEditor::Cursor& cursor); + KTextEditor::DefaultStyle defaultStyleForAttribute(int attr) const; void clearAttributeArrays(); QList attributes(const QString &schema); inline bool noHighlighting() const { return noHl; } // be carefull: all documents hl should be invalidated after calling this method! void dropDynamicContexts(); QString indentation() { return m_indentation; } void getKateExtendedAttributeList(const QString &schema, QList &, KConfig *cfg = 0); void getKateExtendedAttributeListCopy(const QString &schema, QList &, KConfig *cfg = 0); const QHash &getCharacterEncodings(int attrib) const; const KatePrefixStore &getCharacterEncodingsPrefixStore(int attrib) const; const QHash &getReverseCharacterEncodings(int attrib) const; int getEncodedCharactersInsertionPolicy(int attrib) const; /** * Returns a list of names of embedded modes. */ QStringList getEmbeddedHighlightingModes() const; KateHlContext *contextNum(int n) const; private: /** * 'encoding' must not contain new line characters, i.e. '\n' or '\r'! **/ void addCharacterEncoding(const QString &key, const QString &encoding, const QChar &c); private: void init(); void makeContextList(); int makeDynamicContext(KateHlContext *model, const QStringList *args); void handleKateHlIncludeRules(); void handleKateHlIncludeRulesRecursive(int index, KateHlIncludeRules *list); int addToContextList(const QString &ident, int ctx0); void addToKateExtendedAttributeList(); void createKateExtendedAttribute(QList &list); void readGlobalKeywordConfig(); void readWordWrapConfig(); void readCommentConfig(); void readEmptyLineConfig(); void readIndentationConfig(); void readFoldingConfig(); void readSpellCheckingConfig(); /** * update given context stack * @param contextStack context stack to manipulate * @param modification description of the modification of the stack to execute * @param indexLastContextPreviousLine index of the last context from the previous line which still is in the stack * @return current active context, last one of the stack or default context 0 for empty stack */ KateHlContext *generateContextStack(Kate::TextLineData::ContextStack &contextStack, KateHlContextModification modification, int &indexLastContextPreviousLine); KateHlItem *createKateHlItem(KateSyntaxContextData *data, QList &iDl, QStringList *RegionList, QStringList *ContextList); int lookupAttrName(const QString &name, QList &iDl); void createContextNameList(QStringList *ContextNameList, int ctx0); KateHlContextModification getContextModificationFromString(QStringList *ContextNameList, QString tmpLineEndContext,/*NO CONST*/ QString &unres); QList internalIDList; QVector m_contexts; QMap< QPair, short> dynamicCtxs; // make them pointers perhaps // NOTE: gets cleaned once makeContextList() finishes KateEmbeddedHlInfos embeddedHls; // valid once makeContextList() finishes QStringList embeddedHighlightingModes; KateHlUnresolvedCtxRefs unresolvedContextReferences; QStringList RegionList; QStringList ContextNameList; bool noHl; bool folding; bool casesensitive; QString weakDeliminator; QString deliminator; QString iName; QString iNameTranslated; QString iSection; bool iHidden; QString identifier; QString iVersion; QString iStyle; QString iAuthor; QString iLicense; QString m_indentation; int refCount; int startctx, base_startctx; QString errorsAndWarnings; QString buildIdentifier; QString buildPrefix; bool building; uint buildContext0Offset; KateHlIncludeRules includeRules; bool m_foldingIndentationSensitive; // map schema name to attributes... QHash< QString, QList > m_attributeArrays; // list of all created items to delete them later QList m_hlItemCleanupList; /** * This class holds the additional properties for one highlight * definition, such as comment strings, deliminators etc. * * When a highlight is added, a instance of this class is appended to * m_additionalData, and the current position in the attrib and context * arrays are stored in the indexes for look up. You can then use * hlKeyForAttrib or hlKeyForContext to find the relevant instance of this * class from m_additionalData. * * If you need to add a property to a highlight, add it here. */ class HighlightPropertyBag { public: QString singleLineCommentMarker; QString multiLineCommentStart; QString multiLineCommentEnd; QString multiLineRegion; CSLPos singleLineCommentPosition; QString deliminator; QString wordWrapDeliminator; QLinkedList emptyLines; QHash characterEncodings; KatePrefixStore characterEncodingsPrefixStore; QHash reverseCharacterEncodings; int encodedCharactersInsertionPolicy; }; /** * Highlight properties for each included highlight definition. * The key is the identifier */ QHash m_additionalData; /** * Fast lookup of hl properties, based on attribute index * The key is the starting index in the attribute array for each file. * @see hlKeyForAttrib */ QMap m_hlIndex; QMap m_ctxIndex; public: inline bool foldingIndentationSensitive() { return m_foldingIndentationSensitive; } inline bool allowsFolding() { return folding; } }; #endif