diff --git a/src/buffer/katetextblock.cpp b/src/buffer/katetextblock.cpp index ea6e890e..adb82f88 100644 --- a/src/buffer/katetextblock.cpp +++ b/src/buffer/katetextblock.cpp @@ -1,758 +1,758 @@ /* SPDX-License-Identifier: LGPL-2.0-or-later Copyright (C) 2010 Christoph Cullmann 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. */ #include "katetextblock.h" #include "katetextbuffer.h" #include namespace Kate { TextBlock::TextBlock(TextBuffer *buffer, int startLine) : m_buffer(buffer) , m_startLine(startLine) { // reserve the block size m_lines.reserve(m_buffer->m_blockSize); } TextBlock::~TextBlock() { // blocks should be empty before they are deleted! Q_ASSERT(m_lines.empty()); Q_ASSERT(m_cursors.empty()); // it only is a hint for ranges for this block, not the storage of them } void TextBlock::setStartLine(int startLine) { // allow only valid lines Q_ASSERT(startLine >= 0); Q_ASSERT(startLine < m_buffer->lines()); m_startLine = startLine; } TextLine TextBlock::line(int line) const { // right input Q_ASSERT(line >= startLine()); // get text line, at will bail out on out-of-range return m_lines.at(line - startLine()); } void TextBlock::appendLine(const QString &textOfLine) { m_lines.push_back(TextLine::create(textOfLine)); } void TextBlock::clearLines() { m_lines.clear(); } void TextBlock::text(QString &text) const { // combine all lines for (size_t i = 0; i < m_lines.size(); ++i) { // not first line, insert \n if (i > 0 || startLine() > 0) { text.append(QLatin1Char('\n')); } text.append(m_lines.at(i)->text()); } } void TextBlock::wrapLine(const KTextEditor::Cursor &position, int fixStartLinesStartIndex) { // calc internal line int line = position.line() - startLine(); // get text QString &text = m_lines.at(line)->textReadWrite(); // check if valid column Q_ASSERT(position.column() >= 0); Q_ASSERT(position.column() <= text.size()); // create new line and insert it m_lines.insert(m_lines.begin() + line + 1, TextLine(new TextLineData())); // cases for modification: // 1. line is wrapped in the middle // 2. if empty line is wrapped, mark new line as modified // 3. line-to-be-wrapped is already modified if (position.column() > 0 || text.size() == 0 || m_lines.at(line)->markedAsModified()) { m_lines.at(line + 1)->markAsModified(true); } else if (m_lines.at(line)->markedAsSavedOnDisk()) { m_lines.at(line + 1)->markAsSavedOnDisk(true); } // perhaps remove some text from previous line and append it if (position.column() < text.size()) { // text from old line moved first to new one m_lines.at(line + 1)->textReadWrite() = text.right(text.size() - position.column()); // now remove wrapped text from old line text.chop(text.size() - position.column()); // mark line as modified m_lines.at(line)->markAsModified(true); } /** * fix all start lines * we need to do this NOW, else the range update will FAIL! * bug 313759 */ m_buffer->fixStartLines(fixStartLinesStartIndex); /** * notify the text history */ m_buffer->history().wrapLine(position); /** * cursor and range handling below */ // no cursors will leave or join this block // no cursors in this block, no work to do.. if (m_cursors.empty()) { return; } // move all cursors on the line which has the text inserted // remember all ranges modified, optimize for the standard case of a few ranges QVarLengthArray changedRanges; for (TextCursor *cursor : m_cursors) { // skip cursors on lines in front of the wrapped one! if (cursor->lineInBlock() < line) { continue; } // either this is simple, line behind the wrapped one if (cursor->lineInBlock() > line) { // patch line of cursor cursor->m_line++; } // this is the wrapped line else { // skip cursors with too small column if (cursor->column() <= position.column()) { if (cursor->column() < position.column() || !cursor->m_moveOnInsert) { continue; } } // move cursor // patch line of cursor cursor->m_line++; // patch column cursor->m_column -= position.column(); } // remember range, if any, avoid double insert auto range = cursor->kateRange(); if (range && !range->isValidityCheckRequired()) { range->setValidityCheckRequired(); changedRanges.push_back(range); } } // we might need to invalidate ranges or notify about their changes // checkValidity might trigger delete of the range! for (TextRange *range : qAsConst(changedRanges)) { range->checkValidity(); } } void TextBlock::unwrapLine(int line, TextBlock *previousBlock, int fixStartLinesStartIndex) { // calc internal line line = line - startLine(); // two possiblities: either first line of this block or later line if (line == 0) { // we need previous block with at least one line Q_ASSERT(previousBlock); Q_ASSERT(previousBlock->lines() > 0); // move last line of previous block to this one, might result in empty block TextLine oldFirst = m_lines.at(0); int lastLineOfPreviousBlock = previousBlock->lines() - 1; TextLine newFirst = previousBlock->m_lines.back(); m_lines[0] = newFirst; previousBlock->m_lines.erase(previousBlock->m_lines.begin() + (previousBlock->lines() - 1)); const int oldSizeOfPreviousLine = newFirst->text().size(); if (oldFirst->length() > 0) { // append text newFirst->textReadWrite().append(oldFirst->text()); // mark line as modified, since text was appended newFirst->markAsModified(true); } // patch startLine of this block --m_startLine; /** * fix all start lines * we need to do this NOW, else the range update will FAIL! * bug 313759 */ m_buffer->fixStartLines(fixStartLinesStartIndex); /** * notify the text history in advance */ m_buffer->history().unwrapLine(startLine() + line, oldSizeOfPreviousLine); /** * cursor and range handling below */ // no cursors in this block and the previous one, no work to do.. if (m_cursors.empty() && previousBlock->m_cursors.empty()) { return; } // move all cursors because of the unwrapped line // remember all ranges modified, optimize for the standard case of a few ranges QVarLengthArray changedRanges; for (TextCursor *cursor : m_cursors) { // this is the unwrapped line if (cursor->lineInBlock() == 0) { // patch column cursor->m_column += oldSizeOfPreviousLine; // remember range, if any, avoid double insert auto range = cursor->kateRange(); if (range && !range->isValidityCheckRequired()) { range->setValidityCheckRequired(); changedRanges.push_back(range); } } } // move cursors of the moved line from previous block to this block now for (auto it = previousBlock->m_cursors.begin(); it != previousBlock->m_cursors.end();) { auto cursor = *it; if (cursor->lineInBlock() == lastLineOfPreviousBlock) { cursor->m_line = 0; cursor->m_block = this; m_cursors.insert(cursor); // remember range, if any, avoid double insert auto range = cursor->kateRange(); if (range && !range->isValidityCheckRequired()) { range->setValidityCheckRequired(); changedRanges.push_back(range); } // remove from previous block it = previousBlock->m_cursors.erase(it); } else { // keep in previous block ++it; } } // fixup the ranges that might be effected, because they moved from last line to this block // we might need to invalidate ranges or notify about their changes // checkValidity might trigger delete of the range! for (TextRange *range : qAsConst(changedRanges)) { // update both blocks updateRange(range); previousBlock->updateRange(range); // afterwards check validity, might delete this range! range->checkValidity(); } // be done return; } // easy: just move text to previous line and remove current one const int oldSizeOfPreviousLine = m_lines.at(line - 1)->length(); const int sizeOfCurrentLine = m_lines.at(line)->length(); if (sizeOfCurrentLine > 0) { m_lines.at(line - 1)->textReadWrite().append(m_lines.at(line)->text()); } const bool lineChanged = (oldSizeOfPreviousLine > 0 && m_lines.at(line - 1)->markedAsModified()) || (sizeOfCurrentLine > 0 && (oldSizeOfPreviousLine > 0 || m_lines.at(line)->markedAsModified())); m_lines.at(line - 1)->markAsModified(lineChanged); if (oldSizeOfPreviousLine == 0 && m_lines.at(line)->markedAsSavedOnDisk()) { m_lines.at(line - 1)->markAsSavedOnDisk(true); } m_lines.erase(m_lines.begin() + line); /** * fix all start lines * we need to do this NOW, else the range update will FAIL! * bug 313759 */ m_buffer->fixStartLines(fixStartLinesStartIndex); /** * notify the text history in advance */ m_buffer->history().unwrapLine(startLine() + line, oldSizeOfPreviousLine); /** * cursor and range handling below */ // no cursors in this block, no work to do.. if (m_cursors.empty()) { return; } // move all cursors because of the unwrapped line // remember all ranges modified, optimize for the standard case of a few ranges QVarLengthArray changedRanges; for (TextCursor *cursor : m_cursors) { // skip cursors in lines in front of removed one if (cursor->lineInBlock() < line) { continue; } // this is the unwrapped line if (cursor->lineInBlock() == line) { // patch column cursor->m_column += oldSizeOfPreviousLine; } // patch line of cursor cursor->m_line--; // remember range, if any, avoid double insert auto range = cursor->kateRange(); if (range && !range->isValidityCheckRequired()) { range->setValidityCheckRequired(); changedRanges.push_back(range); } } // we might need to invalidate ranges or notify about their changes // checkValidity might trigger delete of the range! for (TextRange *range : qAsConst(changedRanges)) { range->checkValidity(); } } void TextBlock::insertText(const KTextEditor::Cursor &position, const QString &text) { // calc internal line int line = position.line() - startLine(); // get text QString &textOfLine = m_lines.at(line)->textReadWrite(); int oldLength = textOfLine.size(); m_lines.at(line)->markAsModified(true); // check if valid column Q_ASSERT(position.column() >= 0); Q_ASSERT(position.column() <= textOfLine.size()); // insert text textOfLine.insert(position.column(), text); /** * notify the text history */ m_buffer->history().insertText(position, text.size(), oldLength); /** * cursor and range handling below */ // no cursors in this block, no work to do.. if (m_cursors.empty()) { return; } // move all cursors on the line which has the text inserted // remember all ranges modified, optimize for the standard case of a few ranges QVarLengthArray changedRanges; for (TextCursor *cursor : m_cursors) { // skip cursors not on this line! if (cursor->lineInBlock() != line) { continue; } // skip cursors with too small column if (cursor->column() <= position.column()) { if (cursor->column() < position.column() || !cursor->m_moveOnInsert) { continue; } } // patch column of cursor if (cursor->m_column <= oldLength) { cursor->m_column += text.size(); } // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode else if (cursor->m_column < textOfLine.size()) { cursor->m_column = textOfLine.size(); } // remember range, if any, avoid double insert // we only need to trigger checkValidity later if the range has feedback or might be invalidated auto range = cursor->kateRange(); if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) { range->setValidityCheckRequired(); changedRanges.push_back(range); } } // we might need to invalidate ranges or notify about their changes // checkValidity might trigger delete of the range! for (TextRange *range : qAsConst(changedRanges)) { range->checkValidity(); } } void TextBlock::removeText(const KTextEditor::Range &range, QString &removedText) { // calc internal line int line = range.start().line() - startLine(); // get text QString &textOfLine = m_lines.at(line)->textReadWrite(); int oldLength = textOfLine.size(); // check if valid column Q_ASSERT(range.start().column() >= 0); Q_ASSERT(range.start().column() <= textOfLine.size()); Q_ASSERT(range.end().column() >= 0); Q_ASSERT(range.end().column() <= textOfLine.size()); // get text which will be removed removedText = textOfLine.mid(range.start().column(), range.end().column() - range.start().column()); // remove text textOfLine.remove(range.start().column(), range.end().column() - range.start().column()); m_lines.at(line)->markAsModified(true); /** * notify the text history */ m_buffer->history().removeText(range, oldLength); /** * cursor and range handling below */ // no cursors in this block, no work to do.. if (m_cursors.empty()) { return; } // move all cursors on the line which has the text removed // remember all ranges modified, optimize for the standard case of a few ranges QVarLengthArray changedRanges; for (TextCursor *cursor : m_cursors) { // skip cursors not on this line! if (cursor->lineInBlock() != line) { continue; } // skip cursors with too small column if (cursor->column() <= range.start().column()) { continue; } // patch column of cursor if (cursor->column() <= range.end().column()) { cursor->m_column = range.start().column(); } else { cursor->m_column -= (range.end().column() - range.start().column()); } // remember range, if any, avoid double insert // we only need to trigger checkValidity later if the range has feedback or might be invalidated auto range = cursor->kateRange(); if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) { range->setValidityCheckRequired(); changedRanges.push_back(range); } } // we might need to invalidate ranges or notify about their changes // checkValidity might trigger delete of the range! for (TextRange *range : qAsConst(changedRanges)) { range->checkValidity(); } } void TextBlock::debugPrint(int blockIndex) const { // print all blocks for (size_t i = 0; i < m_lines.size(); ++i) printf("%4d - %4lld : %4d : '%s'\n", blockIndex, (unsigned long long)startLine() + i, m_lines.at(i)->text().size(), qPrintable(m_lines.at(i)->text())); } TextBlock *TextBlock::splitBlock(int fromLine) { // half the block int linesOfNewBlock = lines() - fromLine; // create and insert new block TextBlock *newBlock = new TextBlock(m_buffer, startLine() + fromLine); // move lines newBlock->m_lines.reserve(linesOfNewBlock); for (size_t i = fromLine; i < m_lines.size(); ++i) { newBlock->m_lines.push_back(m_lines.at(i)); } m_lines.resize(fromLine); // move cursors for (auto it = m_cursors.begin(); it != m_cursors.end();) { auto cursor = *it; if (cursor->lineInBlock() >= fromLine) { cursor->m_line = cursor->lineInBlock() - fromLine; cursor->m_block = newBlock; // add to new, remove from current newBlock->m_cursors.insert(cursor); it = m_cursors.erase(it); } else { // keep in current ++it; } } // fix ALL ranges! - const QList allRanges = m_uncachedRanges.toList() + m_cachedLineForRanges.keys(); + const QList allRanges = m_uncachedRanges.values() + m_cachedLineForRanges.keys(); for (TextRange *range : qAsConst(allRanges)) { // update both blocks updateRange(range); newBlock->updateRange(range); } // return the new generated block return newBlock; } void TextBlock::mergeBlock(TextBlock *targetBlock) { // move cursors, do this first, now still lines() count is correct for target for (TextCursor *cursor : m_cursors) { cursor->m_line = cursor->lineInBlock() + targetBlock->lines(); cursor->m_block = targetBlock; targetBlock->m_cursors.insert(cursor); } m_cursors.clear(); // move lines targetBlock->m_lines.reserve(targetBlock->lines() + lines()); for (size_t i = 0; i < m_lines.size(); ++i) { targetBlock->m_lines.push_back(m_lines.at(i)); } m_lines.clear(); // fix ALL ranges! - const QList allRanges = m_uncachedRanges.toList() + m_cachedLineForRanges.keys(); + const QList allRanges = m_uncachedRanges.values() + m_cachedLineForRanges.keys(); for (TextRange *range : qAsConst(allRanges)) { // update both blocks updateRange(range); targetBlock->updateRange(range); } } void TextBlock::deleteBlockContent() { // kill cursors, if not belonging to a range // we can do in-place editing of the current set of cursors as // we remove them before deleting for (auto it = m_cursors.begin(); it != m_cursors.end();) { auto cursor = *it; if (!cursor->kateRange()) { // remove it and advance to next element it = m_cursors.erase(it); // delete after cursor is gone from the set // else the destructor will modify it! delete cursor; } else { // keep this cursor ++it; } } // kill lines m_lines.clear(); } void TextBlock::clearBlockContent(TextBlock *targetBlock) { // move cursors, if not belonging to a range // we can do in-place editing of the current set of cursors for (auto it = m_cursors.begin(); it != m_cursors.end();) { auto cursor = *it; if (!cursor->kateRange()) { cursor->m_column = 0; cursor->m_line = 0; cursor->m_block = targetBlock; targetBlock->m_cursors.insert(cursor); // remove it and advance to next element it = m_cursors.erase(it); } else { // keep this cursor ++it; } } // kill lines m_lines.clear(); } void TextBlock::markModifiedLinesAsSaved() { // mark all modified lines as saved for (auto &textLine : m_lines) { if (textLine->markedAsModified()) { textLine->markAsSavedOnDisk(true); } } } void TextBlock::updateRange(TextRange *range) { /** * get some simple facts about our nice range */ const int startLine = range->startInternal().lineInternal(); const int endLine = range->endInternal().lineInternal(); const bool isSingleLine = startLine == endLine; /** * perhaps remove range and be done */ if ((endLine < m_startLine) || (startLine >= (m_startLine + lines()))) { removeRange(range); return; } /** * The range is still a single-line range, and is still cached to the correct line. */ if (isSingleLine && m_cachedLineForRanges.contains(range) && (m_cachedLineForRanges.value(range) == startLine - m_startLine)) { return; } /** * The range is still a multi-line range, and is already in the correct set. */ if (!isSingleLine && m_uncachedRanges.contains(range)) { return; } /** * remove, if already there! */ removeRange(range); /** * simple case: multi-line range */ if (!isSingleLine) { /** * The range cannot be cached per line, as it spans multiple lines */ m_uncachedRanges.insert(range); return; } /** * The range is contained by a single line, put it into the line-cache */ const int lineOffset = startLine - m_startLine; /** * enlarge cache if needed */ if (m_cachedRangesForLine.size() <= lineOffset) { m_cachedRangesForLine.resize(lineOffset + 1); } /** * insert into mapping */ m_cachedRangesForLine[lineOffset].insert(range); m_cachedLineForRanges[range] = lineOffset; } void TextBlock::removeRange(TextRange *range) { /** * uncached range? remove it and be done */ if (m_uncachedRanges.remove(range)) { /** * must be only uncached! */ Q_ASSERT(!m_cachedLineForRanges.contains(range)); return; } /** * cached range? */ QHash::iterator it = m_cachedLineForRanges.find(range); if (it != m_cachedLineForRanges.end()) { /** * must be only cached! */ Q_ASSERT(!m_uncachedRanges.contains(range)); /** * query the range from cache, must be there */ Q_ASSERT(m_cachedRangesForLine.at(*it).contains(range)); /** * remove it and be done */ m_cachedRangesForLine[*it].remove(range); m_cachedLineForRanges.erase(it); return; } /** * else: range was not for this block, just do nothing, removeRange should be "safe" to use */ } } diff --git a/src/utils/kateglobal.h b/src/utils/kateglobal.h index 0006b950..8b8eff6a 100644 --- a/src/utils/kateglobal.h +++ b/src/utils/kateglobal.h @@ -1,579 +1,579 @@ /* SPDX-License-Identifier: LGPL-2.0-or-later Copyright (C) 2001-2010 Christoph Cullmann Copyright (C) 2009 Erlend Hamberg 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_GLOBAL_H #define KATE_GLOBAL_H #include "katescript.h" #include "variable.h" #include #include "ktexteditor/view.h" #include #include #include #include #include #include #include #include #include #include class QStringListModel; class KateCmd; class KateModeManager; class KateSchemaManager; class KateGlobalConfig; class KateDocumentConfig; class KateViewConfig; class KateRendererConfig; namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } class KateScriptManager; class KDirWatch; class KateHlManager; class KateSpellCheckManager; class KateWordCompletionModel; class KateAbstractInputModeFactory; class KateKeywordCompletionModel; class KateDefaultColors; class KateVariableExpansionManager; namespace KTextEditor { /** * KTextEditor::EditorPrivate * One instance of this class is hold alive during * a kate part session, as long as any factory, document * or view stay around, here is the place to put things * which are needed and shared by all this objects ;) */ class KTEXTEDITOR_EXPORT EditorPrivate : public KTextEditor::Editor { Q_OBJECT friend class KTextEditor::Editor; // unit testing support public: /** * Calling this function internally sets a flag such that unitTestMode() * returns \p true. */ static void enableUnitTestMode(); /** * Returns \p true, if the unit test mode was enabled through a call of * enableUnitTestMode(), otherwise \p false. */ static bool unitTestMode(); // for setDefaultEncoding friend class KateDocumentConfig; private: /** * Default constructor, private, as singleton * @param staticInstance pointer to fill with content of this */ explicit EditorPrivate(QPointer &staticInstance); public: /** * Destructor */ ~EditorPrivate(); /** * Create a new document object * @param parent parent object * @return created KTextEditor::Document */ KTextEditor::Document *createDocument(QObject *parent) override; /** * Returns a list of all documents of this editor. * @return list of all existing documents */ QList documents() override { return m_documents.keys(); } /** * Set the global application object. * This will allow the editor component to access * the hosting application. * @param application application object */ void setApplication(KTextEditor::Application *application) override { // switch back to dummy application? m_application = application ? application : &m_dummyApplication; } /** * Current hosting application, if any set. * @return current application object or nullptr */ KTextEditor::Application *application() const override { return m_application; } /** * General Information about this editor */ public: /** * return the about data * @return about data of this editor part */ const KAboutData &aboutData() const override { return m_aboutData; } /** * Configuration management */ public: /** * Shows a config dialog for the part, changes will be applied * to the editor, but not saved anywhere automagically, call * writeConfig to save them */ void configDialog(QWidget *parent) override; /** * Number of available config pages * If the editor returns a number < 1, it doesn't support this * and the embedding app should use the configDialog () instead * @return number of config pages */ int configPages() const override; /** * returns config page with the given number, * config pages from 0 to configPages()-1 are available * if configPages() > 0 */ KTextEditor::ConfigPage *configPage(int number, QWidget *parent) override; /** * Kate Part Internal stuff ;) */ public: /** * singleton accessor * @return instance of the factory */ static KTextEditor::EditorPrivate *self(); /** * register document at the factory * this allows us to loop over all docs for example on config changes * @param doc document to register */ void registerDocument(KTextEditor::DocumentPrivate *doc); /** * unregister document at the factory * @param doc document to register */ void deregisterDocument(KTextEditor::DocumentPrivate *doc); /** * register view at the factory * this allows us to loop over all views for example on config changes * @param view view to register */ void registerView(KTextEditor::ViewPrivate *view); /** * unregister view at the factory * @param view view to unregister */ void deregisterView(KTextEditor::ViewPrivate *view); /** * return a list of all registered views * @return all known views */ QList views() { - return m_views.toList(); + return m_views.values(); } /** * global dirwatch * @return dirwatch instance */ KDirWatch *dirWatch() { return m_dirWatch; } /** * The global configuration of katepart, e.g. katepartrc * @return global shared access to katepartrc config */ static KSharedConfigPtr config(); /** * global mode manager * used to manage the modes centrally * @return mode manager */ KateModeManager *modeManager() { return m_modeManager; } /** * manager for the katepart schemas * @return schema manager */ KateSchemaManager *schemaManager() { return m_schemaManager; } /** * fallback document config * @return default config for all documents */ KateDocumentConfig *documentConfig() { return m_documentConfig; } /** * fallback view config * @return default config for all views */ KateViewConfig *viewConfig() { return m_viewConfig; } /** * fallback renderer config * @return default config for all renderers */ KateRendererConfig *rendererConfig() { return m_rendererConfig; } /** * Global script collection */ KateScriptManager *scriptManager() { return m_scriptManager; } /** * hl manager * @return hl manager */ KateHlManager *hlManager() { return m_hlManager; } /** * command manager * @return command manager */ KateCmd *cmdManager() { return m_cmdManager; } /** * spell check manager * @return spell check manager */ KateSpellCheckManager *spellCheckManager() { return m_spellCheckManager; } /** * global instance of the simple word completion mode * @return global instance of the simple word completion mode */ KateWordCompletionModel *wordCompletionModel() { return m_wordCompletionModel; } /** * Global instance of the language-aware keyword completion model * @return global instance of the keyword completion model */ KateKeywordCompletionModel *keywordCompletionModel() { return m_keywordCompletionModel; } /** * query for command * @param cmd name of command to query for * @return found command or 0 */ KTextEditor::Command *queryCommand(const QString &cmd) const override; /** * Get a list of all registered commands. * \return list of all commands */ QList commands() const override; /** * Get a list of available commandline strings. * \return commandline strings */ QStringList commandList() const override; /** * Copy text to clipboard an remember it in the history * @param text text to copy to clipboard, does nothing if empty! */ void copyToClipboard(const QString &text); /** * Clipboard history, filled with text we ever copied * to clipboard via copyToClipboard. */ const QStringList &clipboardHistory() const { return m_clipboardHistory; } /** * return a list of all registered docs * @return all known documents */ QList kateDocuments() { return m_documents.values(); } /** * Dummy main window to be null safe. * @return dummy main window */ KTextEditor::MainWindow *dummyMainWindow() { return &m_dummyMainWindow; } /** * @return list of available input mode factories */ QList inputModeFactories(); /** * Default colors, once constructed, as expensive * @return default colors */ const KateDefaultColors &defaultColors() const { return *m_defaultColors; } /** * Search pattern history shared among simple/power search instances. */ QStringListModel *searchHistoryModel(); /** * Replace pattern history shared among simple/power search instances. */ QStringListModel *replaceHistoryModel(); /** * Call this function to store the history models to the application config. */ void saveSearchReplaceHistoryModels(); /** * Returns the variable expansion manager. */ KateVariableExpansionManager *variableExpansionManager(); Q_SIGNALS: /** * Emitted if the history of clipboard changes via copyToClipboard */ void clipboardHistoryChanged(); protected: bool eventFilter(QObject *, QEvent *) override; private Q_SLOTS: void updateColorPalette(); private: /** * about data (authors and more) */ KAboutData m_aboutData; /** * registered docs, map from general to specialized pointer */ QHash m_documents; /** * registered views */ QSet m_views; /** * global dirwatch object */ KDirWatch *m_dirWatch; /** * mode manager */ KateModeManager *m_modeManager; /** * schema manager */ KateSchemaManager *m_schemaManager; /** * global config */ KateGlobalConfig *m_globalConfig; /** * fallback document config */ KateDocumentConfig *m_documentConfig; /** * fallback view config */ KateViewConfig *m_viewConfig; /** * fallback renderer config */ KateRendererConfig *m_rendererConfig; /** * internal commands */ QList m_cmds; /** * script manager */ KateScriptManager *m_scriptManager; /** * hl manager */ KateHlManager *m_hlManager; /** * command manager */ KateCmd *m_cmdManager; /** * variable expansion manager */ KateVariableExpansionManager *m_variableExpansionManager; /** * spell check manager */ KateSpellCheckManager *m_spellCheckManager; /** * global instance of the simple word completion mode */ KateWordCompletionModel *m_wordCompletionModel; /** * global instance of the language-specific keyword completion model */ KateKeywordCompletionModel *m_keywordCompletionModel; /** * clipboard history */ QStringList m_clipboardHistory; /** * Dummy application object to be null safe */ KTextEditor::Application m_dummyApplication; /** * access to application */ QPointer m_application; /** * Dummy main window to be null safe */ KTextEditor::MainWindow m_dummyMainWindow; /** * input modes map */ QMap m_inputModeFactories; /** * default colors */ QScopedPointer m_defaultColors; /** * Shared history models for search & replace. */ QStringListModel *m_searchHistoryModel; QStringListModel *m_replaceHistoryModel; }; } #endif diff --git a/src/vimode/emulatedcommandbar/completer.cpp b/src/vimode/emulatedcommandbar/completer.cpp index 3ff55fdf..3fe72281 100644 --- a/src/vimode/emulatedcommandbar/completer.cpp +++ b/src/vimode/emulatedcommandbar/completer.cpp @@ -1,252 +1,252 @@ /* SPDX-License-Identifier: LGPL-2.0-or-later Copyright (C) 2013-2016 Simon St James 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. */ #include "completer.h" #include "emulatedcommandbar.h" using namespace KateVi; #include "kateview.h" #include #include #include #include #include namespace { bool caseInsensitiveLessThan(const QString &s1, const QString &s2) { return s1.toLower() < s2.toLower(); } } Completer::Completer(EmulatedCommandBar *emulatedCommandBar, KTextEditor::ViewPrivate *view, QLineEdit *edit) : m_edit(edit) , m_view(view) { m_completer = new QCompleter(QStringList(), edit); // Can't find a way to stop the QCompleter from auto-completing when attached to a QLineEdit, // so don't actually set it as the QLineEdit's completer. m_completer->setWidget(edit); m_completer->setObjectName(QStringLiteral("completer")); m_completionModel = new QStringListModel(emulatedCommandBar); m_completer->setModel(m_completionModel); m_completer->setCaseSensitivity(Qt::CaseInsensitive); m_completer->popup()->installEventFilter(emulatedCommandBar); } void Completer::startCompletion(const CompletionStartParams &completionStartParams) { if (completionStartParams.completionType != CompletionStartParams::None) { m_completionModel->setStringList(completionStartParams.completions); const QString completionPrefix = m_edit->text().mid(completionStartParams.wordStartPos, m_edit->cursorPosition() - completionStartParams.wordStartPos); m_completer->setCompletionPrefix(completionPrefix); m_completer->complete(); m_currentCompletionStartParams = completionStartParams; m_currentCompletionType = completionStartParams.completionType; } } void Completer::deactivateCompletion() { m_completer->popup()->hide(); m_currentCompletionType = CompletionStartParams::None; } bool Completer::isCompletionActive() const { return m_currentCompletionType != CompletionStartParams::None; } bool Completer::isNextTextChangeDueToCompletionChange() const { return m_isNextTextChangeDueToCompletionChange; } bool Completer::completerHandledKeypress(const QKeyEvent *keyEvent) { if (!m_edit->isVisible()) return false; if (keyEvent->modifiers() == Qt::ControlModifier && (keyEvent->key() == Qt::Key_C || keyEvent->key() == Qt::Key_BracketLeft)) { if (m_currentCompletionType != CompletionStartParams::None && m_completer->popup()->isVisible()) { abortCompletionAndResetToPreCompletion(); return true; } } if (keyEvent->modifiers() == Qt::ControlModifier && keyEvent->key() == Qt::Key_Space) { CompletionStartParams completionStartParams = activateWordFromDocumentCompletion(); startCompletion(completionStartParams); return true; } if ((keyEvent->modifiers() == Qt::ControlModifier && keyEvent->key() == Qt::Key_P) || keyEvent->key() == Qt::Key_Down) { if (!m_completer->popup()->isVisible()) { const CompletionStartParams completionStartParams = m_currentMode->completionInvoked(CompletionInvocation::ExtraContext); startCompletion(completionStartParams); if (m_currentCompletionType != CompletionStartParams::None) { setCompletionIndex(0); } } else { // Descend to next row, wrapping around if necessary. if (m_completer->currentRow() + 1 == m_completer->completionCount()) { setCompletionIndex(0); } else { setCompletionIndex(m_completer->currentRow() + 1); } } return true; } if ((keyEvent->modifiers() == Qt::ControlModifier && keyEvent->key() == Qt::Key_N) || keyEvent->key() == Qt::Key_Up) { if (!m_completer->popup()->isVisible()) { const CompletionStartParams completionStartParams = m_currentMode->completionInvoked(CompletionInvocation::NormalContext); startCompletion(completionStartParams); setCompletionIndex(m_completer->completionCount() - 1); } else { // Ascend to previous row, wrapping around if necessary. if (m_completer->currentRow() == 0) { setCompletionIndex(m_completer->completionCount() - 1); } else { setCompletionIndex(m_completer->currentRow() - 1); } } return true; } if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) { if (!m_completer->popup()->isVisible() || m_currentCompletionType != CompletionStartParams::WordFromDocument) { m_currentMode->completionChosen(); } deactivateCompletion(); return true; } return false; } void Completer::editTextChanged(const QString &newText) { if (!m_isNextTextChangeDueToCompletionChange) { m_textToRevertToIfCompletionAborted = newText; m_cursorPosToRevertToIfCompletionAborted = m_edit->cursorPosition(); } // If we edit the text after having selected a completion, this means we implicitly accept it, // and so we should dismiss it. if (!m_isNextTextChangeDueToCompletionChange && m_completer->popup()->currentIndex().row() != -1) { deactivateCompletion(); } if (m_currentCompletionType != CompletionStartParams::None && !m_isNextTextChangeDueToCompletionChange) { updateCompletionPrefix(); } } void Completer::setCurrentMode(ActiveMode *currentMode) { m_currentMode = currentMode; } void Completer::setCompletionIndex(int index) { const QModelIndex modelIndex = m_completer->popup()->model()->index(index, 0); // Need to set both of these, for some reason. m_completer->popup()->setCurrentIndex(modelIndex); m_completer->setCurrentRow(index); m_completer->popup()->scrollTo(modelIndex); currentCompletionChanged(); } void Completer::currentCompletionChanged() { const QString newCompletion = m_completer->currentCompletion(); if (newCompletion.isEmpty()) { return; } QString transformedCompletion = newCompletion; if (m_currentCompletionStartParams.completionTransform) { transformedCompletion = m_currentCompletionStartParams.completionTransform(newCompletion); } m_isNextTextChangeDueToCompletionChange = true; m_edit->setSelection(m_currentCompletionStartParams.wordStartPos, m_edit->cursorPosition() - m_currentCompletionStartParams.wordStartPos); m_edit->insert(transformedCompletion); m_isNextTextChangeDueToCompletionChange = false; } void Completer::updateCompletionPrefix() { const QString completionPrefix = m_edit->text().mid(m_currentCompletionStartParams.wordStartPos, m_edit->cursorPosition() - m_currentCompletionStartParams.wordStartPos); m_completer->setCompletionPrefix(completionPrefix); // Seem to need a call to complete() else the size of the popup box is not altered appropriately. m_completer->complete(); } CompletionStartParams Completer::activateWordFromDocumentCompletion() { static const QRegularExpression wordRegEx(QStringLiteral("\\w{1,}")); QRegularExpressionMatch match; QStringList foundWords; // Narrow the range of lines we search around the cursor so that we don't die on huge files. const int startLine = qMax(0, m_view->cursorPosition().line() - 4096); const int endLine = qMin(m_view->document()->lines(), m_view->cursorPosition().line() + 4096); for (int lineNum = startLine; lineNum < endLine; lineNum++) { const QString line = m_view->document()->line(lineNum); int wordSearchBeginPos = 0; while ((match = wordRegEx.match(line, wordSearchBeginPos)).hasMatch()) { const QString foundWord = match.captured(); foundWords << foundWord; wordSearchBeginPos = match.capturedEnd(); } } - foundWords = QSet::fromList(foundWords).toList(); + foundWords = QSet::fromList(foundWords).values(); std::sort(foundWords.begin(), foundWords.end(), caseInsensitiveLessThan); CompletionStartParams completionStartParams; completionStartParams.completionType = CompletionStartParams::WordFromDocument; completionStartParams.completions = foundWords; completionStartParams.wordStartPos = wordBeforeCursorBegin(); return completionStartParams; } QString Completer::wordBeforeCursor() { const int wordBeforeCursorBegin = this->wordBeforeCursorBegin(); return m_edit->text().mid(wordBeforeCursorBegin, m_edit->cursorPosition() - wordBeforeCursorBegin); } int Completer::wordBeforeCursorBegin() { int wordBeforeCursorBegin = m_edit->cursorPosition() - 1; while (wordBeforeCursorBegin >= 0 && (m_edit->text()[wordBeforeCursorBegin].isLetterOrNumber() || m_edit->text()[wordBeforeCursorBegin] == QLatin1Char('_'))) { wordBeforeCursorBegin--; } wordBeforeCursorBegin++; return wordBeforeCursorBegin; } void Completer::abortCompletionAndResetToPreCompletion() { deactivateCompletion(); m_isNextTextChangeDueToCompletionChange = true; m_edit->setText(m_textToRevertToIfCompletionAborted); m_edit->setCursorPosition(m_cursorPosToRevertToIfCompletionAborted); m_isNextTextChangeDueToCompletionChange = false; }