diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b0a3283..5b506b77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,102 +1,119 @@ cmake_minimum_required(VERSION 3.0) -set(KF5_VERSION "5.30.0") # handled by release scripts -set(KF5_DEP_VERSION "5.29.0") # handled by release scripts +set(KF5_VERSION "5.37.0") # handled by release scripts +set(KF5_DEP_VERSION "5.36.0") # handled by release scripts project(KTextEditor VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) -find_package(ECM 5.29.0 NO_MODULE) +find_package(ECM 5.36.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) -set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) - +# add own modules to search path, too +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) include(CheckFunctionExists) include(CheckSymbolExists) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(GenerateExportHeader) +include(ECMAddQch) + +option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) +add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version( PROJECT VARIABLE_PREFIX KTEXTEDITOR VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/ktexteditor_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfigVersion.cmake" SOVERSION 5 ) # Dependencies -set(REQUIRED_QT_VERSION 5.5.0) +set(REQUIRED_QT_VERSION 5.6.0) # Required Qt5 components to build this framework find_package(Qt5 ${REQUIRED_QT_VERSION} NO_MODULE REQUIRED Core Widgets Script PrintSupport Xml XmlPatterns) find_package(KF5Archive ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5GuiAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5I18n ${KF5_DEP_VERSION} REQUIRED) find_package(KF5KIO ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Parts ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Sonnet ${KF5_DEP_VERSION} REQUIRED) find_package(KF5IconThemes ${KF5_DEP_VERSION} REQUIRED) find_package(KF5SyntaxHighlighting ${KF5_DEP_VERSION} REQUIRED) # libgit2 integration, at least 0.22 with proper git_libgit2_init() find_package(LibGit2 "0.22.0") +# EditorConfig integration +find_package(EditorConfig) + # vi mode on per default option (BUILD_VIMODE "Build vimode in" ON) # Subdirectories add_definitions(-DTRANSLATION_DOMAIN=\"ktexteditor5\") if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) endif() # Create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5TextEditor") +if (BUILD_QCH) + ecm_install_qch_export( + TARGETS KF5TextEditor_QCH + FILE KF5TextEditorQchTargets.cmake + DESTINATION "${CMAKECONFIG_INSTALL_DIR}" + COMPONENT Devel + ) + set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5TextEditorQchTargets.cmake\")") +endif() + configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5TextEditorConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfig.cmake" INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5TextEditorTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5TextEditorTargets.cmake NAMESPACE KF5:: ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ktexteditor_version.h" DESTINATION "${KDE_INSTALL_INCLUDEDIR_KF5}" COMPONENT Devel ) # config.h check_symbol_exists (fdatasync unistd.h HAVE_FDATASYNC) configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) # let our config.h be found first in any case include_directories (BEFORE ${CMAKE_CURRENT_BINARY_DIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/KF5TextEditorConfig.cmake.in b/KF5TextEditorConfig.cmake.in index 4543400a..28d2c0fb 100644 --- a/KF5TextEditorConfig.cmake.in +++ b/KF5TextEditorConfig.cmake.in @@ -1,7 +1,7 @@ @PACKAGE_INIT@ # KF5 Deps find_package(KF5Parts "@KF5_DEP_VERSION@") include("${CMAKE_CURRENT_LIST_DIR}/KF5TextEditorTargets.cmake") - +@PACKAGE_INCLUDE_QCHTARGETS@ diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 7a6e3520..ce5b7874 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,156 +1,157 @@ find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Test) include(ECMMarkAsTest) include(ECMAddTests) remove_definitions(-DQT_NO_CAST_FROM_ASCII) set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( # for config.h ${CMAKE_BINARY_DIR} # for generated ktexteditor headers ${CMAKE_BINARY_DIR}/src/include # for normal sources ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/include ${CMAKE_SOURCE_DIR}/src/buffer ${CMAKE_SOURCE_DIR}/src/completion ${CMAKE_SOURCE_DIR}/src/dialogs ${CMAKE_SOURCE_DIR}/src/document ${CMAKE_SOURCE_DIR}/src/script ${CMAKE_SOURCE_DIR}/src/mode ${CMAKE_SOURCE_DIR}/src/render ${CMAKE_SOURCE_DIR}/src/search ${CMAKE_SOURCE_DIR}/src/syntax ${CMAKE_SOURCE_DIR}/src/undo ${CMAKE_SOURCE_DIR}/src/utils ${CMAKE_SOURCE_DIR}/src/view ) -add_definitions(-DTEST_DATA_DIR="${CMAKE_SOURCE_DIR}/autotests/input/") -add_definitions(-DJS_DATA_DIR="${CMAKE_SOURCE_DIR}/src/script/data/") +add_definitions(-DTEST_DATA_DIR=\"${CMAKE_SOURCE_DIR}/autotests/input/\") +add_definitions(-DJS_DATA_DIR=\"${CMAKE_SOURCE_DIR}/src/script/data/\") set (KTEXTEDITOR_TEST_LINK_LIBS KF5TextEditor KF5::I18n KF5::IconThemes KF5::GuiAddons Qt5::Script ) include(ECMMarkAsTest) # test executable for encoding add_executable(kateencodingtest src/kateencodingtest.cpp) target_link_libraries(kateencodingtest ${KTEXTEDITOR_TEST_LINK_LIBS}) ecm_mark_as_test(kateencodingtest) # test macro for encoding tests MACRO(KTEXTEDITOR_ENCODING_TEST _encoding _testname) ADD_TEST (NAME encoding_${_testname}_create COMMAND kateencodingtest ${_encoding} ${CMAKE_SOURCE_DIR}/autotests/input/encoding/${_testname} ${CMAKE_CURRENT_BINARY_DIR}/${_testname} ) ADD_TEST (NAME encoding_${_testname}_diff COMMAND ${CMAKE_COMMAND} -E compare_files ${CMAKE_SOURCE_DIR}/autotests/input/encoding/${_testname} ${CMAKE_CURRENT_BINARY_DIR}/${_testname} ) ENDMACRO(KTEXTEDITOR_ENCODING_TEST) # add tests # this file is utf-8, simple KTEXTEDITOR_ENCODING_TEST ("utf-8" "utf8.txt") # this file is latin15, but fallback should work! KTEXTEDITOR_ENCODING_TEST ("utf-8" "latin15.txt") # this file is utf32, little endian, but fallback should work! KTEXTEDITOR_ENCODING_TEST ("utf-8" "utf32.txt") # this file is utf16, little endian, but fallback should work! KTEXTEDITOR_ENCODING_TEST ("utf-8" "utf16.txt") # this file is utf32, big endian, but fallback should work! KTEXTEDITOR_ENCODING_TEST ("utf-8" "utf32be.txt") # this file is utf16, big endian, but fallback should work! KTEXTEDITOR_ENCODING_TEST ("utf-8" "utf16be.txt") # cyrillic utf-8 KTEXTEDITOR_ENCODING_TEST ("utf-8" "cyrillic_utf8.txt") # cyrillic cp1251 KTEXTEDITOR_ENCODING_TEST ("utf-8" "cp1251.txt") # cyrillic koi8-r KTEXTEDITOR_ENCODING_TEST ("utf-8" "koi8-r.txt") # one character latin-15 test, segfaulted KTEXTEDITOR_ENCODING_TEST ("utf-8" "one-char-latin-15.txt") # test executable for indentation add_executable(kateindenttest src/indenttest.cpp src/script_test_base.cpp src/testutils.cpp) target_link_libraries(kateindenttest ${KTEXTEDITOR_TEST_LINK_LIBS} Qt5::Test) ecm_mark_as_test(kateindenttest) # test macro for indentation tests MACRO(KTEXTEDITOR_INDENT_TEST _testname) ADD_TEST (NAME kateindenttest_${_testname} COMMAND kateindenttest ${_testname}) ENDMACRO(KTEXTEDITOR_INDENT_TEST) # test different indenters sepearately to have smaller test chunks, that takes LONG KTEXTEDITOR_INDENT_TEST ("testPython") KTEXTEDITOR_INDENT_TEST ("testCstyle") KTEXTEDITOR_INDENT_TEST ("testCppstyle") KTEXTEDITOR_INDENT_TEST ("testCMake") KTEXTEDITOR_INDENT_TEST ("testRuby") KTEXTEDITOR_INDENT_TEST ("testHaskell") KTEXTEDITOR_INDENT_TEST ("testLatex") KTEXTEDITOR_INDENT_TEST ("testPascal") KTEXTEDITOR_INDENT_TEST ("testAda") KTEXTEDITOR_INDENT_TEST ("testXml") KTEXTEDITOR_INDENT_TEST ("testNormal") KTEXTEDITOR_INDENT_TEST ("testReplicode") macro(ktexteditor_unit_test testname) ecm_add_test(src/${testname}.cpp ${ARGN} TEST_NAME ${testname} LINK_LIBRARIES ${KTEXTEDITOR_TEST_LINK_LIBS} Qt5::Test) endmacro() ecm_add_tests( src/katetextbuffertest.cpp src/range_test.cpp src/undomanager_test.cpp src/plaintextsearch_test.cpp src/regexpsearch_test.cpp src/scriptdocument_test.cpp src/wordcompletiontest.cpp src/searchbar_test.cpp src/movingcursor_test.cpp + src/configinterface_test.cpp src/messagetest.cpp src/kte_documentcursor.cpp src/bug313769.cpp src/katedocument_test.cpp src/movingrange_test.cpp src/kateview_test.cpp src/revision_test.cpp src/modificationsystem_test.cpp src/templatehandler_test.cpp src/katefoldingtest.cpp src/bug286887.cpp src/katewildcardmatcher_test.cpp src/multicursor_test.cpp LINK_LIBRARIES ${KTEXTEDITOR_TEST_LINK_LIBS} Qt5::Test ) ktexteditor_unit_test(completion_test src/codecompletiontestmodel.cpp src/codecompletiontestmodels.cpp) ktexteditor_unit_test(commands_test src/script_test_base.cpp src/testutils.cpp) ktexteditor_unit_test(scripting_test src/script_test_base.cpp src/testutils.cpp) ktexteditor_unit_test(bug313759 src/testutils.cpp) ktexteditor_unit_test(bug317111 src/testutils.cpp) ktexteditor_unit_test(bug205447 src/testutils.cpp) ktexteditor_unit_test(katesyntaxtest) if (BUILD_VIMODE) add_subdirectory(src/vimode) endif() diff --git a/autotests/input/md5checksum.txt b/autotests/input/md5checksum.txt index 95c6f5f5..696e6d35 100644 --- a/autotests/input/md5checksum.txt +++ b/autotests/input/md5checksum.txt @@ -1,5643 +1,5643 @@ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //END includes #if 0 #define EDIT_DEBUG qCDebug(LOG_PART) #else #define EDIT_DEBUG if (0) qCDebug(LOG_PART) #endif inline bool isStartBracket(const QChar &c) { return c == QLatin1Char('{') || c == QLatin1Char('[') || c == QLatin1Char('('); } inline bool isEndBracket(const QChar &c) { return c == QLatin1Char('}') || c == QLatin1Char(']') || c == QLatin1Char(')'); } 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.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(QLatin1String("need init")), m_docNameNumber(0), m_fileType(QLatin1String("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))); /** * 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*))); 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 // 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_PART) << "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_PART) << "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 (const 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 (const 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(QLatin1String("\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_PART) << "try wrap line: " << line; if (l->virtualLength(m_buffer->tabWidth()) > col) { Kate::TextLine nextl = kateTextLine(line + 1); //qCDebug(LOG_PART) << "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), QLatin1String("\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(QLatin1String("\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::Search::SearchOptions options) { // TODO // * support BlockInputRange // * support DotMatchesNewline const bool escapeSequences = options.testFlag(KTextEditor::Search::EscapeSequences); const bool regexMode = options.testFlag(KTextEditor::Search::Regex); const bool backwards = options.testFlag(KTextEditor::Search::Backwards); const bool wholeWords = options.testFlag(KTextEditor::Search::WholeWords); const Qt::CaseSensitivity caseSensitivity = options.testFlag(KTextEditor::Search::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; } KTextEditor::Search::SearchOptions KTextEditor::DocumentPrivate::supportedSearchOptions() const { KTextEditor::Search::SearchOptions supported(KTextEditor::Search::Default); supported |= KTextEditor::Search::Regex; supported |= KTextEditor::Search::CaseInsensitive; supported |= KTextEditor::Search::Backwards; // supported |= KTextEditor::Search::BlockInputRange; supported |= KTextEditor::Search::EscapeSequences; supported |= KTextEditor::Search::WholeWords; // supported |= KTextEditor::Search::DotMatchesNewline; return supported; } //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")) { int mode = KateHlManager::self()->nameFind(kconfig.readEntry("Highlighting")); if (mode >= 0) { m_buffer->setHighlight(mode); } } } // 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()); } // 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); } 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().toString()), KTextEditor::Message::Error); message->setWordWrap(true); QAction *tryAgainAction = new QAction(QIcon::fromTheme(QLatin1String("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(QLatin1String("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().toString()); } //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)); // disable view updates foreach (KTextEditor::ViewPrivate *view, m_views) { view->setUpdatesEnabled(false); } // // 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->setUpdatesEnabled(true); view->updateView(true); } // Inform that the text has changed (required as we're not inside the usual editStart/End stuff) emit textChanged(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().toString(), 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().toString(), 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().toString(), 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().toString(), 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_PART) << "backup src file name: " << url(); qCDebug(LOG_PART) << "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().toString()) , i18n("Failed to create backup copy.") , KGuiItem(i18n("Try to Save Nevertheless")) , KStandardGuiItem::cancel(), QLatin1String("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().toString())); return false; } // update the md5 digest 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(), QString::fromLatin1("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.count()) { 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) { Kate::TextLine textLine = m_buffer->plainLine(view->cursorPosition().line()); if (!textLine) { return false; } /** * filter out non-printable */ QString chars; Q_FOREACH (QChar c, realChars) if (c.isPrint() || c == QChar::fromLatin1('\t')) { chars.append(c); } if (chars.isEmpty()) { return false; } editStart(); if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); } KTextEditor::Cursor oldCur(view->cursorPosition()); if (view->currentInputMode()->overwrite()) { KTextEditor::Range r = KTextEditor::Range(view->cursorPosition(), qMin(chars.length(), textLine->length() - view->cursorPosition().column())); // replace mode needs to know what was removed so it can be restored with backspace if (oldCur.column() < line(view->cursorPosition().line()).length()) { QChar removed = line(view->cursorPosition().line()).at(r.start().column()); view->currentInputMode()->overwrittenChar(removed); } removeText(r); } if (view->blockSelection() && view->selection()) { KTextEditor::Range selectionRange = view->selectionRange(); int startLine = qMax(0, selectionRange.start().line()); int endLine = qMin(selectionRange.end().line(), lastLine()); 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(chars); insertText(view->cursorPosition(), chars); } // end edit session here, to have updated HL in userTypedChar! editEnd(); KTextEditor::Cursor b(view->cursorPosition()); m_indenter->userTypedChar(view, b, chars.isEmpty() ? QChar() : chars.at(chars.length() - 1)); view->slotTextInserted(view, oldCur, chars); return true; } 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 //c.cursor.col--; removeText(KTextEditor::Range(line, col - 1, line, col)); // 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(KTextEditor::Cursor(line, col - 1)); } 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 { removeText(KTextEditor::Range(line, col - 1, line, col)); // 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(KTextEditor::Cursor(line, col - 1)); } } } 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)); } } } } 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()) { removeText(KTextEditor::Range(c, 1)); } 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(), QLatin1String("\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(QString str) const { const bool replacetabs = config()->replaceTabsDyn(); if ( ! replacetabs ) { return str; } const int tabWidth = config()->tabWidth(); static const QLatin1Char tabChar('\t'); static QString replacement; if ( replacement.size() != tabWidth ) { replacement = QStringLiteral(" ").repeated(tabWidth); } return str.replace(tabChar, replacement); } /* 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. */ void KTextEditor::DocumentPrivate::newBracketMark(const KTextEditor::Cursor &cursor, KTextEditor::Range &bm, int maxLines) { // search from cursor for brackets KTextEditor::Range range(cursor, cursor); // if match found, remember the range if (findMatchingBracket(range, maxLines)) { bm = range; return; } // else, invalidate, if still valid if (bm.isValid()) { bm = KTextEditor::Range::invalid(); } } bool KTextEditor::DocumentPrivate::findMatchingBracket(KTextEditor::Range &range, int maxLines) { Kate::TextLine textLine = m_buffer->plainLine(range.start().line()); if (!textLine) { return false; } QChar right = textLine->at(range.start().column()); QChar left = textLine->at(range.start().column() - 1); QChar bracket; if (config()->ovr()) { if (isBracket(right)) { bracket = right; } else { return false; } } else if (isBracket(left)) { range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1)); bracket = left; } else if (isBracket(right)) { bracket = right; } else { return false; } QChar opposite; switch (bracket.toLatin1()) { case '{': opposite = QLatin1Char('}'); break; case '}': opposite = QLatin1Char('{'); break; case '[': opposite = QLatin1Char(']'); break; case ']': opposite = QLatin1Char('['); break; case '(': opposite = QLatin1Char(')'); break; case ')': opposite = QLatin1Char('('); break; default: return false; } const int searchDir = isStartBracket(bracket) ? 1 : -1; uint nesting = 0; int minLine = qMax(range.start().line() - maxLines, 0); 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 false; } 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 true; } nesting--; } else if (c == bracket) { nesting++; } } } return false; } // 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; KateModOnHdPrompt p(this, m_modOnHdReason, reasonedMOHString(), dialogParent()); switch (p.exec()) { case KateModOnHdPrompt::Save: { m_modOnHd = false; const QUrl dst = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url()); if (!dst.isEmpty() && checkOverwrite(dst, dialogParent())) { if (!saveAs(dst)) { KMessageBox::error(dialogParent(), 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; 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()) { 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"), QLatin1String("view-refresh")), KGuiItem(i18n("&Ignore Changes"), QLatin1String("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; foreach (KTextEditor::ViewPrivate *v, m_views.values()) { 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 foreach (KTextEditor::ViewPrivate *v, m_views.values()) { setActiveView(v); v->setCursorPositionInternal(cursorPositions.value(v), m_config->tabWidth(), false); 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; } return false; } bool KTextEditor::DocumentPrivate::documentSave() { if (!url().isValid() || !isReadWrite()) { return documentSaveAs(); } return save(); } bool KTextEditor::DocumentPrivate::documentSaveAs() { const QUrl dst = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url()); if (dst.isEmpty() || !checkOverwrite(dst, dialogParent())) { return false; } return saveAs(dst); } bool KTextEditor::DocumentPrivate::documentSaveCopyAs() { const QUrl dst = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save Copy of File"), url()); if (dst.isEmpty() || !checkOverwrite(dst, 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().toString())); return false; } // KIO move KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file.fileName()), dst); 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 */ QRegExp KTextEditor::DocumentPrivate::kvLine = QRegExp(QLatin1String("kate:(.*)")); QRegExp KTextEditor::DocumentPrivate::kvLineWildcard = QRegExp(QLatin1String("kate-wildcard\\((.*)\\):(.*)")); QRegExp KTextEditor::DocumentPrivate::kvLineMime = QRegExp(QLatin1String("kate-mimetype\\((.*)\\):(.*)")); QRegExp KTextEditor::DocumentPrivate::kvVar = QRegExp(QLatin1String("([\\w\\-]+)\\s+([^;]+)")); 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) { // 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_PART) << "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_PART) << "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_PART) << "guarded variable line kate-mimetype: matched: " << s; } else { // nothing found return; } QStringList vvl; // view variable names vvl << QLatin1String("dynamic-word-wrap") << QLatin1String("dynamic-word-wrap-indicators") << QLatin1String("line-numbers") << QLatin1String("icon-border") << QLatin1String("folding-markers") << QLatin1String("bookmark-sorting") << QLatin1String("auto-center-lines") << QLatin1String("icon-bar-color") // renderer << QLatin1String("background-color") << QLatin1String("selection-color") << QLatin1String("current-line-color") << QLatin1String("bracket-highlight-color") << QLatin1String("word-wrap-marker-color") << QLatin1String("font") << QLatin1String("font-size") << QLatin1String("scheme"); int spaceIndent = -1; // for backward compatibility; see below bool replaceTabsSet = false; int 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_PART) << 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_PART) << 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 << QLatin1String("unix") << QLatin1String("dos") << QLatin1String("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); } m_config->setRemoveSpaces(state ? 1 : 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") || (var == QLatin1String("font-size") && checkIntValue(val, &n))) { 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(); QStringList l; l << QLatin1String("1") << QLatin1String("on") << QLatin1String("true"); if (l.contains(val)) { *result = true; return true; } l.clear(); l << QLatin1String("0") << QLatin1String("off") << QLatin1String("false"); if (l.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 = QLatin1String("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)) { // compare md5 with the one we have (if we have one) if (!checksum().isEmpty()) { QByteArray oldDigest = checksum(); if (createDigest() && oldDigest == checksum()) { return; } } m_modOnHd = true; m_modOnHdReason = OnDiskModified; // reenable dialog if not running atm if (m_isasking == -1) { m_isasking = false; } emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } } void KTextEditor::DocumentPrivate::slotModOnHdCreated(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskCreated)) { m_modOnHd = true; m_modOnHdReason = OnDiskCreated; // reenable dialog if not running atm if (m_isasking == -1) { m_isasking = false; } emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } } void KTextEditor::DocumentPrivate::slotModOnHdDeleted(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskDeleted)) { m_modOnHd = true; m_modOnHdReason = OnDiskDeleted; // reenable dialog if not running atm if (m_isasking == -1) { m_isasking = false; } 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 = QString(QLatin1String("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 QString str = KStringHandler::csqueeze(url().toString()); 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(); } 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); } // get cursor position of active view KTextEditor::Cursor curPos = KTextEditor::Cursor::invalid(); if (activeView()) { curPos = activeView()->cursorPosition(); } 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) { // if the cursor is in the trailing space, only delete behind cursor if (curPos.line() != line || curPos.column() <= p || curPos.column() > p + l) { editRemoveText(line, p, l); } else { editRemoveText(line, curPos.column(), l - (curPos.column() - p)); } } } } 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()) { const QUrl dst = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File")); if (dst.isEmpty() || !checkOverwrite(dst, dialogParent())) { *abortClosing = true; return; } saveAs(dst); *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 { return QStringList() << QLatin1String("tab-width") << QLatin1String("indent-width"); } 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().toString(), 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) { 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_PART) << "new iteration" << newDictionaryRange; if (newDictionaryRange.isEmpty()) { break; } QPair pair = *i; QString dictionarySet = pair.second; KTextEditor::MovingRange *dictionaryRange = pair.first; qCDebug(LOG_PART) << *dictionaryRange << dictionarySet; if (dictionaryRange->contains(newDictionaryRange) && newDictionary == dictionarySet) { qCDebug(LOG_PART) << "dictionaryRange contains newDictionaryRange"; return; } if (newDictionaryRange.contains(*dictionaryRange)) { delete dictionaryRange; i = m_dictionaryRanges.erase(i); qCDebug(LOG_PART) << "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_PART) << "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_PART) << "deleting" << movingRange; for (QList >::iterator i = m_dictionaryRanges.begin(); i != m_dictionaryRanges.end();) { KTextEditor::MovingRange *dictionaryRange = (*i).first; if (dictionaryRange == movingRange) { delete movingRange; i = m_dictionaryRanges.erase(i); } else { ++i; } } } 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(); + KTextEditor::ViewPrivate *view = m_views.empty() ? nullptr : m_views.begin().value(); if (!view) { qCWarning(LOG_PART) << "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_PART) << "----------------------------------------------------------------------"; // foreach( short a, attrs ) { // qCDebug(LOG_PART) << a; // } // qCDebug(LOG_PART) << "----------------------------------------------------------------------"; // qCDebug(LOG_PART) << "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; } //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_PART) << "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(QLatin1String("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 diff --git a/autotests/src/bug205447.cpp b/autotests/src/bug205447.cpp index f43abe93..e64188ef 100644 --- a/autotests/src/bug205447.cpp +++ b/autotests/src/bug205447.cpp @@ -1,113 +1,113 @@ /* This file is part of the KDE libraries * Copyright (C) 2015 Zoe Clifford * * 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 "bug205447.h" #include #include #include #include #include #include #include #include "testutils.h" QTEST_MAIN(BugTest) using namespace KTextEditor; BugTest::BugTest() : QObject() { } BugTest::~BugTest() { } void BugTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void BugTest::cleanupTestCase() { } void BugTest::deleteSurrogates() { // set up document and view and open test file KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); const QUrl url = QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"bug205447.txt")); doc.setEncoding(QStringLiteral("UTF-8")); QVERIFY(doc.openUrl(url)); // test delete // get UTF-32 representation of original line (before any deletes) QVector line = doc.line(0).toUcs4(); QVERIFY(line.size() == 23); // delete from start of line view->setCursorPosition(Cursor(0, 0)); QVERIFY(DocumentCursor(&doc, view->cursorPosition()).isValidTextPosition()); for (int i = 0; i < line.size(); i++) { // get the current line, after `i` delete presses, and convert it to utf32 // then ensure it's the expected substring of the original line QVector current = doc.line(0).toUcs4(); QCOMPARE(current, line.mid(i)); // press the delete key and verify that the new text position isn't invalid view->keyDelete(); QVERIFY(DocumentCursor(&doc, view->cursorPosition()).isValidTextPosition()); } QCOMPARE(doc.lineLength(0), 0); } void BugTest::backspaceSurrogates() { // set up document and view and open test file KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); const QUrl url = QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"bug205447.txt")); doc.setEncoding(QStringLiteral("UTF-8")); QVERIFY(doc.openUrl(url)); // test backspace // get UTF-32 representation of original line (before any backspaces) QVector line = doc.line(0).toUcs4(); QVERIFY(line.size() == 23); // backspace from end of line view->setCursorPosition(Cursor(0, doc.line(0).size())); QVERIFY(DocumentCursor(&doc, view->cursorPosition()).isValidTextPosition()); for (int i = 0; i < line.size(); i++) { // get the current line, after `i` delete presses, and convert it to utf32 // then ensure it's the expected substring of the original line QVector current = doc.line(0).toUcs4(); QCOMPARE(current, line.mid(0, line.size()-i)); // press the backspace key and verify that the new text position isn't invalid view->backspace(); QVERIFY(DocumentCursor(&doc, view->cursorPosition()).isValidTextPosition()); } QCOMPARE(doc.lineLength(0), 0); } #include "moc_bug205447.cpp" diff --git a/autotests/src/bug286887.cpp b/autotests/src/bug286887.cpp index e653b236..35cf3b0f 100644 --- a/autotests/src/bug286887.cpp +++ b/autotests/src/bug286887.cpp @@ -1,90 +1,90 @@ /* This file is part of the KDE libraries Copyright (C) 2012 Dominik Haumann 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 "bug286887.h" #include #include #include #include #include QTEST_MAIN(BugTest) using namespace KTextEditor; BugTest::BugTest() : QObject() { } BugTest::~BugTest() { } void BugTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void BugTest::cleanupTestCase() { } void BugTest::ctrlShiftLeft() { KTextEditor::DocumentPrivate doc(false, false); // view must be visible... - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); // enable block mode, then set cursor after last character, then shift+left doc.clear(); view->setBlockSelection(true); view->setCursorPosition(Cursor(0, 2)); view->shiftCursorLeft(); QTest::qWait(500); // enable block mode, then set cursor after last character, then delete word left doc.clear(); view->setBlockSelection(true); view->setCursorPosition(Cursor(0, 2)); view->deleteWordLeft(); QTest::qWait(500); // disable wrap-cursor, then set cursor after last character, then shift+left doc.clear(); view->setBlockSelection(false); view->setCursorPosition(Cursor(0, 2)); view->shiftCursorLeft(); QTest::qWait(500); // disable wrap-cursor, then set cursor after last character, then delete word left doc.clear(); view->setCursorPosition(Cursor(0, 2)); view->deleteWordLeft(); } #include "moc_bug286887.cpp" \ No newline at end of file diff --git a/autotests/src/bug313759.cpp b/autotests/src/bug313759.cpp index 6d96a9b3..476183c7 100644 --- a/autotests/src/bug313759.cpp +++ b/autotests/src/bug313759.cpp @@ -1,97 +1,97 @@ /* This file is part of the KDE libraries * Copyright (C) 2013 Gerald Senarclens de Grancy * * 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 "bug313759.h" #include #include #include #include #include #include #include "testutils.h" QTEST_MAIN(BugTest) using namespace KTextEditor; BugTest::BugTest() : QObject() { } BugTest::~BugTest() { } void BugTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void BugTest::cleanupTestCase() { } void BugTest::tryCrash() { // set up document and view KMainWindow *toplevel = new KMainWindow(); KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(true, false, toplevel); - KTextEditor::ViewPrivate *view = static_cast(doc->createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc->createView(nullptr)); bool outputWasCustomised = false; TestScriptEnv *env = new TestScriptEnv(doc, outputWasCustomised); const QUrl url = QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"bug313759.txt")); doc->openUrl(url); // load moveLinesDown and moveLinesUp QFile scriptFile(QLatin1String(JS_DATA_DIR "commands/utils.js")); QVERIFY(scriptFile.exists()); QVERIFY(scriptFile.open(QFile::ReadOnly)); QScriptValue result = env->engine()->evaluate(QString::fromLatin1(scriptFile.readAll()), scriptFile.fileName()); QVERIFY2(!result.isError(), qPrintable(QString(result.toString() + QLatin1String("\nat ") + env->engine()->uncaughtExceptionBacktrace().join(QStringLiteral("\n"))))); // enable on the fly spell checking doc->onTheFlySpellCheckingEnabled(true); // view must be visible... view->show(); view->resize(900, 800); view->setCursorPosition(Cursor(0, 0)); doc->editBegin(); // QTest::qWait(200); // evaluate test-script qDebug() << "attempting crash by moving lines w/ otf spell checking enabled"; QFile sourceFile(QLatin1String(TEST_DATA_DIR"bug313759.js")); QVERIFY(sourceFile.open(QFile::ReadOnly)); QTextStream stream(&sourceFile); stream.setCodec("UTF8"); QString code = stream.readAll(); sourceFile.close(); // execute script result = env->engine()->evaluate(code, QLatin1String(TEST_DATA_DIR"bug313759.txt"), 1); QVERIFY2(!result.isError(), result.toString().toUtf8().constData()); doc->editEnd(); qDebug() << "PASS (no crash)"; } diff --git a/autotests/src/bug313769.cpp b/autotests/src/bug313769.cpp index 9c633f80..c07566e0 100644 --- a/autotests/src/bug313769.cpp +++ b/autotests/src/bug313769.cpp @@ -1,99 +1,99 @@ /* This file is part of the KDE libraries Copyright (C) 2012 Dominik Haumann 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 "bug313769.h" #include "moc_bug313769.cpp" #include #include #include #include #include #include #include QTEST_MAIN(BugTest) using namespace KTextEditor; BugTest::BugTest() : QObject() { } BugTest::~BugTest() { } void BugTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void BugTest::cleanupTestCase() { } void BugTest::tryCrash() { KTextEditor::DocumentPrivate doc(false, false); const QUrl url = QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"bug313769.cpp")); doc.openUrl(url); doc.discardDataRecovery(); doc.setHighlightingMode(QStringLiteral("C++")); doc.buffer().ensureHighlighted(doc.lines()); // view must be visible... - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(900, 800); view->config()->setDynWordWrap(true); view->setSelection(Range(2, 0, 74, 0)); view->setCursorPosition(Cursor(74, 0)); doc.editBegin(); QString text = doc.line(1); doc.insertLine(74, text); doc.removeLine(1); view->setCursorPosition(Cursor(1, 0)); doc.editEnd(); QTest::qWait(200); // fold toplevel nodes for (int line = 0; line < doc.lines(); ++line) { if (view->textFolding().isLineVisible(line)) { view->foldLine(line); } } doc.buffer().ensureHighlighted(doc.lines()); view->setCursorPosition(Cursor(0, 0)); QTest::qWait(100); doc.undo(); QTest::qWait(100); doc.redo(); QTest::qWait(500); qDebug() << "!!! Does undo crash?"; doc.undo(); QTest::qWait(500); qDebug() << "!!! No crash (qWait not long enough)? Nice!"; } diff --git a/autotests/src/bug317111.cpp b/autotests/src/bug317111.cpp index 083b9f48..4a0ee4d3 100644 --- a/autotests/src/bug317111.cpp +++ b/autotests/src/bug317111.cpp @@ -1,91 +1,91 @@ /* This file is part of the KDE libraries * Copyright (C) 2013 Gerald Senarclens de Grancy * * 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 "bug317111.h" #include #include #include #include #include #include #include "testutils.h" QTEST_MAIN(BugTest) using namespace KTextEditor; BugTest::BugTest() : QObject() { } BugTest::~BugTest() { } void BugTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void BugTest::cleanupTestCase() { } void BugTest::tryCrash() { // set up document and view KMainWindow *toplevel = new KMainWindow(); KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(true, false, toplevel); - KTextEditor::ViewPrivate *view = static_cast(doc->createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc->createView(nullptr)); bool outputWasCustomised = false; TestScriptEnv *env = new TestScriptEnv(doc, outputWasCustomised); const QUrl url = QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"bug317111.txt")); doc->openUrl(url); // load buggy script QFile scriptFile(QLatin1String(JS_DATA_DIR"commands/utils.js")); QVERIFY(scriptFile.exists()); QVERIFY(scriptFile.open(QFile::ReadOnly)); QScriptValue result = env->engine()->evaluate(QString::fromLatin1(scriptFile.readAll()), scriptFile.fileName()); QVERIFY2(!result.isError(), qPrintable(QString(result.toString() + QLatin1String("\nat ") + env->engine()->uncaughtExceptionBacktrace().join(QStringLiteral("\n"))))); // view must be visible... view->show(); view->resize(900, 800); view->setCursorPosition(Cursor(0, 0)); // evaluate test-script qDebug() << "attempting crash by calling KTextEditor::DocumentPrivate::defStyle(-1, 0)"; QFile sourceFile(QLatin1String(TEST_DATA_DIR "bug317111.js")); QVERIFY(sourceFile.open(QFile::ReadOnly)); QTextStream stream(&sourceFile); stream.setCodec("UTF8"); QString code = stream.readAll(); sourceFile.close(); // execute script result = env->engine()->evaluate(code, QLatin1String(TEST_DATA_DIR "bug317111.txt"), 1); QVERIFY2(!result.isError(), result.toString().toUtf8().constData()); qDebug() << "PASS (no crash)"; } diff --git a/autotests/src/codecompletiontestmodel.h b/autotests/src/codecompletiontestmodel.h index 238823ea..95428493 100644 --- a/autotests/src/codecompletiontestmodel.h +++ b/autotests/src/codecompletiontestmodel.h @@ -1,63 +1,63 @@ /* This file is part of the KDE project Copyright (C) 2005 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License 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 CODECOMPLETIONTEST_H #define CODECOMPLETIONTEST_H #include #include namespace KTextEditor { class View; class CodeCompletionInterface; } class CodeCompletionTestModel : public KTextEditor::CodeCompletionModel { Q_OBJECT public: - CodeCompletionTestModel(KTextEditor::View *parent = 0L, const QString &startText = QString()); + CodeCompletionTestModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()); KTextEditor::View *view() const; KTextEditor::CodeCompletionInterface *cc() const; void completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType invocationType) Q_DECL_OVERRIDE; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; private: QString m_startText; bool m_autoStartText; }; class AbbreviationCodeCompletionTestModel : public CodeCompletionTestModel { Q_OBJECT public: - AbbreviationCodeCompletionTestModel(KTextEditor::View *parent = 0L, const QString &startText = QString()); + AbbreviationCodeCompletionTestModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; private: QStringList m_items; }; #endif diff --git a/autotests/src/codecompletiontestmodels.h b/autotests/src/codecompletiontestmodels.h index 583a734f..b1dc646e 100644 --- a/autotests/src/codecompletiontestmodels.h +++ b/autotests/src/codecompletiontestmodels.h @@ -1,166 +1,166 @@ /* This file is part of the KDE libraries Copyright (C) 2008 Niko Sams 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_COMPLETIONTESTMODELS_H #define KATE_COMPLETIONTESTMODELS_H #include "codecompletiontestmodel.h" #include #include #include using namespace KTextEditor; class CustomRangeModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: - CustomRangeModel(KTextEditor::View *parent = 0L, const QString &startText = QString()) + CustomRangeModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} Range completionRange(View *view, const Cursor &position) Q_DECL_OVERRIDE { Range range = CodeCompletionModelControllerInterface::completionRange(view, position); if (range.start().column() > 0) { KTextEditor::Range preRange(Cursor(range.start().line(), range.start().column() - 1), Cursor(range.start().line(), range.start().column())); qDebug() << preRange << view->document()->text(preRange); if (view->document()->text(preRange) == "$") { range.expandToRange(preRange); qDebug() << "using custom completion range" << range; } } return range; } bool shouldAbortCompletion(View *view, const Range &range, const QString ¤tCompletion) Q_DECL_OVERRIDE { Q_UNUSED(view); Q_UNUSED(range); static const QRegExp allowedText("^\\$?(\\w*)"); return !allowedText.exactMatch(currentCompletion); } }; class CustomAbortModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: - CustomAbortModel(KTextEditor::View *parent = 0L, const QString &startText = QString()) + CustomAbortModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} bool shouldAbortCompletion(View *view, const Range &range, const QString ¤tCompletion) Q_DECL_OVERRIDE { Q_UNUSED(view); Q_UNUSED(range); static const QRegExp allowedText("^([\\w-]*)"); return !allowedText.exactMatch(currentCompletion); } }; class EmptyFilterStringModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: - EmptyFilterStringModel(KTextEditor::View *parent = 0L, const QString &startText = QString()) + EmptyFilterStringModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} QString filterString(View *, const Range &, const Cursor &) Q_DECL_OVERRIDE { return QString(); } }; class UpdateCompletionRangeModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: - UpdateCompletionRangeModel(KTextEditor::View *parent = 0L, const QString &startText = QString()) + UpdateCompletionRangeModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} Range updateCompletionRange(View *view, const Range &range) Q_DECL_OVERRIDE { Q_UNUSED(view); if (view->document()->text(range) == QString("ab")) { return Range(Cursor(range.start().line(), 0), range.end()); } return range; } bool shouldAbortCompletion(View *view, const Range &range, const QString ¤tCompletion) Q_DECL_OVERRIDE { Q_UNUSED(view); Q_UNUSED(range); Q_UNUSED(currentCompletion); return false; } }; class StartCompletionModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: - StartCompletionModel(KTextEditor::View *parent = 0L, const QString &startText = QString()) + StartCompletionModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} bool shouldStartCompletion(View *view, const QString &insertedText, bool userInsertion, const Cursor &position) Q_DECL_OVERRIDE { Q_UNUSED(view); Q_UNUSED(userInsertion); Q_UNUSED(position); if (insertedText.isEmpty()) { return false; } QChar lastChar = insertedText.at(insertedText.count() - 1); if (lastChar == '%') { return true; } return false; } }; class ImmideatelyAbortCompletionModel : public CodeCompletionTestModel, public CodeCompletionModelControllerInterface { Q_OBJECT Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) public: - ImmideatelyAbortCompletionModel(KTextEditor::View *parent = 0L, const QString &startText = QString()) + ImmideatelyAbortCompletionModel(KTextEditor::View *parent = nullptr, const QString &startText = QString()) : CodeCompletionTestModel(parent, startText) {} bool shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) Q_DECL_OVERRIDE { Q_UNUSED(view); Q_UNUSED(range); Q_UNUSED(currentCompletion); return true; } }; #endif diff --git a/autotests/src/completion_test.cpp b/autotests/src/completion_test.cpp index d94cee12..c68c3cb5 100644 --- a/autotests/src/completion_test.cpp +++ b/autotests/src/completion_test.cpp @@ -1,494 +1,494 @@ /* This file is part of the KDE libraries Copyright (C) 2008 Niko Sams 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 "completion_test.h" #include "codecompletiontestmodels.h" //#include "codecompletiontestmodels.moc" #include #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(CompletionTest) using namespace KTextEditor; int countItems(KateCompletionModel *model) { const int topLevel = model->rowCount(QModelIndex()); if (!model->hasGroups()) { return topLevel; } int ret = 0; for (int i = 0; i < topLevel; ++i) { ret += model->rowCount(model->index(i, 0)); } return ret; } static void verifyCompletionStarted(KTextEditor::ViewPrivate *view) { const QDateTime startTime = QDateTime::currentDateTime(); while (startTime.msecsTo(QDateTime::currentDateTime()) < 1000) { QApplication::processEvents(); if (view->completionWidget()->isCompletionActive()) { break; } } QVERIFY(view->completionWidget()->isCompletionActive()); } static void verifyCompletionAborted(KTextEditor::ViewPrivate *view) { const QDateTime startTime = QDateTime::currentDateTime(); while (startTime.msecsTo(QDateTime::currentDateTime()) < 1000) { QApplication::processEvents(); if (!view->completionWidget()->isCompletionActive()) { break; } } QVERIFY(!view->completionWidget()->isCompletionActive()); } static void invokeCompletionBox(KTextEditor::ViewPrivate *view) { view->userInvokedCompletion(); verifyCompletionStarted(view); } void CompletionTest::init() { KTextEditor::EditorPrivate::enableUnitTestMode(); Editor *editor = KTextEditor::Editor::instance(); QVERIFY(editor); m_doc = editor->createDocument(this); QVERIFY(m_doc); m_doc->setText(QStringLiteral("aa bb cc\ndd")); - KTextEditor::View *v = m_doc->createView(0); + KTextEditor::View *v = m_doc->createView(nullptr); QApplication::setActiveWindow(v); m_view = static_cast(v); Q_ASSERT(m_view); //view needs to be shown as completion won't work if the cursor is off screen m_view->show(); } void CompletionTest::cleanup() { delete m_view; delete m_doc; } void CompletionTest::testFilterEmptyRange() { KateCompletionModel *model = m_view->completionWidget()->model(); new CodeCompletionTestModel(m_view, QStringLiteral("a")); m_view->setCursorPosition(Cursor(0, 0)); invokeCompletionBox(m_view); QCOMPARE(countItems(model), 40); m_view->insertText(QStringLiteral("aa")); QTest::qWait(1000); // process events QCOMPARE(countItems(model), 14); } void CompletionTest::testFilterWithRange() { KateCompletionModel *model = m_view->completionWidget()->model(); CodeCompletionTestModel *testModel = new CodeCompletionTestModel(m_view, QStringLiteral("a")); m_view->setCursorPosition(Cursor(0, 2)); invokeCompletionBox(m_view); Range complRange = *m_view->completionWidget()->completionRange(testModel); QCOMPARE(complRange, Range(Cursor(0, 0), Cursor(0, 2))); QCOMPARE(countItems(model), 14); m_view->insertText(QStringLiteral("a")); QTest::qWait(1000); // process events QCOMPARE(countItems(model), 1); } void CompletionTest::testAbortCursorMovedOutOfRange() { KateCompletionModel *model = m_view->completionWidget()->model(); new CodeCompletionTestModel(m_view, QStringLiteral("a")); m_view->setCursorPosition(Cursor(0, 2)); invokeCompletionBox(m_view); QCOMPARE(countItems(model), 14); QVERIFY(m_view->completionWidget()->isCompletionActive()); m_view->setCursorPosition(Cursor(0, 4)); QTest::qWait(1000); // process events QVERIFY(!m_view->completionWidget()->isCompletionActive()); } void CompletionTest::testAbortInvalidText() { KateCompletionModel *model = m_view->completionWidget()->model(); new CodeCompletionTestModel(m_view, QStringLiteral("a")); m_view->setCursorPosition(Cursor(0, 2)); invokeCompletionBox(m_view); QCOMPARE(countItems(model), 14); QVERIFY(m_view->completionWidget()->isCompletionActive()); m_view->insertText(QStringLiteral(".")); verifyCompletionAborted(m_view); } void CompletionTest::testCustomRange1() { m_doc->setText(QStringLiteral("$aa bb cc\ndd")); KateCompletionModel *model = m_view->completionWidget()->model(); CodeCompletionTestModel *testModel = new CustomRangeModel(m_view, QStringLiteral("$a")); m_view->setCursorPosition(Cursor(0, 3)); invokeCompletionBox(m_view); Range complRange = *m_view->completionWidget()->completionRange(testModel); qDebug() << complRange; QCOMPARE(complRange, Range(Cursor(0, 0), Cursor(0, 3))); QCOMPARE(countItems(model), 14); m_view->insertText(QStringLiteral("a")); QTest::qWait(1000); // process events QCOMPARE(countItems(model), 1); } void CompletionTest::testCustomRange2() { m_doc->setText(QStringLiteral("$ bb cc\ndd")); KateCompletionModel *model = m_view->completionWidget()->model(); CodeCompletionTestModel *testModel = new CustomRangeModel(m_view, QStringLiteral("$a")); m_view->setCursorPosition(Cursor(0, 1)); invokeCompletionBox(m_view); Range complRange = *m_view->completionWidget()->completionRange(testModel); QCOMPARE(complRange, Range(Cursor(0, 0), Cursor(0, 1))); QCOMPARE(countItems(model), 40); m_view->insertText(QStringLiteral("aa")); QTest::qWait(1000); // process events QCOMPARE(countItems(model), 14); } void CompletionTest::testCustomRangeMultipleModels() { m_doc->setText(QStringLiteral("$a bb cc\ndd")); KateCompletionModel *model = m_view->completionWidget()->model(); CodeCompletionTestModel *testModel1 = new CustomRangeModel(m_view, QStringLiteral("$a")); CodeCompletionTestModel *testModel2 = new CodeCompletionTestModel(m_view, QStringLiteral("a")); m_view->setCursorPosition(Cursor(0, 1)); invokeCompletionBox(m_view); QCOMPARE(Range(*m_view->completionWidget()->completionRange(testModel1)), Range(Cursor(0, 0), Cursor(0, 2))); QCOMPARE(Range(*m_view->completionWidget()->completionRange(testModel2)), Range(Cursor(0, 1), Cursor(0, 2))); QCOMPARE(model->currentCompletion(testModel1), QStringLiteral("$")); QCOMPARE(model->currentCompletion(testModel2), QString()); QCOMPARE(countItems(model), 80); m_view->insertText(QStringLiteral("aa")); QTest::qWait(1000); // process events QCOMPARE(model->currentCompletion(testModel1), QStringLiteral("$aa")); QCOMPARE(model->currentCompletion(testModel2), QStringLiteral("aa")); QCOMPARE(countItems(model), 14 * 2); } void CompletionTest::testAbortController() { KateCompletionModel *model = m_view->completionWidget()->model(); new CustomRangeModel(m_view, QStringLiteral("$a")); m_view->setCursorPosition(Cursor(0, 0)); invokeCompletionBox(m_view); QCOMPARE(countItems(model), 40); QVERIFY(m_view->completionWidget()->isCompletionActive()); m_view->insertText(QStringLiteral("$a")); QTest::qWait(1000); // process events QVERIFY(m_view->completionWidget()->isCompletionActive()); m_view->insertText(QStringLiteral(".")); verifyCompletionAborted(m_view); } void CompletionTest::testAbortControllerMultipleModels() { KateCompletionModel *model = m_view->completionWidget()->model(); CodeCompletionTestModel *testModel1 = new CodeCompletionTestModel(m_view, QStringLiteral("aa")); CodeCompletionTestModel *testModel2 = new CustomAbortModel(m_view, QStringLiteral("a-")); m_view->setCursorPosition(Cursor(0, 0)); invokeCompletionBox(m_view); QCOMPARE(countItems(model), 80); QVERIFY(m_view->completionWidget()->isCompletionActive()); m_view->insertText(QStringLiteral("a")); QTest::qWait(1000); // process events QVERIFY(m_view->completionWidget()->isCompletionActive()); QCOMPARE(countItems(model), 80); m_view->insertText(QStringLiteral("-")); QTest::qWait(1000); // process events QVERIFY(m_view->completionWidget()->isCompletionActive()); QVERIFY(!m_view->completionWidget()->completionRanges().contains(testModel1)); QVERIFY(m_view->completionWidget()->completionRanges().contains(testModel2)); QCOMPARE(countItems(model), 40); m_view->insertText(QLatin1String(" ")); QTest::qWait(1000); // process events QVERIFY(!m_view->completionWidget()->isCompletionActive()); } void CompletionTest::testEmptyFilterString() { KateCompletionModel *model = m_view->completionWidget()->model(); new EmptyFilterStringModel(m_view, QStringLiteral("aa")); m_view->setCursorPosition(Cursor(0, 0)); invokeCompletionBox(m_view); QCOMPARE(countItems(model), 40); m_view->insertText(QStringLiteral("a")); QTest::qWait(1000); // process events QCOMPARE(countItems(model), 40); m_view->insertText(QStringLiteral("bam")); QTest::qWait(1000); // process events QCOMPARE(countItems(model), 40); } void CompletionTest::testUpdateCompletionRange() { m_doc->setText(QStringLiteral("ab bb cc\ndd")); KateCompletionModel *model = m_view->completionWidget()->model(); CodeCompletionTestModel *testModel = new UpdateCompletionRangeModel(m_view, QStringLiteral("ab ab")); m_view->setCursorPosition(Cursor(0, 3)); invokeCompletionBox(m_view); QCOMPARE(countItems(model), 40); QCOMPARE(Range(*m_view->completionWidget()->completionRange(testModel)), Range(Cursor(0, 3), Cursor(0, 3))); m_view->insertText(QStringLiteral("ab")); QTest::qWait(1000); // process events QCOMPARE(Range(*m_view->completionWidget()->completionRange(testModel)), Range(Cursor(0, 0), Cursor(0, 5))); QCOMPARE(countItems(model), 40); } void CompletionTest::testCustomStartCompl() { KateCompletionModel *model = m_view->completionWidget()->model(); m_view->completionWidget()->setAutomaticInvocationDelay(1); new StartCompletionModel(m_view, QStringLiteral("aa")); m_view->setCursorPosition(Cursor(0, 0)); m_view->insertText("%"); QTest::qWait(1000); QVERIFY(m_view->completionWidget()->isCompletionActive()); QCOMPARE(countItems(model), 40); } void CompletionTest::testKateCompletionModel() { KateCompletionModel *model = m_view->completionWidget()->model(); CodeCompletionTestModel *testModel1 = new CodeCompletionTestModel(m_view, QStringLiteral("aa")); CodeCompletionTestModel *testModel2 = new CodeCompletionTestModel(m_view, QStringLiteral("bb")); model->setCompletionModel(testModel1); QCOMPARE(countItems(model), 40); model->addCompletionModel(testModel2); QCOMPARE(countItems(model), 80); model->removeCompletionModel(testModel2); QCOMPARE(countItems(model), 40); } void CompletionTest::testAbortImmideatelyAfterStart() { new ImmideatelyAbortCompletionModel(m_view); m_view->setCursorPosition(Cursor(0, 3)); QVERIFY(!m_view->completionWidget()->isCompletionActive()); emit m_view->userInvokedCompletion(); QVERIFY(!m_view->completionWidget()->isCompletionActive()); } void CompletionTest::testJumpToListBottomAfterCursorUpWhileAtTop() { new CodeCompletionTestModel(m_view, QStringLiteral("aa")); invokeCompletionBox(m_view); m_view->completionWidget()->cursorUp(); m_view->completionWidget()->bottom(); // TODO - better way of finding the index? QCOMPARE(m_view->completionWidget()->treeView()->selectionModel()->currentIndex().row(), 39); } void CompletionTest::testAbbreviationEngine() { QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("fb"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("foob"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("fbar"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("fba"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBar"), QStringLiteral("foba"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarBazBang"), QStringLiteral("fbbb"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("foo_bar_cat"), QStringLiteral("fbc"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("foo_bar_cat"), QStringLiteral("fb"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fba"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fbara"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fobaar"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fb"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qid"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qualid"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qualidentifier"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("qi"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kcmodel"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kc"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kcomplmodel"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kacomplmodel"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kacom"))); QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("QualifiedIdentifier"), QStringLiteral("identifier"))); QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fobaara"))); QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("FooBarArr"), QStringLiteral("fbac"))); QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("KateCompletionModel"), QStringLiteral("kamodel"))); QVERIFY(KateCompletionModel::matchesAbbreviation(QStringLiteral("AbcdefBcdefCdefDefEfFzZ"), QStringLiteral("AbcdefBcdefCdefDefEfFzZ"))); QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AbcdefBcdefCdefDefEfFzZ"), QStringLiteral("ABCDEFX"))); QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AaaaaaBbbbbCcccDddEeFzZ"), QStringLiteral("XZYBFA"))); } void CompletionTest::benchAbbreviationEngineGoodCase() { QBENCHMARK { for (int i = 0; i < 10000; i++) { QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AaaaaaBbbbbCcccDddEeFzZ"), QStringLiteral("XZYBFA"))); } } } void CompletionTest::benchAbbreviationEngineNormalCase() { QBENCHMARK { for (int i = 0; i < 10000; i++) { QVERIFY(! KateCompletionModel::matchesAbbreviation(QStringLiteral("AaaaaaBbbbbCcccDddEeFzZ"), QStringLiteral("ABCDEFX"))); } } } void CompletionTest::benchAbbreviationEngineWorstCase() { QBENCHMARK { for (int i = 0; i < 10000; i++) { // This case is quite horrible, because it requires a branch at every letter. // The current code will at some point drop out and just return false. KateCompletionModel::matchesAbbreviation(QStringLiteral("XxBbbbbbBbbbbbBbbbbBbbbBbbbbbbBbbbbbBbbbbbBbbbFox"), QStringLiteral("XbbbbbbbbbbbbbbbbbbbbFx")); } } } void CompletionTest::testAbbrevAndContainsMatching() { KateCompletionModel *model = m_view->completionWidget()->model(); new AbbreviationCodeCompletionTestModel(m_view, QString()); m_view->document()->setText(QStringLiteral("SCA")); invokeCompletionBox(m_view); QCOMPARE(model->filteredItemCount(), (uint) 6); m_view->document()->setText(QStringLiteral("SC")); invokeCompletionBox(m_view); QCOMPARE(model->filteredItemCount(), (uint) 6); m_view->document()->setText(QStringLiteral("sca")); invokeCompletionBox(m_view); QCOMPARE(model->filteredItemCount(), (uint) 6); m_view->document()->setText(QStringLiteral("contains")); invokeCompletionBox(m_view); QCOMPARE(model->filteredItemCount(), (uint) 2); m_view->document()->setText(QStringLiteral("CONTAINS")); invokeCompletionBox(m_view); QCOMPARE(model->filteredItemCount(), (uint) 2); m_view->document()->setText(QStringLiteral("containssome")); invokeCompletionBox(m_view); QCOMPARE(model->filteredItemCount(), (uint) 1); m_view->document()->setText(QStringLiteral("matched")); m_view->userInvokedCompletion(); QApplication::processEvents(); QCOMPARE(model->filteredItemCount(), (uint) 0); } void CompletionTest::benchCompletionModel() { const QString text("abcdefg abcdef"); m_doc->setText(text); CodeCompletionTestModel *testModel1 = new CodeCompletionTestModel(m_view, QStringLiteral("abcdefg")); testModel1->setRowCount(500); CodeCompletionTestModel *testModel2 = new CodeCompletionTestModel(m_view, QStringLiteral("abcdef")); testModel2->setRowCount(500); CodeCompletionTestModel *testModel3 = new CodeCompletionTestModel(m_view, QStringLiteral("abcde")); testModel3->setRowCount(500); CodeCompletionTestModel *testModel4 = new CodeCompletionTestModel(m_view, QStringLiteral("abcd")); testModel4->setRowCount(5000); QBENCHMARK_ONCE { for (int i = 0; i < text.size(); ++i) { m_view->setCursorPosition(Cursor(0, i)); invokeCompletionBox(m_view); } } } diff --git a/autotests/src/configinterface_test.cpp b/autotests/src/configinterface_test.cpp new file mode 100644 index 00000000..fb461249 --- /dev/null +++ b/autotests/src/configinterface_test.cpp @@ -0,0 +1,184 @@ +/* This file is part of the KDE libraries + Copyright (C) 2017 Dominik Haumann + + 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 "configinterface_test.h" +#include "moc_configinterface_test.cpp" + +#include +#include +#include +#include + +#include +#include + +using namespace KTextEditor; + +QTEST_MAIN(KateConfigInterfaceTest) + +KateConfigInterfaceTest::KateConfigInterfaceTest() + : QObject() +{ + KTextEditor::EditorPrivate::enableUnitTestMode(); +} + +KateConfigInterfaceTest::~KateConfigInterfaceTest() +{ +} + +void KateConfigInterfaceTest::testDocument() +{ + KTextEditor::DocumentPrivate doc(false, false); + auto iface = qobject_cast(&doc); + QVERIFY(iface); + QVERIFY(!iface->configKeys().isEmpty()); + + iface->setConfigValue(QLatin1String("backup-on-save-local"), true); + QCOMPARE(iface->configValue(QLatin1String("backup-on-save-local")).toBool(), true); + iface->setConfigValue(QLatin1String("backup-on-save-local"), false); + QCOMPARE(iface->configValue(QLatin1String("backup-on-save-local")).toBool(), false); + + iface->setConfigValue(QLatin1String("backup-on-save-remote"), true); + QCOMPARE(iface->configValue(QLatin1String("backup-on-save-remote")).toBool(), true); + iface->setConfigValue(QLatin1String("backup-on-save-remote"), false); + QCOMPARE(iface->configValue(QLatin1String("backup-on-save-remote")).toBool(), false); + + iface->setConfigValue(QLatin1String("replace-tabs"), true); + QCOMPARE(iface->configValue(QLatin1String("replace-tabs")).toBool(), true); + iface->setConfigValue(QLatin1String("replace-tabs"), false); + QCOMPARE(iface->configValue(QLatin1String("replace-tabs")).toBool(), false); + + iface->setConfigValue(QLatin1String("indent-pasted-text"), true); + QCOMPARE(iface->configValue(QLatin1String("indent-pasted-text")).toBool(), true); + iface->setConfigValue(QLatin1String("indent-pasted-text"), false); + QCOMPARE(iface->configValue(QLatin1String("indent-pasted-text")).toBool(), false); + + iface->setConfigValue(QLatin1String("on-the-fly-spellcheck"), true); + QCOMPARE(iface->configValue(QLatin1String("on-the-fly-spellcheck")).toBool(), true); + iface->setConfigValue(QLatin1String("on-the-fly-spellcheck"), false); + QCOMPARE(iface->configValue(QLatin1String("on-the-fly-spellcheck")).toBool(), false); + + iface->setConfigValue(QLatin1String("indent-width"), 13); + QCOMPARE(iface->configValue(QLatin1String("indent-width")).toInt(), 13); + iface->setConfigValue(QLatin1String("indent-width"), 4); + QCOMPARE(iface->configValue(QLatin1String("indent-width")).toInt(), 4); + + iface->setConfigValue(QLatin1String("tab-width"), 13); + QCOMPARE(iface->configValue(QLatin1String("tab-width")).toInt(), 13); + iface->setConfigValue(QLatin1String("tab-width"), 4); + QCOMPARE(iface->configValue(QLatin1String("tab-width")).toInt(), 4); + + iface->setConfigValue(QLatin1String("backup-on-save-suffix"), QLatin1String("_tmp")); + QCOMPARE(iface->configValue(QLatin1String("backup-on-save-suffix")).toString(), QLatin1String("_tmp")); + + iface->setConfigValue(QLatin1String("backup-on-save-prefix"), QLatin1String("abc_")); + QCOMPARE(iface->configValue(QLatin1String("backup-on-save-prefix")).toString(), QLatin1String("abc_")); +} + +void KateConfigInterfaceTest::testView() +{ + KTextEditor::DocumentPrivate doc(false, false); + auto view = static_cast(doc.createView(nullptr)); + QVERIFY(view); + auto iface = qobject_cast(view); + QVERIFY(iface); + QVERIFY(!iface->configKeys().isEmpty()); + + iface->setConfigValue(QLatin1String("line-numbers"), true); + QCOMPARE(iface->configValue(QLatin1String("line-numbers")).toBool(), true); + iface->setConfigValue(QLatin1String("line-numbers"), false); + QCOMPARE(iface->configValue(QLatin1String("line-numbers")).toBool(), false); + + iface->setConfigValue(QLatin1String("icon-bar"), true); + QCOMPARE(iface->configValue(QLatin1String("icon-bar")).toBool(), true); + iface->setConfigValue(QLatin1String("icon-bar"), false); + QCOMPARE(iface->configValue(QLatin1String("icon-bar")).toBool(), false); + + iface->setConfigValue(QLatin1String("folding-bar"), true); + QCOMPARE(iface->configValue(QLatin1String("folding-bar")).toBool(), true); + iface->setConfigValue(QLatin1String("folding-bar"), false); + QCOMPARE(iface->configValue(QLatin1String("folding-bar")).toBool(), false); + + iface->setConfigValue(QLatin1String("folding-preview"), true); + QCOMPARE(iface->configValue(QLatin1String("folding-preview")).toBool(), true); + iface->setConfigValue(QLatin1String("folding-preview"), false); + QCOMPARE(iface->configValue(QLatin1String("folding-preview")).toBool(), false); + + iface->setConfigValue(QLatin1String("dynamic-word-wrap"), true); + QCOMPARE(iface->configValue(QLatin1String("dynamic-word-wrap")).toBool(), true); + iface->setConfigValue(QLatin1String("dynamic-word-wrap"), false); + QCOMPARE(iface->configValue(QLatin1String("dynamic-word-wrap")).toBool(), false); + + iface->setConfigValue(QLatin1String("background-color"), QColor(0, 255, 0)); + QCOMPARE(iface->configValue(QLatin1String("background-color")).value(), QColor(0, 255, 0)); + + iface->setConfigValue(QLatin1String("selection-color"), QColor(0, 255, 0)); + QCOMPARE(iface->configValue(QLatin1String("selection-color")).value(), QColor(0, 255, 0)); + + iface->setConfigValue(QLatin1String("search-highlight-color"), QColor(0, 255, 0)); + QCOMPARE(iface->configValue(QLatin1String("search-highlight-color")).value(), QColor(0, 255, 0)); + + iface->setConfigValue(QLatin1String("replace-highlight-color"), QColor(0, 255, 0)); + QCOMPARE(iface->configValue(QLatin1String("replace-highlight-color")).value(), QColor(0, 255, 0)); + + iface->setConfigValue(QLatin1String("default-mark-type"), 6); + QCOMPARE(iface->configValue(QLatin1String("default-mark-type")).toInt(), 6); + + iface->setConfigValue(QLatin1String("allow-mark-menu"), true); + QCOMPARE(iface->configValue(QLatin1String("allow-mark-menu")).toBool(), true); + iface->setConfigValue(QLatin1String("allow-mark-menu"), false); + QCOMPARE(iface->configValue(QLatin1String("allow-mark-menu")).toBool(), false); + + iface->setConfigValue(QLatin1String("icon-border-color"), QColor(0, 255, 0)); + QCOMPARE(iface->configValue(QLatin1String("icon-border-color")).value(), QColor(0, 255, 0)); + + iface->setConfigValue(QLatin1String("folding-marker-color"), QColor(0, 255, 0)); + QCOMPARE(iface->configValue(QLatin1String("folding-marker-color")).value(), QColor(0, 255, 0)); + + iface->setConfigValue(QLatin1String("line-number-color"), QColor(0, 255, 0)); + QCOMPARE(iface->configValue(QLatin1String("line-number-color")).value(), QColor(0, 255, 0)); + + iface->setConfigValue(QLatin1String("current-line-number-color"), QColor(0, 255, 0)); + QCOMPARE(iface->configValue(QLatin1String("current-line-number-color")).value(), QColor(0, 255, 0)); + + iface->setConfigValue(QLatin1String("modification-markers"), true); + QCOMPARE(iface->configValue(QLatin1String("modification-markers")).toBool(), true); + iface->setConfigValue(QLatin1String("modification-markers"), false); + QCOMPARE(iface->configValue(QLatin1String("modification-markers")).toBool(), false); + + iface->setConfigValue(QLatin1String("word-count"), true); + QCOMPARE(iface->configValue(QLatin1String("word-count")).toBool(), true); + iface->setConfigValue(QLatin1String("word-count"), false); + QCOMPARE(iface->configValue(QLatin1String("word-count")).toBool(), false); + + iface->setConfigValue(QLatin1String("scrollbar-minimap"), true); + QCOMPARE(iface->configValue(QLatin1String("scrollbar-minimap")).toBool(), true); + iface->setConfigValue(QLatin1String("scrollbar-minimap"), false); + QCOMPARE(iface->configValue(QLatin1String("scrollbar-minimap")).toBool(), false); + + iface->setConfigValue(QLatin1String("scrollbar-preview"), true); + QCOMPARE(iface->configValue(QLatin1String("scrollbar-preview")).toBool(), true); + iface->setConfigValue(QLatin1String("scrollbar-preview"), false); + QCOMPARE(iface->configValue(QLatin1String("scrollbar-preview")).toBool(), false); + + iface->setConfigValue(QLatin1String("font"), QFont("Times", 10, QFont::Bold)); + QCOMPARE(iface->configValue(QLatin1String("font")).value(), QFont("Times", 10, QFont::Bold)); +} + +// kate: indent-mode cstyle; indent-width 4; replace-tabs on; diff --git a/autotests/src/kateview_test.h b/autotests/src/configinterface_test.h similarity index 65% copy from autotests/src/kateview_test.h copy to autotests/src/configinterface_test.h index 672e74e6..108b1172 100644 --- a/autotests/src/kateview_test.h +++ b/autotests/src/configinterface_test.h @@ -1,48 +1,38 @@ /* This file is part of the KDE libraries - Copyright (C) 2010 Milian Wolff + Copyright (C) 2017 Dominik Haumann 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_VIEW_TEST_H -#define KATE_VIEW_TEST_H +#ifndef KATE_CONFIG_INTERFACE_TEST_H +#define KATE_CONFIG_INTERFACE_TEST_H #include -class KateViewTest : public QObject +class KateConfigInterfaceTest : public QObject { Q_OBJECT public: - KateViewTest(); - ~KateViewTest(); + KateConfigInterfaceTest(); + ~KateConfigInterfaceTest(); private Q_SLOTS: - void testReloadMultipleViews(); - void testTabCursorOnReload(); - - void testLowerCaseBlockSelection(); - - void testCoordinatesToCursor(); - void testCursorToCoordinates(); - - void testSelection(); - void testKillline(); - - void testFoldFirstLine(); + void testDocument(); + void testView(); }; -#endif // KATE_VIEW_TEST_H +#endif // KATE_CONFIG_INTERFACE_TEST_H diff --git a/autotests/src/katedocument_test.cpp b/autotests/src/katedocument_test.cpp index 0561e3e9..dd7eb535 100644 --- a/autotests/src/katedocument_test.cpp +++ b/autotests/src/katedocument_test.cpp @@ -1,461 +1,462 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Dominik Haumann 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 "katedocument_test.h" #include "moc_katedocument_test.cpp" #include #include #include #include #include #include #include #include ///TODO: is there a FindValgrind cmake command we could use to /// define this automatically? // comment this out and run the test case with: // valgrind --tool=callgrind --instr-atstart=no ./katedocument_test testSetTextPerformance // or similar // // #define USE_VALGRIND #ifdef USE_VALGRIND #include #endif using namespace KTextEditor; QTEST_MAIN(KateDocumentTest) class MovingRangeInvalidator : public QObject { Q_OBJECT public: - explicit MovingRangeInvalidator(QObject *parent = 0) + explicit MovingRangeInvalidator(QObject *parent = nullptr) : QObject(parent) { } void addRange(MovingRange *range) { m_ranges << range; } QList ranges() const { return m_ranges; } public Q_SLOTS: void aboutToInvalidateMovingInterfaceContent() { qDeleteAll(m_ranges); m_ranges.clear(); } private: QList m_ranges; }; KateDocumentTest::KateDocumentTest() : QObject() { } KateDocumentTest::~KateDocumentTest() { } void KateDocumentTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } // tests: // KTextEditor::DocumentPrivate::insertText with word wrap enabled. It is checked whether the // text is correctly wrapped and whether the moving cursors maintain the correct // position. // see also: http://bugs.kde.org/show_bug.cgi?id=168534 void KateDocumentTest::testWordWrap() { KTextEditor::DocumentPrivate doc(false, false); doc.setWordWrap(true); doc.setWordWrapAt(80); const QString content = QLatin1String(".........1.........2.........3.........4.........5.........6 ........7 ........8"); // space after 7 is now kept // else we kill indentation... const QString firstWrap = QLatin1String(".........1.........2.........3.........4.........5.........6 ........7 \n....x....8"); // space after 6 is now kept // else we kill indentation... const QString secondWrap = QLatin1String(".........1.........2.........3.........4.........5.........6 \n....ooooooooooo....7 ....x....8"); doc.setText(content); MovingCursor *c = doc.newMovingCursor(Cursor(0, 75), MovingCursor::MoveOnInsert); QCOMPARE(doc.text(), content); QCOMPARE(c->toCursor(), Cursor(0, 75)); // type a character at (0, 75) doc.insertText(c->toCursor(), QLatin1String("x")); QCOMPARE(doc.text(), firstWrap); QCOMPARE(c->toCursor(), Cursor(1, 5)); // set cursor to (0, 65) and type "ooooooooooo" c->setPosition(Cursor(0, 65)); doc.insertText(c->toCursor(), QLatin1String("ooooooooooo")); QCOMPARE(doc.text(), secondWrap); QCOMPARE(c->toCursor(), Cursor(1, 15)); } void KateDocumentTest::testReplaceQStringList() { KTextEditor::DocumentPrivate doc(false, false); doc.setWordWrap(false); doc.setText(QLatin1String("asdf\n" "foo\n" "foo\n" "bar\n")); doc.replaceText(Range(1, 0, 3, 0), QStringList() << "new" << "text" << "", false); QCOMPARE(doc.text(), QLatin1String("asdf\n" "new\n" "text\n" "bar\n")); } void KateDocumentTest::testMovingInterfaceSignals() { KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate; QSignalSpy aboutToDeleteSpy(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); QSignalSpy aboutToInvalidateSpy(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); QCOMPARE(doc->revision(), qint64(0)); QCOMPARE(aboutToInvalidateSpy.count(), 0); QCOMPARE(aboutToDeleteSpy.count(), 0); QTemporaryFile f; f.open(); doc->openUrl(QUrl::fromLocalFile(f.fileName())); QCOMPARE(doc->revision(), qint64(0)); //TODO: gets emitted once in closeFile and once in openFile - is that OK? QCOMPARE(aboutToInvalidateSpy.count(), 2); QCOMPARE(aboutToDeleteSpy.count(), 0); doc->documentReload(); QCOMPARE(doc->revision(), qint64(0)); QCOMPARE(aboutToInvalidateSpy.count(), 4); //TODO: gets emitted once in closeFile and once in openFile - is that OK? QCOMPARE(aboutToDeleteSpy.count(), 0); delete doc; QCOMPARE(aboutToInvalidateSpy.count(), 4); QCOMPARE(aboutToDeleteSpy.count(), 1); } void KateDocumentTest::testSetTextPerformance() { const int lines = 150; const int columns = 80; const int rangeLength = 4; const int rangeGap = 1; Q_ASSERT(columns % (rangeLength + rangeGap) == 0); KTextEditor::DocumentPrivate doc; MovingRangeInvalidator invalidator; connect(&doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), &invalidator, SLOT(aboutToInvalidateMovingInterfaceContent())); QString text; QVector ranges; ranges.reserve(lines * columns / (rangeLength + rangeGap)); const QString line = QString().fill('a', columns); for (int l = 0; l < lines; ++l) { text.append(line); text.append('\n'); for (int c = 0; c < columns; c += rangeLength + rangeGap) { ranges << Range(l, c, l, c + rangeLength); } } // replace QBENCHMARK { // init doc.setText(text); foreach (const Range &range, ranges) { invalidator.addRange(doc.newMovingRange(range)); } QCOMPARE(invalidator.ranges().size(), ranges.size()); #ifdef USE_VALGRIND CALLGRIND_START_INSTRUMENTATION #endif doc.setText(text); #ifdef USE_VALGRIND CALLGRIND_STOP_INSTRUMENTATION #endif QCOMPARE(doc.text(), text); QVERIFY(invalidator.ranges().isEmpty()); } } void KateDocumentTest::testRemoveTextPerformance() { const int lines = 5000; const int columns = 80; KTextEditor::DocumentPrivate doc; QString text; const QString line = QString().fill('a', columns); for (int l = 0; l < lines; ++l) { text.append(line); text.append('\n'); } doc.setText(text); // replace QBENCHMARK_ONCE { #ifdef USE_VALGRIND CALLGRIND_START_INSTRUMENTATION #endif doc.editStart(); doc.removeText(doc.documentRange()); doc.editEnd(); #ifdef USE_VALGRIND CALLGRIND_STOP_INSTRUMENTATION #endif } } void KateDocumentTest::testForgivingApiUsage() { KTextEditor::DocumentPrivate doc; QVERIFY(doc.isEmpty()); QVERIFY(doc.replaceText(Range(0, 0, 100, 100), "asdf")); QCOMPARE(doc.text(), QString("asdf")); QCOMPARE(doc.lines(), 1); QVERIFY(doc.replaceText(Range(2, 5, 2, 100), "asdf")); QCOMPARE(doc.lines(), 3); QCOMPARE(doc.text(), QLatin1String("asdf\n\n asdf")); QVERIFY(doc.removeText(Range(0, 0, 1000, 1000))); QVERIFY(doc.removeText(Range(0, 0, 0, 100))); QVERIFY(doc.isEmpty()); doc.insertText(Cursor(100, 0), "foobar"); QCOMPARE(doc.line(100), QString("foobar")); doc.setText("nY\nnYY\n"); QVERIFY(doc.removeText(Range(0, 0, 0, 1000))); } void KateDocumentTest::testReplaceTabs() { KTextEditor::DocumentPrivate doc; - auto view = static_cast(doc.createView(Q_NULLPTR)); + auto view = static_cast(doc.createView(nullptr)); auto reset = [&]() { doc.setText(" Hi!"); view->setCursorPosition(Cursor(0, 0)); }; doc.setHighlightingMode ("C++"); doc.config()->setTabWidth(4); doc.config()->setIndentationMode("cppstyle"); // no replace tabs, no indent pasted text doc.setConfigValue("replace-tabs", false); doc.setConfigValue("indent-pasted-text", false); reset(); doc.insertText(Cursor(0, 0), "\t"); QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); reset(); doc.typeChars(view, "\t"); QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); reset(); doc.paste(view, "some\ncode\n 3\nhi\n"); QCOMPARE(doc.text(), QStringLiteral("some\ncode\n 3\nhi\n Hi!")); // now, enable replace tabs doc.setConfigValue("replace-tabs", true); reset(); doc.insertText(Cursor(0, 0), "\t"); // calling insertText does not replace tabs QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); reset(); doc.typeChars(view, "\t"); // typing text replaces tabs QCOMPARE(doc.text(), QStringLiteral(" Hi!")); reset(); doc.paste(view, "\t"); // pasting text does not with indent-pasted off QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); doc.setConfigValue("indent-pasted-text", true); doc.setText("int main() {\n \n}"); view->setCursorPosition(Cursor(1, 4)); doc.paste(view, "\tHi"); // ... and it still does not with indent-pasted on. // This behaviour is up to discussion. QCOMPARE(doc.text(), QStringLiteral("int main() {\n Hi\n}")); reset(); doc.paste(view, "some\ncode\n 3\nhi"); QCOMPARE(doc.text(), QStringLiteral("some\ncode\n3\nhi Hi!")); } /** * Provides slots to check data sent in specific signals. Slot names are derived from corresponding test names. */ class SignalHandler : public QObject { Q_OBJECT public Q_SLOTS: void slotMultipleLinesRemoved(KTextEditor::Document *, const KTextEditor::Range &, const QString &oldText) { QCOMPARE(oldText, QString("line2\nline3\n")); } void slotNewlineInserted(KTextEditor::Document *, const KTextEditor::Range &range) { QCOMPARE(range, Range(Cursor(1, 4), Cursor(2, 0))); } }; void KateDocumentTest::testRemoveMultipleLines() { KTextEditor::DocumentPrivate doc; doc.setText("line1\n" "line2\n" "line3\n" "line4\n"); SignalHandler handler; connect(&doc, &KTextEditor::DocumentPrivate::textRemoved, &handler, &SignalHandler::slotMultipleLinesRemoved); doc.removeText(Range(1, 0, 3, 0)); } void KateDocumentTest::testInsertNewline() { KTextEditor::DocumentPrivate doc; doc.setText("this is line\n" "this is line2\n"); SignalHandler handler; connect(&doc, SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), &handler, SLOT(slotNewlineInserted(KTextEditor::Document*,KTextEditor::Range))); doc.editWrapLine(1, 4); } void KateDocumentTest::testInsertAfterEOF() { KTextEditor::DocumentPrivate doc; doc.setText("line0\n" "line1"); const QString input = QLatin1String("line3\n" "line4"); const QString expected = QLatin1String("line0\n" "line1\n" "\n" "line3\n" "line4"); doc.insertText(KTextEditor::Cursor(3, 0), input); QCOMPARE(doc.text(), expected); } // we have two different ways of creating the checksum: // in KateFileLoader and KTextEditor::DocumentPrivate::createDigest. Make // sure, these two implementations result in the same checksum. void KateDocumentTest::testDigest() { - // git hash of data/md5checksum.txt: 95c6f5f5dbb36abf1151b2a8501b37282e268d13 + // Git hash of test file (git hash-object data/md5checksum.txt): + const QByteArray gitHash = "696e6d35a5d9cc28d16e56bdcb2d2a88126b814e"; // QCryptographicHash is used, therefore we need fromHex here - const QByteArray fileDigest = QByteArray::fromHex("95c6f5f5dbb36abf1151b2a8501b37282e268d13"); + const QByteArray fileDigest = QByteArray::fromHex(gitHash); // make sure, Kate::TextBuffer and KTextEditor::DocumentPrivate::createDigest() equal KTextEditor::DocumentPrivate doc; doc.openUrl(QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"md5checksum.txt"))); const QByteArray bufferDigest(doc.checksum()); QVERIFY(doc.createDigest()); const QByteArray docDigest(doc.checksum()); QCOMPARE(bufferDigest, fileDigest); QCOMPARE(docDigest, fileDigest); } void KateDocumentTest::testDefStyleNum() { KTextEditor::DocumentPrivate doc; doc.setText("foo\nbar\nasdf"); QCOMPARE(doc.defStyleNum(0, 0), 0); } void KateDocumentTest::testTypeCharsWithSurrogateAndNewLine() { KTextEditor::DocumentPrivate doc; - auto view = static_cast(doc.createView(Q_NULLPTR)); + auto view = static_cast(doc.createView(nullptr)); const uint surrogateUcs4String[] = { 0x1f346, '\n', 0x1f346, 0 }; const auto surrogateString = QString::fromUcs4(surrogateUcs4String); doc.typeChars(view, surrogateString); QCOMPARE(doc.text(), surrogateString); } void KateDocumentTest::testRemoveComposedCharacters() { KTextEditor::DocumentPrivate doc; - auto view = static_cast(doc.createView(Q_NULLPTR)); + auto view = static_cast(doc.createView(nullptr)); doc.setText(QString::fromUtf8("व्यक्तियों")); doc.del(view, Cursor(0, 0)); QCOMPARE(doc.text(), QString::fromUtf8(("क्तियों"))); doc.backspace(view, Cursor(0, 7)); QCOMPARE(doc.text(), QString::fromUtf8(("क्ति"))); } #include "katedocument_test.moc" diff --git a/autotests/src/kateencodingtest.cpp b/autotests/src/kateencodingtest.cpp index d1c2efa4..06d7c4f5 100644 --- a/autotests/src/kateencodingtest.cpp +++ b/autotests/src/kateencodingtest.cpp @@ -1,64 +1,64 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * Copyright (C) 2010 Dominik Haumann * * 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 #include "katetextbuffer.h" #include #include int main(int argc, char *argv[]) { // construct core app QCoreApplication app(argc, argv); // test mode KTextEditor::EditorPrivate::enableUnitTestMode(); // get arguments QString encoding = app.arguments().at(1); QString inFile = app.arguments().at(2); QString outFile = app.arguments().at(3); - Kate::TextBuffer buffer(0); + Kate::TextBuffer buffer(nullptr); // set codec buffer.setFallbackTextCodec(QTextCodec::codecForName("ISO 8859-15")); buffer.setTextCodec(QTextCodec::codecForName(encoding.toLatin1())); // switch to Mac EOL, this will test eol detection, as files are normal unix or dos buffer.setEndOfLineMode(Kate::TextBuffer::eolMac); // load file bool encodingErrors = false; bool tooLongLines = false; int longestLineLoaded; if (!buffer.load(inFile, encodingErrors, tooLongLines, longestLineLoaded, false) || encodingErrors) { return 1; } // save file if (!buffer.save(outFile)) { return 1; } return 0; } diff --git a/autotests/src/katefoldingtest.cpp b/autotests/src/katefoldingtest.cpp index 79f88feb..63fe18de 100644 --- a/autotests/src/katefoldingtest.cpp +++ b/autotests/src/katefoldingtest.cpp @@ -1,150 +1,150 @@ /* This file is part of the Kate project. * * Copyright (C) 2013 Dominik Haumann * * 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 "katefoldingtest.h" #include #include #include #include #include #include #include using namespace KTextEditor; QTEST_MAIN(KateFoldingTest) void KateFoldingTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void KateFoldingTest::cleanupTestCase() { } // This is a unit test for bug 311866 (http://bugs.kde.org/show_bug.cgi?id=311866) // It loads 5 lines of C++ code, places the cursor in line 4, and then folds // the code. // Expected behavior: the cursor should be moved so it stays visible // Buggy behavior: the cursor is hidden, and moving the hidden cursor crashes kate void KateFoldingTest::testCrash311866() { KTextEditor::DocumentPrivate doc; const QUrl url = QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"bug311866.cpp")); doc.openUrl(url); doc.setHighlightingMode("C++"); doc.buffer().ensureHighlighted(6); - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); view->setCursorPosition(Cursor(3, 0)); QTest::qWait(100); view->slotFoldToplevelNodes(); doc.buffer().ensureHighlighted(6); qDebug() << "!!! Does the next line crash?"; view->up(); } // This test makes sure that, // - if you have selected text // - that spans a folded range, // - and the cursor is at the end of the text selection, // - and you type a char, e.g. 'x', // then the resulting text is correct, and changing region // visibility does not mess around with the text cursor. // // See https://bugs.kde.org/show_bug.cgi?id=295632 void KateFoldingTest::testBug295632() { KTextEditor::DocumentPrivate doc; QString text = "oooossssssss\n" "{\n" "\n" "}\n" "ssssss----------"; doc.setText(text); // view must be visible... - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); qint64 foldId = view->textFolding().newFoldingRange(KTextEditor::Range(1, 0, 3, 1)); view->textFolding().foldRange(foldId); QVERIFY(view->textFolding().isLineVisible(0)); QVERIFY(view->textFolding().isLineVisible(1)); QVERIFY(!view->textFolding().isLineVisible(2)); QVERIFY(!view->textFolding().isLineVisible(3)); QVERIFY(view->textFolding().isLineVisible(4)); view->setSelection(Range(Cursor(0, 4), Cursor(4, 6))); view->setCursorPosition(Cursor(4, 6)); QTest::qWait(100); doc.typeChars(view, "x"); QTest::qWait(100); QString line = doc.line(0); QCOMPARE(line, QString("oooox----------")); } // This testcase tests the follwing: // - the cursor is first set into the word 'hello' // - then lines 0-3 are folded. // - the real text cursor is still in the word 'hello' // - the important issue is: The display cursor must be in the visible line range // --> if this test passes, KateViewInternal's m_displayCursor is properly adapted. void KateFoldingTest::testCrash367466() { KTextEditor::DocumentPrivate doc; QString text = "fold begin\n" "\n" "\n" "fold end\n" "hello\n" "world\n"; doc.setText(text); // view must be visible... - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); view->setCursorPosition(KTextEditor::Cursor(5, 2)); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(5, 2)); qint64 foldId = view->textFolding().newFoldingRange(KTextEditor::Range(0, 0, 3, 8)); view->textFolding().foldRange(foldId); QVERIFY(view->textFolding().isLineVisible(0)); QVERIFY(!view->textFolding().isLineVisible(1)); QVERIFY(!view->textFolding().isLineVisible(2)); QVERIFY(!view->textFolding().isLineVisible(3)); QVERIFY(view->textFolding().isLineVisible(4)); QVERIFY(view->textFolding().isLineVisible(5)); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(5, 2)); view->up(); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(4, 2)); } diff --git a/autotests/src/katesyntaxtest.cpp b/autotests/src/katesyntaxtest.cpp index 04d74bf2..54db74e0 100644 --- a/autotests/src/katesyntaxtest.cpp +++ b/autotests/src/katesyntaxtest.cpp @@ -1,134 +1,134 @@ /* This file is part of the Kate project. * * Copyright (C) 2013 Dominik Haumann * * 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 "katesyntaxtest.h" #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(KateSyntaxTest) void KateSyntaxTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void KateSyntaxTest::cleanupTestCase() { } void KateSyntaxTest::testSyntaxHighlighting_data() { QTest::addColumn("hlTestCase"); /** * check for directories, one dir == one hl */ - const QString testDir(QLatin1String(TEST_DATA_DIR) + QLatin1String("/syntax/")); + const QString testDir(QLatin1String(TEST_DATA_DIR) + QLatin1String("syntax/")); QDirIterator contents(testDir); while (contents.hasNext()) { const QString hlDir = contents.next(); const QFileInfo info(hlDir); - if (!info.isDir() || hlDir.contains(QLatin1Char('.'))) { + if (!info.isDir() || info.fileName().contains(QLatin1Char('.'))) { continue; } /** * now: get the tests per hl */ QDirIterator contents(hlDir); while (contents.hasNext()) { const QString hlTestCase = contents.next(); const QFileInfo info(hlTestCase); if (!info.isFile()) { continue; } QTest::newRow(info.absoluteFilePath().toLocal8Bit().constData()) << info.absoluteFilePath(); } } } void KateSyntaxTest::testSyntaxHighlighting() { /** * get current test case */ QFETCH(QString, hlTestCase); /** * create a document with a view to be able to export stuff */ KTextEditor::DocumentPrivate doc; - auto view = static_cast(doc.createView(Q_NULLPTR)); + auto view = static_cast(doc.createView(nullptr)); /** * load the test case * enforce UTF-8 to avoid locale problems */ QUrl url; url.setScheme(QLatin1String("file")); url.setPath(hlTestCase); doc.setEncoding(QStringLiteral("UTF-8")); QVERIFY(doc.openUrl(url)); /** * compute needed dirs */ const QFileInfo info(hlTestCase); const QString resultDir(info.absolutePath() + QLatin1String("/results/")); const QString currentResult(resultDir + info.fileName() + QLatin1String(".current.html")); const QString referenceResult(resultDir + info.fileName() + QLatin1String(".reference.html")); /** * export the result */ view->exportHtmlToFile(currentResult); /** * verify the result against reference */ QProcess diff; diff.setProcessChannelMode(QProcess::MergedChannels); QStringList args; args << QLatin1String("-u") << (referenceResult) << (currentResult); diff.start(QLatin1String("diff"), args); diff.waitForFinished(); QByteArray out = diff.readAllStandardOutput(); if (!out.isEmpty()) { printf("DIFF:\n"); QList outLines = out.split('\n'); Q_FOREACH(const QByteArray &line, outLines) { printf("%s\n", qPrintable(line)); } } QCOMPARE(QString::fromLocal8Bit(out), QString()); QCOMPARE(diff.exitCode(), EXIT_SUCCESS); } diff --git a/autotests/src/katetextbuffertest.cpp b/autotests/src/katetextbuffertest.cpp index 4bc0a6c5..bf252497 100644 --- a/autotests/src/katetextbuffertest.cpp +++ b/autotests/src/katetextbuffertest.cpp @@ -1,468 +1,502 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * Copyright (C) 2010 Dominik Haumann * * 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 #include "katetextbuffertest.h" #include "katetextbuffer.h" #include "katetextcursor.h" #include "katetextfolding.h" QTEST_MAIN(KateTextBufferTest) KateTextBufferTest::KateTextBufferTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } KateTextBufferTest::~KateTextBufferTest() { } void KateTextBufferTest::basicBufferTest() { // construct an empty text buffer - Kate::TextBuffer buffer(0, 1); + Kate::TextBuffer buffer(nullptr, 1); // one line per default QVERIFY(buffer.lines() == 1); QVERIFY(buffer.text() == QString()); //FIXME: use QTestLib macros for checking the correct state // start editing buffer.startEditing(); // end editing buffer.finishEditing(); } void KateTextBufferTest::wrapLineTest() { // construct an empty text buffer - Kate::TextBuffer buffer(0, 1); + Kate::TextBuffer buffer(nullptr, 1); // wrap first empty line -> we should have two empty lines buffer.startEditing(); buffer.wrapLine(KTextEditor::Cursor(0, 0)); buffer.finishEditing(); buffer.debugPrint(QLatin1String("Two empty lines")); QVERIFY(buffer.text() == QLatin1String("\n")); // unwrap again -> only one empty line buffer.startEditing(); buffer.unwrapLine(1); buffer.finishEditing(); // print debug buffer.debugPrint(QLatin1String("Empty Buffer")); QVERIFY(buffer.text() == QString()); } void KateTextBufferTest::insertRemoveTextTest() { // construct an empty text buffer - Kate::TextBuffer buffer(0, 1); + Kate::TextBuffer buffer(nullptr, 1); // wrap first line buffer.startEditing(); buffer.wrapLine(KTextEditor::Cursor(0, 0)); buffer.finishEditing(); buffer.debugPrint(QLatin1String("Two empty lines")); QVERIFY(buffer.text() == QLatin1String("\n")); // remember second line Kate::TextLine second = buffer.line(1); // unwrap second line buffer.startEditing(); buffer.unwrapLine(1); buffer.finishEditing(); buffer.debugPrint(QLatin1String("One empty line")); QVERIFY(buffer.text() == QString()); // second text line should be still there //const QString &secondText = second->text (); //QVERIFY (secondText == "") // insert text buffer.startEditing(); buffer.insertText(KTextEditor::Cursor(0, 0), QLatin1String("testremovetext")); buffer.finishEditing(); buffer.debugPrint(QLatin1String("One line")); QVERIFY(buffer.text() == QLatin1String("testremovetext")); // remove text buffer.startEditing(); buffer.removeText(KTextEditor::Range(KTextEditor::Cursor(0, 4), KTextEditor::Cursor(0, 10))); buffer.finishEditing(); buffer.debugPrint(QLatin1String("One line")); QVERIFY(buffer.text() == QLatin1String("testtext")); // wrap text buffer.startEditing(); buffer.wrapLine(KTextEditor::Cursor(0, 2)); buffer.finishEditing(); buffer.debugPrint(QLatin1String("Two line")); QVERIFY(buffer.text() == QLatin1String("te\nsttext")); // unwrap text buffer.startEditing(); buffer.unwrapLine(1); buffer.finishEditing(); buffer.debugPrint(QLatin1String("One line")); QVERIFY(buffer.text() == QLatin1String("testtext")); } void KateTextBufferTest::cursorTest() { // last buffer content, for consistence checks QString lastBufferContent; // test with different block sizes for (int i = 1; i <= 4; ++i) { // construct an empty text buffer - Kate::TextBuffer buffer(0, i); + Kate::TextBuffer buffer(nullptr, i); // wrap first line buffer.startEditing(); buffer.insertText(KTextEditor::Cursor(0, 0), QLatin1String("sfdfjdsklfjlsdfjlsdkfjskldfjklsdfjklsdjkfl")); buffer.wrapLine(KTextEditor::Cursor(0, 8)); buffer.wrapLine(KTextEditor::Cursor(1, 8)); buffer.wrapLine(KTextEditor::Cursor(2, 8)); buffer.finishEditing(); buffer.debugPrint(QLatin1String("Cursor buffer")); // construct cursor Kate::TextCursor *cursor1 = new Kate::TextCursor(buffer, KTextEditor::Cursor(0, 0), Kate::TextCursor::MoveOnInsert); QVERIFY(cursor1->toCursor() == KTextEditor::Cursor(0, 0)); //printf ("cursor %d, %d\n", cursor1->line(), cursor1->column()); //Kate::TextCursor *cursor2 = new Kate::TextCursor (buffer, KTextEditor::Cursor (1, 8), Kate::TextCursor::MoveOnInsert); //printf ("cursor %d, %d\n", cursor2->line(), cursor2->column()); //Kate::TextCursor *cursor3 = new Kate::TextCursor (buffer, KTextEditor::Cursor (0, 123), Kate::TextCursor::MoveOnInsert); //printf ("cursor %d, %d\n", cursor3->line(), cursor3->column()); //Kate::TextCursor *cursor4 = new Kate::TextCursor (buffer, KTextEditor::Cursor (1323, 1), Kate::TextCursor::MoveOnInsert); //printf ("cursor %d, %d\n", cursor4->line(), cursor4->column()); // insert text buffer.startEditing(); buffer.insertText(KTextEditor::Cursor(0, 0), QLatin1String("hallo")); buffer.finishEditing(); buffer.debugPrint(QLatin1String("Cursor buffer")); //printf ("cursor %d, %d\n", cursor1->line(), cursor1->column()); QVERIFY(cursor1->toCursor() == KTextEditor::Cursor(0, 5)); // remove text buffer.startEditing(); buffer.removeText(KTextEditor::Range(KTextEditor::Cursor(0, 4), KTextEditor::Cursor(0, 10))); buffer.finishEditing(); buffer.debugPrint(QLatin1String("Cursor buffer")); //printf ("cursor %d, %d\n", cursor1->line(), cursor1->column()); QVERIFY(cursor1->toCursor() == KTextEditor::Cursor(0, 4)); // wrap line buffer.startEditing(); buffer.wrapLine(KTextEditor::Cursor(0, 3)); buffer.finishEditing(); buffer.debugPrint(QLatin1String("Cursor buffer")); //printf ("cursor %d, %d\n", cursor1->line(), cursor1->column()); QVERIFY(cursor1->toCursor() == KTextEditor::Cursor(1, 1)); // unwrap line buffer.startEditing(); buffer.unwrapLine(1); buffer.finishEditing(); buffer.debugPrint(QLatin1String("Cursor buffer")); //printf ("cursor %d, %d\n", cursor1->line(), cursor1->column()); QVERIFY(cursor1->toCursor() == KTextEditor::Cursor(0, 4)); // verify content if (i > 1) { QVERIFY(lastBufferContent == buffer.text()); } // remember content lastBufferContent = buffer.text(); } } void KateTextBufferTest::foldingTest() { // construct an empty text buffer & folding info - Kate::TextBuffer buffer(0, 1); + Kate::TextBuffer buffer(nullptr, 1); Kate::TextFolding folding(buffer); // insert some text buffer.startEditing(); for (int i = 0; i < 100; ++i) { buffer.insertText(KTextEditor::Cursor(i, 0), QLatin1String("1234567890")); if (i < 99) { buffer.wrapLine(KTextEditor::Cursor(i, 10)); } } buffer.finishEditing(); QVERIFY(buffer.lines() == 100); // starting with empty folding! folding.debugPrint(QLatin1String("Empty Folding")); QVERIFY(folding.debugDump() == QLatin1String("tree - folded ")); // check visibility QVERIFY(folding.isLineVisible(0)); QVERIFY(folding.isLineVisible(99)); // all visible QVERIFY(folding.visibleLines() == 100); // we shall be able to insert new range QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(5, 0), KTextEditor::Cursor(10, 0))) == 0); // we shall have now exactly one range toplevel, that is not folded! folding.debugPrint(QLatin1String("One Toplevel Fold")); QVERIFY(folding.debugDump() == QLatin1String("tree [5:0 10:0] - folded ")); // fold the range! QVERIFY(folding.foldRange(0)); folding.debugPrint(QLatin1String("One Toplevel Fold - Folded")); QVERIFY(folding.debugDump() == QLatin1String("tree [5:0 f 10:0] - folded [5:0 f 10:0]")); // check visibility QVERIFY(folding.isLineVisible(5)); for (int i = 6; i <= 10; ++i) { QVERIFY(!folding.isLineVisible(i)); } QVERIFY(folding.isLineVisible(11)); // 5 lines are hidden QVERIFY(folding.visibleLines() == (100 - 5)); // check line mapping QVERIFY(folding.visibleLineToLine(5) == 5); for (int i = 6; i <= 50; ++i) { QVERIFY(folding.visibleLineToLine(i) == (i + 5)); } // there shall be one range starting at 5 QVector > forLine = folding.foldingRangesStartingOnLine(5); QVERIFY(forLine.size() == 1); QVERIFY(forLine[0].first == 0); QVERIFY(forLine[0].second & Kate::TextFolding::Folded); // we shall be able to insert new range QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(20, 0), KTextEditor::Cursor(30, 0)), Kate::TextFolding::Folded) == 1); // we shall have now exactly two range toplevel folding.debugPrint(QLatin1String("Two Toplevel Folds")); QVERIFY(folding.debugDump() == QLatin1String("tree [5:0 f 10:0] [20:0 f 30:0] - folded [5:0 f 10:0] [20:0 f 30:0]")); // check visibility QVERIFY(folding.isLineVisible(5)); for (int i = 6; i <= 10; ++i) { QVERIFY(!folding.isLineVisible(i)); } QVERIFY(folding.isLineVisible(11)); QVERIFY(folding.isLineVisible(20)); for (int i = 21; i <= 30; ++i) { QVERIFY(!folding.isLineVisible(i)); } QVERIFY(folding.isLineVisible(31)); // 15 lines are hidden QVERIFY(folding.visibleLines() == (100 - 5 - 10)); // check line mapping QVERIFY(folding.visibleLineToLine(5) == 5); for (int i = 6; i <= 15; ++i) { QVERIFY(folding.visibleLineToLine(i) == (i + 5)); } for (int i = 16; i <= 50; ++i) { QVERIFY(folding.visibleLineToLine(i) == (i + 15)); } // check line mapping QVERIFY(folding.lineToVisibleLine(5) == 5); for (int i = 11; i <= 20; ++i) { QVERIFY(folding.lineToVisibleLine(i) == (i - 5)); } for (int i = 31; i <= 40; ++i) { QVERIFY(folding.lineToVisibleLine(i) == (i - 15)); } // there shall be one range starting at 20 forLine = folding.foldingRangesStartingOnLine(20); QVERIFY(forLine.size() == 1); QVERIFY(forLine[0].first == 1); QVERIFY(forLine[0].second & Kate::TextFolding::Folded); // this shall fail to be inserted, as it badly overlaps with the first range! QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(6, 0), KTextEditor::Cursor(15, 0)), Kate::TextFolding::Folded) == -1); // this shall fail to be inserted, as it badly overlaps with the second range! QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(15, 0), KTextEditor::Cursor(25, 0)), Kate::TextFolding::Folded) == -1); // we shall still have now exactly two range toplevel folding.debugPrint(QLatin1String("Still Two Toplevel Folds")); QVERIFY(folding.debugDump() == QLatin1String("tree [5:0 f 10:0] [20:0 f 30:0] - folded [5:0 f 10:0] [20:0 f 30:0]")); // still 15 lines are hidden QVERIFY(folding.visibleLines() == (100 - 5 - 10)); // we shall be able to insert new range, should lead to nested folds! QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(15, 0), KTextEditor::Cursor(35, 0)), Kate::TextFolding::Folded) == 2); // we shall have now exactly two range toplevel and one embedded fold folding.debugPrint(QLatin1String("Two Toplevel Folds, One Nested Fold")); QVERIFY(folding.debugDump() == QLatin1String("tree [5:0 f 10:0] [15:0 f [20:0 f 30:0] 35:0] - folded [5:0 f 10:0] [15:0 f 35:0]")); // 25 lines are hidden QVERIFY(folding.visibleLines() == (100 - 5 - 20)); // check line mapping QVERIFY(folding.lineToVisibleLine(5) == 5); for (int i = 11; i <= 15; ++i) { QVERIFY(folding.lineToVisibleLine(i) == (i - 5)); } // special case: hidden lines, should fall ack to last visible one! for (int i = 16; i <= 35; ++i) { QVERIFY(folding.lineToVisibleLine(i) == 10); } for (int i = 36; i <= 40; ++i) { QVERIFY(folding.lineToVisibleLine(i) == (i - 25)); } // we shall be able to insert new range, should lead to nested folds! QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(0, 0), KTextEditor::Cursor(50, 0)), Kate::TextFolding::Folded) == 3); // we shall have now exactly one range toplevel and many embedded fold folding.debugPrint(QLatin1String("One Toplevel + Embedded Folds")); QVERIFY(folding.debugDump() == QLatin1String("tree [0:0 f [5:0 f 10:0] [15:0 f [20:0 f 30:0] 35:0] 50:0] - folded [0:0 f 50:0]")); // there shall still be one range starting at 20 forLine = folding.foldingRangesStartingOnLine(20); QVERIFY(forLine.size() == 1); QVERIFY(forLine[0].first == 1); QVERIFY(forLine[0].second & Kate::TextFolding::Folded); // add more regions starting at 20 QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(20, 5), KTextEditor::Cursor(24, 0)), Kate::TextFolding::Folded) == 4); QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(20, 3), KTextEditor::Cursor(25, 0)), Kate::TextFolding::Folded) == 5); folding.debugPrint(QLatin1String("More ranges at 20")); // there shall still be three ranges starting at 20 forLine = folding.foldingRangesStartingOnLine(20); QVERIFY(forLine.size() == 3); QVERIFY(forLine[0].first == 1); QVERIFY(forLine[0].second & Kate::TextFolding::Folded); QVERIFY(forLine[1].first == 5); QVERIFY(forLine[1].second & Kate::TextFolding::Folded); QVERIFY(forLine[2].first == 4); QVERIFY(forLine[2].second & Kate::TextFolding::Folded); // 50 lines are hidden QVERIFY(folding.visibleLines() == (100 - 50)); // save state QJsonDocument folds = folding.exportFoldingRanges(); QString textDump = folding.debugDump(); // clear folds folding.clear(); QVERIFY(folding.debugDump() == QLatin1String("tree - folded ")); // restore state folding.importFoldingRanges(folds); QVERIFY(folding.debugDump() == textDump); } void KateTextBufferTest::nestedFoldingTest() { // construct an empty text buffer & folding info - Kate::TextBuffer buffer(0, 1); + Kate::TextBuffer buffer(nullptr, 1); Kate::TextFolding folding(buffer); // insert two nested folds in 5 lines buffer.startEditing(); for (int i = 0; i < 4; ++i) { buffer.wrapLine(KTextEditor::Cursor(0, 0)); } buffer.finishEditing(); QVERIFY(buffer.lines() == 5); // folding for line 1 QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(0, 0), KTextEditor::Cursor(3, 0)), Kate::TextFolding::Folded) == 0); QVERIFY(folding.newFoldingRange(KTextEditor::Range(KTextEditor::Cursor(1, 0), KTextEditor::Cursor(2, 0)), Kate::TextFolding::Folded) == 1); QVERIFY(folding.foldRange(1)); QVERIFY(folding.foldRange(0)); QVERIFY(folding.unfoldRange(0)); QVERIFY(folding.unfoldRange(1)); } void KateTextBufferTest::saveFileInUnwritableFolder() { // create temp dir and get file name inside QTemporaryDir dir; QVERIFY(dir.isValid()); const QString folder_name = dir.path(); const QString file_path = folder_name + QLatin1String("/foo"); QFile f(file_path); QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate)); f.write("1234567890"); QVERIFY(f.flush()); f.close(); QFile::setPermissions(folder_name, QFile::ExeOwner); - Kate::TextBuffer buffer(0, 1); + Kate::TextBuffer buffer(nullptr, 1); buffer.setTextCodec(QTextCodec::codecForName("UTF-8")); buffer.setFallbackTextCodec(QTextCodec::codecForName("UTF-8")); bool a, b; int c; buffer.load(file_path, a, b, c, true); buffer.clear(); buffer.startEditing(); buffer.insertText(KTextEditor::Cursor(0, 0), QLatin1String("ABC")); buffer.finishEditing(); qDebug() << buffer.text(); buffer.save(file_path); f.open(QIODevice::ReadOnly); QCOMPARE(f.readAll(), QByteArray("ABC")); f.close(); QFile::setPermissions(folder_name, QFile::WriteOwner | QFile::ExeOwner); QVERIFY(f.remove()); QVERIFY(dir.remove()); } + +void KateTextBufferTest::saveFileWithElevatedPrivileges() +{ + // create temp dir and get file name inside + QTemporaryDir dir; + QVERIFY(dir.isValid()); + const QString file_path = dir.path() + QLatin1String("/foo"); + + QFile f(file_path); + QVERIFY(f.open(QIODevice::WriteOnly | QIODevice::Truncate)); + f.write("1234567890"); + QVERIFY(f.flush()); + f.close(); + + Kate::TextBuffer buffer(nullptr, 1, true); + buffer.setTextCodec(QTextCodec::codecForName("UTF-8")); + buffer.setFallbackTextCodec(QTextCodec::codecForName("UTF-8")); + bool a, b; + int c; + buffer.load(file_path, a, b, c, true); + buffer.clear(); + buffer.startEditing(); + buffer.insertText(KTextEditor::Cursor(0, 0), QLatin1String("ABC")); + buffer.finishEditing(); + qDebug() << buffer.text(); + buffer.save(file_path); + + f.open(QIODevice::ReadOnly); + QCOMPARE(f.readAll(), QByteArray("ABC")); + f.close(); + + QVERIFY(f.remove()); + QVERIFY(dir.remove()); +} diff --git a/autotests/src/katetextbuffertest.h b/autotests/src/katetextbuffertest.h index 0ddcff08..7a3df3c1 100644 --- a/autotests/src/katetextbuffertest.h +++ b/autotests/src/katetextbuffertest.h @@ -1,45 +1,46 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * Copyright (C) 2010 Dominik Haumann * 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 KATEBUFFERTEST_H #define KATEBUFFERTEST_H #include #include class KateTextBufferTest : public QObject { Q_OBJECT public: KateTextBufferTest(); virtual ~KateTextBufferTest(); private Q_SLOTS: void basicBufferTest(); void wrapLineTest(); void insertRemoveTextTest(); void cursorTest(); void foldingTest(); void nestedFoldingTest(); void saveFileInUnwritableFolder(); + void saveFileWithElevatedPrivileges(); }; #endif // KATEBUFFERTEST_H diff --git a/autotests/src/kateview_test.cpp b/autotests/src/kateview_test.cpp index 950184f4..aab724e9 100644 --- a/autotests/src/kateview_test.cpp +++ b/autotests/src/kateview_test.cpp @@ -1,320 +1,404 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Milian Wolff 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 "kateview_test.h" #include "moc_kateview_test.cpp" #include #include #include #include #include #include #include #include #include using namespace KTextEditor; QTEST_MAIN(KateViewTest) KateViewTest::KateViewTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } KateViewTest::~KateViewTest() { } void KateViewTest::testCoordinatesToCursor() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("Hi World!\nHi\n"); - KTextEditor::View* view1 = static_cast(doc.createView(Q_NULLPTR)); + KTextEditor::View* view1 = static_cast(doc.createView(nullptr)); view1->show(); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); // behind end of line should give an invalid cursor QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); + // check consistency between cursorToCoordinate(view->cursorPosition() and cursorPositionCoordinates() + // random position + view1->setCursorPosition(KTextEditor::Cursor(0, 3)); + QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(view1->cursorPosition())), + KTextEditor::Cursor(0, 3)); + QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 3)); + // end of line + view1->setCursorPosition(KTextEditor::Cursor(0, 9)); + QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 9))), + KTextEditor::Cursor(0, 9)); + QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 9)); + // empty line + view1->setCursorPosition(KTextEditor::Cursor(2, 0)); + QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(2, 0))), + KTextEditor::Cursor(2, 0)); + QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(2, 0)); + // same test again, but with message widget on top visible KTextEditor::Message *message = new KTextEditor::Message("Jo World!", KTextEditor::Message::Information); doc.postMessage(message); // wait 500ms until show animation is finished, so the message widget is visible QTest::qWait(500); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); // behind end of line should give an invalid cursor QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); } void KateViewTest::testCursorToCoordinates() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("int a;"); - KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, 0); + KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->config()->setDynWordWrap(true); view->show(); // don't crash, see https://bugs.kde.org/show_bug.cgi?id=337863 view->cursorToCoordinate(Cursor(0, 0)); view->cursorToCoordinate(Cursor(1, 0)); view->cursorToCoordinate(Cursor(-1, 0)); } void KateViewTest::testReloadMultipleViews() { QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); const QString line = "const char* foo = \"asdf\"\n"; for (int i = 0; i < 200; ++i) { stream << line; } stream << flush; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.highlightingMode(), QString("C++")); - KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, 0); - KTextEditor::ViewPrivate *view2 = new KTextEditor::ViewPrivate(&doc, 0); + KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); + KTextEditor::ViewPrivate *view2 = new KTextEditor::ViewPrivate(&doc, nullptr); view1->show(); view2->show(); QCOMPARE(doc.views().count(), 2); QVERIFY(doc.documentReload()); } void KateViewTest::testTabCursorOnReload() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); stream << "\tfoo\n"; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); - KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, 0); + KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); const KTextEditor::Cursor cursor(0, 4); view->setCursorPosition(cursor); QCOMPARE(view->cursorPosition(), cursor); QVERIFY(doc.documentReload()); QCOMPARE(view->cursorPosition(), cursor); } void KateViewTest::testLowerCaseBlockSelection() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 KTextEditor::DocumentPrivate doc; doc.setText("nY\nnYY\n"); - KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, 0); + KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); view1->setBlockSelection(true); view1->setSelection(Range(0, 1, 1, 3)); view1->lowercase(); QCOMPARE(doc.text(), QString("ny\nnyy\n")); } void KateViewTest::testSelection() { // see also: https://bugs.kde.org/show_bug.cgi?id=277422 // wrong behavior before: // Open file with text // click at end of some line (A) and drag to right, i.e. without selecting anything // click somewhere else (B) // shift click to another place (C) // => expected: selection from B to C // => actual: selection from A to C QTemporaryFile file("XXXXXX.txt"); file.open(); QTextStream stream(&file); stream << "A\n" << "B\n" << "C"; stream << flush; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); - KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, 0); + KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->resize(100, 200); view->show(); QObject *internalView = nullptr; foreach (QObject* child, view->children()) { if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { internalView = child; break; } } QVERIFY(internalView); const QPoint afterA = view->cursorToCoordinate(Cursor(0, 1)); const QPoint afterB = view->cursorToCoordinate(Cursor(1, 1)); const QPoint afterC = view->cursorToCoordinate(Cursor(2, 1)); // click after A QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); // drag to right QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseMove, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); QVERIFY(!view->selection()); // click after C QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(2, 1)); // shift+click after B QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier)); QCOMPARE(view->cursorPosition(), Cursor(1, 1)); QCOMPARE(view->selectionRange(), Range(1, 1, 2, 1)); } void KateViewTest::testKillline() { KTextEditor::DocumentPrivate doc; doc.insertLines(0, QStringList() << "foo" << "bar" << "baz" ); - KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, 0); + KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); view->killLine(); QCOMPARE(doc.text(), QLatin1String("foo\nbaz\n")); doc.clear(); QVERIFY(doc.isEmpty()); doc.insertLines(0, QStringList() << "foo" << "bar" << "baz" << "xxx" ); view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); view->shiftDown(); view->killLine(); QCOMPARE(doc.text(), QLatin1String("foo\nxxx\n")); } void KateViewTest::testFoldFirstLine() { QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); stream << "/**\n" << " * foo\n" << " */\n" << "\n" << "int main() {}\n"; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.highlightingMode(), QString("C++")); - KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, 0); + KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->config()->setFoldFirstLine(false); view->setCursorPosition({4, 0}); // initially, nothing is folded QVERIFY(view->textFolding().isLineVisible(1)); // now change the config, and expect the header to be folded view->config()->setFoldFirstLine(true); qint64 foldedRangeId = 0; QVERIFY(!view->textFolding().isLineVisible(1, &foldedRangeId)); // now unfold the range QVERIFY(view->textFolding().unfoldRange(foldedRangeId)); QVERIFY(view->textFolding().isLineVisible(1)); // and save the file, we do not expect the folding to change then doc.setModified(true); doc.saveFile(); QVERIFY(view->textFolding().isLineVisible(1)); // now reload the document, nothing should change doc.setModified(false); QVERIFY(doc.documentReload()); QVERIFY(view->textFolding().isLineVisible(1)); } +// test for bug https://bugs.kde.org/374163 +void KateViewTest::testDragAndDrop() +{ + KTextEditor::DocumentPrivate doc(false, false); + doc.setText("line0\n" + "line1\n" + "line2\n" + "\n" + "line4"); + + KTextEditor::View* view = static_cast(doc.createView(nullptr)); + view->show(); + view->resize(400, 300); + + QWidget *internalView = nullptr; + foreach (QObject* child, view->children()) { + if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { + internalView = qobject_cast(child); + break; + } + } + QVERIFY(internalView); + + // select "line1\n" + view->setSelection(Range(1, 0, 2, 0)); + QCOMPARE(view->selectionRange(), Range(1, 0, 2, 0)); + + QTest::qWaitForWindowExposed(view); + const QPoint startDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(1, 2))); + const QPoint endDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(3, 0))); + const QPoint gStartDragPos = internalView->mapToGlobal(startDragPos); + const QPoint gEndDragPos = internalView->mapToGlobal(endDragPos); + + // now drag and drop selected text to Cursor(3, 0) + QMouseEvent pressEvent(QEvent::MouseButtonPress, startDragPos, gStartDragPos, + Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QCoreApplication::sendEvent(internalView, &pressEvent); + + // ugly workaround: Drag & Drop has own blocking event queue. Therefore, we need a single-shot timer to + // break out of the blocking event queue, see (*) + QTimer::singleShot(50, [&](){ + QMouseEvent moveEvent(QEvent::MouseMove, endDragPos + QPoint(5, 0), gEndDragPos + QPoint(5, 0), + Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QMouseEvent releaseEvent(QEvent::MouseButtonRelease, endDragPos, gEndDragPos, + Qt::LeftButton, Qt::NoButton, Qt::NoModifier); + QCoreApplication::sendEvent(internalView, &moveEvent); + QCoreApplication::sendEvent(internalView, &releaseEvent); + }); + + // (*) this somehow blocks... + QMouseEvent moveEvent1(QEvent::MouseMove, endDragPos + QPoint(10, 0), gEndDragPos + QPoint(10, 0), + Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QCoreApplication::sendEvent(internalView, &moveEvent1); + + QTest::qWait(100); + + // final tests of dragged text + QCOMPARE(doc.text(), QString("line0\n" + "line2\n" + "line1\n" + "\n" + "line4")); + + QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(3, 0)); + QCOMPARE(view->selectionRange(), Range(2, 0, 3, 0)); +} + // kate: indent-mode cstyle; indent-width 4; replace-tabs on; diff --git a/autotests/src/kateview_test.h b/autotests/src/kateview_test.h index 672e74e6..a48720d9 100644 --- a/autotests/src/kateview_test.h +++ b/autotests/src/kateview_test.h @@ -1,48 +1,50 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Milian Wolff 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_VIEW_TEST_H #define KATE_VIEW_TEST_H #include class KateViewTest : public QObject { Q_OBJECT public: KateViewTest(); ~KateViewTest(); private Q_SLOTS: void testReloadMultipleViews(); void testTabCursorOnReload(); void testLowerCaseBlockSelection(); void testCoordinatesToCursor(); void testCursorToCoordinates(); void testSelection(); void testKillline(); void testFoldFirstLine(); + + void testDragAndDrop(); }; #endif // KATE_VIEW_TEST_H diff --git a/autotests/src/kte_documentcursor.cpp b/autotests/src/kte_documentcursor.cpp index 0eefb345..3f2364ac 100644 --- a/autotests/src/kte_documentcursor.cpp +++ b/autotests/src/kte_documentcursor.cpp @@ -1,335 +1,335 @@ /* This file is part of the KDE libraries Copyright (C) 2012 Dominik Haumann 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 "kte_documentcursor.h" #include #include #include #include #include QTEST_MAIN(DocumentCursorTest) using namespace KTextEditor; DocumentCursorTest::DocumentCursorTest() : QObject() { } DocumentCursorTest::~DocumentCursorTest() { } void DocumentCursorTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void DocumentCursorTest::cleanupTestCase() { } // tests: // - atStartOfDocument // - atStartOfLine // - atEndOfDocument // - atEndOfLine // - move forward with Wrap // - move forward with NoWrap // - move backward // - gotoNextLine // - gotoPreviousLine void DocumentCursorTest::testConvenienceApi() { KTextEditor::DocumentPrivate doc; doc.setText("\n" "1\n" "22\n" "333\n" "4444\n" "55555"); // check start and end of document DocumentCursor startOfDoc(&doc); startOfDoc.setPosition(Cursor(0, 0)); DocumentCursor endOfDoc(&doc); endOfDoc.setPosition(Cursor(5, 5)); QVERIFY(startOfDoc.atStartOfDocument()); QVERIFY(startOfDoc.atStartOfLine()); QVERIFY(endOfDoc.atEndOfDocument()); QVERIFY(endOfDoc.atEndOfLine()); // set cursor to (2, 2) and then move to the left two times DocumentCursor moving(&doc); moving.setPosition(Cursor(2, 2)); QVERIFY(moving.atEndOfLine()); // at 2, 2 QVERIFY(moving.move(-1)); // at 2, 1 QCOMPARE(moving.toCursor(), Cursor(2, 1)); QVERIFY(!moving.atEndOfLine()); QVERIFY(moving.move(-1)); // at 2, 0 QCOMPARE(moving.toCursor(), Cursor(2, 0)); QVERIFY(moving.atStartOfLine()); // now move again to the left, should wrap to (1, 1) QVERIFY(moving.move(-1)); // at 1, 1 QCOMPARE(moving.toCursor(), Cursor(1, 1)); QVERIFY(moving.atEndOfLine()); // advance 7 characters to position (3, 3) QVERIFY(moving.move(7)); // at 3, 3 QCOMPARE(moving.toCursor(), Cursor(3, 3)); // advance 20 characters in NoWrap mode, then go back 10 characters QVERIFY(moving.move(20, DocumentCursor::NoWrap)); // at 3, 23 QCOMPARE(moving.toCursor(), Cursor(3, 23)); QVERIFY(moving.move(-10)); // at 3, 13 QCOMPARE(moving.toCursor(), Cursor(3, 13)); // still at invalid text position. move one char to wrap around QVERIFY(!moving.isValidTextPosition()); // at 3, 13 QVERIFY(moving.move(1)); // at 4, 0 QCOMPARE(moving.toCursor(), Cursor(4, 0)); // moving 11 characters in wrap mode moves to (5, 6), which is not a valid // text position anymore. Hence, moving should be rejected. QVERIFY(!moving.move(11)); QVERIFY(moving.move(10)); QVERIFY(moving.atEndOfDocument()); // try to move to next line, which fails. then go to previous line QVERIFY(!moving.gotoNextLine()); QVERIFY(moving.gotoPreviousLine()); QCOMPARE(moving.toCursor(), Cursor(4, 0)); } void DocumentCursorTest::testOperators() { KTextEditor::DocumentPrivate doc; doc.setText("--oo--\n" "--oo--\n" "--oo--"); // create lots of cursors for comparison Cursor invalid = Cursor::invalid(); Cursor c02(0, 2); Cursor c04(0, 4); Cursor c14(1, 4); DocumentCursor m02(&doc); DocumentCursor m04(&doc); DocumentCursor m14(&doc); QVERIFY(m02 == invalid); QVERIFY(m04 == invalid); QVERIFY(m14 == invalid); m02.setPosition(Cursor(0, 2)); m04.setPosition(Cursor(0, 4)); m14.setPosition(Cursor(1, 4)); // invalid comparison //cppcheck-suppress duplicateExpression QVERIFY(invalid == invalid); QVERIFY(invalid <= c02); QVERIFY(invalid < c02); QVERIFY(!(invalid > c02)); QVERIFY(!(invalid >= c02)); QVERIFY(!(invalid == m02)); QVERIFY(invalid <= m02); QVERIFY(invalid < m02); QVERIFY(!(invalid > m02)); QVERIFY(!(invalid >= m02)); QVERIFY(!(m02 == invalid)); QVERIFY(!(m02 <= invalid)); QVERIFY(!(m02 < invalid)); QVERIFY(m02 > invalid); QVERIFY(m02 >= invalid); // DocumentCursor <-> DocumentCursor //cppcheck-suppress duplicateExpression QVERIFY(m02 == m02); //cppcheck-suppress duplicateExpression QVERIFY(m02 <= m02); //cppcheck-suppress duplicateExpression QVERIFY(m02 >= m02); //cppcheck-suppress duplicateExpression QVERIFY(!(m02 < m02)); //cppcheck-suppress duplicateExpression QVERIFY(!(m02 > m02)); //cppcheck-suppress duplicateExpression QVERIFY(!(m02 != m02)); QVERIFY(!(m02 == m04)); QVERIFY(m02 <= m04); QVERIFY(!(m02 >= m04)); QVERIFY(m02 < m04); QVERIFY(!(m02 > m04)); QVERIFY(m02 != m04); QVERIFY(!(m04 == m02)); QVERIFY(!(m04 <= m02)); QVERIFY(m04 >= m02); QVERIFY(!(m04 < m02)); QVERIFY(m04 > m02); QVERIFY(m04 != m02); QVERIFY(!(m02 == m14)); QVERIFY(m02 <= m14); QVERIFY(!(m02 >= m14)); QVERIFY(m02 < m14); QVERIFY(!(m02 > m14)); QVERIFY(m02 != m14); QVERIFY(!(m14 == m02)); QVERIFY(!(m14 <= m02)); QVERIFY(m14 >= m02); QVERIFY(!(m14 < m02)); QVERIFY(m14 > m02); QVERIFY(m14 != m02); // DocumentCursor <-> Cursor QVERIFY(m02 == c02); QVERIFY(m02 <= c02); QVERIFY(m02 >= c02); QVERIFY(!(m02 < c02)); QVERIFY(!(m02 > c02)); QVERIFY(!(m02 != c02)); QVERIFY(!(m02 == c04)); QVERIFY(m02 <= c04); QVERIFY(!(m02 >= c04)); QVERIFY(m02 < c04); QVERIFY(!(m02 > c04)); QVERIFY(m02 != c04); QVERIFY(!(m04 == c02)); QVERIFY(!(m04 <= c02)); QVERIFY(m04 >= c02); QVERIFY(!(m04 < c02)); QVERIFY(m04 > c02); QVERIFY(m04 != c02); QVERIFY(!(m02 == c14)); QVERIFY(m02 <= c14); QVERIFY(!(m02 >= c14)); QVERIFY(m02 < c14); QVERIFY(!(m02 > c14)); QVERIFY(m02 != c14); QVERIFY(!(m14 == c02)); QVERIFY(!(m14 <= c02)); QVERIFY(m14 >= c02); QVERIFY(!(m14 < c02)); QVERIFY(m14 > c02); QVERIFY(m14 != c02); // Cursor <-> DocumentCursor QVERIFY(c02 == m02); QVERIFY(c02 <= m02); QVERIFY(c02 >= m02); QVERIFY(!(c02 < m02)); QVERIFY(!(c02 > m02)); QVERIFY(!(c02 != m02)); QVERIFY(!(c02 == m04)); QVERIFY(c02 <= m04); QVERIFY(!(c02 >= m04)); QVERIFY(c02 < m04); QVERIFY(!(c02 > m04)); QVERIFY(c02 != m04); QVERIFY(!(c04 == m02)); QVERIFY(!(c04 <= m02)); QVERIFY(c04 >= m02); QVERIFY(!(c04 < m02)); QVERIFY(c04 > m02); QVERIFY(c04 != m02); QVERIFY(!(c02 == m14)); QVERIFY(c02 <= m14); QVERIFY(!(c02 >= m14)); QVERIFY(c02 < m14); QVERIFY(!(c02 > m14)); QVERIFY(c02 != m14); QVERIFY(!(c14 == m02)); QVERIFY(!(c14 <= m02)); QVERIFY(c14 >= m02); QVERIFY(!(c14 < m02)); QVERIFY(c14 > m02); QVERIFY(c14 != m02); } void DocumentCursorTest::testValidTextPosition() { // test: utf-32 characters that are visually only one single character (Unicode surrogates) // Inside such a utf-32 character, the text position is not valid. KTextEditor::DocumentPrivate doc; DocumentCursor c(&doc); // 0xfffe: byte-order-mark (BOM) // 0x002d: '-' // 0x3dd8, 0x38de: an utf32-surrogate, see http://www.fileformat.info/info/unicode/char/1f638/browsertest.htm const unsigned short line0[] = {0xfffe, 0x2d00, 0x3dd8, 0x38de, 0x2d00}; // -xx- where xx is one utf32 char const unsigned short line1[] = {0xfffe, 0x2d00, 0x3dd8, 0x2d00, 0x2d00}; // -x-- where x is a high surrogate (broken utf32) const unsigned short line2[] = {0xfffe, 0x2d00, 0x2d00, 0x38de, 0x2d00}; // --x- where x is a low surrogate (broken utf32) doc.setText(QString::fromUtf16(line0, 5)); doc.insertLine(1, QString::fromUtf16(line1, 5)); doc.insertLine(2, QString::fromUtf16(line2, 5)); // set to true if you want to see the contents const bool showView = false; if (showView) { - doc.createView(0)->show(); + doc.createView(nullptr)->show(); QTest::qWait(5000); } // line 0: invalid in utf-32 char c.setPosition(0, 0); QVERIFY( c.isValidTextPosition()); c.setPosition(0, 1); QVERIFY( c.isValidTextPosition()); c.setPosition(0, 2); QVERIFY(! c.isValidTextPosition()); c.setPosition(0, 3); QVERIFY( c.isValidTextPosition()); c.setPosition(0, 4); QVERIFY( c.isValidTextPosition()); c.setPosition(0, 5); QVERIFY(! c.isValidTextPosition()); // line 1, valid in broken utf-32 char (2nd part missing) c.setPosition(1, 0); QVERIFY( c.isValidTextPosition()); c.setPosition(1, 1); QVERIFY( c.isValidTextPosition()); c.setPosition(1, 2); QVERIFY( c.isValidTextPosition()); c.setPosition(1, 3); QVERIFY( c.isValidTextPosition()); c.setPosition(1, 4); QVERIFY( c.isValidTextPosition()); c.setPosition(1, 5); QVERIFY(! c.isValidTextPosition()); // line 2, valid in broken utf-32 char (1st part missing) c.setPosition(2, 0); QVERIFY( c.isValidTextPosition()); c.setPosition(2, 1); QVERIFY( c.isValidTextPosition()); c.setPosition(2, 2); QVERIFY( c.isValidTextPosition()); c.setPosition(2, 3); QVERIFY( c.isValidTextPosition()); c.setPosition(2, 4); QVERIFY( c.isValidTextPosition()); c.setPosition(2, 5); QVERIFY(! c.isValidTextPosition()); // test out-of-range c.setPosition(-1, 0); QVERIFY(!c.isValidTextPosition()); c.setPosition(3, 0); QVERIFY(!c.isValidTextPosition()); c.setPosition(0, -1); QVERIFY(!c.isValidTextPosition()); } #include "moc_kte_documentcursor.cpp" \ No newline at end of file diff --git a/autotests/src/messagetest.cpp b/autotests/src/messagetest.cpp index 4aee9db2..c70edf2a 100644 --- a/autotests/src/messagetest.cpp +++ b/autotests/src/messagetest.cpp @@ -1,419 +1,419 @@ /* This file is part of the Kate project. * * Copyright (C) 2013 Dominik Haumann * * 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 "messagetest.h" #include #include #include #include #include #include using namespace KTextEditor; QTEST_MAIN(MessageTest) void MessageTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void MessageTest::cleanupTestCase() { } void MessageTest::testPostMessage() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); QVERIFY(QTest::qWaitForWindowExposed(view)); QPointer message = new Message("Message text", Message::Information); message->setPosition(Message::TopInView); // posing message should succeed QVERIFY(doc.postMessage(message)); // // show message for one second, then delete again // QTest::qWait(500); QVERIFY(view->messageWidget()); QVERIFY(view->messageWidget()->isVisible()); - QVERIFY(message != 0); + QVERIFY(message != nullptr); delete message; QTest::qWait(600); // fadeout animation takes 500 ms QVERIFY(!view->messageWidget()->isVisible()); } void MessageTest::testAutoHide() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); QVERIFY(QTest::qWaitForWindowExposed(view)); // // show a message with autoHide. Check, if it's deleted correctly // auto hide mode: Message::Immediate // QPointer message = new Message("Message text", Message::Information); message->setPosition(Message::TopInView); message->setAutoHide(1000); message->setAutoHideMode(Message::Immediate); doc.postMessage(message); QTest::qWait(500); QVERIFY(view->messageWidget()->isVisible()); // should be deleted after 1.5 seconds QTest::qWait(1000); - QVERIFY(message.data() == 0); + QVERIFY(message.data() == nullptr); // message widget should be hidden after 2 seconds QTest::qWait(500); QVERIFY(!view->messageWidget()->isVisible()); } void MessageTest::testAutoHideAfterUserInteraction() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); QVERIFY(QTest::qWaitForWindowExposed(view)); QTest::qWait(1000); // // show a message with autoHide. Check, if it's deleted correctly // auto hide mode: Message::AfterUserInteraction // QPointer message = new Message("Message text", Message::Information); message->setPosition(Message::TopInView); message->setAutoHide(2000); QVERIFY(message->autoHideMode() == Message::AfterUserInteraction); doc.postMessage(message); QTest::qWait(1000); QVERIFY(view->messageWidget()->isVisible()); // now trigger user interaction after 1 second view->insertText("Hello world"); view->setCursorPosition(Cursor(0, 5)); // should still be there after deleted after another 1.9 seconds QTest::qWait(1900); - QVERIFY(message.data() != 0); + QVERIFY(message.data() != nullptr); QVERIFY(view->messageWidget()->isVisible()); // another 200ms later: 3.1 seconds are gone, message should be deleted // and fade animation should be active QTest::qWait(200); - QVERIFY(message.data() == 0); + QVERIFY(message.data() == nullptr); QVERIFY(view->messageWidget()->isVisible()); // after a total of 3.6 seconds, widget should be hidden QTest::qWait(500); QVERIFY(!view->messageWidget()->isVisible()); } void MessageTest::testMessageQueue() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); QVERIFY(QTest::qWaitForWindowExposed(view)); QTest::qWait(1000); // // add two messages, both with autoHide to 1 second, and check that the queue is processed correctly // auto hide mode: Message::Immediate // QPointer m1 = new Message("Info text", Message::Information); m1->setPosition(Message::TopInView); m1->setAutoHide(1000); m1->setAutoHideMode(Message::Immediate); QPointer m2 = new Message("Error text", Message::Error); m2->setPosition(Message::TopInView); m2->setAutoHide(1000); m2->setAutoHideMode(Message::Immediate); // post both messages QVERIFY(doc.postMessage(m1)); QVERIFY(doc.postMessage(m2)); // after 0.5s, first message should be visible, (timer of m1 triggered) QTest::qWait(500); QVERIFY(view->messageWidget()->isVisible()); - QVERIFY(m1.data() != 0); - QVERIFY(m2.data() != 0); + QVERIFY(m1.data() != nullptr); + QVERIFY(m2.data() != nullptr); // after 1.2s, first message is deleted, and hide animation is active QTest::qWait(700); QVERIFY(view->messageWidget()->isVisible()); - QVERIFY(m1.data() == 0); - QVERIFY(m2.data() != 0); + QVERIFY(m1.data() == nullptr); + QVERIFY(m2.data() != nullptr); // timer of m2 triggered after 1.5s, i.e. after hide animation if finished QTest::qWait(500); // after 2.1s, second message should be visible QTest::qWait(500); QVERIFY(view->messageWidget()->isVisible()); - QVERIFY(m2.data() != 0); + QVERIFY(m2.data() != nullptr); // after 2.6s, second message is deleted, and hide animation is active QTest::qWait(500); QVERIFY(view->messageWidget()->isVisible()); - QVERIFY(m2.data() == 0); + QVERIFY(m2.data() == nullptr); // after a total of 3.1s, animation is finished and widget is hidden QTest::qWait(500); QVERIFY(!view->messageWidget()->isVisible()); } void MessageTest::testPriority() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); QVERIFY(QTest::qWaitForWindowExposed(view)); QTest::qWait(1000); // // add two messages // - m1: no auto hide timer, priority 0 // - m2: auto hide timer of 1 second, priority 1 // test: // - m1 should be hidden in favour of m2 // - changing text of m1 while m2 is displayed should not change the displayed text // QPointer m1 = new Message("m1", Message::Positive); m1->setPosition(Message::TopInView); QVERIFY(m1->priority() == 0); QPointer m2 = new Message("m2", Message::Error); m2->setPosition(Message::TopInView); m2->setAutoHide(1000); m2->setAutoHideMode(Message::Immediate); m2->setPriority(1); QVERIFY(m2->priority() == 1); // post m1 QVERIFY(doc.postMessage(m1)); // after 1s, message should be displayed QTest::qWait(1000); QVERIFY(view->messageWidget()->isVisible()); QCOMPARE(view->messageWidget()->text(), QString("m1")); - QVERIFY(m1.data() != 0); + QVERIFY(m1.data() != nullptr); // post m2, m1 should be hidden, and m2 visible QVERIFY(doc.postMessage(m2)); - QVERIFY(m2.data() != 0); + QVERIFY(m2.data() != nullptr); // alter text of m1 when m2 is visible, shouldn't influence m2 QTest::qWait(600); m1->setText("m1 changed"); // after 0.7 seconds, m2 is visible QTest::qWait(100); QCOMPARE(view->messageWidget()->text(), QString("m2")); - QVERIFY(m2.data() != 0); + QVERIFY(m2.data() != nullptr); // after 1.6 seconds, m2 is hidden again and m1 is visible again QTest::qWait(900); QVERIFY(view->messageWidget()->isVisible()); - QVERIFY(m1.data() != 0); - QVERIFY(m2.data() == 0); + QVERIFY(m1.data() != nullptr); + QVERIFY(m2.data() == nullptr); // finally check m1 agagin QTest::qWait(1000); QCOMPARE(view->messageWidget()->text(), QString("m1 changed")); } void MessageTest::testCreateView() { KTextEditor::DocumentPrivate doc; // // - first post a message // - then create two views // // test: // - verify that both views get the message // - verify that, once the message is deleted, both views hide the message // QPointer m1 = new Message("message", Message::Positive); m1->setPosition(Message::TopInView); QVERIFY(m1->priority() == 0); // first post message to doc without views QVERIFY(doc.postMessage(m1)); // now create views - KTextEditor::ViewPrivate *v1 = static_cast(doc.createView(0)); - KTextEditor::ViewPrivate *v2 = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *v1 = static_cast(doc.createView(nullptr)); + KTextEditor::ViewPrivate *v2 = static_cast(doc.createView(nullptr)); v1->show(); v2->show(); v1->resize(400, 300); v2->resize(400, 300); QVERIFY(QTest::qWaitForWindowExposed(v1)); QVERIFY(QTest::qWaitForWindowExposed(v2)); QTest::qWait(1000); // make sure both views show the message QVERIFY(v1->messageWidget()->isVisible()); QVERIFY(v2->messageWidget()->isVisible()); QCOMPARE(v1->messageWidget()->text(), QString("message")); QCOMPARE(v2->messageWidget()->text(), QString("message")); - QVERIFY(m1.data() != 0); + QVERIFY(m1.data() != nullptr); // delete message, then check after fadeout time 0f 0.5s whether message is gone delete m1; QTest::qWait(600); QVERIFY(!v1->messageWidget()->isVisible()); QVERIFY(!v2->messageWidget()->isVisible()); } void MessageTest::testHideView() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); QVERIFY(QTest::qWaitForWindowExposed(view)); QTest::qWait(1000); // create message that hides after 2s immediately QPointer message = new Message("Message text", Message::Information); message->setAutoHide(2000); message->setAutoHideMode(Message::Immediate); message->setPosition(Message::TopInView); // posting message should succeed QVERIFY(doc.postMessage(message)); // // test: // - show the message for 1.5s, then hide the view // - the auto hide timer will continue, no matter what // - showing the view again after the auto hide timer is finished + animation time really hide the widget // QTest::qWait(1100); QVERIFY(view->messageWidget()->isVisible()); QCOMPARE(view->messageWidget()->text(), QString("Message text")); // hide view view->hide(); // wait 1s, message should be null (after total of 2100 ms) QTest::qWait(1000); - QVERIFY(message.data() == 0); + QVERIFY(message.data() == nullptr); // show view again, message contents should be fading for the lasting 400 ms view->show(); QVERIFY(view->messageWidget()->isVisible()); QCOMPARE(view->messageWidget()->text(), QString("Message text")); // wait another 0.5s, then message widget should be hidden QTest::qWait(500); - QVERIFY(message.data() == 0); + QVERIFY(message.data() == nullptr); QVERIFY(!view->messageWidget()->isVisible()); } void MessageTest::testHideViewAfterUserInteraction() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); QVERIFY(QTest::qWaitForWindowExposed(view)); QTest::qWait(1000); // create message that hides after 2s immediately QPointer message = new Message("Message text", Message::Information); message->setAutoHide(2000); QVERIFY(message->autoHideMode() == Message::AfterUserInteraction); message->setPosition(Message::TopInView); // posting message should succeed QVERIFY(doc.postMessage(message)); // // test: // - show the message for 1.5s, then hide the view // - this should stop the autoHide timer // - showing the view again should restart the autoHide timer (again 2s) // QTest::qWait(1500); QVERIFY(view->messageWidget()->isVisible()); QCOMPARE(view->messageWidget()->text(), QString("Message text")); // hide view view->hide(); // wait 1s, check that message is still valid QTest::qWait(1000); - QVERIFY(message.data() != 0); + QVERIFY(message.data() != nullptr); // // show view again, and trigger user interaction through resize: // should retrigger the autoHide timer // view->show(); QTest::qWait(2000); view->insertText("Hello world"); view->setCursorPosition(Cursor(0, 5)); // wait 1.5s and check that message is still displayed QTest::qWait(1500); - QVERIFY(message.data() != 0); + QVERIFY(message.data() != nullptr); QVERIFY(view->messageWidget()->isVisible()); QCOMPARE(view->messageWidget()->text(), QString("Message text")); // wait another 0.8s, then the message is deleted QTest::qWait(800); - QVERIFY(message.data() == 0); + QVERIFY(message.data() == nullptr); QVERIFY(view->messageWidget()->isVisible()); // another 0.5s, and the message widget should be hidden QTest::qWait(600); QVERIFY(!view->messageWidget()->isVisible()); } diff --git a/autotests/src/movingrange_test.cpp b/autotests/src/movingrange_test.cpp index da8fbc78..a4aae438 100644 --- a/autotests/src/movingrange_test.cpp +++ b/autotests/src/movingrange_test.cpp @@ -1,443 +1,443 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Dominik Haumann 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 "movingrange_test.h" #include "moc_movingrange_test.cpp" #include #include #include #include #include #include #include using namespace KTextEditor; QTEST_MAIN(MovingRangeTest) class RangeFeedback : public MovingRangeFeedback { public: RangeFeedback() : MovingRangeFeedback() { reset(); } void rangeEmpty(MovingRange * /*range*/) Q_DECL_OVERRIDE { m_rangeEmptyCalled = true; } void rangeInvalid(MovingRange * /*range*/) Q_DECL_OVERRIDE { m_rangeInvalidCalled = true; } void mouseEnteredRange(MovingRange * /*range*/, View * /*view*/) Q_DECL_OVERRIDE { m_mouseEnteredRangeCalled = true; } void mouseExitedRange(MovingRange * /*range*/, View * /*view*/) Q_DECL_OVERRIDE { m_mouseExitedRangeCalled = true; } void caretEnteredRange(MovingRange * /*range*/, View * /*view*/) Q_DECL_OVERRIDE { m_caretEnteredRangeCalled = true; } void caretExitedRange(MovingRange * /*range*/, View * /*view*/) Q_DECL_OVERRIDE { m_caretExitedRangeCalled = true; } // // Test functions to reset feedback watcher // public: void reset() { m_rangeEmptyCalled = false; m_rangeInvalidCalled = false; m_mouseEnteredRangeCalled = false; m_mouseExitedRangeCalled = false; m_caretEnteredRangeCalled = false; m_caretExitedRangeCalled = false; } void verifyReset() { QVERIFY(!m_rangeEmptyCalled); QVERIFY(!m_rangeInvalidCalled); QVERIFY(!m_mouseEnteredRangeCalled); QVERIFY(!m_mouseExitedRangeCalled); QVERIFY(!m_caretEnteredRangeCalled); QVERIFY(!m_caretExitedRangeCalled); } bool rangeEmptyCalled() const { return m_rangeEmptyCalled; } bool rangeInvalidCalled() const { return m_rangeInvalidCalled; } bool mouseEnteredRangeCalled() const { return m_mouseEnteredRangeCalled; } bool mouseExitedRangeCalled() const { return m_mouseExitedRangeCalled; } bool caretEnteredRangeCalled() const { return m_caretEnteredRangeCalled; } bool caretExitedRangeCalled() const { return m_caretExitedRangeCalled; } private: bool m_rangeEmptyCalled; bool m_rangeInvalidCalled; bool m_mouseEnteredRangeCalled; bool m_mouseExitedRangeCalled; bool m_caretEnteredRangeCalled; bool m_caretExitedRangeCalled; }; MovingRangeTest::MovingRangeTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } MovingRangeTest::~MovingRangeTest() { } // tests: // - RangeFeedback::rangeEmpty void MovingRangeTest::testFeedbackEmptyRange() { KTextEditor::DocumentPrivate doc; // the range created below will span the 'x' characters QString text("..xxxx\n" "xxxx.."); doc.setText(text); // create range feedback RangeFeedback rf; // allow empty MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::AllowEmpty); range->setFeedback(&rf); rf.verifyReset(); // remove exact range doc.removeText(range->toRange()); QVERIFY(rf.rangeEmptyCalled()); QVERIFY(!rf.rangeInvalidCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // clear document: should call rangeInvalid rf.reset(); rf.verifyReset(); doc.clear(); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // setText: should behave just like clear document: call rangeInvalid again doc.setText(text); range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); rf.reset(); rf.verifyReset(); doc.setText("--yyyy\nyyyy--"); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // now remove entire document range. In this case, emptyRange should be called // instead of rangeInvalid doc.setText(text); range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); rf.reset(); rf.verifyReset(); doc.removeText(doc.documentRange()); QVERIFY(rf.rangeEmptyCalled()); QVERIFY(!rf.rangeInvalidCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); } // tests: // - RangeFeedback::rangeInvalid void MovingRangeTest::testFeedbackInvalidRange() { KTextEditor::DocumentPrivate doc; // the range created below will span the 'x' characters QString text("..xxxx\n" "xxxx.."); doc.setText(text); // create range feedback RangeFeedback rf; // allow empty MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::InvalidateIfEmpty); range->setFeedback(&rf); rf.verifyReset(); // remove exact range doc.removeText(range->toRange()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // clear document: should call rangeInvalid again doc.setText(text); range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); rf.reset(); rf.verifyReset(); doc.clear(); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // setText: should behave just like clear document: call rangeInvalid again doc.setText(text); range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); rf.reset(); rf.verifyReset(); doc.setText("--yyyy\nyyyy--"); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); // now remove entire document range. Call rangeInvalid again doc.setText(text); range->setRange(Range(Cursor(0, 2), Cursor(1, 4))); rf.reset(); rf.verifyReset(); doc.removeText(doc.documentRange()); QVERIFY(rf.rangeInvalidCalled()); QVERIFY(!rf.rangeEmptyCalled()); QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); } // tests: // - RangeFeedback::caretEnteredRange // - RangeFeedback::caretExitedRange void MovingRangeTest::testFeedbackCaret() { KTextEditor::DocumentPrivate doc; // the range created below will span the 'x' characters QString text("..xxxx\n" "xxxx.."); doc.setText(text); - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); // create range feedback RangeFeedback rf; // first test: with ExpandLeft | ExpandRight { view->setCursorPosition(Cursor(1, 6)); MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight, KTextEditor::MovingRange::InvalidateIfEmpty); rf.reset(); range->setFeedback(&rf); rf.verifyReset(); // left view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 5)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 4)); QVERIFY(rf.caretEnteredRangeCalled()); // ExpandRight: include cursor already now QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 3)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->up(); QCOMPARE(view->cursorPosition(), Cursor(0, 3)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(0, 2)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); // ExpandLeft: now we left it, not before QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(rf.caretExitedRangeCalled()); delete range; } // second test: with DoNotExpand { view->setCursorPosition(Cursor(1, 6)); MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::InvalidateIfEmpty); rf.reset(); range->setFeedback(&rf); rf.verifyReset(); // left view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 5)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 4)); QVERIFY(!rf.caretEnteredRangeCalled()); // DoNotExpand: does not include cursor QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(1, 3)); QVERIFY(rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->up(); QCOMPARE(view->cursorPosition(), Cursor(0, 3)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(0, 2)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(rf.caretExitedRangeCalled()); // DoNotExpand: that's why we leave already now rf.reset(); view->cursorLeft(); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); QVERIFY(!rf.caretEnteredRangeCalled()); QVERIFY(!rf.caretExitedRangeCalled()); delete range; } } // tests: // - RangeFeedback::mouseEnteredRange // - RangeFeedback::mouseExitedRange void MovingRangeTest::testFeedbackMouse() { KTextEditor::DocumentPrivate doc; // the range created below will span the 'x' characters QString text("..xxxx\n" "xxxx.."); doc.setText(text); - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); view->setCursorPosition(Cursor(1, 6)); view->show(); view->resize(200, 100); // create range feedback RangeFeedback rf; QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); // allow empty MovingRange *range = doc.newMovingRange(Range(Cursor(0, 2), Cursor(1, 4)), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight, KTextEditor::MovingRange::InvalidateIfEmpty); range->setFeedback(&rf); rf.verifyReset(); // left (nothing) QTest::mouseMove(view, view->cursorToCoordinate(Cursor(0, 0)) + QPoint(0, 5)); QTest::qWait(200); // process mouse events. do not move mouse manually QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); // middle (enter) rf.reset(); QTest::mouseMove(view, view->cursorToCoordinate(Cursor(0, 3)) + QPoint(0, 5)); QTest::qWait(200); // process mouse events. do not move mouse manually QVERIFY(rf.mouseEnteredRangeCalled()); QVERIFY(!rf.mouseExitedRangeCalled()); // right (exit) rf.reset(); QTest::mouseMove(view, view->cursorToCoordinate(Cursor(1, 6)) + QPoint(10, 5)); QTest::qWait(200); // process mouse events. do not move mouse manually QVERIFY(!rf.mouseEnteredRangeCalled()); QVERIFY(rf.mouseExitedRangeCalled()); } diff --git a/autotests/src/multicursor_test.cpp b/autotests/src/multicursor_test.cpp index 4680ce20..50cbc0cb 100644 --- a/autotests/src/multicursor_test.cpp +++ b/autotests/src/multicursor_test.cpp @@ -1,512 +1,515 @@ /* This file is part of the KDE libraries Copyright (C) 2016 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 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 "multicursor_test.h" #include "moc_multicursor_test.cpp" #include #include #include #include #include using namespace KTextEditor; QTEST_MAIN(MulticursorTest) #define STOP view->show(); QEventLoop loop; loop.exec(); // Implementation of the multicursor test DSL KTextEditor::Cursor parseCursor(const QString& s) { auto parts = s.split(','); Q_ASSERT(parts.size() == 2); bool ok1, ok2; auto cur = KTextEditor::Cursor(parts[0].toInt(&ok1), parts[1].toInt(&ok2)); Q_ASSERT(ok1); Q_ASSERT(ok2); Q_ASSERT(cur.isValid()); return cur; }; struct MulticursorScriptRunner { enum Mode { Move, MouseSelect }; MulticursorScriptRunner(QString script, QString states) { m_script = script; m_script.remove(" "); m_script.remove("\t"); m_script.remove("\n"); Q_ASSERT(script.count('|') == states.count('|')); states.remove(" "); states.remove("\t"); states.remove("\n"); m_states = states.split('|'); } bool execNextPart(KateMultiCursor* s) { qDebug() << "exec" << part << pos; auto nextCursor = [this]() { auto start = pos; auto end1 = m_script.indexOf(')', pos); auto end2 = m_script.indexOf(';', pos); auto end = end1 == -1 && end2 != -1 ? end2 : end1 != -1 && end2 == -1 ? end1 : qMin(end1, end2); // sigh Q_ASSERT(end != -1); pos = end; return parseCursor(m_script.mid(start, end-start)); }; for ( ; pos < m_script.size(); ) { QChar c = m_script.at(pos); pos++; if ( mode == Move ) { switch ( c.unicode() ) { case '|': // next part part++; return true; case '[': select = true; break; case ']': select = false; break; case 'L': s->moveCursorsLeft(select, 1); break; case 'R': s->moveCursorsRight(select, 1); break; case '>': s->moveCursorsEndOfLine(select); break; case '<': s->moveCursorsStartOfLine(select); break; case 'U': s->moveCursorsUp(select, 1); break; case 'D': s->moveCursorsDown(select, 1); break; case '+': s->toggleSecondaryCursorAt(s->primaryCursor()); s->setSecondaryFrozen(true); break; case 'N': s->moveCursorsWordNext(select); break; case 'P': s->moveCursorsWordPrevious(select); break; case '$': s->clearSecondaryCursors(); break; case '#': s->toggleSecondaryFrozen(); break; case '(': { KateMultiSelection::SelectionFlags flags = KateMultiSelection::UsePrimaryCursor; if ( m_script.at(pos) == '+' ) { pos++; flags = KateMultiSelection::AddNewCursor; } KateMultiSelection::SelectionMode smode = KateMultiSelection::None; auto modeChar = m_script.at(pos); pos++; if ( modeChar == 'C' ) { smode = KateMultiSelection::Mouse; } else if ( modeChar == 'W' ) { smode = KateMultiSelection::Word; } else if ( modeChar == 'L' ) { smode = KateMultiSelection::Line; } else { qWarning() << "invalid mode char" << modeChar << "in script" << m_script << "pos"; } auto anchor = nextCursor(); mode = MouseSelect; s->selections()->beginNewSelection(anchor, smode, flags); break; } default: qWarning() << "unhandled character" << c << "in script:" << m_script; } } else if ( mode == MouseSelect ) { switch ( c.unicode() ) { case ')': s->selections()->finishNewSelection(); mode = Move; break; default: auto next = nextCursor(); s->selections()->updateNewSelection(next); break; } } } part++; return false; } bool compareState(KateMultiCursor* c, const QString& state) { auto cursors = c->cursors(); auto selections = c->selections()->selections(); + selections.erase(std::remove_if(selections.begin(), selections.end(), [](const KTextEditor::Range& r) { + return r.isEmpty(); + }), selections.end()); auto items = state.split(';'); qDebug() << QString("[State %1]").arg(part) << "compare:" << state << cursors << selections; Q_FOREACH ( const auto& item, items ) { if ( item.contains("->") ) { auto parts = item.split("->"); auto range = KTextEditor::Range(parseCursor(parts[0]), parseCursor(parts[1])); if ( ! selections.contains(range) ) { qWarning() << "Selection" << range << "not found in" << selections; return false; } selections.removeOne(range); } else { auto cursor = parseCursor(item); if ( ! cursors.contains(cursor) ) { qWarning() << "Cursor" << cursor << "not found in" << cursors; return false; } cursors.removeOne(cursor); } } if ( ! cursors.isEmpty() ) { qWarning() << cursors.size() << "cursors remain:" << cursors; return false; } if ( ! selections.isEmpty() ) { qWarning() << selections.size() << "selections remain:" << selections; return false; } return true; } QString currentState() const { return m_states.at(part-1); } QString m_script; QStringList m_states; size_t pos = 0; size_t part = 0; bool select = false; Mode mode = Move; }; void MulticursorTest::testCursorMovement() { QFETCH(QString, script); QFETCH(QString, states); KTextEditor::DocumentPrivate doc; // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890 QString playground("This is a test document\n" // 0 "with multiple lines, some [ special chars ]\n" // 1 " some space indent and trailing spaces \n" // 2 " some space indent and trailing spaces \n" // 3 "\tsome tab indent\n" // 4 "\t\tsome mixed indent\n" // 5 " some more space indent\n"); // 6 doc.setText(playground); auto view = static_cast(doc.createView(nullptr, nullptr)); // much easier to test like this, doesn't require the view to show // should have a separate test for dynwrap view->config()->setDynWordWrap(false); MulticursorScriptRunner runner(script, states); forever { auto cont = runner.execNextPart(view->cursors()); QVERIFY(runner.compareState(view->cursors(), runner.currentState())); if ( ! cont ) { break; } } } void MulticursorTest::testCursorMovement_data() { QTest::addColumn("script"); QTest::addColumn("states"); QTest::newRow("move_around") << "RRR|LL" << "0,3 | 0,1"; QTest::newRow("move_word") << "N|P" << "0,5 | 0,0"; QTest::newRow("select_word") << "[N|P]" << "0,5 ; 0,0->0,5 | 0,0"; QTest::newRow("select_two_words") << "[NN|P|P]" << "0,8 ; 0,0->0,8 | 0,5 ; 0,0->0,5 | 0,0"; QTest::newRow("move_up_down") << "RRRDRR|ULL" << "1,5 | 0,3"; QTest::newRow("remember_x") << ">D|>|DDD|U" << "1,23 | 1,43 | 4,16 | 3,43"; QTest::newRow("select_down") << "RRR[D]" << "1,3 ; 0,3->1,3"; QTest::newRow("select_up") << "RRRD[U]" << "0,3 ; 0,3->1,3"; QTest::newRow("reduce_selection_left") << "RRRRR[LLL]|[R]" << "0,2 ; 0,2->0,5 | 0,3 ; 0,3->0,5"; QTest::newRow("reduce_selection_right") << "RRRRR[RRR]|[L]" << "0,8 ; 0,5->0,8 | 0,7 ; 0,5->0,7"; QTest::newRow("umklapp") << ">LLL[P]|[N]|[P]" << "0,15 ; 0,15->0,20 | 0,23 ; 0,20->0,23 | 0,15 ; 0,15->0,20"; QTest::newRow("two_cursors") << "+RRR|#RR" << "0,0 ; 0,3 | 0,2 ; 0,5"; QTest::newRow("join_right") << "+RR#RR [RR] | [R]" << "0,4 ; 0,6 ; 0,2->0,4 ; 0,4->0,6 | 0,7 ; 0,2->0,7"; QTest::newRow("join_left") << "+RR#RRR [LL] | [L]" << "0,1 ; 0,3 ; 0,1->0,3 ; 0,3->0,5 | 0,0 ; 0,0->0,5"; QTest::newRow("multi_select_up") << "RRRD +D +D + [U] | [U]" << "0,3 ; 1,3 ; 2,3 ; 0,3->1,3 ; 1,3->2,3 ; 2,3->3,3 | 0,0 ; 0,0->3,3"; QTest::newRow("multi_select_up2") << "RRRD +D +D [U] | [U]" << "0,3 ; 1,3 ; 2,3 ; 0,3->1,3 ; 1,3->2,3 ; 2,3->3,3 | 0,0 ; 0,0->3,3"; QTest::newRow("multi_select_down_right") << "RRR +D +D [D] | [R]" << "1,3 ; 2,3 ; 3,3 ; 0,3->1,3 ; 1,3->2,3 ; 2,3->3,3 | 3,4 ; 0,3->3,4"; QTest::newRow("multi_select_up_intersect") << "RRRD +DL +DL [U] | [U]" << "0,3; 0,3->3,1 | 0,0 ; 0,0->3,1"; QTest::newRow("simple_mouse") << "RRR(C 0,5;0,7)" << "0,7; 0,5->0,7"; QTest::newRow("simple_mouse_add") << "RRR(+C 0,5;0,7)" << "0,3; 0,7; 0,5->0,7"; QTest::newRow("two_selections") << "RRR(+C 0,5;0,7) (+C 1,10;1,13)" << "0,3; 0,7; 1,13; 0,5->0,7; 1,10->1,13"; QTest::newRow("multiselect_clear") << "RRR(+C 0,5;0,7) (C 1,10;1,13)" << "1,13; 1,10->1,13"; QTest::newRow("multiselect_reverse_range") << "RRR(+C 0,5;0,7) (+C 1,13;1,10)" << "0,3; 0,7; 1,10; 0,5->0,7; 1,10->1,13"; QTest::newRow("multiselect_stepwise") << "RRR(+C 0,5;0,6;0,6;0,7) (+C 1,10;1,11;1,13)" << "0,3; 0,7; 1,13; 0,5->0,7; 1,10->1,13"; QTest::newRow("multiselect_overlap_undo") << "(C 0,5;0,7) (+C 0,9;0,8;0,7;0,2;0,8)" << "0,7; 0,8; 0,5->0,7; 0,8->0,9"; QTest::newRow("multiselect_overlap_join") << "(C 0,5;0,7) (+C 0,9;0,8;0,7;0,2)" << "0,2; 0,2->0,9"; QTest::newRow("multiselect_overlap_join_into") << "(C 0,5;0,7) (+C 0,9;0,8;0,7;0,6)" << "0,5; 0,5->0,9"; QTest::newRow("multiselect_overlap_join_into2") << "(C 0,5;0,10) (+C 0,2;0,4;0,5;0,6)" << "0,10; 0,2->0,10"; QTest::newRow("multiselect_overlap_join_into3") << "(C 0,10;0,5) (+C 0,2;0,4;0,5;0,6)" << "0,10; 0,2->0,10"; QTest::newRow("multiselect_overlap_full") << "(C 0,5;0,10) (+C 0,9;0,8;0,7;0,2)" << "0,2; 0,2->0,10"; QTest::newRow("multiselect_start_inside") << "(C 0,5;0,10) (+C 0,7;0,12)" << "0,12; 0,5->0,12"; } char* toString(const QVector& t) { char* ret = 0; if ( t.isEmpty() ) { ret = new char[3]; strcpy(ret, "[]"); } else { QString s; s = "[ "; Q_FOREACH ( const auto& c, t ) { s.append(QString::number(c.line())).append(QLatin1String(",")).append(QString::number(c.column())); s.append(QLatin1String(", ")); } s = s.left(s.size() - 2); s.append(QLatin1String(" ]")); ret = new char[s.toUtf8().size()+1]; strcpy(ret, s.toUtf8().data()); } return ret; } void MulticursorTest::testBlockModeView() { QSKIP("Not implemented yet"); KTextEditor::DocumentPrivate doc; const QString testText("0123456789ABCDEF\n" // 0 "0123456789ABCDEF\n" // 1 "0123456789ABCDEFG\n" // 2 "0123456789ABCDEFGHI\n" // 3 "0123456789ABCDEF\n" // 4 "0123456789ABCDEF\n" // 5 "0123456789ABCDEF\n"); // 6 doc.setText(testText); auto view = static_cast(doc.createView(nullptr, nullptr)); view->show(); QApplication::processEvents(); view->setBlockSelection(true); view->setCursorPosition({0, 4}); view->shiftDown(); view->shiftDown(); view->shiftDown(); view->doc()->typeChars(view, "X"); QCOMPARE(doc.text(), QString("0123X456789ABCDEF\n" // 0 "0123X456789ABCDEF\n" // 1 "0123X456789ABCDEFG\n" // 2 "0123X456789ABCDEFGHI\n" // 3 "0123456789ABCDEF\n" // 4 "0123456789ABCDEF\n" // 5 "0123456789ABCDEF\n")); // 6)) view->backspace(); QCOMPARE(doc.text(), testText); view->doc()->typeChars(view, "X"); view->cursorLeft(); view->keyDelete(); QCOMPARE(doc.text(), testText); view->toggleInsert(); view->doc()->typeChars(view, "X"); QCOMPARE(doc.text(), QString("0123X56789ABCDEF\n" // 0 "0123X56789ABCDEF\n" // 1 "0123X56789ABCDEFG\n" // 2 "0123X56789ABCDEFGHI\n" // 3 "0123456789ABCDEF\n" // 4 "0123456789ABCDEF\n" // 5 "0123456789ABCDEF\n")); // 6 view->backspace(); QEXPECT_FAIL("", "Fixme: backspace in block overwrite mode", Continue); QCOMPARE(doc.text(), testText); } void MulticursorTest::testNavigationKeysView() { QSKIP("Not working yet"); KTextEditor::DocumentPrivate doc; // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890 QString playground("This is a test document\n" // 0 "with multiple lines, some [ special chars ]\n" // 1 " some space indent and trailing spaces \n" // 2 " some space indent and trailing spaces \n" // 3 "\tsome tab indent\n" // 4 "\t\tsome mixed indent\n" // 5 " some more space indent\n"); // 6 doc.setText(playground); auto view = static_cast(doc.createView(nullptr, nullptr)); QVERIFY(view); // needed for some layout cache related testing view->show(); auto right = view->actionCollection()->action("move_cursor_right"); auto left = view->actionCollection()->action("move_cusor_left"); auto toMatchingBracket = view->actionCollection()->action("to_matching_bracket"); auto wordRight = view->actionCollection()->action("word_right"); auto wordLeft = view->actionCollection()->action("word_left"); auto end = view->actionCollection()->action("end_of_line"); auto toggleMC = view->actionCollection()->action("add_virtual_cursor"); auto freezeMC = view->actionCollection()->action("freeze_secondary_cursors"); using C = KTextEditor::Cursor; using CL = QVector; // BEGIN GENERAL view->setCursorPosition({1, 3}); right->trigger(); QCOMPARE(view->cursorPosition(), C(1, 4)); toggleMC->trigger(); view->setCursorPosition({2, 5}); toggleMC->trigger(); view->setCursorPosition({5, 9}); { auto expected = CL{{5, 9}, {2, 5}, {1, 4}}; QCOMPARE(view->cursors()->cursors(), expected); auto expected2 = CL{{2, 5}, {1, 4}}; QCOMPARE(view->cursors()->secondaryCursors(), expected2); } QVERIFY(freezeMC->isChecked()); QVERIFY(view->cursors()->secondaryFrozen()); freezeMC->trigger(); QVERIFY(!view->cursors()->secondaryFrozen()); view->cursors()->clearSecondaryCursors(); { auto expected = CL{view->cursorPosition()}; auto expected2 = CL{{5, 9}}; QCOMPARE(expected, expected2); QCOMPARE(view->allCursors(), expected); } // END GENERAL // Some basic left-right pressing without newline transitions view->setCursorPosition({2, 3}); toggleMC->trigger(); QVERIFY(view->cursors()->secondaryFrozen()); right->trigger(); right->trigger(); { auto expected = CL{{2, 5}, {2, 3}}; QCOMPARE(view->allCursors(), expected); } freezeMC->trigger(); right->trigger(); right->trigger(); left->trigger(); { auto expected = CL{{2, 6}, {2, 4}}; QCOMPARE(view->allCursors(), expected); } // Use end key and navigate to a newline. end->trigger(); { auto expected = CL{{2, 47}}; QCOMPARE(view->allCursors(), expected); QCOMPARE(view->cursors()->secondaryCursors(), expected); } view->cursors()->setSecondaryFrozen(true); view->up(); toggleMC->trigger(); view->cursors()->setSecondaryFrozen(false); auto prev = view->allCursors(); right->trigger(); { auto expected = CL{{3, 0}, {2, 0}}; QCOMPARE(view->allCursors(), expected); } // Go back. left->trigger(); QCOMPARE(view->allCursors(), prev); QApplication::processEvents(); // needed here to update layout cache apparently? // Try going to the beginning of the line. view->home(); { auto expected = CL{{2, 3}, {1, 0}}; QCOMPARE(view->allCursors(), expected); } // toggle start of line / start of text view->down(); view->home(); { auto expected = CL{{3, 0}, {2, 3}}; QCOMPARE(view->allCursors(), expected); } // word navigation view->cursors()->clearSecondaryCursors(); view->cursors()->toggleSecondaryCursorAt({2, 8}); view->setCursorPosition({4, 6}); prev = view->allCursors(); wordRight->trigger(); { auto expected = CL{{4, 10}, {2, 14}}; QCOMPARE(view->allCursors(), expected); } wordLeft->trigger(); QCOMPARE(prev, view->allCursors()); // bracket navigation view->cursors()->clearSecondaryCursors(); view->setCursorPosition({3, 14}); view->cursors()->toggleSecondaryCursorAt({1, 26}); prev = view->allCursors(); toMatchingBracket->trigger(); QVERIFY(! view->cursors()->hasSecondaryCursors()); /// FIXME } diff --git a/autotests/src/plaintextsearch_test.cpp b/autotests/src/plaintextsearch_test.cpp index aadaaeef..179e9bf1 100644 --- a/autotests/src/plaintextsearch_test.cpp +++ b/autotests/src/plaintextsearch_test.cpp @@ -1,169 +1,169 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow 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 "plaintextsearch_test.h" #include "moc_plaintextsearch_test.cpp" #include #include #include #include QTEST_MAIN(PlainTextSearchTest) -QtMessageHandler PlainTextSearchTest::s_msgHandler = 0; +QtMessageHandler PlainTextSearchTest::s_msgHandler = nullptr; void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { switch (type) { case QtDebugMsg: /* do nothing */ break; default: PlainTextSearchTest::s_msgHandler(type, context, msg); } } void PlainTextSearchTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); s_msgHandler = qInstallMessageHandler(myMessageOutput); } void PlainTextSearchTest::cleanupTestCase() { - qInstallMessageHandler(0); + qInstallMessageHandler(nullptr); } PlainTextSearchTest::PlainTextSearchTest() : QObject() - , m_doc(0) - , m_search(0) + , m_doc(nullptr) + , m_search(nullptr) { } PlainTextSearchTest::~PlainTextSearchTest() { } void PlainTextSearchTest::init() { - m_doc = new KTextEditor::DocumentPrivate(false, false, 0, this); + m_doc = new KTextEditor::DocumentPrivate(false, false, nullptr, this); m_search = new KatePlainTextSearch(m_doc, Qt::CaseSensitive, false); } void PlainTextSearchTest::cleanup() { delete m_search; delete m_doc; } void PlainTextSearchTest::testSearchBackward_data() { QTest::addColumn("searchRange"); QTest::addColumn("expectedResult"); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 10) << KTextEditor::Range(1, 6, 1, 10); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 5) << KTextEditor::Range(1, 0, 1, 4); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 0) << KTextEditor::Range(0, 10, 0, 14); } void PlainTextSearchTest::testSearchBackward() { QFETCH(KTextEditor::Range, searchRange); QFETCH(KTextEditor::Range, expectedResult); m_doc->setText(QLatin1String("aaaa aaaa aaaa\n" "aaaa aaaa")); QCOMPARE(m_search->search(QLatin1String("aaaa"), searchRange, true), expectedResult); } void PlainTextSearchTest::testSingleLineDocument_data() { QTest::addColumn("searchRange"); QTest::addColumn("forwardResult"); QTest::addColumn("backwardResult"); QTest::newRow("[a a a a a a a a a a a a]") << KTextEditor::Range(0, 0, 0, 23) << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 18, 0, 23); QTest::newRow("[a a a a a a a a a a a ]a") << KTextEditor::Range(0, 0, 0, 22) << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 16, 0, 21); QTest::newRow("a[ a a a a a a a a a a a]") << KTextEditor::Range(0, 1, 0, 23) << KTextEditor::Range(0, 2, 0, 7) << KTextEditor::Range(0, 18, 0, 23); QTest::newRow("a[ a a a a a a a a a a ]a") << KTextEditor::Range(0, 1, 0, 22) << KTextEditor::Range(0, 2, 0, 7) << KTextEditor::Range(0, 16, 0, 21); QTest::newRow("[a a a a] a a a a a a a a") << KTextEditor::Range(0, 0, 0, 7) << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 2, 0, 7); QTest::newRow("[a a a ]a a a a a a a a a") << KTextEditor::Range(0, 0, 0, 6) << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 0, 0, 5); QTest::newRow("[a a a] a a a a a a a a a") << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 0, 0, 5) << KTextEditor::Range(0, 0, 0, 5); QTest::newRow("[a a ]a a a a a a a a a a") << KTextEditor::Range(0, 0, 0, 4) << KTextEditor::Range::invalid() << KTextEditor::Range::invalid(); QTest::newRow("a a a a a a a a [a a a a]") << KTextEditor::Range(0, 16, 0, 23) << KTextEditor::Range(0, 16, 0, 21) << KTextEditor::Range(0, 18, 0, 23); QTest::newRow("a a a a a a a a a[ a a a]") << KTextEditor::Range(0, 17, 0, 23) << KTextEditor::Range(0, 18, 0, 23) << KTextEditor::Range(0, 18, 0, 23); QTest::newRow("a a a a a a a a a [a a a]") << KTextEditor::Range(0, 18, 0, 23) << KTextEditor::Range(0, 18, 0, 23) << KTextEditor::Range(0, 18, 0, 23); QTest::newRow("a a a a a a a a a a[ a a]") << KTextEditor::Range(0, 19, 0, 23) << KTextEditor::Range::invalid() << KTextEditor::Range::invalid(); QTest::newRow("a a a a a[ a a a a] a a a") << KTextEditor::Range(0, 9, 0, 17) << KTextEditor::Range(0, 10, 0, 15) << KTextEditor::Range(0, 12, 0, 17); QTest::newRow("a a a a a[ a a] a a a a a") << KTextEditor::Range(0, 9, 0, 13) << KTextEditor::Range::invalid() << KTextEditor::Range::invalid(); } void PlainTextSearchTest::testSingleLineDocument() { QFETCH(KTextEditor::Range, searchRange); QFETCH(KTextEditor::Range, forwardResult); QFETCH(KTextEditor::Range, backwardResult); m_doc->setText(QLatin1String("a a a a a a a a a a a a")); QCOMPARE(m_search->search(QLatin1String("a a a"), searchRange, false), forwardResult); QCOMPARE(m_search->search(QLatin1String("a a a"), searchRange, true), backwardResult); } void PlainTextSearchTest::testMultilineSearch_data() { QTest::addColumn("pattern"); QTest::addColumn("inputRange"); QTest::addColumn("forwardResult"); QTest::newRow("") << "a a a\na a\na a a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 2, 5); QTest::newRow("") << "a a a\na a\na a " << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 2, 4); QTest::newRow("") << "a a a\na a\na a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 2, 3); QTest::newRow("") << "a a a\na a\na" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 2, 1); QTest::newRow("") << "a a a\na a\n" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 2, 0); QTest::newRow("") << "a a a\na a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 0, 1, 3); QTest::newRow("") << "a a\na a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 2, 1, 3); QTest::newRow("") << "a a\na a\na a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 2, 2, 3); QTest::newRow("") << "\na a\na a" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 5, 2, 3); QTest::newRow("") << "\na a\n" << KTextEditor::Range(0, 0, 2, 5) << KTextEditor::Range(0, 5, 2, 0); QTest::newRow("") << "a a a\na a\na a a" << KTextEditor::Range(0, 0, 2, 4) << KTextEditor::Range::invalid(); QTest::newRow("") << "a a a\na a\na a " << KTextEditor::Range(0, 0, 2, 4) << KTextEditor::Range(0, 0, 2, 4); QTest::newRow("") << "a a a\na a\n" << KTextEditor::Range(0, 0, 2, 0) << KTextEditor::Range(0, 0, 2, 0); QTest::newRow("") << "a a a\na a\n" << KTextEditor::Range(0, 0, 1, 3) << KTextEditor::Range::invalid(); QTest::newRow("") << "a a\n" << KTextEditor::Range(0, 0, 1, 3) << KTextEditor::Range(0, 2, 1, 0); QTest::newRow("") << "a \n" << KTextEditor::Range(0, 0, 1, 3) << KTextEditor::Range::invalid(); } void PlainTextSearchTest::testMultilineSearch() { QFETCH(QString, pattern); QFETCH(KTextEditor::Range, inputRange); QFETCH(KTextEditor::Range, forwardResult); m_doc->setText(QLatin1String("a a a\n" "a a\n" "a a a")); QCOMPARE(m_search->search(pattern, inputRange, false), forwardResult); } diff --git a/autotests/src/script_test_base.cpp b/autotests/src/script_test_base.cpp index 2799211a..0bc3afa2 100644 --- a/autotests/src/script_test_base.cpp +++ b/autotests/src/script_test_base.cpp @@ -1,193 +1,193 @@ /** * This file is part of the KDE project * * Copyright (C) 2001,2003 Peter Kelly (pmk@post.com) * Copyright (C) 2003,2004 Stephan Kulow (coolo@kde.org) * Copyright (C) 2004 Dirk Mueller ( mueller@kde.org ) * Copyright 2006, 2007 Leo Savernik (l.savernik@aon.at) * Copyright (C) 2010 Milian Wolff * Copyright (C) 2013 Gerald Senarclens de Grancy * * 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. * */ //BEGIN Includes #include "kateview.h" #include "katedocument.h" #include "kateglobal.h" #include #include #include #include #include #include #include "testutils.h" #include "script_test_base.h" const QString testDataPath(QLatin1String(TEST_DATA_DIR)); -QtMessageHandler ScriptTestBase::m_msgHandler = 0; +QtMessageHandler ScriptTestBase::m_msgHandler = nullptr; void noDebugMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { switch (type) { case QtDebugMsg: break; default: ScriptTestBase::m_msgHandler(type, context, msg); } } void ScriptTestBase::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); m_msgHandler = qInstallMessageHandler(noDebugMessageOutput); m_toplevel = new QMainWindow(); m_document = new KTextEditor::DocumentPrivate(true, false, m_toplevel); m_view = static_cast(m_document->widget()); m_env = new TestScriptEnv(m_document, m_outputWasCustomised); } void ScriptTestBase::cleanupTestCase() { qInstallMessageHandler(m_msgHandler); } void ScriptTestBase::getTestData(const QString &script) { QTest::addColumn("testcase"); // make sure the script files are valid if (!m_script_dir.isEmpty()) { QFile scriptFile(QLatin1String(JS_DATA_DIR) + m_script_dir + QLatin1Char('/') + script + QLatin1String(".js")); if (scriptFile.exists()) { QVERIFY(scriptFile.open(QFile::ReadOnly)); QScriptValue result = m_env->engine()->evaluate(QString::fromLatin1(scriptFile.readAll()), scriptFile.fileName()); QVERIFY2(!result.isError(), qPrintable(QString(result.toString() + QLatin1String("\nat ") + m_env->engine()->uncaughtExceptionBacktrace().join(QLatin1String("\n"))))); } } const QString testDir(testDataPath + m_section + QLatin1Char('/') + script + QLatin1Char('/')); if (!QFile::exists(testDir)) { QSKIP(qPrintable(QString(testDir + QLatin1String(" does not exist"))), SkipAll); } QDirIterator contents(testDir); while (contents.hasNext()) { QString entry = contents.next(); if (entry.endsWith(QLatin1Char('.'))) { continue; } QFileInfo info(entry); if (!info.isDir()) { continue; } QTest::newRow(info.baseName().toLocal8Bit().constData()) << info.absoluteFilePath(); } } void ScriptTestBase::runTest(const ExpectedFailures &failures) { if (!QFile::exists(testDataPath + m_section)) { QSKIP(qPrintable(QString(testDataPath + m_section + QLatin1String(" does not exist"))), SkipAll); } QFETCH(QString, testcase); m_toplevel->resize(800, 600); // restore size // load page QUrl url; url.setScheme(QLatin1String("file")); url.setPath(testcase + QLatin1String("/origin")); m_document->openUrl(url); // evaluate test-script QFile sourceFile(testcase + QLatin1String("/input.js")); if (!sourceFile.open(QFile::ReadOnly)) { QFAIL(qPrintable(QString::fromLatin1("Failed to open file: %1").arg(sourceFile.fileName()))); } QTextStream stream(&sourceFile); stream.setCodec("UTF8"); QString code = stream.readAll(); sourceFile.close(); // Execute script QScriptValue result = m_env->engine()->evaluate(code, testcase + QLatin1String("/input.js"), 1); QVERIFY2(!result.isError(), result.toString().toUtf8().constData()); const QString fileExpected = testcase + QLatin1String("/expected"); const QString fileActual = testcase + QLatin1String("/actual"); url.setPath(fileActual); m_document->saveAs(url); const QByteArray actualChecksum = m_document->checksum(); const QByteArray expectedChecksum = digestForFile(fileExpected); if (actualChecksum != expectedChecksum) { // diff actual and expected QProcess diff; QStringList args(QStringList() << QLatin1String("-u") << fileExpected << fileActual); diff.start(QLatin1String("diff"), args); diff.waitForFinished(); QByteArray out = diff.readAllStandardOutput(); QByteArray err = diff.readAllStandardError(); if (!err.isEmpty()) { qWarning() << err; } if (diff.exitCode() != EXIT_SUCCESS) { QTextStream(stdout) << out << endl; } for (const Failure &failure : failures) { QEXPECT_FAIL(failure.first, failure.second, Abort); } QCOMPARE(diff.exitCode(), EXIT_SUCCESS); } m_document->closeUrl(); } QByteArray ScriptTestBase::digestForFile(const QString &file) { QByteArray digest; QFile f(file); if (f.open(QIODevice::ReadOnly)) { // init the hash with the git header QCryptographicHash crypto(QCryptographicHash::Sha1); const QString header = QString(QLatin1String("blob %1")).arg(f.size()); crypto.addData(header.toLatin1() + '\0'); while (!f.atEnd()) { crypto.addData(f.read(256 * 1024)); } digest = crypto.result(); } return digest; } diff --git a/autotests/src/scriptdocument_test.cpp b/autotests/src/scriptdocument_test.cpp index b714dd20..fc9d92bd 100644 --- a/autotests/src/scriptdocument_test.cpp +++ b/autotests/src/scriptdocument_test.cpp @@ -1,128 +1,128 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow 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 "scriptdocument_test.h" #include #include #include #include #include QTEST_MAIN(ScriptDocumentTest) -QtMessageHandler ScriptDocumentTest::s_msgHandler = 0; +QtMessageHandler ScriptDocumentTest::s_msgHandler = nullptr; void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { switch (type) { case QtDebugMsg: /* do nothing */ break; default: ScriptDocumentTest::s_msgHandler(type, context, msg); } } void ScriptDocumentTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); s_msgHandler = qInstallMessageHandler(myMessageOutput); } void ScriptDocumentTest::cleanupTestCase() { - qInstallMessageHandler(0); + qInstallMessageHandler(nullptr); } ScriptDocumentTest::ScriptDocumentTest() : QObject() - , m_doc(0) - , m_view(0) - , m_scriptDoc(0) + , m_doc(nullptr) + , m_view(nullptr) + , m_scriptDoc(nullptr) { } ScriptDocumentTest::~ScriptDocumentTest() { } void ScriptDocumentTest::init() { m_doc = new KTextEditor::DocumentPrivate; - m_view = m_doc->createView(0); + m_view = m_doc->createView(nullptr); m_scriptDoc = new KateScriptDocument(this); m_scriptDoc->setDocument(m_doc); } void ScriptDocumentTest::cleanup() { delete m_scriptDoc; delete m_view; delete m_doc; } #if 0 void ScriptDocumentTest::testRfind_data() { QTest::addColumn("searchRange"); QTest::addColumn("expectedResult"); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 10) << KTextEditor::Range(1, 6, 1, 10); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 5) << KTextEditor::Range(1, 0, 1, 4); QTest::newRow("") << KTextEditor::Range(0, 0, 1, 0) << KTextEditor::Range(0, 10, 0, 14); } void ScriptDocumentTest::testRfind() { QFETCH(KTextEditor::Range, searchRange); QFETCH(KTextEditor::Range, expectedResult); m_doc->setText("aaaa aaaa aaaa\n" "aaaa aaaa"); QCOMPARE(m_search->search(searchRange, "aaaa", true), expectedResult); } #endif void ScriptDocumentTest::testRfind_data() { QTest::addColumn("searchStart"); QTest::addColumn("result"); QTest::newRow("a a a a a a a a a a a a|") << KTextEditor::Cursor(0, 23) << KTextEditor::Cursor(0, 18); QTest::newRow("a a a a a a a a a a a |a") << KTextEditor::Cursor(0, 22) << KTextEditor::Cursor(0, 16); QTest::newRow("a a a a| a a a a a a a a") << KTextEditor::Cursor(0, 7) << KTextEditor::Cursor(0, 2); QTest::newRow("a a a |a a a a a a a a a") << KTextEditor::Cursor(0, 6) << KTextEditor::Cursor(0, 0); QTest::newRow("a a a| a a a a a a a a a") << KTextEditor::Cursor(0, 5) << KTextEditor::Cursor(0, 0); QTest::newRow("a a |a a a a a a a a a a") << KTextEditor::Cursor(0, 4) << KTextEditor::Cursor::invalid(); } void ScriptDocumentTest::testRfind() { QFETCH(KTextEditor::Cursor, searchStart); QFETCH(KTextEditor::Cursor, result); m_scriptDoc->setText("a a a a a a a a a a a a"); QCOMPARE(m_scriptDoc->rfind(searchStart.line(), searchStart.column(), "a a a"), result); } #include "moc_scriptdocument_test.cpp" diff --git a/autotests/src/searchbar_test.cpp b/autotests/src/searchbar_test.cpp index e3c568ec..246f4cc2 100644 --- a/autotests/src/searchbar_test.cpp +++ b/autotests/src/searchbar_test.cpp @@ -1,643 +1,643 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow 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 "searchbar_test.h" #include "ui_searchbarincremental.h" #include "ui_searchbarpower.h" #include #include #include #include #include #include #include #include #include QTEST_MAIN(SearchBarTest) #define testNewRow() (QTest::newRow(QString("line %1").arg(__LINE__).toLatin1().data())) using namespace KTextEditor; SearchBarTest::SearchBarTest() : QObject() { } SearchBarTest::~SearchBarTest() { } void SearchBarTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); KMessageBox::saveDontShowAgainYesNo(QLatin1String("DoNotShowAgainContinueSearchDialog"), KMessageBox::Yes); } void SearchBarTest::cleanupTestCase() { } void SearchBarTest::testFindNextIncremental() { KTextEditor::DocumentPrivate doc; doc.setText("a a a b b"); - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); KateSearchBar bar(false, &view, &config); bar.setSearchPattern("b"); QCOMPARE(view.selectionRange(), Range(0, 6, 0, 7)); bar.findNext(); QCOMPARE(view.selectionRange(), Range(0, 8, 0, 9)); bar.setSearchPattern("a"); QCOMPARE(view.selectionRange(), Range(0, 0, 0, 1)); bar.findNext(); QCOMPARE(view.selectionRange(), Range(0, 2, 0, 3)); bar.findNext(); QCOMPARE(view.selectionRange(), Range(0, 4, 0, 5)); bar.findNext(); QCOMPARE(view.selectionRange(), Range(0, 0, 0, 1)); } void SearchBarTest::testSetMatchCaseIncremental() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("a A a"); KateSearchBar bar(false, &view, &config); QVERIFY(!bar.isPower()); QVERIFY(!view.selection()); bar.setMatchCase(false); bar.setSearchPattern("A"); QVERIFY(!bar.matchCase()); QCOMPARE(view.selectionRange(), Range(0, 0, 0, 1)); bar.setMatchCase(true); QVERIFY(bar.matchCase()); QCOMPARE(view.selectionRange(), Range(0, 2, 0, 3)); bar.setMatchCase(false); QVERIFY(!bar.matchCase()); QCOMPARE(view.selectionRange(), Range(0, 0, 0, 1)); bar.setMatchCase(true); QVERIFY(bar.matchCase()); QCOMPARE(view.selectionRange(), Range(0, 2, 0, 3)); } void SearchBarTest::testSetMatchCasePower() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("a A a"); view.setCursorPosition(Cursor(0, 0)); KateSearchBar bar(true, &view, &config); QVERIFY(bar.isPower()); QVERIFY(!view.selection()); bar.setMatchCase(false); bar.setSearchPattern("A"); bar.findNext(); QCOMPARE(bar.searchPattern(), QString("A")); QVERIFY(!bar.matchCase()); QCOMPARE(view.selectionRange(), Range(0, 0, 0, 1)); bar.setMatchCase(true); QCOMPARE(view.selectionRange(), Range(0, 0, 0, 1)); bar.findNext(); QVERIFY(bar.matchCase()); QCOMPARE(view.selectionRange(), Range(0, 2, 0, 3)); bar.setMatchCase(false); QVERIFY(!bar.matchCase()); QCOMPARE(view.selectionRange(), Range(0, 2, 0, 3)); bar.findNext(); QCOMPARE(view.selectionRange(), Range(0, 4, 0, 5)); } void SearchBarTest::testSetSelectionOnlyPower() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("a a a"); KateSearchBar bar(true, &view, &config); bar.setSearchPattern("a"); QVERIFY(bar.isPower()); QVERIFY(!view.selection()); bar.setSelectionOnly(false); bar.findNext(); QVERIFY(!bar.selectionOnly()); QCOMPARE(view.selectionRange(), Range(0, 0, 0, 1)); view.setSelection(Range(0, 2, 0, 5)); bar.setSelectionOnly(true); QVERIFY(bar.selectionOnly()); bar.findNext(); QCOMPARE(view.selectionRange(), Range(0, 2, 0, 3)); QVERIFY(bar.selectionOnly()); bar.setSelectionOnly(false); bar.findNext(); QCOMPARE(view.selectionRange(), Range(0, 4, 0, 5)); QVERIFY(!bar.selectionOnly()); } void SearchBarTest::testSetSearchPattern_data() { QTest::addColumn("power"); QTest::addColumn("numMatches2"); testNewRow() << false << 0; testNewRow() << true << 3; } void SearchBarTest::testSetSearchPattern() { QFETCH(bool, power); QFETCH(int, numMatches2); KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("a a a"); KateSearchBar bar(power, &view, &config); bar.setSearchPattern("a"); bar.findAll(); QCOMPARE(bar.m_hlRanges.size(), 3); bar.setSearchPattern("a "); QCOMPARE(bar.m_hlRanges.size(), numMatches2); bar.findAll(); QCOMPARE(bar.m_hlRanges.size(), 2); } void SearchBarTest::testSetSelectionOnly() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("a a a"); view.setSelection(Range(0, 0, 0, 3)); KateSearchBar bar(false, &view, &config); bar.setSelectionOnly(false); bar.setSearchPattern("a"); bar.findAll(); QCOMPARE(bar.m_hlRanges.size(), 3); bar.setSelectionOnly(true); QCOMPARE(bar.m_hlRanges.size(), 3); } void SearchBarTest::testFindAll_data() { QTest::addColumn("power"); QTest::addColumn("numMatches2"); QTest::addColumn("numMatches4"); testNewRow() << false << 0 << 0; testNewRow() << true << 3 << 2; } void SearchBarTest::testFindAll() { QFETCH(bool, power); QFETCH(int, numMatches2); QFETCH(int, numMatches4); KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("a a a"); KateSearchBar bar(power, &view, &config); QCOMPARE(bar.isPower(), power); bar.setSearchPattern("a"); bar.findAll(); QCOMPARE(bar.m_hlRanges.size(), 3); QCOMPARE(bar.m_hlRanges.at(0)->toRange(), Range(0, 0, 0, 1)); QCOMPARE(bar.m_hlRanges.at(1)->toRange(), Range(0, 2, 0, 3)); QCOMPARE(bar.m_hlRanges.at(2)->toRange(), Range(0, 4, 0, 5)); bar.setSearchPattern("a "); QCOMPARE(bar.m_hlRanges.size(), numMatches2); bar.findAll(); QCOMPARE(bar.m_hlRanges.size(), 2); bar.setSearchPattern("a "); QCOMPARE(bar.m_hlRanges.size(), numMatches4); bar.findAll(); QCOMPARE(bar.m_hlRanges.size(), 0); } void SearchBarTest::testReplaceAll() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("a a a"); KateSearchBar bar(true, &view, &config); bar.setSearchPattern("a"); bar.setReplacementPattern(""); bar.replaceAll(); QCOMPARE(bar.m_hlRanges.size(), 3); QCOMPARE(bar.m_hlRanges.at(0)->toRange(), Range(0, 0, 0, 0)); QCOMPARE(bar.m_hlRanges.at(1)->toRange(), Range(0, 1, 0, 1)); QCOMPARE(bar.m_hlRanges.at(2)->toRange(), Range(0, 2, 0, 2)); bar.setSearchPattern(" "); bar.setReplacementPattern("b"); bar.replaceAll(); QCOMPARE(bar.m_hlRanges.size(), 2); QCOMPARE(bar.m_hlRanges.at(0)->toRange(), Range(0, 0, 0, 1)); QCOMPARE(bar.m_hlRanges.at(1)->toRange(), Range(0, 1, 0, 2)); } void SearchBarTest::testFindSelectionForward_data() { QTest::addColumn("text"); QTest::addColumn("selectionOnly"); QTest::addColumn("selectionRange"); QTest::addColumn("match"); testNewRow() << "a a a" << false << Range(0, 0, 0, 1) << Range(0, 0, 0, 2); testNewRow() << "a a a" << true << Range(0, 0, 0, 1) << Range(0, 0, 0, 1); testNewRow() << "a a a" << false << Range(0, 0, 0, 2) << Range(0, 2, 0, 4); testNewRow() << "a a a" << true << Range(0, 0, 0, 2) << Range(0, 0, 0, 2); testNewRow() << "a a a" << false << Range(0, 0, 0, 3) << Range(0, 0, 0, 2); testNewRow() << "a a a" << true << Range(0, 0, 0, 3) << Range(0, 0, 0, 2); testNewRow() << "a a a" << false << Range(0, 2, 0, 4) << Range(0, 0, 0, 2); testNewRow() << "a a a" << true << Range(0, 2, 0, 4) << Range(0, 2, 0, 4); testNewRow() << "a a a" << false << Range(0, 3, 0, 4) << Range(0, 0, 0, 2); testNewRow() << "a a a" << true << Range(0, 3, 0, 4) << Range(0, 3, 0, 4); } void SearchBarTest::testFindSelectionForward() { QFETCH(QString, text); QFETCH(bool, selectionOnly); QFETCH(Range, selectionRange); QFETCH(Range, match); KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText(text); view.setSelection(Range(0, 0, 0, 2)); KateSearchBar bar(true, &view, &config); QVERIFY(bar.searchPattern() == QString("a ")); view.setSelection(selectionRange); QCOMPARE(view.selectionRange(), selectionRange); bar.setSelectionOnly(selectionOnly); bar.findNext(); QCOMPARE(view.selectionRange(), match); } void SearchBarTest::testRemoveWithSelectionForward_data() { QTest::addColumn("selectionRange"); QTest::addColumn("match"); testNewRow() << Range(0, 0, 0, 1) << Range(0, 0, 0, 2); testNewRow() << Range(0, 0, 0, 2) << Range(0, 0, 0, 2); testNewRow() << Range(0, 0, 0, 3) << Range(0, 0, 0, 2); testNewRow() << Range(0, 2, 0, 4) << Range(0, 0, 0, 2); testNewRow() << Range(0, 3, 0, 4) << Range(0, 0, 0, 2); } void SearchBarTest::testRemoveWithSelectionForward() { QFETCH(Range, selectionRange); QFETCH(Range, match); KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("a a a"); view.setSelection(selectionRange); KateSearchBar bar(true, &view, &config); bar.setSearchPattern("a "); bar.setSelectionOnly(false); bar.replaceNext(); QCOMPARE(view.selectionRange(), match); } void SearchBarTest::testRemoveInSelectionForward_data() { QTest::addColumn("selectionRange"); QTest::addColumn("match"); testNewRow() << Range(0, 0, 0, 1) << Range(0, 0, 0, 1); testNewRow() << Range(0, 0, 0, 2) << Range(0, 0, 0, 0); testNewRow() << Range(0, 0, 0, 3) << Range(0, 0, 0, 2); testNewRow() << Range(0, 0, 0, 4) << Range(0, 0, 0, 2); testNewRow() << Range(0, 2, 0, 4) << Range(0, 2, 0, 2); testNewRow() << Range(0, 3, 0, 4) << Range(0, 3, 0, 4); } void SearchBarTest::testRemoveInSelectionForward() { QFETCH(Range, selectionRange); QFETCH(Range, match); KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("a a a"); view.setSelection(selectionRange); KateSearchBar bar(true, &view, &config); bar.setSearchPattern("a "); bar.setSelectionOnly(true); QVERIFY(bar.replacementPattern().isEmpty()); bar.replaceNext(); QCOMPARE(view.selectionRange(), match); } void SearchBarTest::testReplaceWithDoubleSelecion_data() { QTest::addColumn("text"); QTest::addColumn("selectionRange"); QTest::addColumn("result"); QTest::addColumn("match"); // testNewRow() << "a" << Range(0, 0, 0, 1) << "aa" << Range(?, ?, ?, ?); testNewRow() << "aa" << Range(0, 1, 0, 2) << "aaa" << Range(0, 0, 0, 1); testNewRow() << "aa" << Range(0, 0, 0, 1) << "aaa" << Range(0, 2, 0, 3); // testNewRow() << "ab" << Range(0, 0, 0, 1) << "aab" << Range(?, ?, ?, ?); testNewRow() << "aab" << Range(0, 0, 0, 1) << "aaab" << Range(0, 2, 0, 3); testNewRow() << "aba" << Range(0, 0, 0, 1) << "aaba" << Range(0, 3, 0, 4); // testNewRow() << "ab" << Range(0, 0, 0, 2) << "abab" << Range(?, ?, ?, ?); testNewRow() << "abab" << Range(0, 0, 0, 2) << "ababab" << Range(0, 4, 0, 6); testNewRow() << "abab" << Range(0, 2, 0, 4) << "ababab" << Range(0, 0, 0, 2); } void SearchBarTest::testReplaceWithDoubleSelecion() { QFETCH(QString, text); QFETCH(Range, selectionRange); QFETCH(QString, result); QFETCH(Range, match); KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText(text); view.setSelection(selectionRange); KateSearchBar bar(true, &view, &config); bar.setSelectionOnly(false); bar.setReplacementPattern(bar.searchPattern() + bar.searchPattern()); bar.replaceNext(); QCOMPARE(doc.text(), result); QCOMPARE(view.selectionRange(), match); } void SearchBarTest::testReplaceDollar() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("aaa\nbbb\nccc\n\n\naaa\nbbb\nccc\nddd\n"); KateSearchBar bar(true, &view, &config); bar.setSearchPattern("$"); bar.setSearchMode(KateSearchBar::MODE_REGEX); bar.setReplacementPattern("D"); bar.replaceAll(); QCOMPARE(doc.text(), QString("aaaD\nbbbD\ncccD\nD\nD\naaaD\nbbbD\ncccD\ndddD\n")); } void SearchBarTest::testSearchHistoryIncremental() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig *const config = view.config(); KTextEditor::EditorPrivate::self()->searchHistoryModel()->setStringList(QStringList()); doc.setText("foo bar"); KateSearchBar bar(false, &view, config); bar.setSearchPattern("foo"); bar.findNext(); QCOMPARE(bar.m_incUi->pattern->findText("foo"), 0); bar.setSearchPattern("bar"); bar.findNext(); QCOMPARE(bar.m_incUi->pattern->findText("bar"), 0); QCOMPARE(bar.m_incUi->pattern->findText("foo"), 1); KTextEditor::DocumentPrivate doc2; - KTextEditor::ViewPrivate view2(&doc2, 0); + KTextEditor::ViewPrivate view2(&doc2, nullptr); KateViewConfig *const config2 = view2.config(); KateSearchBar bar2(false, &view2, config2); QCOMPARE(bar2.m_incUi->pattern->findText("bar"), 0); QCOMPARE(bar2.m_incUi->pattern->findText("foo"), 1); //testcase for https://bugs.kde.org/show_bug.cgi?id=248305 bar2.m_incUi->pattern->setCurrentIndex(1); QCOMPARE(bar2.searchPattern(), QLatin1String("foo")); bar2.findNext(); QCOMPARE(bar2.searchPattern(), QLatin1String("foo")); } void SearchBarTest::testSearchHistoryPower() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig *const config = view.config(); KTextEditor::EditorPrivate::self()->searchHistoryModel()->setStringList(QStringList()); doc.setText("foo bar"); KateSearchBar bar(true, &view, config); QCOMPARE(bar.m_powerUi->pattern->count(), 0); bar.setSearchPattern("foo"); bar.findNext(); QCOMPARE(bar.m_powerUi->pattern->findText("foo"), 0); bar.findNext(); QCOMPARE(bar.m_powerUi->pattern->findText("foo"), 0); QCOMPARE(bar.m_powerUi->pattern->count(), 1); bar.setSearchPattern("bar"); bar.findNext(); QCOMPARE(bar.m_powerUi->pattern->findText("bar"), 0); QCOMPARE(bar.m_powerUi->pattern->findText("foo"), 1); QCOMPARE(bar.m_powerUi->pattern->count(), 2); KTextEditor::DocumentPrivate doc2; - KTextEditor::ViewPrivate view2(&doc2, 0); + KTextEditor::ViewPrivate view2(&doc2, nullptr); KateViewConfig *const config2 = view2.config(); KateSearchBar bar2(true, &view2, config2); QCOMPARE(bar2.m_powerUi->pattern->findText("bar"), 0); QCOMPARE(bar2.m_powerUi->pattern->findText("foo"), 1); } // Make sure Kate doesn't replace anything outside selection in block mode (see bug 253191) void SearchBarTest::testReplaceInBlockMode() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); view.setInputMode(View::NormalInputMode); KateViewConfig config(&view); doc.setText("111\n111"); view.setBlockSelection(true); view.setSelection(KTextEditor::Range(0, 1, 1, 2)); KateSearchBar bar(true, &view, &config); bar.setSearchPattern("1"); bar.setReplacementPattern("2"); bar.replaceAll(); QCOMPARE(doc.text(), QString("121\n121")); } void SearchBarTest::testReplaceManyCapturesBug365124() { KTextEditor::DocumentPrivate doc; - KTextEditor::ViewPrivate view(&doc, 0); + KTextEditor::ViewPrivate view(&doc, nullptr); KateViewConfig config(&view); doc.setText("one two three four five six seven eight nine ten eleven twelve thirteen\n"); KateSearchBar bar(true, &view, &config); bar.setSearchPattern("^(.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*) (.*)$"); bar.setSearchMode(KateSearchBar::MODE_REGEX); bar.setReplacementPattern("\\{1}::\\2::\\3::\\4::\\5::\\6::\\7::\\8::\\9::\\{10}::\\{11}::\\{12}::\\{13}"); bar.replaceAll(); QCOMPARE(doc.text(), QString("one::two::three::four::five::six::seven::eight::nine::ten::eleven::twelve::thirteen\n")); } #include "moc_searchbar_test.cpp" diff --git a/autotests/src/templatehandler_test.cpp b/autotests/src/templatehandler_test.cpp index b72dd285..e8863184 100644 --- a/autotests/src/templatehandler_test.cpp +++ b/autotests/src/templatehandler_test.cpp @@ -1,372 +1,372 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow 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 "templatehandler_test.h" #include #include #include #include #include #include #include #include QTEST_MAIN(TemplateHandlerTest) using namespace KTextEditor; TemplateHandlerTest::TemplateHandlerTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } void TemplateHandlerTest::testUndo() { const QString snippet = "for (${type=\"int\"} ${index=\"i\"} = ; ${index} < ; ++${index})\n" "{\n" " ${index}\n" "}"; - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); // fixed indentation options doc->config()->setTabWidth(8); doc->config()->setIndentationWidth(4); doc->config()->setReplaceTabsDyn(true); view->insertTemplate({0, 0}, snippet); const QString result = "for (int i = ; i < ; ++i)\n" "{\n" " i\n" "}"; QCOMPARE(doc->text(), result); doc->replaceText(Range(0, 9, 0, 10), "j"); const QString result2 = "for (int j = ; j < ; ++j)\n" "{\n" " j\n" "}"; QCOMPARE(doc->text(), result2); doc->undo(); QCOMPARE(doc->text(), result); doc->redo(); QCOMPARE(doc->text(), result2); doc->insertText(Cursor(0, 10), "j"); doc->insertText(Cursor(0, 11), "j"); const QString result3 = "for (int jjj = ; jjj < ; ++jjj)\n" "{\n" " jjj\n" "}"; QCOMPARE(doc->text(), result3); doc->undo(); QCOMPARE(doc->text(), result); doc->redo(); QCOMPARE(doc->text(), result3); doc->undo(); QCOMPARE(doc->text(), result); doc->undo(); QCOMPARE(doc->text(), QString()); } void TemplateHandlerTest::testEscapes() { - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, QStringLiteral("\\${field} ${bar} \\${foo=3} \\\\${baz=7}")); QCOMPARE(doc->text(), QStringLiteral("${field} bar ${foo=3} \\${baz=7}")); } void TemplateHandlerTest::testSimpleMirror() { QFETCH(QString, text); - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, text); QCOMPARE(doc->text(), QString(text).replace("${foo}", "foo")); doc->insertText({0, 0}, "xx"); QCOMPARE(doc->text(), QString(text).replace("${foo}", "xxfoo")); doc->removeText(KTextEditor::Range({0, 0}, {0, 2})); QCOMPARE(doc->text(), QString(text).replace("${foo}", "foo")); delete doc; } void TemplateHandlerTest::testSimpleMirror_data() { QTest::addColumn("text"); QTest::newRow("one") << "${foo}"; QTest::newRow("several") << "${foo} ${foo} Foo ${foo}"; } void TemplateHandlerTest::testAdjacentRanges() { - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); using S = QString; view->insertTemplate({0, 0}, S("${foo} ${foo}")); QCOMPARE(doc->text(), S("foo foo")); doc->removeText(KTextEditor::Range({0, 3}, {0, 4})); QCOMPARE(doc->text(), S("foofoo")); doc->insertText({0, 1}, S("x")); QCOMPARE(doc->text(), S("fxoofxoo")); doc->insertText({0, 4}, S("y")); QCOMPARE(doc->text(), S("fxooyfxooy")); doc->removeText(KTextEditor::Range({0, 4}, {0, 5})); QCOMPARE(doc->text(), S("fxoofxoo")); delete doc; } void TemplateHandlerTest::testTab() { QFETCH(QString, tpl); QFETCH(int, cursor); - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, tpl); view->setCursorPosition({0, cursor}); // no idea why the event needs to be posted to the focus proxy QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QTEST(view->cursorPosition().column(), "expected_cursor"); QTest::keyClick(view->focusProxy(), Qt::Key_Tab, Qt::ShiftModifier); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QTEST(view->cursorPosition().column(), "expected_cursor"); delete doc; } void TemplateHandlerTest::testTab_data() { QTest::addColumn("tpl"); QTest::addColumn("cursor"); QTest::addColumn("expected_cursor"); QTest::newRow("simple_start") << "${foo} ${bar}" << 0 << 4; QTest::newRow("simple_mid") << "${foo} ${bar}" << 2 << 4; QTest::newRow("simple_end") << "${foo} ${bar}" << 3 << 4; QTest::newRow("wrap_start") << "${foo} ${bar}" << 4 << 0; QTest::newRow("wrap_mid") << "${foo} ${bar}" << 5 << 0; QTest::newRow("wrap_end") << "${foo} ${bar}" << 6 << 0; QTest::newRow("non_editable_start") << "${foo} ${foo}" << 0 << 0; QTest::newRow("non_editable_mid") << "${foo} ${foo}" << 2 << 0; QTest::newRow("non_editable_end") << "${foo} ${foo}" << 3 << 0; QTest::newRow("skip_non_editable") << "${foo} ${foo} ${bar}" << 0 << 8; QTest::newRow("skip_non_editable_at_end") << "${foo} ${bar} ${foo}" << 4 << 0; QTest::newRow("jump_to_cursor") << "${foo} ${cursor}" << 0 << 4; QTest::newRow("jump_to_cursor_last") << "${foo} ${cursor} ${bar}" << 0 << 5; QTest::newRow("jump_to_cursor_last2") << "${foo} ${cursor} ${bar}" << 5 << 4; } void TemplateHandlerTest::testExitAtCursor() { - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, QStringLiteral("${foo} ${bar} ${cursor} ${foo}")); view->setCursorPosition({0, 0}); // check it jumps to the cursor QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QCOMPARE(view->cursorPosition().column(), 4); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QCOMPARE(view->cursorPosition().column(), 8); // insert an a at cursor position QTest::keyClick(view->focusProxy(), Qt::Key_A); // check it was inserted QCOMPARE(doc->text(), QStringLiteral("foo bar a foo")); // required to process the deleteLater() used to exit the template handler - QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); QApplication::processEvents(); // go to the first field and verify it's not mirrored any more (i.e. the handler exited) view->setCursorPosition({0, 0}); QTest::keyClick(view->focusProxy(), Qt::Key_A); QCOMPARE(doc->text(), QStringLiteral("afoo bar a foo")); delete doc; } void TemplateHandlerTest::testDefaultMirror() { - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); using S = QString; view->insertTemplate({0, 0}, S("${foo=uppercase(\"hi\")} ${bar=3} ${foo}"), S("function uppercase(x) { return x.toUpperCase(); }")); QCOMPARE(doc->text(), S("HI 3 HI")); doc->insertText({0, 0}, "xy@"); QCOMPARE(doc->text(), S("xy@HI 3 xy@HI")); delete doc; } void TemplateHandlerTest::testFunctionMirror() { - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); using S = QString; view->insertTemplate({0, 0}, S("${foo} hi ${uppercase(foo)}"), S("function uppercase(x) { return x.toUpperCase(); }")); QCOMPARE(doc->text(), S("foo hi FOO")); doc->insertText({0, 0}, "xy@"); QCOMPARE(doc->text(), S("xy@foo hi XY@FOO")); delete doc; } void TemplateHandlerTest::testAutoSelection() { - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, "${foo} ${bar} ${bar} ${cursor} ${baz}"); QCOMPARE(doc->text(), QStringLiteral("foo bar bar baz")); QCOMPARE(view->selectionText(), QStringLiteral("foo")); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QCOMPARE(view->selectionText(), QStringLiteral("bar")); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QCOMPARE(view->selectionText(), QStringLiteral("baz")); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QVERIFY(view->selectionRange().isEmpty()); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QCOMPARE(view->selectionText(), QStringLiteral("foo")); QTest::keyClick(view->focusProxy(), Qt::Key_A); QCOMPARE(doc->text(), QStringLiteral("a bar bar baz")); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QTest::keyClick(view->focusProxy(), Qt::Key_Tab); QVERIFY(view->selectionRange().isEmpty()); } void TemplateHandlerTest::testNotEditableFields() { QFETCH(QString, input); QFETCH(int, change_offset); - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); view->insertTemplate({0, 0}, input); doc->insertText({0, change_offset}, "xxx"); QTEST(doc->text(), "expected"); } void TemplateHandlerTest::testNotEditableFields_data() { QTest::addColumn("input"); QTest::addColumn("change_offset"); QTest::addColumn("expected"); using S = QString; QTest::newRow("mirror") << S("${foo} ${foo}") << 6 << "foo foxxxo"; } void TemplateHandlerTest::testCanRetrieveSelection() { - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); view->insertText("hi world"); view->setSelection(KTextEditor::Range(0, 1, 0, 4)); view->insertTemplate({0, 1}, QStringLiteral("xx${foo=sel()}xx"), QStringLiteral("function sel() { return view.selectedText(); }") ); QCOMPARE(doc->text(), QStringLiteral("hxxi wxxorld")); } void TemplateHandlerTest::testDefaults_data() { QTest::addColumn("input"); QTest::addColumn("expected"); QTest::addColumn("function"); using S = QString; QTest::newRow("empty") << S() << S() << S(); QTest::newRow("foo") << S("${foo}") << S("foo") << S(); QTest::newRow("foo=3") << S("${foo=3}") << S("3") << S(); QTest::newRow("${foo=3+5}") << S("${foo=3+5}") << S("8") << S(); QTest::newRow("string") << S("${foo=\"3+5\"}") << S("3+5") << S(); QTest::newRow("string_mirror") << S("${foo=\"Bar\"} ${foo}") << S("Bar Bar") << S(); QTest::newRow("func_simple") << S("${foo=myfunc()}") << S("hi") << S("function myfunc() { return 'hi'; }"); QTest::newRow("func_fixed") << S("${myfunc()}") << S("hi") << S("function myfunc() { return 'hi'; }"); QTest::newRow("func_constant_arg") << S("${foo=uppercase(\"Foo\")}") << S("FOO") << S("function uppercase(x) { return x.toUpperCase(); }"); QTest::newRow("func_constant_arg_mirror") << S("${foo=uppercase(\"hi\")} ${bar=3} ${foo}") << S("HI 3 HI") << S("function uppercase(x) { return x.toUpperCase(); }"); QTest::newRow("cursor") << S("${foo} ${cursor}") << S("foo ") << S(); QTest::newRow("only_cursor") << S("${cursor}") << S("") << S(); QTest::newRow("only_cursor_stuff") << S("fdas ${cursor} asdf") << S("fdas asdf") << S(); } void TemplateHandlerTest::testDefaults() { - auto doc = new KTextEditor::DocumentPrivate(false, false, 0, 0); + auto doc = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); auto view = static_cast(doc->createView(nullptr)); QFETCH(QString, input); QFETCH(QString, function); view->insertTemplate({0, 0}, input, function); QTEST(doc->text(), "expected"); view->selectAll(); view->keyDelete(); QCOMPARE(doc->text(), QString()); delete doc; } #include "moc_templatehandler_test.cpp" diff --git a/autotests/src/testutils.cpp b/autotests/src/testutils.cpp index 17a822f6..e2589e11 100644 --- a/autotests/src/testutils.cpp +++ b/autotests/src/testutils.cpp @@ -1,353 +1,353 @@ /** * This file is part of the KDE project * * Copyright (C) 2001,2003 Peter Kelly (pmk@post.com) * Copyright (C) 2003,2004 Stephan Kulow (coolo@kde.org) * Copyright (C) 2004 Dirk Mueller ( mueller@kde.org ) * Copyright 2006, 2007 Leo Savernik (l.savernik@aon.at) * * 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. * */ //BEGIN Includes #include "testutils.h" #include "kateview.h" #include "kateconfig.h" #include "katedocument.h" #include "katescripthelpers.h" #include #include //END Includes //BEGIN TestScriptEnv //BEGIN conversion functions for Cursors and Ranges /** Converstion function from KTextEditor::Cursor to QtScript cursor */ static QScriptValue cursorToScriptValue(QScriptEngine *engine, const KTextEditor::Cursor &cursor) { QString code = QStringLiteral("new Cursor(%1, %2);").arg(cursor.line()) .arg(cursor.column()); return engine->evaluate(code); } /** Converstion function from QtScript cursor to KTextEditor::Cursor */ static void cursorFromScriptValue(const QScriptValue &obj, KTextEditor::Cursor &cursor) { cursor.setPosition(obj.property(QStringLiteral("line")).toInt32(), obj.property(QStringLiteral("column")).toInt32()); } /** Converstion function from QtScript range to KTextEditor::Range */ static QScriptValue rangeToScriptValue(QScriptEngine *engine, const KTextEditor::Range &range) { QString code = QStringLiteral("new Range(%1, %2, %3, %4);").arg(range.start().line()) .arg(range.start().column()) .arg(range.end().line()) .arg(range.end().column()); return engine->evaluate(code); } /** Converstion function from QtScript range to KTextEditor::Range */ static void rangeFromScriptValue(const QScriptValue &obj, KTextEditor::Range &range) { range.setStart(KTextEditor::Cursor( obj.property(QStringLiteral("start")).property(QStringLiteral("line")).toInt32(), obj.property(QStringLiteral("start")).property(QStringLiteral("column")).toInt32() )); range.setEnd(KTextEditor::Cursor( obj.property(QStringLiteral("end")).property(QStringLiteral("line")).toInt32(), obj.property(QStringLiteral("end")).property(QLatin1String("column")).toInt32() )); } //END TestScriptEnv::TestScriptEnv(KTextEditor::DocumentPrivate *part, bool &cflag) - : m_engine(0), m_viewObj(0), m_docObj(0), m_output(0) + : m_engine(nullptr), m_viewObj(nullptr), m_docObj(nullptr), m_output(nullptr) { m_engine = new QScriptEngine(this); qScriptRegisterMetaType(m_engine, cursorToScriptValue, cursorFromScriptValue); qScriptRegisterMetaType(m_engine, rangeToScriptValue, rangeFromScriptValue); // export read & require function and add the require guard object m_engine->globalObject().setProperty(QStringLiteral("read"), m_engine->newFunction(Kate::Script::read)); m_engine->globalObject().setProperty(QStringLiteral("require"), m_engine->newFunction(Kate::Script::require)); m_engine->globalObject().setProperty(QStringLiteral("require_guard"), m_engine->newObject()); // export debug function m_engine->globalObject().setProperty(QStringLiteral("debug"), m_engine->newFunction(Kate::Script::debug)); // export translation functions m_engine->globalObject().setProperty(QStringLiteral("i18n"), m_engine->newFunction(Kate::Script::i18n)); m_engine->globalObject().setProperty(QStringLiteral("i18nc"), m_engine->newFunction(Kate::Script::i18nc)); m_engine->globalObject().setProperty(QStringLiteral("i18ncp"), m_engine->newFunction(Kate::Script::i18ncp)); m_engine->globalObject().setProperty(QStringLiteral("i18np"), m_engine->newFunction(Kate::Script::i18np)); KTextEditor::ViewPrivate *view = qobject_cast(part->widget()); m_viewObj = new KateViewObject(view); QScriptValue sv = m_engine->newQObject(m_viewObj); m_engine->globalObject().setProperty(QStringLiteral("view"), sv); m_engine->globalObject().setProperty(QStringLiteral("v"), sv); m_docObj = new KateDocumentObject(view->doc()); QScriptValue sd = m_engine->newQObject(m_docObj); m_engine->globalObject().setProperty(QStringLiteral("document"), sd); m_engine->globalObject().setProperty(QStringLiteral("d"), sd); m_output = new OutputObject(view, cflag); QScriptValue so = m_engine->newQObject(m_output); m_engine->globalObject().setProperty(QStringLiteral("output"), so); m_engine->globalObject().setProperty(QStringLiteral("out"), so); m_engine->globalObject().setProperty(QStringLiteral("o"), so); } TestScriptEnv::~TestScriptEnv() { // delete explicitly, as the parent is the KTE::Document kpart, which is // reused for all tests. Hence, we explicitly have to delete the bindings. - delete m_output; m_output = 0; - delete m_docObj; m_docObj = 0; - delete m_viewObj; m_viewObj = 0; + delete m_output; m_output = nullptr; + delete m_docObj; m_docObj = nullptr; + delete m_viewObj; m_viewObj = nullptr; // delete this too, although this should also be automagically be freed - delete m_engine; m_engine = 0; + delete m_engine; m_engine = nullptr; // kDebug() << "deleted"; } //END TestScriptEnv //BEGIN KateViewObject KateViewObject::KateViewObject(KTextEditor::ViewPrivate *view) : KateScriptView() { setView(view); } KateViewObject::~KateViewObject() { // kDebug() << "deleted"; } // Implements a function that calls an edit function repeatedly as specified by // its first parameter (once if not specified). #define REP_CALL(func) \ void KateViewObject::func(int cnt) { \ while (cnt--) { view()->func(); } \ } REP_CALL(keyReturn) REP_CALL(backspace) REP_CALL(deleteWordLeft) REP_CALL(keyDelete) REP_CALL(deleteWordRight) REP_CALL(transpose) REP_CALL(cursorLeft) REP_CALL(shiftCursorLeft) REP_CALL(cursorRight) REP_CALL(shiftCursorRight) REP_CALL(wordLeft) REP_CALL(shiftWordLeft) REP_CALL(wordRight) REP_CALL(shiftWordRight) REP_CALL(home) REP_CALL(shiftHome) REP_CALL(end) REP_CALL(shiftEnd) REP_CALL(up) REP_CALL(shiftUp) REP_CALL(down) REP_CALL(shiftDown) REP_CALL(scrollUp) REP_CALL(scrollDown) REP_CALL(topOfView) REP_CALL(shiftTopOfView) REP_CALL(bottomOfView) REP_CALL(shiftBottomOfView) REP_CALL(pageUp) REP_CALL(shiftPageUp) REP_CALL(pageDown) REP_CALL(shiftPageDown) REP_CALL(top) REP_CALL(shiftTop) REP_CALL(bottom) REP_CALL(shiftBottom) REP_CALL(toMatchingBracket) REP_CALL(shiftToMatchingBracket) #undef REP_CALL bool KateViewObject::type(const QString &str) { return view()->doc()->typeChars(view(), str); } void KateViewObject::setAutoBrackets(bool enable) { view()->config()->setAutoBrackets(enable); } #define ALIAS(alias, func) \ void KateViewObject::alias(int cnt) { \ func(cnt); \ } ALIAS(enter, keyReturn) ALIAS(cursorPrev, cursorLeft) ALIAS(left, cursorLeft) ALIAS(prev, cursorLeft) ALIAS(shiftCursorPrev, shiftCursorLeft) ALIAS(shiftLeft, shiftCursorLeft) ALIAS(shiftPrev, shiftCursorLeft) ALIAS(cursorNext, cursorRight) ALIAS(right, cursorRight) ALIAS(next, cursorRight) ALIAS(shiftCursorNext, shiftCursorRight) ALIAS(shiftRight, shiftCursorRight) ALIAS(shiftNext, shiftCursorRight) ALIAS(wordPrev, wordLeft) ALIAS(shiftWordPrev, shiftWordLeft) ALIAS(wordNext, wordRight) ALIAS(shiftWordNext, shiftWordRight) #undef ALIAS //END KateViewObject //BEGIN KateDocumentObject KateDocumentObject::KateDocumentObject(KTextEditor::DocumentPrivate *doc) : KateScriptDocument() { setDocument(doc); } KateDocumentObject::~KateDocumentObject() { // kDebug() << "deleted"; } //END KateDocumentObject //BEGIN OutputObject OutputObject::OutputObject(KTextEditor::ViewPrivate *v, bool &cflag) : view(v), cflag(cflag) { } OutputObject::~OutputObject() { // kDebug() << "deleted"; } void OutputObject::output(bool cp, bool ln) { QString str; for (int i = 0; i < context()->argumentCount(); ++i) { QScriptValue arg = context()->argument(i); str += arg.toString(); } if (cp) { KTextEditor::Cursor c = view->cursorPosition(); str += QLatin1Char('(') + QString::number(c.line()) + QLatin1Char(',') + QString::number(c.column()) + QLatin1Char(')'); } if (ln) { str += QLatin1Char('\n'); } view->insertText(str); cflag = true; } void OutputObject::write() { output(false, false); } void OutputObject::writeln() { output(false, true); } void OutputObject::writeLn() { output(false, true); } void OutputObject::print() { output(false, false); } void OutputObject::println() { output(false, true); } void OutputObject::printLn() { output(false, true); } void OutputObject::writeCursorPosition() { output(true, false); } void OutputObject::writeCursorPositionln() { output(true, true); } void OutputObject::cursorPosition() { output(true, false); } void OutputObject::cursorPositionln() { output(true, true); } void OutputObject::cursorPositionLn() { output(true, true); } void OutputObject::pos() { output(true, false); } void OutputObject::posln() { output(true, true); } void OutputObject::posLn() { output(true, true); } //END OutputObject diff --git a/autotests/src/undomanager_test.cpp b/autotests/src/undomanager_test.cpp index 80346db5..1c0c26cf 100644 --- a/autotests/src/undomanager_test.cpp +++ b/autotests/src/undomanager_test.cpp @@ -1,242 +1,242 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Bernhard Beschow Copyright (C) 2009 Dominik Haumann 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 "undomanager_test.h" #include #include #include #include #include QTEST_MAIN(UndoManagerTest) using namespace KTextEditor; UndoManagerTest::UndoManagerTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } class UndoManagerTest::TestDocument : public KTextEditor::DocumentPrivate { public: TestDocument() - : KTextEditor::DocumentPrivate(false, false, 0, 0) + : KTextEditor::DocumentPrivate(false, false, nullptr, nullptr) {} }; void UndoManagerTest::testUndoRedoCount() { TestDocument doc; KateUndoManager *undoManager = doc.undoManager(); // no undo/redo items at the beginning QCOMPARE(undoManager->undoCount(), 0u); QCOMPARE(undoManager->redoCount(), 0u); doc.insertText(Cursor(0, 0), QLatin1String("a")); // create one insert-group QCOMPARE(undoManager->undoCount(), 1u); QCOMPARE(undoManager->redoCount(), 0u); doc.undo(); // move insert-group to redo items QCOMPARE(undoManager->undoCount(), 0u); QCOMPARE(undoManager->redoCount(), 1u); doc.redo(); // move insert-group back to undo items QCOMPARE(undoManager->undoCount(), 1u); QCOMPARE(undoManager->redoCount(), 0u); doc.insertText(Cursor(0, 1), QLatin1String("b")); // merge "b" into insert-group QCOMPARE(undoManager->undoCount(), 1u); QCOMPARE(undoManager->redoCount(), 0u); doc.removeText(Range(0, 1, 0, 2)); // create an additional remove-group QCOMPARE(undoManager->undoCount(), 2u); QCOMPARE(undoManager->redoCount(), 0u); doc.undo(); // move remove-group to redo items QCOMPARE(undoManager->undoCount(), 1u); QCOMPARE(undoManager->redoCount(), 1u); doc.insertText(Cursor(0, 1), QLatin1String("b")); // merge "b" into insert-group // and remove remove-group QCOMPARE(undoManager->undoCount(), 1u); QCOMPARE(undoManager->redoCount(), 0u); } void UndoManagerTest::testSafePoint() { TestDocument doc; KateUndoManager *undoManager = doc.undoManager(); doc.insertText(Cursor(0, 0), QLatin1String("a")); // create one undo group QCOMPARE(undoManager->undoCount(), 1u); QCOMPARE(undoManager->redoCount(), 0u); undoManager->undoSafePoint(); doc.insertText(Cursor(0, 1), QLatin1String("b")); // create a second undo group (don't merge) QCOMPARE(undoManager->undoCount(), 2u); doc.undo(); // move second undo group to redo items QCOMPARE(undoManager->undoCount(), 1u); QCOMPARE(undoManager->redoCount(), 1u); doc.insertText(Cursor(0, 1), QLatin1String("b")); // create a second undo group again, (don't merge) QCOMPARE(undoManager->undoCount(), 2u); QCOMPARE(undoManager->redoCount(), 0u); doc.editStart(); doc.insertText(Cursor(0, 2), QLatin1String("c")); undoManager->undoSafePoint(); doc.insertText(Cursor(0, 3), QLatin1String("d")); doc.editEnd(); // merge both edits into second undo group QCOMPARE(undoManager->undoCount(), 2u); QCOMPARE(undoManager->redoCount(), 0u); doc.insertText(Cursor(0, 4), QLatin1String("e")); // create a third undo group (don't merge) QCOMPARE(undoManager->undoCount(), 3u); QCOMPARE(undoManager->redoCount(), 0u); } void UndoManagerTest::testCursorPosition() { TestDocument doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); doc.setText(QLatin1String("aaaa bbbb cccc\n" "dddd ffff")); view->setCursorPosition(KTextEditor::Cursor(1, 5)); doc.typeChars(view, QLatin1String("eeee")); // cursor position: "dddd eeee| ffff" QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(1, 9)); // undo once to remove "eeee", cursor position: "dddd | ffff" doc.undo(); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(1, 5)); // redo once to insert "eeee" again. cursor position: "dddd eeee| ffff" doc.redo(); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(1, 9)); delete view; } void UndoManagerTest::testSelectionUndo() { TestDocument doc; - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); doc.setText(QLatin1String("aaaa bbbb cccc\n" "dddd eeee ffff")); view->setCursorPosition(KTextEditor::Cursor(1, 9)); KTextEditor::Range selectionRange(KTextEditor::Cursor(0, 5), KTextEditor::Cursor(1, 9)); view->setSelection(selectionRange); doc.typeChars(view, QLatin1String(QLatin1String("eeee"))); // cursor position: "aaaa eeee| ffff", no selection anymore QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(0, 9)); QCOMPARE(view->selection(), false); // undo to remove "eeee" and add selection and text again doc.undo(); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(1, 9)); QCOMPARE(view->selection(), true); QCOMPARE(view->selectionRange(), selectionRange); // redo to insert "eeee" again and remove selection // cursor position: "aaaa eeee| ffff", no selection anymore doc.redo(); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(0, 9)); QCOMPARE(view->selection(), false); delete view; } void UndoManagerTest::testUndoWordWrapBug301367() { TestDocument doc; doc.setWordWrap(true); doc.setWordWrapAt(20); - KTextEditor::ViewPrivate *view = static_cast(doc.createView(0)); + KTextEditor::ViewPrivate *view = static_cast(doc.createView(nullptr)); QString text = QString::fromLatin1("1234 1234 1234 1234\n" "1234 1234 1234 1234"); doc.setText(text); view->setCursorPosition(KTextEditor::Cursor(0, 0)); doc.typeChars(view, QLatin1String(" ")); while (doc.undoCount() > 1) { doc.undo(); } // test must be exactly the same as before QCOMPARE(doc.text(), text); while (doc.redoCount() > 1) { doc.redo(); } while (doc.undoCount() > 1) { doc.undo(); } // test must be exactly the same as before QCOMPARE(doc.text(), text); delete view; } #include "moc_undomanager_test.cpp" diff --git a/autotests/src/vimode/base.cpp b/autotests/src/vimode/base.cpp index 4844965e..d2c100f9 100644 --- a/autotests/src/vimode/base.cpp +++ b/autotests/src/vimode/base.cpp @@ -1,334 +1,334 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * 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 #include #include #include #include #include "base.h" #include "vimode/macros.h" #include "vimode/mappings.h" #include "vimode/globalstate.h" using namespace KateVi; using namespace KTextEditor; //BEGIN: BaseTest BaseTest::BaseTest() { - kate_view = Q_NULLPTR; - kate_document = Q_NULLPTR; + kate_view = nullptr; + kate_document = nullptr; mainWindow = new QMainWindow; mainWindowLayout = new QVBoxLayout(mainWindow); mainWindow->setLayout(mainWindowLayout); m_codesToModifiers.insert("ctrl", Qt::ControlModifier); m_codesToModifiers.insert("alt", Qt::AltModifier); m_codesToModifiers.insert("meta", Qt::MetaModifier); m_codesToModifiers.insert("keypad", Qt::KeypadModifier); m_codesToSpecialKeys.insert("backspace", Qt::Key_Backspace); m_codesToSpecialKeys.insert("esc", Qt::Key_Escape); m_codesToSpecialKeys.insert("return", Qt::Key_Return); m_codesToSpecialKeys.insert("enter", Qt::Key_Enter); m_codesToSpecialKeys.insert("left", Qt::Key_Left); m_codesToSpecialKeys.insert("right", Qt::Key_Right); m_codesToSpecialKeys.insert("up", Qt::Key_Up); m_codesToSpecialKeys.insert("down", Qt::Key_Down); m_codesToSpecialKeys.insert("home", Qt::Key_Home); m_codesToSpecialKeys.insert("end", Qt::Key_End); m_codesToSpecialKeys.insert("delete", Qt::Key_Delete); m_codesToSpecialKeys.insert("insert", Qt::Key_Insert); m_codesToSpecialKeys.insert("pageup", Qt::Key_PageUp); m_codesToSpecialKeys.insert("pagedown", Qt::Key_PageDown); } BaseTest::~BaseTest() { delete kate_document; } void BaseTest::waitForCompletionWidgetToActivate(KTextEditor::ViewPrivate *kate_view) { const QDateTime start = QDateTime::currentDateTime(); while (start.msecsTo(QDateTime::currentDateTime()) < 1000) { if (kate_view->isCompletionActive()) { break; } QApplication::processEvents(); } QVERIFY(kate_view->isCompletionActive()); } void BaseTest::init() { delete kate_view; delete kate_document; - kate_document = new KTextEditor::DocumentPrivate(false, false, 0, NULL); + kate_document = new KTextEditor::DocumentPrivate(false, false, nullptr, nullptr); // fixed indentation options kate_document->config()->setTabWidth(8); kate_document->config()->setIndentationWidth(2); kate_document->config()->setReplaceTabsDyn(false); kate_view = new KTextEditor::ViewPrivate(kate_document, mainWindow); mainWindowLayout->addWidget(kate_view); kate_view->setInputMode(View::ViInputMode); Q_ASSERT(kate_view->currentInputMode()->viewInputMode() == KTextEditor::View::ViInputMode); vi_input_mode = dynamic_cast(kate_view->currentInputMode()); vi_input_mode_manager = vi_input_mode->viInputModeManager(); Q_ASSERT(vi_input_mode_manager); vi_global = vi_input_mode->globalState(); Q_ASSERT(vi_global); kate_document->config()->setShowSpaces(true); // Flush out some issues in the KateRenderer when rendering spaces. kate_view->config()->setScrollBarMiniMap(false); kate_view->config()->setScrollBarPreview(false); connect(kate_document, &KTextEditor::DocumentPrivate::textInserted, this, &BaseTest::textInserted); connect(kate_document, &KTextEditor::DocumentPrivate::textRemoved, this, &BaseTest::textRemoved); } void BaseTest::TestPressKey(const QString &str) { if (m_firstBatchOfKeypressesForTest) { qDebug() << "\n\n>>> running command " << str << " on text " << kate_document->text(); } else { qDebug() << "\n>>> running further keypresses " << str << " on text " << kate_document->text(); } m_firstBatchOfKeypressesForTest = false; for (int i = 0; i < str.length(); i++) { Qt::KeyboardModifiers keyboard_modifier = Qt::NoModifier; QString key; int keyCode = -1; // Looking for keyboard modifiers if (str[i] == QChar('\\')) { int endOfModifier = -1; Qt::KeyboardModifier parsedModifier = parseCodedModifier(str, i, &endOfModifier); int endOfSpecialKey = -1; Qt::Key parsedSpecialKey = parseCodedSpecialKey(str, i, &endOfSpecialKey); if (parsedModifier != Qt::NoModifier) { keyboard_modifier = parsedModifier; // Move to the character after the '-' in the modifier. i = endOfModifier + 1; // Is this a modifier plus special key? int endOfSpecialKeyAfterModifier = -1; const Qt::Key parsedCodedSpecialKeyAfterModifier = parseCodedSpecialKey(str, i, &endOfSpecialKeyAfterModifier); if (parsedCodedSpecialKeyAfterModifier != Qt::Key_unknown) { key = QString(parsedCodedSpecialKeyAfterModifier); keyCode = parsedCodedSpecialKeyAfterModifier; i = endOfSpecialKeyAfterModifier; } } else if (parsedSpecialKey != Qt::Key_unknown) { key = QString(parsedSpecialKey); keyCode = parsedSpecialKey; i = endOfSpecialKey; } else if (str.mid(i, 2) == QString("\\:")) { int start_cmd = i + 2; for (i += 2; true; i++) { if (str.at(i) == '\\') { if (i + 1 < str.length() && str.at(i + 1) == '\\') { // A backslash within a command; skip. i += 2; } else { // End of command. break; } } } const QString commandToExecute = str.mid(start_cmd, i - start_cmd).replace("\\\\", "\\"); qDebug() << "Executing command directly from ViModeTest:\n" << commandToExecute; vi_input_mode->viModeEmulatedCommandBar()->executeCommand(commandToExecute); // We've handled the command; go back round the loop, avoiding sending // the closing \ to vi_input_mode_manager. continue; } else if (str.mid(i, 2) == QString("\\\\")) { key = QString("\\"); keyCode = Qt::Key_Backslash; i++; } else { Q_ASSERT(false); //Do not use "\" in tests except for modifiers, command mode (\\:) and literal backslashes "\\\\") } } if (keyCode == -1) { key = str[i]; keyCode = key[0].unicode(); // Kate Vim mode's internals identifier e.g. CTRL-C by Qt::Key_C plus the control modifier, // so we need to translate e.g. 'c' to Key_C. if (key[0].isLetter()) { if (key[0].toLower() == key[0]) { keyCode = keyCode - 'a' + Qt::Key_A; } else { keyCode = keyCode - 'A' + Qt::Key_A; keyboard_modifier |= Qt::ShiftModifier; } } } QKeyEvent *key_event = new QKeyEvent(QEvent::KeyPress, keyCode, keyboard_modifier, key); // Attempt to simulate how Qt usually sends events - typically, we want to send them // to kate_view->focusProxy() (which is a KateViewInternal). - QWidget *destWidget = Q_NULLPTR; + QWidget *destWidget = nullptr; if (QApplication::activePopupWidget()) { // According to the docs, the activePopupWidget, if present, takes all events. destWidget = QApplication::activePopupWidget(); } else if (QApplication::focusWidget()) { if (QApplication::focusWidget()->focusProxy()) { destWidget = QApplication::focusWidget()->focusProxy(); } else { destWidget = QApplication::focusWidget(); } } else { destWidget = kate_view->focusProxy(); } QApplication::postEvent(destWidget, key_event); QApplication::sendPostedEvents(); } } void BaseTest::BeginTest(const QString &original) { vi_input_mode_manager->viEnterNormalMode(); vi_input_mode->reset(); vi_input_mode_manager = vi_input_mode->viInputModeManager(); kate_document->setText(original); kate_document->undoManager()->clearUndo(); kate_document->undoManager()->clearRedo(); kate_view->setCursorPosition(Cursor(0, 0)); m_firstBatchOfKeypressesForTest = true; } void BaseTest::FinishTest_(int line, const char *file, const QString &expected, Expectation expectation, const QString &failureReason) { if (expectation == ShouldFail) { if (!QTest::qExpectFail("", failureReason.toLocal8Bit().constData(), QTest::Continue, file, line)) { return; } qDebug() << "Actual text:\n\t" << kate_document->text() << "\nShould be (for this test to pass):\n\t" << expected; } if (!QTest::qCompare(kate_document->text(), expected, "kate_document->text()", "expected_text", file, line)) { return; } Q_ASSERT(!emulatedCommandBarTextEdit()->isVisible() && "Make sure you close the command bar before the end of a test!"); } void BaseTest::DoTest_(int line, const char *file, const QString &original, const QString &command, const QString &expected, Expectation expectation, const QString &failureReason) { BeginTest(original); TestPressKey(command); FinishTest_(line, file, expected, expectation, failureReason); } Qt::KeyboardModifier BaseTest::parseCodedModifier(const QString &string, int startPos, int *destEndOfCodedModifier) { foreach (const QString &modifierCode, m_codesToModifiers.keys()) { // The "+2" is from the leading '\' and the trailing '-' if (string.mid(startPos, modifierCode.length() + 2) == QString("\\") + modifierCode + "-") { if (destEndOfCodedModifier) { // destEndOfCodeModifier lies on the trailing '-'. *destEndOfCodedModifier = startPos + modifierCode.length() + 1; Q_ASSERT(string[*destEndOfCodedModifier] == '-'); } return m_codesToModifiers.value(modifierCode); } } return Qt::NoModifier; } Qt::Key BaseTest::parseCodedSpecialKey(const QString &string, int startPos, int *destEndOfCodedKey) { foreach (const QString &specialKeyCode, m_codesToSpecialKeys.keys()) { // "+1" is for the leading '\'. if (string.mid(startPos, specialKeyCode.length() + 1) == QString("\\") + specialKeyCode) { if (destEndOfCodedKey) { *destEndOfCodedKey = startPos + specialKeyCode.length(); } return m_codesToSpecialKeys.value(specialKeyCode); } } return Qt::Key_unknown; } KateVi::EmulatedCommandBar * BaseTest::emulatedCommandBar() { KateVi::EmulatedCommandBar *emulatedCommandBar = vi_input_mode->viModeEmulatedCommandBar(); Q_ASSERT(emulatedCommandBar); return emulatedCommandBar; } QLineEdit * BaseTest::emulatedCommandBarTextEdit() { QLineEdit *emulatedCommandBarText = emulatedCommandBar()->findChild("commandtext"); Q_ASSERT(emulatedCommandBarText); return emulatedCommandBarText; } void BaseTest::ensureKateViewVisible() { mainWindow->show(); kate_view->show(); QApplication::setActiveWindow(mainWindow); kate_view->setFocus(); const QDateTime startTime = QDateTime::currentDateTime(); while (startTime.msecsTo(QDateTime::currentDateTime()) < 3000 && !mainWindow->isActiveWindow()) { QApplication::processEvents(); } QVERIFY(kate_view->isVisible()); QVERIFY(mainWindow->isActiveWindow()); } void BaseTest::clearAllMappings() { vi_global->mappings()->clear(Mappings::NormalModeMapping); vi_global->mappings()->clear(Mappings::VisualModeMapping); vi_global->mappings()->clear(Mappings::InsertModeMapping); vi_global->mappings()->clear(Mappings::CommandModeMapping); } void BaseTest::clearAllMacros() { vi_global->macros()->clear(); } void BaseTest::textInserted(Document *document, KTextEditor::Range range) { m_docChanges.append(DocChange(DocChange::TextInserted, range, document->text(range))); } void BaseTest::textRemoved(Document *document, KTextEditor::Range range) { Q_UNUSED(document); m_docChanges.append(DocChange(DocChange::TextRemoved, range)); } //END: BaseTest diff --git a/autotests/src/vimode/emulatedcommandbar.cpp b/autotests/src/vimode/emulatedcommandbar.cpp index 7a99c9c6..c1a89b14 100644 --- a/autotests/src/vimode/emulatedcommandbar.cpp +++ b/autotests/src/vimode/emulatedcommandbar.cpp @@ -1,3377 +1,3377 @@ /* This file is part of the KDE libraries Copyright (C) 2011 Kuzmich Svyatoslav Copyright (C) 2012 - 2013 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 #include #include "emulatedcommandbar.h" #include #include #include #include "keys.h" #include "view.h" #include "emulatedcommandbarsetupandteardown.h" #include "vimode/mappings.h" #include "vimode/globalstate.h" #include #include #include #include #include #include #include QTEST_MAIN(EmulatedCommandBarTest) using namespace KTextEditor; using namespace KateVi; void EmulatedCommandBarTest::EmulatedCommandBarTests() { // Ensure that some preconditions for these tests are setup, and (more importantly) // ensure that they are reverted no matter how these tests end. EmulatedCommandBarSetUpAndTearDown emulatedCommandBarSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // Verify that we can get a non-null pointer to the emulated command bar. EmulatedCommandBar *emulatedCommandBar = vi_input_mode->viModeEmulatedCommandBar(); QVERIFY(emulatedCommandBar); // Should initially be hidden. QVERIFY(!emulatedCommandBar->isVisible()); // Test that "/" invokes the emulated command bar (if we are configured to use it) BeginTest(""); TestPressKey("/"); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandTypeIndicator()->text(), QString("/")); QVERIFY(emulatedCommandTypeIndicator()->isVisible()); QVERIFY(emulatedCommandBarTextEdit()); QVERIFY(emulatedCommandBarTextEdit()->text().isEmpty()); // Make sure the keypresses end up changing the text. QVERIFY(emulatedCommandBarTextEdit()->isVisible()); TestPressKey("foo"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); // Make sure ctrl-c dismisses it (assuming we allow Vim to steal the ctrl-c shortcut). TestPressKey("\\ctrl-c"); QVERIFY(!emulatedCommandBar->isVisible()); // Ensure that ESC dismisses it, too. BeginTest(""); TestPressKey("/"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\esc"); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); // Ensure that Ctrl-[ dismisses it, too. BeginTest(""); TestPressKey("/"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\ctrl-["); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); // Ensure that Enter dismisses it, too. BeginTest(""); TestPressKey("/"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); // Ensure that Return dismisses it, too. BeginTest(""); TestPressKey("/"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\return"); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); // Ensure that text is always initially empty. BeginTest(""); TestPressKey("/a\\enter"); TestPressKey("/"); QVERIFY(emulatedCommandBarTextEdit()->text().isEmpty()); TestPressKey("\\enter"); FinishTest(""); // Check backspace works. BeginTest(""); TestPressKey("/foo\\backspace"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("fo")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-h works. BeginTest(""); TestPressKey("/bar\\ctrl-h"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("ba")); TestPressKey("\\enter"); FinishTest(""); // ctrl-h should dismiss bar when empty. BeginTest(""); TestPressKey("/\\ctrl-h"); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); // ctrl-h should not dismiss bar when there is stuff to the left of cursor. BeginTest(""); TestPressKey("/a\\ctrl-h"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); FinishTest(""); // ctrl-h should not dismiss bar when bar is not empty, even if there is nothing to the left of cursor. BeginTest(""); TestPressKey("/a\\left\\ctrl-h"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); FinishTest(""); // Same for backspace. BeginTest(""); TestPressKey("/bar\\backspace"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("ba")); TestPressKey("\\enter"); FinishTest(""); BeginTest(""); TestPressKey("/\\backspace"); QVERIFY(!emulatedCommandBar->isVisible()); FinishTest(""); BeginTest(""); TestPressKey("/a\\backspace"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); FinishTest(""); BeginTest(""); TestPressKey("/a\\left\\backspace"); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-b works. BeginTest(""); TestPressKey("/bar foo xyz\\ctrl-bX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("Xbar foo xyz")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-e works. BeginTest(""); TestPressKey("/bar foo xyz\\ctrl-b\\ctrl-eX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("bar foo xyzX")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works. BeginTest(""); TestPressKey("/foo bar\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo ")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works on empty command bar. BeginTest(""); TestPressKey("/\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works in middle of word. BeginTest(""); TestPressKey("/foo bar\\left\\left\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo ar")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w leaves the cursor in the right place when in the middle of word. BeginTest(""); TestPressKey("/foo bar\\left\\left\\ctrl-wX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo Xar")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works when at the beginning of the text. BeginTest(""); TestPressKey("/foo\\left\\left\\left\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works when the character to the left is a space. BeginTest(""); TestPressKey("/foo bar \\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo ")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works when all characters to the left of the cursor are spaces. BeginTest(""); TestPressKey("/ \\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w works when all characters to the left of the cursor are non-spaces. BeginTest(""); TestPressKey("/foo\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w does not continue to delete subsequent alphanumerics if the characters to the left of the cursor // are non-space, non-alphanumerics. BeginTest(""); TestPressKey("/foo!!!\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w does not continue to delete subsequent alphanumerics if the characters to the left of the cursor // are non-space, non-alphanumerics. BeginTest(""); TestPressKey("/foo!!!\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w deletes underscores and alphanumerics to the left of the cursor, but stops when it reaches a // character that is none of these. BeginTest(""); TestPressKey("/foo!!!_d1\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo!!!")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w doesn't swallow the spaces preceding the block of non-word chars. BeginTest(""); TestPressKey("/foo !!!\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo ")); TestPressKey("\\enter"); FinishTest(""); // Check ctrl-w doesn't swallow the spaces preceding the word. BeginTest(""); TestPressKey("/foo 1d_\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo ")); TestPressKey("\\enter"); FinishTest(""); // Check there is a "waiting for register" indicator, initially hidden. BeginTest(""); TestPressKey("/"); QLabel* waitingForRegisterIndicator = emulatedCommandBar->findChild("waitingforregisterindicator"); QVERIFY(waitingForRegisterIndicator); QVERIFY(!waitingForRegisterIndicator->isVisible()); QCOMPARE(waitingForRegisterIndicator->text(), QString("\"")); TestPressKey("\\enter"); FinishTest(""); // Test that ctrl-r causes it to become visible. It is displayed to the right of the text edit. BeginTest(""); TestPressKey("/\\ctrl-r"); QVERIFY(waitingForRegisterIndicator->isVisible()); QVERIFY(waitingForRegisterIndicator->x() >= emulatedCommandBarTextEdit()->x() + emulatedCommandBarTextEdit()->width()); TestPressKey("\\ctrl-c"); TestPressKey("\\ctrl-c"); FinishTest(""); // The first ctrl-c after ctrl-r (when no register entered) hides the waiting for register // indicator, but not the bar. BeginTest(""); TestPressKey("/\\ctrl-r"); QVERIFY(waitingForRegisterIndicator->isVisible()); TestPressKey("\\ctrl-c"); QVERIFY(!waitingForRegisterIndicator->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss the bar. FinishTest(""); // The first ctrl-c after ctrl-r (when no register entered) aborts waiting for register. BeginTest("foo"); TestPressKey("\"cyiw/\\ctrl-r\\ctrl-ca"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("a")); TestPressKey("\\ctrl-c"); // Dismiss the bar. FinishTest("foo"); // Same as above, but for ctrl-[ instead of ctrl-c. BeginTest(""); TestPressKey("/\\ctrl-r"); QVERIFY(waitingForRegisterIndicator->isVisible()); TestPressKey("\\ctrl-["); QVERIFY(!waitingForRegisterIndicator->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss the bar. FinishTest(""); BeginTest("foo"); TestPressKey("\"cyiw/\\ctrl-r\\ctrl-[a"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("a")); TestPressKey("\\ctrl-c"); // Dismiss the bar. FinishTest("foo"); // Check ctrl-r works with registers, and hides the "waiting for register" indicator. BeginTest("xyz"); TestPressKey("\"ayiw/foo\\ctrl-ra"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("fooxyz")); QVERIFY(!waitingForRegisterIndicator->isVisible()); TestPressKey("\\enter"); FinishTest("xyz"); // Check ctrl-r inserts text at the current cursor position. BeginTest("xyz"); TestPressKey("\"ayiw/foo\\left\\ctrl-ra"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foxyzo")); TestPressKey("\\enter"); FinishTest("xyz"); // Check ctrl-r ctrl-w inserts word under the cursor, and hides the "waiting for register" indicator. BeginTest("foo bar xyz"); TestPressKey("w/\\left\\ctrl-r\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("bar")); QVERIFY(!waitingForRegisterIndicator->isVisible()); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Check ctrl-r ctrl-w doesn't insert the contents of register w! BeginTest("foo baz xyz"); TestPressKey("\"wyiww/\\left\\ctrl-r\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("baz")); TestPressKey("\\enter"); FinishTest("foo baz xyz"); // Check ctrl-r ctrl-w inserts at the current cursor position. BeginTest("foo nose xyz"); TestPressKey("w/bar\\left\\ctrl-r\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("banoser")); TestPressKey("\\enter"); FinishTest("foo nose xyz"); // Cursor position is at the end of the inserted text after ctrl-r ctrl-w. BeginTest("foo nose xyz"); TestPressKey("w/bar\\left\\ctrl-r\\ctrl-wX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("banoseXr")); TestPressKey("\\enter"); FinishTest("foo nose xyz"); // Cursor position is at the end of the inserted register contents after ctrl-r. BeginTest("xyz"); TestPressKey("\"ayiw/foo\\left\\ctrl-raX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foxyzXo")); TestPressKey("\\enter"); FinishTest("xyz"); // Insert clipboard contents on ctrl-r +. We implicitly need to test the ability to handle // shift key key events when waiting for register (they should be ignored). BeginTest("xyz"); QApplication::clipboard()->setText("vimodetestclipboardtext"); TestPressKey("/\\ctrl-r"); QKeyEvent *shiftKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::NoModifier); QApplication::postEvent(emulatedCommandBarTextEdit(), shiftKeyDown); QApplication::sendPostedEvents(); TestPressKey("+"); QKeyEvent *shiftKeyUp = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::NoModifier); QApplication::postEvent(emulatedCommandBarTextEdit(), shiftKeyUp); QApplication::sendPostedEvents(); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("vimodetestclipboardtext")); TestPressKey("\\enter"); FinishTest("xyz"); // Similarly, test that we can press "ctrl" after ctrl-r without it being taken for a register. BeginTest("wordundercursor"); TestPressKey("/\\ctrl-r"); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(emulatedCommandBarTextEdit(), ctrlKeyDown); QApplication::sendPostedEvents(); QKeyEvent *ctrlKeyUp = new QKeyEvent(QEvent::KeyRelease, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(emulatedCommandBarTextEdit(), ctrlKeyUp); QApplication::sendPostedEvents(); QVERIFY(waitingForRegisterIndicator->isVisible()); TestPressKey("\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("wordundercursor")); TestPressKey("\\ctrl-c"); // Dismiss the bar. FinishTest("wordundercursor"); // Begin tests for ctrl-g, which is almost identical to ctrl-r save that the contents, when added, // are escaped for searching. // Normal register contents/ word under cursor are added as normal. BeginTest("wordinregisterb wordundercursor"); TestPressKey("\"byiw"); TestPressKey("/\\ctrl-g"); QVERIFY(waitingForRegisterIndicator->isVisible()); QVERIFY(waitingForRegisterIndicator->x() >= emulatedCommandBarTextEdit()->x() + emulatedCommandBarTextEdit()->width()); TestPressKey("b"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("wordinregisterb")); QVERIFY(!waitingForRegisterIndicator->isVisible()); TestPressKey("\\ctrl-c\\ctrl-cw/\\ctrl-g\\ctrl-w"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("wordundercursor")); QVERIFY(!waitingForRegisterIndicator->isVisible()); TestPressKey("\\ctrl-c"); TestPressKey("\\ctrl-c"); FinishTest("wordinregisterb wordundercursor"); // \'s must be escaped when inserted via ctrl-g. DoTest("foo a\\b\\\\c\\\\\\d", "wYb/\\ctrl-g0\\enterrX", "foo X\\b\\\\c\\\\\\d"); // $'s must be escaped when inserted via ctrl-g. DoTest("foo a$b", "wYb/\\ctrl-g0\\enterrX", "foo X$b"); DoTest("foo a$b$c", "wYb/\\ctrl-g0\\enterrX", "foo X$b$c"); DoTest("foo a\\$b\\$c", "wYb/\\ctrl-g0\\enterrX", "foo X\\$b\\$c"); // ^'s must be escaped when inserted via ctrl-g. DoTest("foo a^b", "wYb/\\ctrl-g0\\enterrX", "foo X^b"); DoTest("foo a^b^c", "wYb/\\ctrl-g0\\enterrX", "foo X^b^c"); DoTest("foo a\\^b\\^c", "wYb/\\ctrl-g0\\enterrX", "foo X\\^b\\^c"); // .'s must be escaped when inserted via ctrl-g. DoTest("foo axb a.b", "wwYgg/\\ctrl-g0\\enterrX", "foo axb X.b"); DoTest("foo a\\xb Na\\.b", "fNlYgg/\\ctrl-g0\\enterrX", "foo a\\xb NX\\.b"); // *'s must be escaped when inserted via ctrl-g DoTest("foo axxxxb ax*b", "wwYgg/\\ctrl-g0\\enterrX", "foo axxxxb Xx*b"); DoTest("foo a\\xxxxb Na\\x*X", "fNlYgg/\\ctrl-g0\\enterrX", "foo a\\xxxxb NX\\x*X"); // /'s must be escaped when inserted via ctrl-g. DoTest("foo a a/b", "wwYgg/\\ctrl-g0\\enterrX", "foo a X/b"); DoTest("foo a a/b/c", "wwYgg/\\ctrl-g0\\enterrX", "foo a X/b/c"); DoTest("foo a a\\/b\\/c", "wwYgg/\\ctrl-g0\\enterrX", "foo a X\\/b\\/c"); // ['s and ]'s must be escaped when inserted via ctrl-g. DoTest("foo axb a[xyz]b", "wwYgg/\\ctrl-g0\\enterrX", "foo axb X[xyz]b"); DoTest("foo a[b", "wYb/\\ctrl-g0\\enterrX", "foo X[b"); DoTest("foo a[b[c", "wYb/\\ctrl-g0\\enterrX", "foo X[b[c"); DoTest("foo a\\[b\\[c", "wYb/\\ctrl-g0\\enterrX", "foo X\\[b\\[c"); DoTest("foo a]b", "wYb/\\ctrl-g0\\enterrX", "foo X]b"); DoTest("foo a]b]c", "wYb/\\ctrl-g0\\enterrX", "foo X]b]c"); DoTest("foo a\\]b\\]c", "wYb/\\ctrl-g0\\enterrX", "foo X\\]b\\]c"); // Test that expressions involving {'s and }'s work when inserted via ctrl-g. DoTest("foo {", "wYgg/\\ctrl-g0\\enterrX", "foo X"); DoTest("foo }", "wYgg/\\ctrl-g0\\enterrX", "foo X"); DoTest("foo aaaaa \\aaaaa a\\{5}", "WWWYgg/\\ctrl-g0\\enterrX", "foo aaaaa \\aaaaa X\\{5}"); DoTest("foo }", "wYgg/\\ctrl-g0\\enterrX", "foo X"); // Transform newlines into "\\n" when inserted via ctrl-g. DoTest(" \nfoo\nfoo\nxyz\nbar\n123", "jjvjjllygg/\\ctrl-g0\\enterrX", " \nfoo\nXoo\nxyz\nbar\n123"); DoTest(" \nfoo\nfoo\nxyz\nbar\n123", "jjvjjllygg/\\ctrl-g0/e\\enterrX", " \nfoo\nfoo\nxyz\nbaX\n123"); // Don't do any escaping for ctrl-r, though. BeginTest("foo .*$^\\/"); TestPressKey("wY/\\ctrl-r0"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".*$^\\/")); TestPressKey("\\ctrl-c"); TestPressKey("\\ctrl-c"); FinishTest("foo .*$^\\/"); // Ensure that the flag that says "next register insertion should be escaped for searching" // is cleared if we do ctrl-g but then abort with ctrl-c. DoTest("foo a$b", "/\\ctrl-g\\ctrl-c\\ctrl-cwYgg/\\ctrl-r0\\enterrX", "Xoo a$b"); // Ensure that we actually perform a search while typing. BeginTest("abcd"); TestPressKey("/c"); verifyCursorAt(Cursor(0, 2)); TestPressKey("\\enter"); FinishTest("abcd"); // Ensure that the search is from the cursor. BeginTest("acbcd"); TestPressKey("ll/c"); verifyCursorAt(Cursor(0, 3)); TestPressKey("\\enter"); FinishTest("acbcd"); // Reset the cursor to the original position on Ctrl-C BeginTest("acbcd"); TestPressKey("ll/c\\ctrl-crX"); FinishTest("acXcd"); // Reset the cursor to the original position on Ctrl-[ BeginTest("acbcd"); TestPressKey("ll/c\\ctrl-[rX"); FinishTest("acXcd"); // Reset the cursor to the original position on ESC BeginTest("acbcd"); TestPressKey("ll/c\\escrX"); FinishTest("acXcd"); // *Do not* reset the cursor to the original position on Enter. BeginTest("acbcd"); TestPressKey("ll/c\\enterrX"); FinishTest("acbXd"); // *Do not* reset the cursor to the original position on Return. BeginTest("acbcd"); TestPressKey("ll/c\\returnrX"); FinishTest("acbXd"); // Should work with mappings. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmapping", "/crX", Mappings::Recursive); BeginTest("acbcd"); TestPressKey("'testmapping"); FinishTest("aXbcd"); clearAllMappings(); // Don't send keys that were part of a mapping to the emulated command bar. vi_global->mappings()->add(Mappings::NormalModeMapping, "H", "/a", Mappings::Recursive); BeginTest("foo a aH"); TestPressKey("H\\enterrX"); FinishTest("foo X aH"); clearAllMappings(); // Incremental searching from the original position. BeginTest("foo bar foop fool food"); TestPressKey("ll/foo"); verifyCursorAt(Cursor(0, 8)); TestPressKey("l"); verifyCursorAt(Cursor(0, 13)); TestPressKey("\\backspace"); verifyCursorAt(Cursor(0, 8)); TestPressKey("\\enter"); FinishTest("foo bar foop fool food"); // End up back at the start if no match found BeginTest("foo bar foop fool food"); TestPressKey("ll/fool"); verifyCursorAt(Cursor(0, 13)); TestPressKey("\\backspacex"); verifyCursorAt(Cursor(0, 2)); TestPressKey("\\enter"); FinishTest("foo bar foop fool food"); // Wrap around if no match found. BeginTest("afoom bar foop fool food"); TestPressKey("lll/foom"); verifyCursorAt(Cursor(0, 1)); TestPressKey("\\enter"); FinishTest("afoom bar foop fool food"); // SmartCase: match case-insensitively if the search text is all lower-case. DoTest("foo BaR", "ll/bar\\enterrX", "foo XaR"); // SmartCase: match case-sensitively if the search text is mixed case. DoTest("foo BaR bAr", "ll/bAr\\enterrX", "foo BaR XAr"); // Assume regex by default. DoTest("foo bwibblear", "ll/b.*ar\\enterrX", "foo Xwibblear"); // Set the last search pattern. DoTest("foo bar", "ll/bar\\enterggnrX", "foo Xar"); // Make sure the last search pattern is a regex, too. DoTest("foo bwibblear", "ll/b.*ar\\enterggnrX", "foo Xwibblear"); // 'n' should search case-insensitively if the original search was case-insensitive. DoTest("foo bAR", "ll/bar\\enterggnrX", "foo XAR"); // 'n' should search case-sensitively if the original search was case-sensitive. DoTest("foo bar bAR", "ll/bAR\\enterggnrX", "foo bar XAR"); // 'N' should search case-insensitively if the original search was case-insensitive. DoTest("foo bAR xyz", "ll/bar\\enter$NrX", "foo XAR xyz"); // 'N' should search case-sensitively if the original search was case-sensitive. DoTest("foo bAR bar", "ll/bAR\\enter$NrX", "foo XAR bar"); // Don't forget to set the last search to case-insensitive. DoTest("foo bAR bar", "ll/bAR\\enter^/bar\\enter^nrX", "foo XAR bar"); // Usage of \C for manually specifying case sensitivity. // Strip occurrences of "\C" from the pattern to find. DoTest("foo bar", "/\\\\Cba\\\\Cr\\enterrX", "foo Xar"); // Be careful about escaping, though! DoTest("foo \\Cba\\Cr", "/\\\\\\\\Cb\\\\Ca\\\\\\\\C\\\\C\\\\Cr\\enterrX", "foo XCba\\Cr"); // The item added to the search history should contain all the original \C's. clearSearchHistory(); BeginTest("foo \\Cba\\Cr"); TestPressKey("/\\\\\\\\Cb\\\\Ca\\\\\\\\C\\\\C\\\\Cr\\enterrX"); QCOMPARE(searchHistory().first(), QString("\\\\Cb\\Ca\\\\C\\C\\Cr")); FinishTest("foo XCba\\Cr"); // If there is an escaped C, assume case sensitivity. DoTest("foo bAr BAr bar", "/ba\\\\Cr\\enterrX", "foo bAr BAr Xar"); // The last search pattern should be the last search with escaped C's stripped. DoTest("foo \\Cbar\nfoo \\Cbar", "/\\\\\\\\Cba\\\\C\\\\Cr\\enterggjnrX", "foo \\Cbar\nfoo XCbar"); // If the last search pattern had an escaped "\C", then the next search should be case-sensitive. DoTest("foo bar\nfoo bAr BAr bar", "/ba\\\\Cr\\enterggjnrX", "foo bar\nfoo bAr BAr Xar"); // Don't set the last search parameters if we abort, though. DoTest("foo bar xyz", "/bar\\enter/xyz\\ctrl-cggnrX", "foo Xar xyz"); DoTest("foo bar bAr", "/bar\\enter/bA\\ctrl-cggnrX", "foo Xar bAr"); DoTest("foo bar bar", "/bar\\enter?ba\\ctrl-cggnrX", "foo Xar bar"); // Don't let ":" trample all over the search parameters, either. DoTest("foo bar xyz foo", "/bar\\entergg*:yank\\enterggnrX", "foo bar xyz Xoo"); // Some mirror tests for "?" // Test that "?" summons the search bar, with empty text and with the "?" indicator. QVERIFY(!emulatedCommandBar->isVisible()); BeginTest(""); TestPressKey("?"); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandTypeIndicator()->text(), QString("?")); QVERIFY(emulatedCommandTypeIndicator()->isVisible()); QVERIFY(emulatedCommandBarTextEdit()); QVERIFY(emulatedCommandBarTextEdit()->text().isEmpty()); TestPressKey("\\enter"); FinishTest(""); // Search backwards. DoTest("foo foo bar foo foo", "ww?foo\\enterrX", "foo Xoo bar foo foo"); // Reset cursor if we find nothing. BeginTest("foo foo bar foo foo"); TestPressKey("ww?foo"); verifyCursorAt(Cursor(0, 4)); TestPressKey("d"); verifyCursorAt(Cursor(0, 8)); TestPressKey("\\enter"); FinishTest("foo foo bar foo foo"); // Wrap to the end if we find nothing. DoTest("foo foo bar xyz xyz", "ww?xyz\\enterrX", "foo foo bar xyz Xyz"); // Specify that the last was backwards when using '?' DoTest("foo foo bar foo foo", "ww?foo\\enter^wwnrX", "foo Xoo bar foo foo"); // ... and make sure we do the equivalent with "/" BeginTest("foo foo bar foo foo"); TestPressKey("ww?foo\\enter^ww/foo"); QCOMPARE(emulatedCommandTypeIndicator()->text(), QString("/")); TestPressKey("\\enter^wwnrX"); FinishTest("foo foo bar Xoo foo"); // If we are at the beginning of a word, that word is not the first match in a search // for that word. DoTest("foo foo foo", "w/foo\\enterrX", "foo foo Xoo"); DoTest("foo foo foo", "w?foo\\enterrX", "Xoo foo foo"); // When searching backwards, ensure we can find a match whose range includes the starting cursor position, // if we allow it to wrap around. DoTest("foo foofoofoo bar", "wlll?foofoofoo\\enterrX", "foo Xoofoofoo bar"); // When searching backwards, ensure we can find a match whose range includes the starting cursor position, // even if we don't allow it to wrap around. DoTest("foo foofoofoo foofoofoo", "wlll?foofoofoo\\enterrX", "foo Xoofoofoo foofoofoo"); // The same, but where we the match ends at the end of the line or document. DoTest("foo foofoofoo\nfoofoofoo", "wlll?foofoofoo\\enterrX", "foo Xoofoofoo\nfoofoofoo"); DoTest("foo foofoofoo", "wlll?foofoofoo\\enterrX", "foo Xoofoofoo"); // Searching forwards for just "/" repeats last search. DoTest("foo bar", "/bar\\entergg//\\enterrX", "foo Xar"); // The "last search" can be one initiated via e.g. "*". DoTest("foo bar foo", "/bar\\entergg*gg//\\enterrX", "foo bar Xoo"); // Searching backwards for just "?" repeats last search. DoTest("foo bar bar", "/bar\\entergg??\\enterrX", "foo bar Xar"); // Search forwards treats "?" as a literal. DoTest("foo ?ba?r", "/?ba?r\\enterrX", "foo Xba?r"); // As always, be careful with escaping! DoTest("foo ?ba\\?r", "/?ba\\\\\\\\\\\\?r\\enterrX", "foo Xba\\?r"); // Searching forwards for just "?" finds literal question marks. DoTest("foo ??", "/?\\enterrX", "foo X?"); // Searching backwards for just "/" finds literal forward slashes. DoTest("foo //", "?/\\enterrX", "foo /X"); // Searching forwards, stuff after (and including) an unescaped "/" is ignored. DoTest("foo ba bar bar/xyz", "/bar/xyz\\enterrX", "foo ba Xar bar/xyz"); // Needs to be unescaped, though! DoTest("foo bar bar/xyz", "/bar\\\\/xyz\\enterrX", "foo bar Xar/xyz"); DoTest("foo bar bar\\/xyz", "/bar\\\\\\\\/xyz\\enterrX", "foo bar Xar\\/xyz"); // Searching backwards, stuff after (and including) an unescaped "?" is ignored. DoTest("foo bar bar?xyz bar ba", "?bar?xyz\\enterrX", "foo bar bar?xyz Xar ba"); // Needs to be unescaped, though! DoTest("foo bar bar?xyz bar ba", "?bar\\\\?xyz\\enterrX", "foo bar Xar?xyz bar ba"); DoTest("foo bar bar\\?xyz bar ba", "?bar\\\\\\\\?xyz\\enterrX", "foo bar Xar\\?xyz bar ba"); // If, in a forward search, the first character after the first unescaped "/" is an e, then // we place the cursor at the end of the word. DoTest("foo ba bar bar/eyz", "/bar/e\\enterrX", "foo ba baX bar/eyz"); // Needs to be unescaped, though! DoTest("foo bar bar/eyz", "/bar\\\\/e\\enterrX", "foo bar Xar/eyz"); DoTest("foo bar bar\\/xyz", "/bar\\\\\\\\/e\\enterrX", "foo bar barX/xyz"); // If, in a backward search, the first character after the first unescaped "?" is an e, then // we place the cursor at the end of the word. DoTest("foo bar bar?eyz bar ba", "?bar?e\\enterrX", "foo bar bar?eyz baX ba"); // Needs to be unescaped, though! DoTest("foo bar bar?eyz bar ba", "?bar\\\\?e\\enterrX", "foo bar Xar?eyz bar ba"); DoTest("foo bar bar\\?eyz bar ba", "?bar\\\\\\\\?e\\enterrX", "foo bar barX?eyz bar ba"); // Quick check that repeating the last search and placing the cursor at the end of the match works. DoTest("foo bar bar", "/bar\\entergg//e\\enterrX", "foo baX bar"); DoTest("foo bar bar", "?bar\\entergg??e\\enterrX", "foo bar baX"); // When repeating a change, don't try to convert from Vim to Qt regex again. DoTest("foo bar()", "/bar()\\entergg//e\\enterrX", "foo bar(X"); DoTest("foo bar()", "?bar()\\entergg??e\\enterrX", "foo bar(X"); // If the last search said that we should place the cursor at the end of the match, then // do this with n & N. DoTest("foo bar bar foo", "/bar/e\\enterggnrX", "foo baX bar foo"); DoTest("foo bar bar foo", "/bar/e\\enterggNrX", "foo bar baX foo"); // Don't do this if that search was aborted, though. DoTest("foo bar bar foo", "/bar\\enter/bar/e\\ctrl-cggnrX", "foo Xar bar foo"); DoTest("foo bar bar foo", "/bar\\enter/bar/e\\ctrl-cggNrX", "foo bar Xar foo"); // "#" and "*" reset the "place cursor at the end of the match" to false. DoTest("foo bar bar foo", "/bar/e\\enterggw*nrX", "foo Xar bar foo"); DoTest("foo bar bar foo", "/bar/e\\enterggw#nrX", "foo Xar bar foo"); // "/" and "?" should be usable as motions. DoTest("foo bar", "ld/bar\\enter", "fbar"); // They are not linewise. DoTest("foo bar\nxyz", "ld/yz\\enter", "fyz"); DoTest("foo bar\nxyz", "jld?oo\\enter", "fyz"); // Should be usable in Visual Mode without aborting Visual Mode. DoTest("foo bar", "lv/bar\\enterd", "far"); // Same for ?. DoTest("foo bar", "$hd?oo\\enter", "far"); DoTest("foo bar", "$hv?oo\\enterd", "fr"); DoTest("foo bar", "lv?bar\\enterd", "far"); // If we abort the "/" / "?" motion, the command should be aborted, too. DoTest("foo bar", "d/bar\\esc", "foo bar"); DoTest("foo bar", "d/bar\\ctrl-c", "foo bar"); DoTest("foo bar", "d/bar\\ctrl-[", "foo bar"); // We should be able to repeat a command using "/" or "?" as the motion. DoTest("foo bar bar bar", "d/bar\\enter.", "bar bar"); // The "synthetic" Enter keypress should not be logged as part of the command to be repeated. DoTest("foo bar bar bar\nxyz", "d/bar\\enter.rX", "Xar bar\nxyz"); // Counting. DoTest("foo bar bar bar", "2/bar\\enterrX", "foo bar Xar bar"); // Counting with wraparound. DoTest("foo bar bar bar", "4/bar\\enterrX", "foo Xar bar bar"); // Counting in Visual Mode. DoTest("foo bar bar bar", "v2/bar\\enterd", "ar bar"); // Should update the selection in Visual Mode as we search. BeginTest("foo bar bbc"); TestPressKey("vl/b"); QCOMPARE(kate_view->selectionText(), QString("foo b")); TestPressKey("b"); QCOMPARE(kate_view->selectionText(), QString("foo bar b")); TestPressKey("\\ctrl-h"); QCOMPARE(kate_view->selectionText(), QString("foo b")); TestPressKey("notexists"); QCOMPARE(kate_view->selectionText(), QString("fo")); TestPressKey("\\enter"); // Dismiss bar. QCOMPARE(kate_view->selectionText(), QString("fo")); FinishTest("foo bar bbc"); BeginTest("foo\nxyz\nbar\nbbc"); TestPressKey("Vj/b"); QCOMPARE(kate_view->selectionText(), QString("foo\nxyz\nbar")); TestPressKey("b"); QCOMPARE(kate_view->selectionText(), QString("foo\nxyz\nbar\nbbc")); TestPressKey("\\ctrl-h"); QCOMPARE(kate_view->selectionText(), QString("foo\nxyz\nbar")); TestPressKey("notexists"); QCOMPARE(kate_view->selectionText(), QString("foo\nxyz")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo\nxyz\nbar\nbbc"); // Dismissing the search bar in visual mode should leave original selection. BeginTest("foo bar bbc"); TestPressKey("vl/\\ctrl-c"); QCOMPARE(kate_view->selectionText(), QString("fo")); FinishTest("foo bar bbc"); BeginTest("foo bar bbc"); TestPressKey("vl?\\ctrl-c"); QCOMPARE(kate_view->selectionText(), QString("fo")); FinishTest("foo bar bbc"); BeginTest("foo bar bbc"); TestPressKey("vl/b\\ctrl-c"); QCOMPARE(kate_view->selectionText(), QString("fo")); FinishTest("foo bar bbc"); BeginTest("foo\nbar\nbbc"); TestPressKey("Vl/b\\ctrl-c"); QCOMPARE(kate_view->selectionText(), QString("foo")); FinishTest("foo\nbar\nbbc"); // Search-highlighting tests. const QColor searchHighlightColour = kate_view->renderer()->config()->searchHighlightColor(); BeginTest("foo bar xyz"); // Sanity test. const QList rangesInitial = rangesOnFirstLine(); Q_ASSERT(rangesInitial.isEmpty() && "Assumptions about ranges are wrong - this test is invalid and may need updating!"); FinishTest("foo bar xyz"); // Test highlighting single character match. BeginTest("foo bar xyz"); TestPressKey("/b"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->attribute()->background().color(), searchHighlightColour); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 4); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 5); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test highlighting two character match. BeginTest("foo bar xyz"); TestPressKey("/ba"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 4); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 6); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test no highlighting if no longer a match. BeginTest("foo bar xyz"); TestPressKey("/baz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test highlighting on wraparound. BeginTest(" foo bar xyz"); TestPressKey("ww/foo"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 1); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 4); TestPressKey("\\enter"); FinishTest(" foo bar xyz"); // Test highlighting backwards BeginTest("foo bar xyz"); TestPressKey("$?ba"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 4); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 6); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test no highlighting when no match is found searching backwards BeginTest("foo bar xyz"); TestPressKey("$?baz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test highlight when wrapping around after searching backwards. BeginTest("foo bar xyz"); TestPressKey("w?xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 8); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 11); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Test no highlighting when bar is dismissed. DoTest("foo bar xyz", "/bar\\ctrl-c", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); DoTest("foo bar xyz", "/bar\\enter", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); DoTest("foo bar xyz", "/bar\\ctrl-[", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); DoTest("foo bar xyz", "/bar\\return", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); DoTest("foo bar xyz", "/bar\\esc", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); // Update colour on config change. BeginTest("foo bar xyz"); TestPressKey("/xyz"); const QColor newSearchHighlightColour = QColor(255, 0, 0); kate_view->renderer()->config()->setSearchHighlightColor(newSearchHighlightColour); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->attribute()->background().color(), newSearchHighlightColour); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Set the background colour appropriately. KColorScheme currentColorScheme(QPalette::Normal); const QColor normalBackgroundColour = QPalette().brush(QPalette::Base).color(); const QColor matchBackgroundColour = currentColorScheme.background(KColorScheme::PositiveBackground).color(); const QColor noMatchBackgroundColour = currentColorScheme.background(KColorScheme::NegativeBackground).color(); BeginTest("foo bar xyz"); TestPressKey("/xyz"); verifyTextEditBackgroundColour(matchBackgroundColour); TestPressKey("a"); verifyTextEditBackgroundColour(noMatchBackgroundColour); TestPressKey("\\ctrl-w"); verifyTextEditBackgroundColour(normalBackgroundColour); TestPressKey("/xyz\\enter/"); verifyTextEditBackgroundColour(normalBackgroundColour); TestPressKey("\\enter"); FinishTest("foo bar xyz"); // Escape regex's in a Vim-ish style. // Unescaped ( and ) are always literals. DoTest("foo bar( xyz", "/bar(\\enterrX", "foo Xar( xyz"); DoTest("foo bar) xyz", "/bar)\\enterrX", "foo Xar) xyz"); // + is literal, unless it is already escaped. DoTest("foo bar+ xyz", "/bar+ \\enterrX", "foo Xar+ xyz"); DoTest(" foo+AAAAbar", "/foo+A\\\\+bar\\enterrX", " Xoo+AAAAbar"); DoTest(" foo++++bar", "/foo+\\\\+bar\\enterrX", " Xoo++++bar"); DoTest(" foo++++bar", "/+\\enterrX", " fooX+++bar"); // An escaped "\" is a literal, of course. DoTest("foo x\\y", "/x\\\\\\\\y\\enterrX", "foo X\\y"); // ( and ), if escaped, are not literals. DoTest("foo barbarxyz", "/ \\\\(bar\\\\)\\\\+xyz\\enterrX", "foo Xbarbarxyz"); // Handle escaping correctly if we have an escaped and unescaped bracket next to each other. DoTest("foo x(A)y", "/x(\\\\(.\\\\))y\\enterrX", "foo X(A)y"); // |, if unescaped, is literal. DoTest("foo |bar", "/|\\enterrX", "foo Xbar"); // |, if escaped, is not a literal. DoTest("foo xfoo\\y xbary", "/x\\\\(foo\\\\|bar\\\\)y\\enterrX", "foo xfoo\\y Xbary"); // A single [ is a literal. DoTest("foo bar[", "/bar[\\enterrX", "foo Xar["); // A single ] is a literal. DoTest("foo bar]", "/bar]\\enterrX", "foo Xar]"); // A matching [ and ] are *not* literals. DoTest("foo xbcay", "/x[abc]\\\\+y\\enterrX", "foo Xbcay"); DoTest("foo xbcay", "/[abc]\\\\+y\\enterrX", "foo xXcay"); DoTest("foo xbaadcdcy", "/x[ab]\\\\+[cd]\\\\+y\\enterrX", "foo Xbaadcdcy"); // Need to be an unescaped match, though. DoTest("foo xbcay", "/x[abc\\\\]\\\\+y\\enterrX", "Xoo xbcay"); DoTest("foo xbcay", "/x\\\\[abc]\\\\+y\\enterrX", "Xoo xbcay"); DoTest("foo x[abc]]]]]y", "/x\\\\[abc]\\\\+y\\enterrX", "foo X[abc]]]]]y"); // An escaped '[' between matching unescaped '[' and ']' is treated as a literal '[' DoTest("foo xb[cay", "/x[a\\\\[bc]\\\\+y\\enterrX", "foo Xb[cay"); // An escaped ']' between matching unescaped '[' and ']' is treated as a literal ']' DoTest("foo xb]cay", "/x[a\\\\]bc]\\\\+y\\enterrX", "foo Xb]cay"); // An escaped '[' not between other square brackets is a literal. DoTest("foo xb[cay", "/xb\\\\[\\enterrX", "foo Xb[cay"); DoTest("foo xb[cay", "/\\\\[ca\\enterrX", "foo xbXcay"); // An escaped ']' not between other square brackets is a literal. DoTest("foo xb]cay", "/xb\\\\]\\enterrX", "foo Xb]cay"); DoTest("foo xb]cay", "/\\\\]ca\\enterrX", "foo xbXcay"); // An unescaped '[' not between other square brackets is a literal. DoTest("foo xbaba[y", "/x[ab]\\\\+[y\\enterrX", "foo Xbaba[y"); DoTest("foo xbaba[dcdcy", "/x[ab]\\\\+[[cd]\\\\+y\\enterrX", "foo Xbaba[dcdcy"); // An unescaped ']' not between other square brackets is a literal. DoTest("foo xbaba]y", "/x[ab]\\\\+]y\\enterrX", "foo Xbaba]y"); DoTest("foo xbaba]dcdcy", "/x[ab]\\\\+][cd]\\\\+y\\enterrX", "foo Xbaba]dcdcy"); // Be more clever about how we indentify escaping: the presence of a preceding // backslash is not always sufficient! DoTest("foo x\\babay", "/x\\\\\\\\[ab]\\\\+y\\enterrX", "foo X\\babay"); DoTest("foo x\\[abc]]]]y", "/x\\\\\\\\\\\\[abc]\\\\+y\\enterrX", "foo X\\[abc]]]]y"); DoTest("foo xa\\b\\c\\y", "/x[abc\\\\\\\\]\\\\+y\\enterrX", "foo Xa\\b\\c\\y"); DoTest("foo x[abc\\]]]]y", "/x[abc\\\\\\\\\\\\]\\\\+y\\enterrX", "foo X[abc\\]]]]y"); DoTest("foo xa[\\b\\[y", "/x[ab\\\\\\\\[]\\\\+y\\enterrX", "foo Xa[\\b\\[y"); DoTest("foo x\\[y", "/x\\\\\\\\[y\\enterrX", "foo X\\[y"); DoTest("foo x\\]y", "/x\\\\\\\\]y\\enterrX", "foo X\\]y"); DoTest("foo x\\+y", "/x\\\\\\\\+y\\enterrX", "foo X\\+y"); // A dot is not a literal, nor is a star. DoTest("foo bar", "/o.*b\\enterrX", "fXo bar"); // Escaped dots and stars are literals, though. DoTest("foo xay x.y", "/x\\\\.y\\enterrX", "foo xay X.y"); DoTest("foo xaaaay xa*y", "/xa\\\\*y\\enterrX", "foo xaaaay Xa*y"); // Unescaped curly braces are literals. DoTest("foo x{}y", "/x{}y\\enterrX", "foo X{}y"); // Escaped curly brackets are quantifers. DoTest("foo xaaaaay", "/xa\\\\{5\\\\}y\\enterrX", "foo Xaaaaay"); // Matching curly brackets where only the first is escaped are also quantifiers. DoTest("foo xaaaaaybbbz", "/xa\\\\{5}yb\\\\{3}z\\enterrX", "foo Xaaaaaybbbz"); // Make sure it really is escaped, though! DoTest("foo xa\\{5}", "/xa\\\\\\\\{5}\\enterrX", "foo Xa\\{5}"); // Don't crash if the first character is a } DoTest("foo aaaaay", "/{\\enterrX", "Xoo aaaaay"); // Vim's '\<' and '\>' map, roughly, to Qt's '\b' DoTest("foo xbar barx bar", "/bar\\\\>\\enterrX", "foo xXar barx bar"); DoTest("foo xbar barx bar", "/\\\\\\enterrX", "foo xbar barx Xar "); DoTest("foo xbar barx bar", "/\\\\\\enterrX", "foo xbar barx Xar"); DoTest("foo xbar barx\nbar", "/\\\\\\enterrX", "foo xbar barx\nXar"); // Escaped "^" and "$" are treated as literals. DoTest("foo x^$y", "/x\\\\^\\\\$y\\enterrX", "foo X^$y"); // Ensure that it is the escaped version of the pattern that is recorded as the last search pattern. DoTest("foo bar( xyz", "/bar(\\enterggnrX", "foo Xar( xyz"); // Don't log keypresses sent to the emulated command bar as commands to be repeated via "."! DoTest("foo", "/diw\\enterciwbar\\ctrl-c.", "bar"); // Don't leave Visual mode on aborting a search. DoTest("foo bar", "vw/\\ctrl-cd", "ar"); DoTest("foo bar", "vw/\\ctrl-[d", "ar"); // Don't crash on leaving Visual Mode on aborting a search. This is perhaps the most opaque regression // test ever; what it's testing for is the situation where the synthetic keypress issue by the emulated // command bar on the "ctrl-[" is sent to the key mapper. This in turn converts it into a weird character // which is then, upon not being recognised as part of a mapping, sent back around the keypress processing, // where it ends up being sent to the emulated command bar's text edit, which in turn issues a "text changed" // event where the text is still empty, which tries to move the cursor to (-1, -1), which causes a crash deep // within Kate. So, in a nutshell: this test ensures that the keymapper never handles the synthetic keypress :) DoTest("", "ifoo\\ctrl-cv/\\ctrl-[", "foo"); // History auto-completion tests. clearSearchHistory(); QVERIFY(searchHistory().isEmpty()); vi_global->searchHistory()->append("foo"); vi_global->searchHistory()->append("bar"); QCOMPARE(searchHistory(), QStringList() << "foo" << "bar"); clearSearchHistory(); QVERIFY(searchHistory().isEmpty()); // Ensure current search bar text is added to the history if we press enter. DoTest("foo bar", "/bar\\enter", "foo bar"); DoTest("foo bar", "/xyz\\enter", "foo bar"); QCOMPARE(searchHistory(), QStringList() << "bar" << "xyz"); // Interesting - Vim adds the search bar text to the history even if we abort via e.g. ctrl-c, ctrl-[, etc. clearSearchHistory(); DoTest("foo bar", "/baz\\ctrl-[", "foo bar"); QCOMPARE(searchHistory(), QStringList() << "baz"); clearSearchHistory(); DoTest("foo bar", "/foo\\esc", "foo bar"); QCOMPARE(searchHistory(), QStringList() << "foo"); clearSearchHistory(); DoTest("foo bar", "/nose\\ctrl-c", "foo bar"); QCOMPARE(searchHistory(), QStringList() << "nose"); clearSearchHistory(); vi_global->searchHistory()->append("foo"); vi_global->searchHistory()->append("bar"); - QVERIFY(emulatedCommandBarCompleter() != NULL); + QVERIFY(emulatedCommandBarCompleter() != nullptr); BeginTest("foo bar"); TestPressKey("/\\ctrl-p"); verifyCommandBarCompletionVisible(); // Make sure the completion appears in roughly the correct place: this is a little fragile :/ const QPoint completerRectTopLeft = emulatedCommandBarCompleter()->popup()->mapToGlobal(emulatedCommandBarCompleter()->popup()->rect().topLeft()) ; const QPoint barEditBottomLeft = emulatedCommandBarTextEdit()->mapToGlobal(emulatedCommandBarTextEdit()->rect().bottomLeft()); QCOMPARE(completerRectTopLeft.x(), barEditBottomLeft.x()); QVERIFY(qAbs(completerRectTopLeft.y() - barEditBottomLeft.y()) <= 1); // Will activate the current completion item, activating the search, and dismissing the bar. TestPressKey("\\enter"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); // Close the command bar. FinishTest("foo bar"); // Don't show completion with an empty search bar. clearSearchHistory(); vi_global->searchHistory()->append("foo"); BeginTest("foo bar"); TestPressKey("/"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); FinishTest("foo bar"); // Don't auto-complete, either. clearSearchHistory(); vi_global->searchHistory()->append("foo"); BeginTest("foo bar"); TestPressKey("/f"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); FinishTest("foo bar"); clearSearchHistory(); vi_global->searchHistory()->append("xyz"); vi_global->searchHistory()->append("bar"); - QVERIFY(emulatedCommandBarCompleter() != NULL); + QVERIFY(emulatedCommandBarCompleter() != nullptr); BeginTest("foo bar"); TestPressKey("/\\ctrl-p"); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("bar")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo bar"); clearSearchHistory(); vi_global->searchHistory()->append("xyz"); vi_global->searchHistory()->append("bar"); vi_global->searchHistory()->append("foo"); - QVERIFY(emulatedCommandBarCompleter() != NULL); + QVERIFY(emulatedCommandBarCompleter() != nullptr); BeginTest("foo bar"); TestPressKey("/\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 0); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("bar")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("bar")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 1); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("xyz")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 2); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo")); // Wrap-around QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 0); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo bar"); clearSearchHistory(); vi_global->searchHistory()->append("xyz"); vi_global->searchHistory()->append("bar"); vi_global->searchHistory()->append("foo"); - QVERIFY(emulatedCommandBarCompleter() != NULL); + QVERIFY(emulatedCommandBarCompleter() != nullptr); BeginTest("foo bar"); TestPressKey("/\\ctrl-n"); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("xyz")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 2); TestPressKey("\\ctrl-n"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("bar")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("bar")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 1); TestPressKey("\\ctrl-n"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo")); QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 0); TestPressKey("\\ctrl-n"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz")); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("xyz")); // Wrap-around. QCOMPARE(emulatedCommandBarCompleter()->popup()->currentIndex().row(), 2); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo bar"); clearSearchHistory(); vi_global->searchHistory()->append("xyz"); vi_global->searchHistory()->append("bar"); vi_global->searchHistory()->append("foo"); BeginTest("foo bar"); TestPressKey("/\\ctrl-n\\ctrl-n"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("bar")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo bar"); // If we add something to the history, remove any earliest occurrences (this is what Vim appears to do) // and append to the end. clearSearchHistory(); vi_global->searchHistory()->append("bar"); vi_global->searchHistory()->append("xyz"); vi_global->searchHistory()->append("foo"); vi_global->searchHistory()->append("xyz"); QCOMPARE(searchHistory(), QStringList() << "bar" << "foo" << "xyz"); // Push out older entries if we have too many search items in the history. const int HISTORY_SIZE_LIMIT = 100; clearSearchHistory(); for (int i = 1; i <= HISTORY_SIZE_LIMIT; i++) { vi_global->searchHistory()->append(QString("searchhistoryitem %1").arg(i)); } QCOMPARE(searchHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(searchHistory().first(), QString("searchhistoryitem 1")); QCOMPARE(searchHistory().last(), QString("searchhistoryitem 100")); vi_global->searchHistory()->append(QString("searchhistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); QCOMPARE(searchHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(searchHistory().first(), QString("searchhistoryitem 2")); QCOMPARE(searchHistory().last(), QString("searchhistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); // Don't add empty searches to the history. clearSearchHistory(); DoTest("foo bar", "/\\enter", "foo bar"); QVERIFY(searchHistory().isEmpty()); // "*" and "#" should add the relevant word to the search history, enclosed between \< and \> clearSearchHistory(); BeginTest("foo bar"); TestPressKey("*"); QVERIFY(!searchHistory().isEmpty()); QCOMPARE(searchHistory().last(), QString("\\")); TestPressKey("w#"); QCOMPARE(searchHistory().size(), 2); QCOMPARE(searchHistory().last(), QString("\\")); // Auto-complete words from the document on ctrl-space. // Test that we can actually find a single word and add it to the list of completions. BeginTest("foo"); TestPressKey("/\\ctrl- "); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo")); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo"); // Count digits and underscores as being part of a word. BeginTest("foo_12"); TestPressKey("/\\ctrl- "); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo_12")); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo_12"); // This feels a bit better to me, usability-wise: in the special case of completion from document, where // the completion list is manually summoned, allow one to press Enter without the bar being dismissed // (just dismiss the completion list instead). BeginTest("foo_12"); TestPressKey("/\\ctrl- \\ctrl-p\\enter"); QVERIFY(emulatedCommandBar->isVisible()); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo_12"); // Check that we can find multiple words on one line. BeginTest("bar (foo) [xyz]"); TestPressKey("/\\ctrl- "); QStringListModel *completerStringListModel = dynamic_cast(emulatedCommandBarCompleter()->model()); Q_ASSERT(completerStringListModel); QCOMPARE(completerStringListModel->stringList(), QStringList() << "bar" << "foo" << "xyz"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("bar (foo) [xyz]"); // Check that we arrange the found words in case-insensitive sorted order. BeginTest("D c e a b f"); TestPressKey("/\\ctrl- "); verifyCommandBarCompletionsMatches(QStringList() << "a" << "b" << "c" << "D" << "e" << "f"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("D c e a b f"); // Check that we don't include the same word multiple times. BeginTest("foo bar bar bar foo"); TestPressKey("/\\ctrl- "); verifyCommandBarCompletionsMatches(QStringList() << "bar" << "foo"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo bar bar bar foo"); // Check that we search only a narrow portion of the document, around the cursor (4096 lines either // side, say). QStringList manyLines; for (int i = 1; i < 2 * 4096 + 3; i++) { // Pad the digits so that when sorted alphabetically, they are also sorted numerically. manyLines << QString("word%1").arg(i, 5, 10, QChar('0')); } QStringList allButFirstAndLastOfManyLines = manyLines; allButFirstAndLastOfManyLines.removeFirst(); allButFirstAndLastOfManyLines.removeLast(); BeginTest(manyLines.join("\n")); TestPressKey("4097j/\\ctrl- "); verifyCommandBarCompletionsMatches(allButFirstAndLastOfManyLines); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest(manyLines.join("\n")); // "The current word" means the word before the cursor in the command bar, and includes numbers // and underscores. Make sure also that the completion prefix is set when the completion is first invoked. BeginTest("foo fee foa_11 foa_11b"); // Write "bar(foa112$nose" and position cursor before the "2", then invoke completion. TestPressKey("/bar(foa_112$nose\\left\\left\\left\\left\\left\\left\\ctrl- "); verifyCommandBarCompletionsMatches(QStringList() << "foa_11" << "foa_11b"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo fee foa_11 foa_11b"); // But don't count "-" as being part of the current word. BeginTest("foo_12"); TestPressKey("/bar-foo\\ctrl- "); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("foo_12")); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo_12"); // Be case insensitive. BeginTest("foo Fo12 fOo13 FO45"); TestPressKey("/fo\\ctrl- "); verifyCommandBarCompletionsMatches(QStringList() << "Fo12" << "FO45" << "foo" << "fOo13"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo Fo12 fOo13 FO45"); // Feed the current word to complete to the completer as we type/ edit. BeginTest("foo fee foa foab"); TestPressKey("/xyz|f\\ctrl- o"); verifyCommandBarCompletionsMatches(QStringList() << "foa" << "foab" << "foo"); TestPressKey("a"); verifyCommandBarCompletionsMatches(QStringList() << "foa" << "foab"); TestPressKey("\\ctrl-h"); verifyCommandBarCompletionsMatches(QStringList() << "foa" << "foab" << "foo"); TestPressKey("o"); verifyCommandBarCompletionsMatches(QStringList() << "foo"); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo fee foa foab"); // Upon selecting a completion with an empty command bar, add the completed text to the command bar. BeginTest("foo fee fob foables"); TestPressKey("/\\ctrl- foa\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foables")); verifyCommandBarCompletionVisible(); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo fee fob foables"); // If bar is non-empty, replace the word under the cursor. BeginTest("foo fee foa foab"); TestPressKey("/xyz|f$nose\\left\\left\\left\\left\\left\\ctrl- oa\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz|foab$nose")); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("foo fee foa foab"); // Place the cursor at the end of the completed text. BeginTest("foo fee foa foab"); TestPressKey("/xyz|f$nose\\left\\left\\left\\left\\left\\ctrl- oa\\ctrl-p\\enterX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz|foabX$nose")); TestPressKey("\\ctrl-c"); // Dismiss completion, then bar. FinishTest("foo fee foa foab"); // If we're completing from history, though, the entire text gets set, and the completion prefix // is the beginning of the entire text, not the current word before the cursor. clearSearchHistory(); vi_global->searchHistory()->append("foo(bar"); BeginTest(""); TestPressKey("/foo(b\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "foo(bar"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo(bar")); TestPressKey("\\enter"); // Dismiss bar. FinishTest(""); // If we're completing from history and we abort the completion via ctrl-c or ctrl-[, we revert the whole // text to the last manually typed text. clearSearchHistory(); vi_global->searchHistory()->append("foo(b|ar"); BeginTest(""); TestPressKey("/foo(b\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "foo(b|ar"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo(b|ar")); TestPressKey("\\ctrl-c"); // Dismiss completion. QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo(b")); TestPressKey("\\enter"); // Dismiss bar. FinishTest(""); // Scroll completion list if necessary so that currently selected completion is visible. BeginTest("a b c d e f g h i j k l m n o p q r s t u v w x y z"); TestPressKey("/\\ctrl- "); const int lastItemRow = 25; const QRect initialLastCompletionItemRect = emulatedCommandBarCompleter()->popup()->visualRect(emulatedCommandBarCompleter()->popup()->model()->index(lastItemRow, 0)); QVERIFY(!emulatedCommandBarCompleter()->popup()->rect().contains(initialLastCompletionItemRect)); // If this fails, then we have an error in the test setup: initally, the last item in the list should be outside of the bounds of the popup. TestPressKey("\\ctrl-n"); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("z")); const QRect lastCompletionItemRect = emulatedCommandBarCompleter()->popup()->visualRect(emulatedCommandBarCompleter()->popup()->model()->index(lastItemRow, 0)); QVERIFY(emulatedCommandBarCompleter()->popup()->rect().contains(lastCompletionItemRect)); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("a b c d e f g h i j k l m n o p q r s t u v w x y z"); // Ensure that the completion list changes size appropriately as the number of candidate completions changes. BeginTest("a ab abc"); TestPressKey("/\\ctrl- "); const int initialPopupHeight = emulatedCommandBarCompleter()->popup()->height(); TestPressKey("ab"); const int popupHeightAfterEliminatingOne = emulatedCommandBarCompleter()->popup()->height(); QVERIFY(popupHeightAfterEliminatingOne < initialPopupHeight); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("a ab abc"); // Ensure that the completion list disappears when no candidate completions are found, but re-appears // when some are found. BeginTest("a ab abc"); TestPressKey("/\\ctrl- "); TestPressKey("abd"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-h"); verifyCommandBarCompletionVisible(); TestPressKey("\\enter\\enter"); // Dismiss completion, then bar. FinishTest("a ab abc"); // ctrl-c and ctrl-[ when the completion list is visible should dismiss the completion list, but *not* // the emulated command bar. TODO - same goes for ESC, but this is harder as KateViewInternal dismisses it // itself. BeginTest("a ab abc"); TestPressKey("/\\ctrl- \\ctrl-cdiw"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); // Dismiss bar. TestPressKey("/\\ctrl- \\ctrl-[diw"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\enter"); // Dismiss bar. FinishTest("a ab abc"); // If we implicitly choose an element from the summoned completion list (by highlighting it, then // continuing to edit the text), the completion box should not re-appear unless explicitly summoned // again, even if the current word has a valid completion. BeginTest("a ab abc"); TestPressKey("/\\ctrl- \\ctrl-p"); TestPressKey(".a"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); // Dismiss bar. FinishTest("a ab abc"); // If we dismiss the summoned completion list via ctrl-c or ctrl-[, it should not re-appear unless explicitly summoned // again, even if the current word has a valid completion. BeginTest("a ab abc"); TestPressKey("/\\ctrl- \\ctrl-c"); TestPressKey(".a"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); TestPressKey("/\\ctrl- \\ctrl-["); TestPressKey(".a"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\enter"); // Dismiss bar. FinishTest("a ab abc"); // If we select a completion from an empty bar, but then dismiss it via ctrl-c or ctrl-[, then we // should restore the empty text. BeginTest("foo"); TestPressKey("/\\ctrl- \\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); TestPressKey("\\ctrl-c"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo"); BeginTest("foo"); TestPressKey("/\\ctrl- \\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("foo")); TestPressKey("\\ctrl-["); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("foo"); // If we select a completion but then dismiss it via ctrl-c or ctrl-[, then we // should restore the last manually typed word. BeginTest("fooabc"); TestPressKey("/f\\ctrl- o\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("fooabc")); TestPressKey("\\ctrl-c"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("fo")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("fooabc"); // If we select a completion but then dismiss it via ctrl-c or ctrl-[, then we // should restore the word currently being typed to the last manually typed word. BeginTest("fooabc"); TestPressKey("/ab\\ctrl- |fo\\ctrl-p"); TestPressKey("\\ctrl-c"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("ab|fo")); TestPressKey("\\enter"); // Dismiss bar. FinishTest("fooabc"); // Set the completion prefix for the search history completion as soon as it is shown. clearSearchHistory(); vi_global->searchHistory()->append("foo(bar"); vi_global->searchHistory()->append("xyz"); BeginTest(""); TestPressKey("/f\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "foo(bar"); TestPressKey("\\enter"); // Dismiss bar. FinishTest(""); // Command Mode (:) tests. // ":" should summon the command bar, with ":" as the label. BeginTest(""); TestPressKey(":"); QVERIFY(emulatedCommandBar->isVisible()); QCOMPARE(emulatedCommandTypeIndicator()->text(), QString(":")); QVERIFY(emulatedCommandTypeIndicator()->isVisible()); QVERIFY(emulatedCommandBarTextEdit()); QVERIFY(emulatedCommandBarTextEdit()->text().isEmpty()); TestPressKey("\\esc"); FinishTest(""); // If we have a selection, it should be encoded as a range in the text edit. BeginTest("d\nb\na\nc"); TestPressKey("Vjjj:"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("'<,'>")); TestPressKey("\\esc"); FinishTest("d\nb\na\nc"); // If we have a count, it should be encoded as a range in the text edit. BeginTest("d\nb\na\nc"); TestPressKey("7:"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+6")); TestPressKey("\\esc"); FinishTest("d\nb\na\nc"); // Don't go doing an incremental search when we press keys! BeginTest("foo bar xyz"); TestPressKey(":bar"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); TestPressKey("\\esc"); FinishTest("foo bar xyz"); // Execute the command on Enter. DoTest("d\nb\na\nc", "Vjjj:sort\\enter", "a\nb\nc\nd"); // Don't crash if we call a non-existent command with a range. DoTest("123", ":42nonexistentcommand\\enter", "123"); // Bar background should always be normal for command bar. BeginTest("foo"); TestPressKey("/foo\\enter:"); verifyTextEditBackgroundColour(normalBackgroundColour); TestPressKey("\\ctrl-c/bar\\enter:"); verifyTextEditBackgroundColour(normalBackgroundColour); TestPressKey("\\esc"); FinishTest("foo"); const int commandResponseMessageTimeOutMSOverride = QString::fromLatin1(qgetenv("KATE_VIMODE_TEST_COMMANDRESPONSEMESSAGETIMEOUTMS")).toInt(); const long commandResponseMessageTimeOutMS = (commandResponseMessageTimeOutMSOverride > 0) ? commandResponseMessageTimeOutMSOverride : 4000; { // If there is any output from the command, show it in a label for a short amount of time // (make sure the bar type indicator is hidden, here, as it looks messy). emulatedCommandBar->setCommandResponseMessageTimeout(commandResponseMessageTimeOutMS); BeginTest("foo bar xyz"); const QDateTime timeJustBeforeCommandExecuted = QDateTime::currentDateTime(); TestPressKey(":commandthatdoesnotexist\\enter"); QVERIFY(emulatedCommandBar->isVisible()); QVERIFY(commandResponseMessageDisplay()); QVERIFY(commandResponseMessageDisplay()->isVisible()); QVERIFY(!emulatedCommandBarTextEdit()->isVisible()); QVERIFY(!emulatedCommandTypeIndicator()->isVisible()); // Be a bit vague about the exact message, due to i18n, etc. QVERIFY(commandResponseMessageDisplay()->text().contains("commandthatdoesnotexist")); waitForEmulatedCommandBarToHide(4 * commandResponseMessageTimeOutMS); QVERIFY(timeJustBeforeCommandExecuted.msecsTo(QDateTime::currentDateTime()) >= commandResponseMessageTimeOutMS - 500); // "- 500" because coarse timers can fire up to 500ms *prematurely*. QVERIFY(!emulatedCommandBar->isVisible()); // Piggy-back on this test, as the bug we're about to test for would actually make setting // up the conditions again in a separate test impossible ;) // When we next summon the bar, the response message should be invisible; the editor visible & editable; // and the bar type indicator visible again. TestPressKey("/"); QVERIFY(!commandResponseMessageDisplay()->isVisible()); QVERIFY(emulatedCommandBarTextEdit()->isVisible()); QVERIFY(emulatedCommandBarTextEdit()->isEnabled()); QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\esc"); // Dismiss the bar. FinishTest("foo bar xyz"); } { // Show the same message twice in a row. BeginTest("foo bar xyz"); TestPressKey(":othercommandthatdoesnotexist\\enter"); QDateTime startWaitingForMessageToHide = QDateTime::currentDateTime(); waitForEmulatedCommandBarToHide(4 * commandResponseMessageTimeOutMS); TestPressKey(":othercommandthatdoesnotexist\\enter"); QVERIFY(commandResponseMessageDisplay()->isVisible()); // Wait for it to disappear again, as a courtesy for the next test. waitForEmulatedCommandBarToHide(4 * commandResponseMessageTimeOutMS); } { // Emulated command bar should not steal keypresses when it is merely showing the results of an executed command. BeginTest("foo bar"); TestPressKey(":commandthatdoesnotexist\\enterrX"); Q_ASSERT_X(commandResponseMessageDisplay()->isVisible(), "running test", "Need to increase timeJustBeforeCommandExecuted!"); FinishTest("Xoo bar"); } { // Don't send the synthetic "enter" keypress (for making search-as-a-motion work) when we finally hide. BeginTest("foo bar\nbar"); TestPressKey(":commandthatdoesnotexist\\enter"); Q_ASSERT_X(commandResponseMessageDisplay()->isVisible(), "running test", "Need to increase timeJustBeforeCommandExecuted!"); waitForEmulatedCommandBarToHide(commandResponseMessageTimeOutMS * 4); TestPressKey("rX"); FinishTest("Xoo bar\nbar"); } { // The timeout should be cancelled when we invoke the command bar again. BeginTest(""); TestPressKey(":commandthatdoesnotexist\\enter"); const QDateTime waitStartedTime = QDateTime::currentDateTime(); TestPressKey(":"); // Wait ample time for the timeout to fire. Do not use waitForEmulatedCommandBarToHide for this! while(waitStartedTime.msecsTo(QDateTime::currentDateTime()) < commandResponseMessageTimeOutMS * 2) { QApplication::processEvents(); } QVERIFY(emulatedCommandBar->isVisible()); TestPressKey("\\esc"); // Dismiss the bar. FinishTest(""); } { // The timeout should not cause kate_view to regain focus if we have manually taken it away. qDebug()<< " NOTE: this test is weirdly fragile, so if it starts failing, comment it out and e-mail me: it may well be more trouble that it's worth."; BeginTest(""); TestPressKey(":commandthatdoesnotexist\\enter"); while (QApplication::hasPendingEvents()) { // Wait for any focus changes to take effect. QApplication::processEvents(); } const QDateTime waitStartedTime = QDateTime::currentDateTime(); QLineEdit *dummyToFocus = new QLineEdit(QString("Sausage"), mainWindow); // Take focus away from kate_view by giving it to dummyToFocus. QApplication::setActiveWindow(mainWindow); kate_view->setFocus(); mainWindowLayout->addWidget(dummyToFocus); dummyToFocus->show(); dummyToFocus->setEnabled(true); dummyToFocus->setFocus(); // Allow dummyToFocus to receive focus. while(!dummyToFocus->hasFocus()) { QApplication::processEvents(); } QVERIFY(dummyToFocus->hasFocus()); // Wait ample time for the timeout to fire. Do not use waitForEmulatedCommandBarToHide for this - // the bar never actually hides in this instance, and I think it would take some deep changes in // Kate to make it do so (the KateCommandLineBar as the same issue). while(waitStartedTime.msecsTo(QDateTime::currentDateTime()) < commandResponseMessageTimeOutMS * 2) { QApplication::processEvents(); } QVERIFY(dummyToFocus->hasFocus()); QVERIFY(emulatedCommandBar->isVisible()); mainWindowLayout->removeWidget(dummyToFocus); // Restore focus to the kate_view. kate_view->setFocus(); while(!kate_view->hasFocus()) { QApplication::processEvents(); } // *Now* wait for the command bar to disappear - giving kate_view focus should trigger it. waitForEmulatedCommandBarToHide(commandResponseMessageTimeOutMS * 4); FinishTest(""); } { // No completion should be shown when the bar is first shown: this gives us an opportunity // to invoke command history via ctrl-p and ctrl-n. BeginTest(""); TestPressKey(":"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); } { // Should be able to switch to completion from document, even when we have a completion from commands. BeginTest("soggy1 soggy2"); TestPressKey(":so"); verifyCommandBarCompletionContains(QStringList() << "sort"); TestPressKey("\\ctrl- "); verifyCommandBarCompletionsMatches(QStringList() << "soggy1" << "soggy2"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest("soggy1 soggy2"); } { // If we dismiss the command completion then change the text, it should summon the completion // again. BeginTest(""); TestPressKey(":so"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("r"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionContains(QStringList() << "sort"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); } { // Completion should be dismissed when we are showing command response text. BeginTest(""); TestPressKey(":set-au\\enter"); QVERIFY(commandResponseMessageDisplay()->isVisible()); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); waitForEmulatedCommandBarToHide(commandResponseMessageTimeOutMS * 4); FinishTest(""); } // If we abort completion via ctrl-c or ctrl-[, we should revert the current word to the last // manually entered word. BeginTest(""); TestPressKey(":se\\ctrl-p"); verifyCommandBarCompletionVisible(); QVERIFY(emulatedCommandBarTextEdit()->text() != "se"); TestPressKey("\\ctrl-c"); // Dismiss completer QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("se")); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // In practice, it's annoying if, as we enter ":s/se", completions pop up after the "se": // for now, only summon completion if we are on the first word in the text. BeginTest(""); TestPressKey(":s/se"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); BeginTest(""); TestPressKey(":.,.+7s/se"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // Don't blank the text if we activate command history completion with no command history. BeginTest(""); clearCommandHistory(); TestPressKey(":s/se\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/se")); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // On completion, only update the command in front of the cursor. BeginTest(""); clearCommandHistory(); TestPressKey(":.,.+6s/se\\left\\left\\leftet-auto-in\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+6set-auto-indent/se")); TestPressKey("\\ctrl-c"); // Dismiss completer. TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // On completion, place the cursor after the new command. BeginTest(""); clearCommandHistory(); TestPressKey(":.,.+6s/fo\\left\\left\\leftet-auto-in\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+6set-auto-indentX/fo")); TestPressKey("\\ctrl-c"); // Dismiss completer. TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // "The current word", for Commands, can contain "-". BeginTest(""); TestPressKey(":set-\\ctrl-p"); verifyCommandBarCompletionVisible(); QVERIFY(emulatedCommandBarTextEdit()->text() != "set-"); QVERIFY(emulatedCommandBarCompleter()->currentCompletion().startsWith("set-")); QCOMPARE(emulatedCommandBarTextEdit()->text(), emulatedCommandBarCompleter()->currentCompletion()); TestPressKey("\\ctrl-c"); // Dismiss completion. TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); { // Don't switch from word-from-document to command-completion just because we press a key, though! BeginTest("soggy1 soggy2"); TestPressKey(":\\ctrl- s"); TestPressKey("o"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "soggy1" << "soggy2"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest("soggy1 soggy2"); } { // If we're in a place where there is no command completion allowed, don't go hiding the word // completion as we type. BeginTest("soggy1 soggy2"); TestPressKey(":s/s\\ctrl- o"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "soggy1" << "soggy2"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest("soggy1 soggy2"); } { // Don't show command completion before we start typing a command: we want ctrl-p/n // to go through command history instead (we'll test for that second part later). BeginTest("soggy1 soggy2"); TestPressKey(":"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-cvl:"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest("soggy1 soggy2"); } { // Aborting ":" should leave us in normal mode with no selection. BeginTest("foo bar"); TestPressKey("vw:\\ctrl-["); QVERIFY(kate_view->selectionText().isEmpty()); TestPressKey("wdiw"); BeginTest("foo "); } // Command history tests. clearCommandHistory(); QVERIFY(commandHistory().isEmpty()); vi_global->commandHistory()->append("foo"); vi_global->commandHistory()->append("bar"); QCOMPARE(commandHistory(), QStringList() << "foo" << "bar"); clearCommandHistory(); QVERIFY(commandHistory().isEmpty()); // If we add something to the history, remove any earliest occurrences (this is what Vim appears to do) // and append to the end. clearCommandHistory(); vi_global->commandHistory()->append("bar"); vi_global->commandHistory()->append("xyz"); vi_global->commandHistory()->append("foo"); vi_global->commandHistory()->append("xyz"); QCOMPARE(commandHistory(), QStringList() << "bar" << "foo" << "xyz"); // Push out older entries if we have too many command items in the history. clearCommandHistory(); for (int i = 1; i <= HISTORY_SIZE_LIMIT; i++) { vi_global->commandHistory()->append(QString("commandhistoryitem %1").arg(i)); } QCOMPARE(commandHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(commandHistory().first(), QString("commandhistoryitem 1")); QCOMPARE(commandHistory().last(), QString("commandhistoryitem 100")); vi_global->commandHistory()->append(QString("commandhistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); QCOMPARE(commandHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(commandHistory().first(), QString("commandhistoryitem 2")); QCOMPARE(commandHistory().last(), QString("commandhistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); // Don't add empty commands to the history. clearCommandHistory(); DoTest("foo bar", ":\\enter", "foo bar"); QVERIFY(commandHistory().isEmpty()); clearCommandHistory(); BeginTest(""); TestPressKey(":sort\\enter"); QCOMPARE(commandHistory(), QStringList() << "sort"); TestPressKey(":yank\\enter"); QCOMPARE(commandHistory(), QStringList() << "sort" << "yank"); // Add to history immediately: don't wait for the command response display to timeout. TestPressKey(":commandthatdoesnotexist\\enter"); QCOMPARE(commandHistory(), QStringList() << "sort" << "yank" << "commandthatdoesnotexist"); // Vim adds aborted commands to the history too, oddly. TestPressKey(":abortedcommand\\ctrl-c"); QCOMPARE(commandHistory(), QStringList() << "sort" << "yank" << "commandthatdoesnotexist" << "abortedcommand"); // Only add for commands, not searches! TestPressKey("/donotaddme\\enter?donotaddmeeither\\enter/donotaddme\\ctrl-c?donotaddmeeither\\ctrl-c"); QCOMPARE(commandHistory(), QStringList() << "sort" << "yank" << "commandthatdoesnotexist" << "abortedcommand"); FinishTest(""); // Commands should not be added to the search history! clearCommandHistory(); clearSearchHistory(); BeginTest(""); TestPressKey(":sort\\enter"); QVERIFY(searchHistory().isEmpty()); FinishTest(""); // With an empty command bar, ctrl-p / ctrl-n should go through history. clearCommandHistory(); vi_global->commandHistory()->append("command1"); vi_global->commandHistory()->append("command2"); BeginTest(""); TestPressKey(":\\ctrl-p"); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("command2")); QCOMPARE(emulatedCommandBarTextEdit()->text(), emulatedCommandBarCompleter()->currentCompletion()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); clearCommandHistory(); vi_global->commandHistory()->append("command1"); vi_global->commandHistory()->append("command2"); BeginTest(""); TestPressKey(":\\ctrl-n"); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("command1")); QCOMPARE(emulatedCommandBarTextEdit()->text(), emulatedCommandBarCompleter()->currentCompletion()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // If we're at a place where command completions are not allowed, ctrl-p/n should go through history. clearCommandHistory(); vi_global->commandHistory()->append("s/command1"); vi_global->commandHistory()->append("s/command2"); BeginTest(""); TestPressKey(":s/\\ctrl-p"); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("s/command2")); QCOMPARE(emulatedCommandBarTextEdit()->text(), emulatedCommandBarCompleter()->currentCompletion()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); clearCommandHistory(); vi_global->commandHistory()->append("s/command1"); vi_global->commandHistory()->append("s/command2"); BeginTest(""); TestPressKey(":s/\\ctrl-n"); verifyCommandBarCompletionVisible(); QCOMPARE(emulatedCommandBarCompleter()->currentCompletion(), QString("s/command1")); QCOMPARE(emulatedCommandBarTextEdit()->text(), emulatedCommandBarCompleter()->currentCompletion()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest(""); // Cancelling word-from-document completion should revert the whole text to what it was before. BeginTest("sausage bacon"); TestPressKey(":s/b\\ctrl- \\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/bacon")); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/b")); TestPressKey("\\ctrl-c"); // Dismiss bar FinishTest("sausage bacon"); // "Replace" history tests. clearReplaceHistory(); QVERIFY(replaceHistory().isEmpty()); vi_global->replaceHistory()->append("foo"); vi_global->replaceHistory()->append("bar"); QCOMPARE(replaceHistory(), QStringList() << "foo" << "bar"); clearReplaceHistory(); QVERIFY(replaceHistory().isEmpty()); // If we add something to the history, remove any earliest occurrences (this is what Vim appears to do) // and append to the end. clearReplaceHistory(); vi_global->replaceHistory()->append("bar"); vi_global->replaceHistory()->append("xyz"); vi_global->replaceHistory()->append("foo"); vi_global->replaceHistory()->append("xyz"); QCOMPARE(replaceHistory(), QStringList() << "bar" << "foo" << "xyz"); // Push out older entries if we have too many replace items in the history. clearReplaceHistory(); for (int i = 1; i <= HISTORY_SIZE_LIMIT; i++) { vi_global->replaceHistory()->append(QString("replacehistoryitem %1").arg(i)); } QCOMPARE(replaceHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(replaceHistory().first(), QString("replacehistoryitem 1")); QCOMPARE(replaceHistory().last(), QString("replacehistoryitem 100")); vi_global->replaceHistory()->append(QString("replacehistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); QCOMPARE(replaceHistory().size(), HISTORY_SIZE_LIMIT); QCOMPARE(replaceHistory().first(), QString("replacehistoryitem 2")); QCOMPARE(replaceHistory().last(), QString("replacehistoryitem %1").arg(HISTORY_SIZE_LIMIT + 1)); // Don't add empty replaces to the history. clearReplaceHistory(); vi_global->replaceHistory()->append(""); QVERIFY(replaceHistory().isEmpty()); // Some misc SedReplace tests. DoTest("x\\/y", ":s/\\\\//replace/g\\enter", "x\\replacey"); DoTest("x\\/y", ":s/\\\\\\\\\\\\//replace/g\\enter", "xreplacey"); DoTest("x\\/y", ":s:/:replace:g\\enter", "x\\replacey"); DoTest("foo\nbar\nxyz", ":%delete\\enter", ""); DoTest("foo\nbar\nxyz\nbaz", "jVj:delete\\enter", "foo\nbaz"); DoTest("foo\nbar\nxyz\nbaz", "j2:delete\\enter", "foo\nbaz"); // On ctrl-d, delete the "search" term in a s/search/replace/xx BeginTest("foo bar"); TestPressKey(":s/x\\\\\\\\\\\\/yz/rep\\\\\\\\\\\\/lace/g\\ctrl-d"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s//rep\\\\\\/lace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Move cursor to position of deleted search term. BeginTest("foo bar"); TestPressKey(":s/x\\\\\\\\\\\\/yz/rep\\\\\\\\\\\\/lace/g\\ctrl-dX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/X/rep\\\\\\/lace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Do nothing on ctrl-d in search mode. BeginTest("foo bar"); TestPressKey("/s/search/replace/g\\ctrl-d"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/replace/g")); TestPressKey("\\ctrl-c?s/searchbackwards/replace/g\\ctrl-d"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/searchbackwards/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // On ctrl-f, delete "replace" term in a s/search/replace/xx BeginTest("foo bar"); TestPressKey(":s/a\\\\\\\\\\\\/bc/rep\\\\\\\\\\\\/lace/g\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/a\\\\\\/bc//g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Move cursor to position of deleted replace term. BeginTest("foo bar"); TestPressKey(":s:a/bc:replace:g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:a/bc:X:g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Do nothing on ctrl-d in search mode. BeginTest("foo bar"); TestPressKey("/s/search/replace/g\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/replace/g")); TestPressKey("\\ctrl-c?s/searchbackwards/replace/g\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/searchbackwards/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Do nothing on ctrl-d / ctrl-f if the current expression is not a sed expression. BeginTest("foo bar"); TestPressKey(":s/notasedreplaceexpression::gi\\ctrl-f\\ctrl-dX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/notasedreplaceexpression::giX")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Need to convert Vim-style regex's to Qt one's in Sed Replace. DoTest("foo xbacba(boo)|[y", ":s/x[abc]\\\\+(boo)|[y/boo/g\\enter", "foo boo"); DoTest("foo xbacba(boo)|[y\nfoo xbacba(boo)|[y", "Vj:s/x[abc]\\\\+(boo)|[y/boo/g\\enter", "foo boo\nfoo boo"); // Just convert the search term, please :) DoTest("foo xbacba(boo)|[y", ":s/x[abc]\\\\+(boo)|[y/boo()/g\\enter", "foo boo()"); // With an empty search expression, ctrl-d should still position the cursor correctly. BeginTest("foo bar"); TestPressKey(":s//replace/g\\ctrl-dX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/X/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":s::replace:g\\ctrl-dX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:X:replace:g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // With an empty replace expression, ctrl-f should still position the cursor correctly. BeginTest("foo bar"); TestPressKey(":s/sear\\\\/ch//g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/sear\\/ch/X/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":s:sear\\\\:ch::g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:sear\\:ch:X:g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // With both empty search *and* replace expressions, ctrl-f should still position the cursor correctly. BeginTest("foo bar"); TestPressKey(":s///g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s//X/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":s:::g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s::X:g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Should be able to undo ctrl-f or ctrl-d. BeginTest("foo bar"); TestPressKey(":s/find/replace/g\\ctrl-d"); emulatedCommandBarTextEdit()->undo(); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/find/replace/g")); TestPressKey("\\ctrl-f"); emulatedCommandBarTextEdit()->undo(); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/find/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // ctrl-f / ctrl-d should cleanly finish sed find/ replace history completion. clearReplaceHistory(); clearSearchHistory(); vi_global->searchHistory()->append("searchxyz"); vi_global->replaceHistory()->append("replacexyz"); TestPressKey(":s///g\\ctrl-d\\ctrl-p"); QVERIFY(emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/searchxyz//g")); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-p"); QVERIFY(emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-d"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s//replacexyz/g")); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Don't hang if we execute a sed replace with empty search term. DoTest("foo bar", ":s//replace/g\\enter", "foo bar"); // ctrl-f & ctrl-d should work even when there is a range expression at the beginning of the sed replace. BeginTest("foo bar"); TestPressKey(":'<,'>s/search/replace/g\\ctrl-d"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("'<,'>s//replace/g")); TestPressKey("\\ctrl-c:.,.+6s/search/replace/g\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+6s/search//g")); TestPressKey("\\ctrl-c:%s/search/replace/g\\ctrl-f"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("%s/search//g")); // Place the cursor in the right place even when there is a range expression. TestPressKey("\\ctrl-c:.,.+6s/search/replace/g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+6s/search/X/g")); TestPressKey("\\ctrl-c:%s/search/replace/g\\ctrl-fX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("%s/search/X/g")); TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest("foo bar"); // Don't crash on ctrl-f/d if we have an empty command. DoTest("", ":\\ctrl-f\\ctrl-d\\ctrl-c", ""); // Parser regression test: Don't crash on ctrl-f/d with ".,.+". DoTest("", ":.,.+\\ctrl-f\\ctrl-d\\ctrl-c", ""); // Command-completion should be invoked on the command being typed even when preceded by a range expression. BeginTest(""); TestPressKey(":0,'>so"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Command-completion should ignore the range expression. BeginTest(""); TestPressKey(":.,.+6so"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // A sed-replace should immediately add the search term to the search history. clearSearchHistory(); BeginTest(""); TestPressKey(":s/search/replace/g\\enter"); QCOMPARE(searchHistory(), QStringList() << "search"); FinishTest(""); // An aborted sed-replace should not add the search term to the search history. clearSearchHistory(); BeginTest(""); TestPressKey(":s/search/replace/g\\ctrl-c"); QCOMPARE(searchHistory(), QStringList()); FinishTest(""); // A non-sed-replace should leave the search history unchanged. clearSearchHistory(); BeginTest(""); TestPressKey(":s,search/replace/g\\enter"); QCOMPARE(searchHistory(), QStringList()); FinishTest(""); // A sed-replace should immediately add the replace term to the replace history. clearReplaceHistory(); BeginTest(""); TestPressKey(":s/search/replace/g\\enter"); QCOMPARE(replaceHistory(), QStringList() << "replace"); clearReplaceHistory(); TestPressKey(":'<,'>s/search/replace1/g\\enter"); QCOMPARE(replaceHistory(), QStringList() << "replace1"); FinishTest(""); // An aborted sed-replace should not add the replace term to the replace history. clearReplaceHistory(); BeginTest(""); TestPressKey(":s/search/replace/g\\ctrl-c"); QCOMPARE(replaceHistory(), QStringList()); FinishTest(""); // A non-sed-replace should leave the replace history unchanged. clearReplaceHistory(); BeginTest(""); TestPressKey(":s,search/replace/g\\enter"); QCOMPARE(replaceHistory(), QStringList()); FinishTest(""); // Misc tests for sed replace. These are for the *generic* Kate sed replace; they should all // use EmulatedCommandBarTests' built-in command execution stuff (\\:\\\) rather than // invoking a EmulatedCommandBar and potentially doing some Vim-specific transforms to // the command. DoTest("foo foo foo", "\\:s/foo/bar/\\", "bar foo foo"); DoTest("foo foo xyz foo", "\\:s/foo/bar/g\\", "bar bar xyz bar"); DoTest("foofooxyzfoo", "\\:s/foo/bar/g\\", "barbarxyzbar"); DoTest("foofooxyzfoo", "\\:s/foo/b/g\\", "bbxyzb"); DoTest("ffxyzf", "\\:s/f/b/g\\", "bbxyzb"); DoTest("ffxyzf", "\\:s/f/bar/g\\", "barbarxyzbar"); DoTest("foo Foo fOO FOO foo", "\\:s/foo/bar/\\", "bar Foo fOO FOO foo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/bar/\\", "Foo bar fOO FOO foo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/bar/g\\", "Foo bar fOO FOO bar"); DoTest("foo Foo fOO FOO foo", "\\:s/foo/bar/i\\", "bar Foo fOO FOO foo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/bar/i\\", "bar foo fOO FOO foo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/bar/gi\\", "bar bar bar bar bar"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/bar/ig\\", "bar bar bar bar bar"); // There are some oddities to do with how EmulatedCommandBarTest's "execute command directly" stuff works with selected ranges: // basically, we need to do our selection in Visual mode, then exit back to Normal mode before running the //command. DoTest("foo foo\nbar foo foo\nxyz foo foo\nfoo bar foo", "jVj\\esc\\:'<,'>s/foo/bar/\\", "foo foo\nbar bar foo\nxyz bar foo\nfoo bar foo"); DoTest("foo foo\nbar foo foo\nxyz foo foo\nfoo bar foo", "jVj\\esc\\:'<,'>s/foo/bar/g\\", "foo foo\nbar bar bar\nxyz bar bar\nfoo bar foo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/barfoo/g\\", "Foo barfoo fOO FOO barfoo"); DoTest("Foo foo fOO FOO foo", "\\:s/foo/foobar/g\\", "Foo foobar fOO FOO foobar"); DoTest("axyzb", "\\:s/a(.*)b/d\\\\1f/\\", "dxyzf"); DoTest("ayxzzyxzfddeefdb", "\\:s/a([xyz]+)([def]+)b/<\\\\1|\\\\2>/\\", ""); DoTest("foo", "\\:s/.*//g\\", ""); DoTest("foo", "\\:s/.*/f/g\\", "f"); DoTest("foo/bar", "\\:s/foo\\\\/bar/123\\\\/xyz/g\\", "123/xyz"); DoTest("foo:bar", "\\:s:foo\\\\:bar:123\\\\:xyz:g\\", "123:xyz"); const bool oldReplaceTabsDyn = kate_document->config()->replaceTabsDyn(); kate_document->config()->setReplaceTabsDyn(false); DoTest("foo\tbar", "\\:s/foo\\\\tbar/replace/g\\", "replace"); DoTest("foo\tbar", "\\:s/foo\\\\tbar/rep\\\\tlace/g\\", "rep\tlace"); kate_document->config()->setReplaceTabsDyn(oldReplaceTabsDyn); DoTest("foo", "\\:s/foo/replaceline1\\\\nreplaceline2/g\\", "replaceline1\nreplaceline2"); DoTest("foofoo", "\\:s/foo/replaceline1\\\\nreplaceline2/g\\", "replaceline1\nreplaceline2replaceline1\nreplaceline2"); DoTest("foofoo\nfoo", "\\:s/foo/replaceline1\\\\nreplaceline2/g\\", "replaceline1\nreplaceline2replaceline1\nreplaceline2\nfoo"); DoTest("fooafoob\nfooc\nfood", "Vj\\esc\\:'<,'>s/foo/replaceline1\\\\nreplaceline2/g\\", "replaceline1\nreplaceline2areplaceline1\nreplaceline2b\nreplaceline1\nreplaceline2c\nfood"); DoTest("fooafoob\nfooc\nfood", "Vj\\esc\\:'<,'>s/foo/replaceline1\\\\nreplaceline2/\\", "replaceline1\nreplaceline2afoob\nreplaceline1\nreplaceline2c\nfood"); DoTest("fooafoob\nfooc\nfood", "Vj\\esc\\:'<,'>s/foo/replaceline1\\\\nreplaceline2\\\\nreplaceline3/g\\", "replaceline1\nreplaceline2\nreplaceline3areplaceline1\nreplaceline2\nreplaceline3b\nreplaceline1\nreplaceline2\nreplaceline3c\nfood"); DoTest("foofoo", "\\:s/foo/replace\\\\nfoo/g\\", "replace\nfooreplace\nfoo"); DoTest("foofoo", "\\:s/foo/replacefoo\\\\nfoo/g\\", "replacefoo\nfooreplacefoo\nfoo"); DoTest("foofoo", "\\:s/foo/replacefoo\\\\n/g\\", "replacefoo\nreplacefoo\n"); DoTest("ff", "\\:s/f/f\\\\nf/g\\", "f\nff\nf"); DoTest("ff", "\\:s/f/f\\\\n/g\\", "f\nf\n"); DoTest("foo\nbar", "\\:s/foo\\\\n//g\\", "bar"); DoTest("foo\n\n\nbar", "\\:s/foo(\\\\n)*bar//g\\", ""); DoTest("foo\n\n\nbar", "\\:s/foo(\\\\n*)bar/123\\\\1456/g\\", "123\n\n\n456"); DoTest("xAbCy", "\\:s/x(.)(.)(.)y/\\\\L\\\\1\\\\U\\\\2\\\\3/g\\", "aBC"); DoTest("foo", "\\:s/foo/\\\\a/g\\", "\x07"); // End "generic" (i.e. not involving any Vi mode tricks/ transformations) sed replace tests: the remaining // ones should go via the EmulatedCommandBar. BeginTest("foo foo\nxyz\nfoo"); TestPressKey(":%s/foo/bar/g\\enter"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(3, 2); FinishTest("bar bar\nxyz\nbar"); // ctrl-p on the first character of the search term in a sed-replace should // invoke search history completion. clearSearchHistory(); vi_global->searchHistory()->append("search"); BeginTest(""); TestPressKey(":s/search/replace/g\\ctrl-b\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/search/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on the last character of the search term in a sed-replace should // invoke search history completion. clearSearchHistory(); vi_global->searchHistory()->append("xyz"); BeginTest(""); TestPressKey(":s/xyz/replace/g\\ctrl-b\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. QVERIFY(!emulatedCommandBar->isVisible()); TestPressKey(":'<,'>s/xyz/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on some arbitrary character of the search term in a sed-replace should // invoke search history completion. clearSearchHistory(); vi_global->searchHistory()->append("xyzaaaaaa"); BeginTest(""); TestPressKey(":s/xyzaaaaaa/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/xyzaaaaaa/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on some character *after" the search term should // *not* invoke search history completion. // Note: in s/xyz/replace/g, the "/" after the "z" is counted as part of the find term; // this allows us to do xyz and get completions. clearSearchHistory(); clearCommandHistory(); clearReplaceHistory(); vi_global->searchHistory()->append("xyz"); BeginTest(""); TestPressKey(":s/xyz/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. clearSearchHistory(); clearCommandHistory(); TestPressKey(":'<,'>s/xyz/replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. // Make sure it's the search history we're invoking. clearSearchHistory(); vi_global->searchHistory()->append("xyzaaaaaa"); BeginTest(""); TestPressKey(":s//replace/g\\ctrl-b\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "xyzaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":.,.+6s//replace/g\\ctrl-b\\right\\right\\right\\right\\right\\right\\right\\ctrl-p"); verifyCommandBarCompletionsMatches(QStringList() << "xyzaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // (Search history should be reversed). clearSearchHistory(); vi_global->searchHistory()->append("xyzaaaaaa"); vi_global->searchHistory()->append("abc"); vi_global->searchHistory()->append("def"); BeginTest(""); TestPressKey(":s//replace/g\\ctrl-b\\right\\right\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "def" << "abc" << "xyzaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Completion prefix is the current find term. clearSearchHistory(); vi_global->searchHistory()->append("xy:zaaaaaa"); vi_global->searchHistory()->append("abc"); vi_global->searchHistory()->append("def"); vi_global->searchHistory()->append("xy:zbaaaaa"); vi_global->searchHistory()->append("xy:zcaaaaa"); BeginTest(""); TestPressKey(":s//replace/g\\ctrl-dxy:z\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "xy:zcaaaaa" << "xy:zbaaaaa" << "xy:zaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Replace entire search term with completion. clearSearchHistory(); vi_global->searchHistory()->append("ab,cd"); vi_global->searchHistory()->append("ab,xy"); BeginTest(""); TestPressKey(":s//replace/g\\ctrl-dab,\\ctrl-p\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/ab,cd/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s//replace/g\\ctrl-dab,\\ctrl-p\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("'<,'>s/ab,cd/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Place the cursor at the end of find term. clearSearchHistory(); vi_global->searchHistory()->append("ab,xy"); BeginTest(""); TestPressKey(":s//replace/g\\ctrl-dab,\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/ab,xyX/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":.,.+7s//replace/g\\ctrl-dab,\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+7s/ab,xyX/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Leave find term unchanged if there is no search history. clearSearchHistory(); BeginTest(""); TestPressKey(":s/nose/replace/g\\ctrl-b\\right\\right\\right\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/nose/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Leave cursor position unchanged if there is no search history. clearSearchHistory(); BeginTest(""); TestPressKey(":s/nose/replace/g\\ctrl-b\\right\\right\\right\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/nXose/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on the first character of the replace term in a sed-replace should // invoke replace history completion. clearSearchHistory(); clearReplaceHistory(); clearCommandHistory(); vi_global->replaceHistory()->append("replace"); BeginTest(""); TestPressKey(":s/search/replace/g\\left\\left\\left\\left\\left\\left\\left\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/search/replace/g\\left\\left\\left\\left\\left\\left\\left\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on the last character of the replace term in a sed-replace should // invoke replace history completion. clearReplaceHistory(); vi_global->replaceHistory()->append("replace"); BeginTest(""); TestPressKey(":s/xyz/replace/g\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/xyz/replace/g\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on some arbitrary character of the search term in a sed-replace should // invoke search history completion. clearReplaceHistory(); vi_global->replaceHistory()->append("replaceaaaaaa"); BeginTest(""); TestPressKey(":s/xyzaaaaaa/replace/g\\left\\left\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/xyzaaaaaa/replace/g\\left\\left\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // ctrl-p on some character *after" the replace term should // *not* invoke replace history completion. // Note: in s/xyz/replace/g, the "/" after the "e" is counted as part of the replace term; // this allows us to do replace and get completions. clearSearchHistory(); clearCommandHistory(); clearReplaceHistory(); vi_global->replaceHistory()->append("xyz"); BeginTest(""); TestPressKey(":s/xyz/replace/g\\left\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. clearSearchHistory(); clearCommandHistory(); TestPressKey(":'<,'>s/xyz/replace/g\\left\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. // (Replace history should be reversed). clearReplaceHistory(); vi_global->replaceHistory()->append("xyzaaaaaa"); vi_global->replaceHistory()->append("abc"); vi_global->replaceHistory()->append("def"); BeginTest(""); TestPressKey(":s/search//g\\left\\left\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "def" << "abc" << "xyzaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Completion prefix is the current replace term. clearReplaceHistory(); vi_global->replaceHistory()->append("xy:zaaaaaa"); vi_global->replaceHistory()->append("abc"); vi_global->replaceHistory()->append("def"); vi_global->replaceHistory()->append("xy:zbaaaaa"); vi_global->replaceHistory()->append("xy:zcaaaaa"); BeginTest(""); TestPressKey(":'<,'>s/replace/search/g\\ctrl-fxy:z\\ctrl-p"); verifyCommandBarCompletionVisible(); verifyCommandBarCompletionsMatches(QStringList() << "xy:zcaaaaa" << "xy:zbaaaaa" << "xy:zaaaaaa"); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Replace entire search term with completion. clearReplaceHistory(); clearSearchHistory(); vi_global->replaceHistory()->append("ab,cd"); vi_global->replaceHistory()->append("ab,xy"); BeginTest(""); TestPressKey(":s/search//g\\ctrl-fab,\\ctrl-p\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/ab,cd/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":'<,'>s/search//g\\ctrl-fab,\\ctrl-p\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("'<,'>s/search/ab,cd/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Place the cursor at the end of replace term. clearReplaceHistory(); vi_global->replaceHistory()->append("ab,xy"); BeginTest(""); TestPressKey(":s/search//g\\ctrl-fab,\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/ab,xyX/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. TestPressKey(":.,.+7s/search//g\\ctrl-fab,\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString(".,.+7s/search/ab,xyX/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Leave replace term unchanged if there is no replace history. clearReplaceHistory(); BeginTest(""); TestPressKey(":s/nose/replace/g\\left\\left\\left\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/nose/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Leave cursor position unchanged if there is no replace history. clearSearchHistory(); BeginTest(""); TestPressKey(":s/nose/replace/g\\left\\left\\left\\left\\ctrl-pX"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/nose/replaXce/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Invoke replacement history even when the "find" term is empty. BeginTest(""); clearReplaceHistory(); clearSearchHistory(); vi_global->replaceHistory()->append("ab,xy"); vi_global->searchHistory()->append("whoops"); TestPressKey(":s///g\\ctrl-f\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s//ab,xy/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Move the cursor back to the last manual edit point when aborting completion. BeginTest(""); clearSearchHistory(); vi_global->searchHistory()->append("xyzaaaaa"); TestPressKey(":s/xyz/replace/g\\ctrl-b\\right\\right\\right\\right\\righta\\ctrl-p\\ctrl-[X"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/xyzaX/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Don't blank the "find" term if there is no search history that begins with the // current "find" term. BeginTest(""); clearSearchHistory(); vi_global->searchHistory()->append("doesnothavexyzasaprefix"); TestPressKey(":s//replace/g\\ctrl-dxyz\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/xyz/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Escape the delimiter if it occurs in a search history term - searching for it likely won't // work, but at least it won't crash! BeginTest(""); clearSearchHistory(); vi_global->searchHistory()->append("search"); vi_global->searchHistory()->append("aa/aa\\/a"); vi_global->searchHistory()->append("ss/ss"); TestPressKey(":s//replace/g\\ctrl-d\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/ss\\/ss/replace/g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/aa\\/aa\\/a/replace/g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. clearSearchHistory(); // Now do the same, but with a different delimiter. vi_global->searchHistory()->append("search"); vi_global->searchHistory()->append("aa:aa\\:a"); vi_global->searchHistory()->append("ss:ss"); TestPressKey(":s::replace:g\\ctrl-d\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:ss\\:ss:replace:g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:aa\\:aa\\:a:replace:g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:search:replace:g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Remove \C if occurs in search history. BeginTest(""); clearSearchHistory(); vi_global->searchHistory()->append("s\\Cear\\\\Cch"); TestPressKey(":s::replace:g\\ctrl-d\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:sear\\\\Cch:replace:g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Don't blank the "replace" term if there is no search history that begins with the // current "replace" term. BeginTest(""); clearReplaceHistory(); vi_global->replaceHistory()->append("doesnothavexyzasaprefix"); TestPressKey(":s/search//g\\ctrl-fxyz\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/xyz/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Escape the delimiter if it occurs in a replace history term - searching for it likely won't // work, but at least it won't crash! BeginTest(""); clearReplaceHistory(); vi_global->replaceHistory()->append("replace"); vi_global->replaceHistory()->append("aa/aa\\/a"); vi_global->replaceHistory()->append("ss/ss"); TestPressKey(":s/search//g\\ctrl-f\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/ss\\/ss/g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/aa\\/aa\\/a/g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/search/replace/g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. clearReplaceHistory(); // Now do the same, but with a different delimiter. vi_global->replaceHistory()->append("replace"); vi_global->replaceHistory()->append("aa:aa\\:a"); vi_global->replaceHistory()->append("ss:ss"); TestPressKey(":s:search::g\\ctrl-f\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:search:ss\\:ss:g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:search:aa\\:aa\\:a:g")); TestPressKey("\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s:search:replace:g")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // In search mode, don't blank current text on completion if there is no item in the search history which // has the current text as a prefix. BeginTest(""); clearSearchHistory(); vi_global->searchHistory()->append("doesnothavexyzasaprefix"); TestPressKey("/xyz\\ctrl-p"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("xyz")); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Don't dismiss the command completion just because the cursor ends up *temporarily* at a place where // command completion is disallowed when cycling through completions. BeginTest(""); TestPressKey(":set/se\\left\\left\\left-\\ctrl-p"); verifyCommandBarCompletionVisible(); TestPressKey("\\ctrl-c"); // Dismiss completer TestPressKey("\\ctrl-c"); // Dismiss bar. FinishTest(""); // Don't expand mappings meant for Normal mode in the emulated command bar. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "foo", "xyz", Mappings::NonRecursive); DoTest("bar foo xyz", "/foo\\enterrX", "bar Xoo xyz"); clearAllMappings(); // Incremental search and replace. QLabel* interactiveSedReplaceLabel = emulatedCommandBar->findChild("interactivesedreplace"); QVERIFY(interactiveSedReplaceLabel); BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); QVERIFY(!commandResponseMessageDisplay()->isVisible()); QVERIFY(!emulatedCommandBarTextEdit()->isVisible()); QVERIFY(!emulatedCommandTypeIndicator()->isVisible()); TestPressKey("\\ctrl-c"); // Dismiss search and replace. QVERIFY(!emulatedCommandBar->isVisible()); FinishTest("foo"); // Clear the flag that stops the command response from being shown after an incremental search and // replace, and also make sure that the edit and bar type indicator are not forcibly hidden. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter\\ctrl-c"); TestPressKey(":s/foo/bar/"); QVERIFY(emulatedCommandBarTextEdit()->isVisible()); QVERIFY(emulatedCommandTypeIndicator()->isVisible()); TestPressKey("\\enter"); QVERIFY(commandResponseMessageDisplay()->isVisible()); FinishTest("bar"); // Hide the incremental search and replace label when we show the bar. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter\\ctrl-c"); TestPressKey(":"); QVERIFY(!interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); // The "c" marker can be anywhere in the three chars following the delimiter. BeginTest("foo"); TestPressKey(":s/foo/bar/cgi\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); BeginTest("foo"); TestPressKey(":s/foo/bar/igc\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); BeginTest("foo"); TestPressKey(":s/foo/bar/icg\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); BeginTest("foo"); TestPressKey(":s/foo/bar/ic\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); BeginTest("foo"); TestPressKey(":s/foo/bar/ci\\enter"); QVERIFY(interactiveSedReplaceLabel->isVisible()); TestPressKey("\\ctrl-c"); FinishTest("foo"); // Emulated command bar is still active during an incremental search and replace. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("idef\\esc"); FinishTest("foo"); // Emulated command bar text is not edited during an incremental search and replace. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("def"); QCOMPARE(emulatedCommandBarTextEdit()->text(), QString("s/foo/bar/c")); TestPressKey("\\ctrl-c"); FinishTest("foo"); // Pressing "n" when there is only a single change we can make aborts incremental search // and replace. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("n"); QVERIFY(!interactiveSedReplaceLabel->isVisible()); TestPressKey("ixyz\\esc"); FinishTest("xyzfoo"); // Pressing "n" when there is only a single change we can make aborts incremental search // and replace, and shows the no replacements on no lines. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("n"); QVERIFY(commandResponseMessageDisplay()->isVisible()); verifyShowsNumberOfReplacementsAcrossNumberOfLines(0, 0); FinishTest("foo"); // First possible match is highlighted when we start an incremental search and replace, and // cleared if we press 'n'. BeginTest(" xyz 123 foo bar"); TestPressKey(":s/foo/bar/gc\\enter"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 10); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 13); TestPressKey("n"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); FinishTest(" xyz 123 foo bar"); // Second possible match highlighted if we start incremental search and replace and press 'n', // cleared if we press 'n' again. BeginTest(" xyz 123 foo foo bar"); TestPressKey(":s/foo/bar/gc\\enter"); TestPressKey("n"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 14); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 17); TestPressKey("n"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); FinishTest(" xyz 123 foo foo bar"); // Perform replacement if we press 'y' on the first match. BeginTest(" xyz foo 123 foo bar"); TestPressKey(":s/foo/bar/gc\\enter"); TestPressKey("y"); TestPressKey("\\ctrl-c"); FinishTest(" xyz bar 123 foo bar"); // Replacement uses grouping, etc. BeginTest(" xyz def 123 foo bar"); TestPressKey(":s/d\\\\(e\\\\)\\\\(f\\\\)/x\\\\1\\\\U\\\\2/gc\\enter"); TestPressKey("y"); TestPressKey("\\ctrl-c"); FinishTest(" xyz xeF 123 foo bar"); // On replacement, highlight next match. BeginTest(" xyz foo 123 foo bar"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("y"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 14); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 17); TestPressKey("\\ctrl-c"); FinishTest(" xyz bar 123 foo bar"); // On replacement, if there is no further match, abort incremental search and replace. BeginTest(" xyz foo 123 foa bar"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("y"); QVERIFY(commandResponseMessageDisplay()->isVisible()); TestPressKey("ggidone\\esc"); FinishTest("done xyz bar 123 foa bar"); // After replacement, the next match is sought after the end of the replacement text. BeginTest("foofoo"); TestPressKey(":s/foo/barfoo/cg\\enter"); TestPressKey("y"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); QCOMPARE(rangesOnFirstLine().first()->start().line(), 0); QCOMPARE(rangesOnFirstLine().first()->start().column(), 6); QCOMPARE(rangesOnFirstLine().first()->end().line(), 0); QCOMPARE(rangesOnFirstLine().first()->end().column(), 9); TestPressKey("\\ctrl-c"); FinishTest("barfoofoo"); BeginTest("xffy"); TestPressKey(":s/f/bf/cg\\enter"); TestPressKey("yy"); FinishTest("xbfbfy"); // Make sure the incremental search bar label contains the "instruction" keypresses. const QString interactiveSedReplaceShortcuts = "(y/n/a/q/l)"; BeginTest("foofoo"); TestPressKey(":s/foo/barfoo/cg\\enter"); QVERIFY(interactiveSedReplaceLabel->text().contains(interactiveSedReplaceShortcuts)); TestPressKey("\\ctrl-c"); FinishTest("foofoo"); // Make sure the incremental search bar label contains a reference to the text we're going to // replace with. // We're going to be a bit vague about the precise text due to localisation issues. BeginTest("fabababbbar"); TestPressKey(":s/f\\\\([ab]\\\\+\\\\)/1\\\\U\\\\12/c\\enter"); QVERIFY(interactiveSedReplaceLabel->text().contains("1ABABABBBA2")); TestPressKey("\\ctrl-c"); FinishTest("fabababbbar"); // Replace newlines in the "replace?" message with "\\n" BeginTest("foo"); TestPressKey(":s/foo/bar\\\\nxyz\\\\n123/c\\enter"); QVERIFY(interactiveSedReplaceLabel->text().contains("bar\\nxyz\\n123")); TestPressKey("\\ctrl-c"); FinishTest("foo"); // Update the "confirm replace?" message on pressing "y". BeginTest("fabababbbar fabbb"); TestPressKey(":s/f\\\\([ab]\\\\+\\\\)/1\\\\U\\\\12/gc\\enter"); TestPressKey("y"); QVERIFY(interactiveSedReplaceLabel->text().contains("1ABBB2")); QVERIFY(interactiveSedReplaceLabel->text().contains(interactiveSedReplaceShortcuts)); TestPressKey("\\ctrl-c"); FinishTest("1ABABABBBA2r fabbb"); // Update the "confirm replace?" message on pressing "n". BeginTest("fabababbbar fabab"); TestPressKey(":s/f\\\\([ab]\\\\+\\\\)/1\\\\U\\\\12/gc\\enter"); TestPressKey("n"); QVERIFY(interactiveSedReplaceLabel->text().contains("1ABAB2")); QVERIFY(interactiveSedReplaceLabel->text().contains(interactiveSedReplaceShortcuts)); TestPressKey("\\ctrl-c"); FinishTest("fabababbbar fabab"); // Cursor is placed at the beginning of first match. BeginTest(" foo foo foo"); TestPressKey(":s/foo/bar/c\\enter"); verifyCursorAt(Cursor(0, 2)); TestPressKey("\\ctrl-c"); FinishTest(" foo foo foo"); // "y" and "n" update the cursor pos. BeginTest(" foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("y"); verifyCursorAt(Cursor(0, 8)); TestPressKey("n"); verifyCursorAt(Cursor(0, 12)); TestPressKey("\\ctrl-c"); FinishTest(" bar foo foo"); // If we end due to a "y" or "n" on the final match, leave the cursor at the beginning of the final match. BeginTest(" foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("y"); verifyCursorAt(Cursor(0, 2)); FinishTest(" bar"); BeginTest(" foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("n"); verifyCursorAt(Cursor(0, 2)); FinishTest(" foo"); // Respect ranges. BeginTest("foo foo\nfoo foo\nfoo foo\nfoo foo\n"); TestPressKey("jVj:s/foo/bar/gc\\enter"); TestPressKey("ynny"); QVERIFY(commandResponseMessageDisplay()->isVisible()); TestPressKey("ggidone \\ctrl-c"); FinishTest("done foo foo\nbar foo\nfoo bar\nfoo foo\n"); BeginTest("foo foo\nfoo foo\nfoo foo\nfoo foo\n"); TestPressKey("jVj:s/foo/bar/gc\\enter"); TestPressKey("nyyn"); QVERIFY(commandResponseMessageDisplay()->isVisible()); TestPressKey("ggidone \\ctrl-c"); FinishTest("done foo foo\nfoo bar\nbar foo\nfoo foo\n"); BeginTest("foo foo\nfoo foo\nfoo foo\nfoo foo\n"); TestPressKey("j:s/foo/bar/gc\\enter"); TestPressKey("ny"); QVERIFY(commandResponseMessageDisplay()->isVisible()); TestPressKey("ggidone \\ctrl-c"); FinishTest("done foo foo\nfoo bar\nfoo foo\nfoo foo\n"); BeginTest("foo foo\nfoo foo\nfoo foo\nfoo foo\n"); TestPressKey("j:s/foo/bar/gc\\enter"); TestPressKey("yn"); QVERIFY(commandResponseMessageDisplay()->isVisible()); TestPressKey("ggidone \\ctrl-c"); FinishTest("done foo foo\nbar foo\nfoo foo\nfoo foo\n"); // If no initial match can be found, abort and show a "no replacements" message. // The cursor position should remain unnchanged. BeginTest("fab"); TestPressKey("l:s/fee/bar/c\\enter"); QVERIFY(commandResponseMessageDisplay()->isVisible()); verifyShowsNumberOfReplacementsAcrossNumberOfLines(0, 0); QVERIFY(!interactiveSedReplaceLabel->isVisible()); TestPressKey("rX"); BeginTest("fXb"); // Case-sensitive by default. BeginTest("foo Foo FOo foo foO"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar Foo FOo bar foO"); // Case-insensitive if "i" flag is used. BeginTest("foo Foo FOo foo foO"); TestPressKey(":s/foo/bar/icg\\enter"); TestPressKey("yyyyyggidone\\esc"); FinishTest("donebar bar bar bar bar"); // Only one replacement per-line unless "g" flag is used. BeginTest("boo foo 123 foo\nxyz foo foo\nfoo foo foo\nxyz\nfoo foo\nfoo 123 foo"); TestPressKey("jVjjj:s/foo/bar/c\\enter"); TestPressKey("yynggidone\\esc"); FinishTest("doneboo foo 123 foo\nxyz bar foo\nbar foo foo\nxyz\nfoo foo\nfoo 123 foo"); BeginTest("boo foo 123 foo\nxyz foo foo\nfoo foo foo\nxyz\nfoo foo\nfoo 123 foo"); TestPressKey("jVjjj:s/foo/bar/c\\enter"); TestPressKey("nnyggidone\\esc"); FinishTest("doneboo foo 123 foo\nxyz foo foo\nfoo foo foo\nxyz\nbar foo\nfoo 123 foo"); // If replacement contains new lines, adjust the end line down. BeginTest("foo\nfoo1\nfoo2\nfoo3"); TestPressKey("jVj:s/foo/bar\\\\n/gc\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donefoo\nbar\n1\nbar\n2\nfoo3"); BeginTest("foo\nfoo1\nfoo2\nfoo3"); TestPressKey("jVj:s/foo/bar\\\\nboo\\\\n/gc\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donefoo\nbar\nboo\n1\nbar\nboo\n2\nfoo3"); // With "g" and a replacement that involves multiple lines, resume search from the end of the last line added. BeginTest("foofoo"); TestPressKey(":s/foo/bar\\\\n/gc\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar\nbar\n"); BeginTest("foofoo"); TestPressKey(":s/foo/bar\\\\nxyz\\\\nfoo/gc\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar\nxyz\nfoobar\nxyz\nfoo"); // Without "g" and with a replacement that involves multiple lines, resume search from the line after the line just added. BeginTest("foofoo1\nfoo2\nfoo3"); TestPressKey("Vj:s/foo/bar\\\\nxyz\\\\nfoo/c\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar\nxyz\nfoofoo1\nbar\nxyz\nfoo2\nfoo3"); // Regression test: handle 'g' when it occurs before 'i' and 'c'. BeginTest("foo fOo"); TestPressKey(":s/foo/bar/gci\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar bar"); // When the search terms swallows several lines, move the endline up accordingly. BeginTest("foo\nfoo1\nfoo\nfoo2\nfoo\nfoo3"); TestPressKey("V3j:s/foo\\\\nfoo/bar/cg\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar1\nbar2\nfoo\nfoo3"); BeginTest("foo\nfoo\nfoo1\nfoo\nfoo\nfoo2\nfoo\nfoo\nfoo3"); TestPressKey("V5j:s/foo\\\\nfoo\\\\nfoo/bar/cg\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar1\nbar2\nfoo\nfoo\nfoo3"); // Make sure we still adjust endline down if the replacement text has '\n's. BeginTest("foo\nfoo\nfoo1\nfoo\nfoo\nfoo2\nfoo\nfoo\nfoo3"); TestPressKey("V5j:s/foo\\\\nfoo\\\\nfoo/bar\\\\n/cg\\enter"); TestPressKey("yyggidone\\esc"); FinishTest("donebar\n1\nbar\n2\nfoo\nfoo\nfoo3"); // Status reports. BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); TestPressKey("y"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(1, 1); FinishTest("bar"); BeginTest("foo foo foo"); TestPressKey(":s/foo/bar/gc\\enter"); TestPressKey("yyy"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(3, 1); FinishTest("bar bar bar"); BeginTest("foo foo foo"); TestPressKey(":s/foo/bar/gc\\enter"); TestPressKey("yny"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(2, 1); FinishTest("bar foo bar"); BeginTest("foo\nfoo"); TestPressKey(":%s/foo/bar/gc\\enter"); TestPressKey("yy"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(2, 2); FinishTest("bar\nbar"); BeginTest("foo foo\nfoo foo\nfoo foo"); TestPressKey(":%s/foo/bar/gc\\enter"); TestPressKey("yynnyy"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(4, 2); FinishTest("bar bar\nfoo foo\nbar bar"); BeginTest("foofoo"); TestPressKey(":s/foo/bar\\\\nxyz/gc\\enter"); TestPressKey("yy"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(2, 1); FinishTest("bar\nxyzbar\nxyz"); BeginTest("foofoofoo"); TestPressKey(":s/foo/bar\\\\nxyz\\\\nboo/gc\\enter"); TestPressKey("yyy"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(3, 1); FinishTest("bar\nxyz\nboobar\nxyz\nboobar\nxyz\nboo"); // Tricky one: how many lines are "touched" if a single replacement // swallows multiple lines? I'm going to say the number of lines swallowed. BeginTest("foo\nfoo\nfoo"); TestPressKey(":s/foo\\\\nfoo\\\\nfoo/bar/c\\enter"); TestPressKey("y"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(1, 3); FinishTest("bar"); BeginTest("foo\nfoo\nfoo\n"); TestPressKey(":s/foo\\\\nfoo\\\\nfoo\\\\n/bar/c\\enter"); TestPressKey("y"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(1, 4); FinishTest("bar"); // "Undo" undoes last replacement. BeginTest("foo foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("nyynu"); FinishTest("foo bar foo foo"); // "l" does the current replacement then exits. BeginTest("foo foo foo foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("nnl"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(1, 1); FinishTest("foo foo bar foo foo foo"); // "q" just exits. BeginTest("foo foo foo foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("yyq"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(2, 1); FinishTest("bar bar foo foo foo foo"); // "a" replaces all remaining, then exits. BeginTest("foo foo foo foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("nna"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(4, 1); FinishTest("foo foo bar bar bar bar"); // The results of "a" can be undone in one go. BeginTest("foo foo foo foo foo foo"); TestPressKey(":s/foo/bar/cg\\enter"); TestPressKey("ya"); verifyShowsNumberOfReplacementsAcrossNumberOfLines(6, 1); TestPressKey("u"); FinishTest("bar foo foo foo foo foo"); #if 0 // XXX - as of Qt 5.5, simply replaying the correct QKeyEvents does *not* cause shortcuts // to be triggered, so these tests cannot pass. // It's possible that a solution involving QTestLib will be workable in the future, though. { // Test the test suite: ensure that shortcuts are still being sent and received correctly. // The test shortcut chosen should be one that does not conflict with built-in Kate ones. FailsIfSlotNotCalled failsIfActionNotTriggered; QAction *dummyAction = kate_view->actionCollection()->addAction("Woo"); dummyAction->setShortcut(QKeySequence("Ctrl+]")); QVERIFY(connect(dummyAction, SIGNAL(triggered()), &failsIfActionNotTriggered, SLOT(slot()))); DoTest("foo", "\\ctrl-]", "foo"); // Processing shortcuts seems to require events to be processed. while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } delete dummyAction; } { // Test that shortcuts involving ctrl+ work correctly. FailsIfSlotNotCalled failsIfActionNotTriggered; QAction *dummyAction = kate_view->actionCollection()->addAction("Woo"); dummyAction->setShortcut(QKeySequence("Ctrl+1")); QVERIFY(connect(dummyAction, SIGNAL(triggered()), &failsIfActionNotTriggered, SLOT(slot()))); DoTest("foo", "\\ctrl-1", "foo"); // Processing shortcuts seems to require events to be processed. while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } delete dummyAction; } { // Test that shortcuts involving alt+ work correctly. FailsIfSlotNotCalled failsIfActionNotTriggered; QAction *dummyAction = kate_view->actionCollection()->addAction("Woo"); dummyAction->setShortcut(QKeySequence("Alt+1")); QVERIFY(connect(dummyAction, SIGNAL(triggered()), &failsIfActionNotTriggered, SLOT(slot()))); DoTest("foo", "\\alt-1", "foo"); // Processing shortcuts seems to require events to be processed. while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } delete dummyAction; } #endif // Find the "Print" action for later use. - QAction *printAction = NULL; + QAction *printAction = nullptr; foreach(QAction* action, kate_view->actionCollection()->actions()) { if (action->shortcut() == QKeySequence("Ctrl+p")) { printAction = action; break; } } // Test that we don't inadvertantly trigger shortcuts in kate_view when typing them in the // emulated command bar. Requires the above test for shortcuts to be sent and received correctly // to pass. { QVERIFY(mainWindow->isActiveWindow()); QVERIFY(printAction); FailsIfSlotCalled failsIfActionTriggered("The kate_view shortcut should not be triggered by typing it in emulated command bar!"); // Don't invoke Print on failure, as this hangs instead of failing. //disconnect(printAction, SIGNAL(triggered(bool)), kate_document, SLOT(print())); connect(printAction, SIGNAL(triggered(bool)), &failsIfActionTriggered, SLOT(slot())); DoTest("foo bar foo bar", "/bar\\enterggd/\\ctrl-p\\enter.", "bar"); // Processing shortcuts seems to require events to be processed. while (QApplication::hasPendingEvents()) { QApplication::processEvents(); } } // Test that the interactive search replace does not handle general keypresses like ctrl-p ("invoke // completion in emulated command bar"). // Unfortunately, "ctrl-p" in kate_view, which is what will be triggered if this // test succeeds, hangs due to showing the print dialog, so we need to temporarily // block the Print action. clearCommandHistory(); if (printAction) { printAction->blockSignals(true); } vi_global->commandHistory()->append("s/foo/bar/caa"); BeginTest("foo"); TestPressKey(":s/foo/bar/c\\ctrl-b\\enter\\ctrl-p"); QVERIFY(!emulatedCommandBarCompleter()->popup()->isVisible()); TestPressKey("\\ctrl-c"); if (printAction) { printAction->blockSignals(false); } FinishTest("foo"); // The interactive sed replace command is added to the history straight away. clearCommandHistory(); BeginTest("foo"); TestPressKey(":s/foo/bar/c\\enter"); QCOMPARE(commandHistory(), QStringList() << "s/foo/bar/c"); TestPressKey("\\ctrl-c"); FinishTest("foo"); clearCommandHistory(); BeginTest("foo"); TestPressKey(":s/notfound/bar/c\\enter"); QCOMPARE(commandHistory(), QStringList() << "s/notfound/bar/c"); TestPressKey("\\ctrl-c"); FinishTest("foo"); // Should be usable in mappings. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "H", ":s/foo/bar/gcnnyyl", Mappings::Recursive); DoTest("foo foo foo foo foo foo", "H", "foo foo bar bar bar foo"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "H", ":s/foo/bar/gcnna", Mappings::Recursive); DoTest("foo foo foo foo foo foo", "H", "foo foo bar bar bar bar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "H", ":s/foo/bar/gcnnyqggidone", Mappings::Recursive); DoTest("foo foo foo foo foo foo", "H", "donefoo foo bar foo foo foo"); // Don't swallow "Ctrl+" meant for the text edit. if (QKeySequence::keyBindings(QKeySequence::Undo).contains(QKeySequence("Ctrl+Z"))) { DoTest("foo bar", "/bar\\ctrl-z\\enterrX", "Xoo bar"); } else { qWarning() << "Skipped test: Ctrl+Z is not Undo on this platform"; } // Don't give invalid cursor position to updateCursor in Visual Mode: it will cause a crash! DoTest("xyz\nfoo\nbar\n123", "/foo\\\\nbar\\\\n\\enterggv//e\\enter\\ctrl-crX", "xyz\nfoo\nbaX\n123"); DoTest("\nfooxyz\nbar;\n" , "/foo.*\\\\n.*;\\enterggv//e\\enter\\ctrl-crX", "\nfooxyz\nbarX\n"); } QCompleter* EmulatedCommandBarTest::emulatedCommandBarCompleter() { return vi_input_mode->viModeEmulatedCommandBar()->findChild("completer"); } void EmulatedCommandBarTest::verifyCommandBarCompletionVisible() { if (!emulatedCommandBarCompleter()->popup()->isVisible()) { qDebug() << "Emulated command bar completer not visible."; QStringListModel *completionModel = qobject_cast(emulatedCommandBarCompleter()->model()); Q_ASSERT(completionModel); QStringList allAvailableCompletions = completionModel->stringList(); qDebug() << " Completion list: " << allAvailableCompletions; qDebug() << " Completion prefix: " << emulatedCommandBarCompleter()->completionPrefix(); bool candidateCompletionFound = false; foreach (const QString& availableCompletion, allAvailableCompletions) { if (availableCompletion.startsWith(emulatedCommandBarCompleter()->completionPrefix())) { candidateCompletionFound = true; break; } } if (candidateCompletionFound) { qDebug() << " The current completion prefix is a prefix of one of the available completions, so either complete() was not called, or the popup was manually hidden since then"; } else { qDebug() << " The current completion prefix is not a prefix of one of the available completions; this may or may not be why it is not visible"; } } QVERIFY(emulatedCommandBarCompleter()->popup()->isVisible()); } void EmulatedCommandBarTest::verifyCommandBarCompletionsMatches(const QStringList& expectedCompletionList) { verifyCommandBarCompletionVisible(); QStringList actualCompletionList; for (int i = 0; emulatedCommandBarCompleter()->setCurrentRow(i); i++) actualCompletionList << emulatedCommandBarCompleter()->currentCompletion(); if (expectedCompletionList != actualCompletionList) { qDebug() << "Actual completions:\n " << actualCompletionList << "\n\ndo not match expected:\n" << expectedCompletionList; } QCOMPARE(actualCompletionList, expectedCompletionList); } void EmulatedCommandBarTest::verifyCommandBarCompletionContains(const QStringList& expectedCompletionList) { verifyCommandBarCompletionVisible(); QStringList actualCompletionList; for (int i = 0; emulatedCommandBarCompleter()->setCurrentRow(i); i++) { actualCompletionList << emulatedCommandBarCompleter()->currentCompletion(); } foreach(const QString& expectedCompletion, expectedCompletionList) { if (!actualCompletionList.contains(expectedCompletion)) { qDebug() << "Whoops: " << actualCompletionList << " does not contain " << expectedCompletion; } QVERIFY(actualCompletionList.contains(expectedCompletion)); } } QLabel* EmulatedCommandBarTest::emulatedCommandTypeIndicator() { return emulatedCommandBar()->findChild("bartypeindicator"); } void EmulatedCommandBarTest::verifyCursorAt(const Cursor& expectedCursorPos) { QCOMPARE(kate_view->cursorPosition().line(), expectedCursorPos.line()); QCOMPARE(kate_view->cursorPosition().column(), expectedCursorPos.column()); } void EmulatedCommandBarTest::clearSearchHistory() { vi_global->searchHistory()->clear(); } QStringList EmulatedCommandBarTest::searchHistory() { return vi_global->searchHistory()->items(); } void EmulatedCommandBarTest::clearCommandHistory() { vi_global->commandHistory()->clear(); } QStringList EmulatedCommandBarTest::commandHistory() { return vi_global->commandHistory()->items(); } void EmulatedCommandBarTest::clearReplaceHistory() { vi_global->replaceHistory()->clear(); } QStringList EmulatedCommandBarTest::replaceHistory() { return vi_global->replaceHistory()->items(); } QList EmulatedCommandBarTest::rangesOnFirstLine() { return kate_document->buffer().rangesForLine(0, kate_view, true); } void EmulatedCommandBarTest::verifyTextEditBackgroundColour(const QColor& expectedBackgroundColour) { QCOMPARE(emulatedCommandBarTextEdit()->palette().brush(QPalette::Base).color(), expectedBackgroundColour); } QLabel* EmulatedCommandBarTest::commandResponseMessageDisplay() { QLabel* commandResponseMessageDisplay = emulatedCommandBar()->findChild("commandresponsemessage"); Q_ASSERT(commandResponseMessageDisplay); return commandResponseMessageDisplay; } void EmulatedCommandBarTest::waitForEmulatedCommandBarToHide(long int timeout) { const QDateTime waitStartedTime = QDateTime::currentDateTime(); while(emulatedCommandBar()->isVisible() && waitStartedTime.msecsTo(QDateTime::currentDateTime()) < timeout) { QApplication::processEvents(); } QVERIFY(!emulatedCommandBar()->isVisible()); } void EmulatedCommandBarTest::verifyShowsNumberOfReplacementsAcrossNumberOfLines(int numReplacements, int acrossNumLines) { QVERIFY(commandResponseMessageDisplay()->isVisible()); QVERIFY(!emulatedCommandTypeIndicator()->isVisible()); const QString commandMessageResponseText = commandResponseMessageDisplay()->text(); const QString expectedNumReplacementsAsString = QString::number(numReplacements); const QString expectedAcrossNumLinesAsString = QString::number(acrossNumLines); // Be a bit vague about the actual contents due to e.g. localisation. // TODO - see if we can insist that en_US is available on the Kate Jenkins server and // insist that we use it ... ? QRegExp numReplacementsMessageRegex("^.*(\\d+).*(\\d+).*$"); QVERIFY(numReplacementsMessageRegex.exactMatch(commandMessageResponseText)); const QString actualNumReplacementsAsString = numReplacementsMessageRegex.cap(1); const QString actualAcrossNumLinesAsString = numReplacementsMessageRegex.cap(2); QCOMPARE(actualNumReplacementsAsString, expectedNumReplacementsAsString); QCOMPARE(actualAcrossNumLinesAsString, expectedAcrossNumLinesAsString); } FailsIfSlotNotCalled::FailsIfSlotNotCalled(): QObject(), m_slotWasCalled(false) { } FailsIfSlotNotCalled::~FailsIfSlotNotCalled() { QVERIFY(m_slotWasCalled); } void FailsIfSlotNotCalled::slot() { m_slotWasCalled = true; } FailsIfSlotCalled::FailsIfSlotCalled(const QString& failureMessage): QObject(), m_failureMessage(failureMessage) { } void FailsIfSlotCalled::slot() { QFAIL(qPrintable(m_failureMessage.toLatin1())); } diff --git a/autotests/src/vimode/keys.cpp b/autotests/src/vimode/keys.cpp index 8e66b63a..3c232618 100644 --- a/autotests/src/vimode/keys.cpp +++ b/autotests/src/vimode/keys.cpp @@ -1,1581 +1,1581 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * 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 #include #include #include #include #include #include "keys.h" #include "emulatedcommandbarsetupandteardown.h" #include "fakecodecompletiontestmodel.h" #include "vimode/mappings.h" #include "vimode/globalstate.h" using namespace KTextEditor; using KateVi::Mappings; using KateVi::KeyParser; QTEST_MAIN(KeysTest) //BEGIN: KeysTest void KeysTest::MappingTests() { // QVERIFY(false); const int mappingTimeoutMSOverride = QString::fromLocal8Bit(qgetenv("KATE_VIMODE_TEST_MAPPINGTIMEOUTMS")).toInt(); const int mappingTimeoutMS = (mappingTimeoutMSOverride > 0) ? mappingTimeoutMSOverride : 2000; KateViewConfig::global()->setViInputModeStealKeys(true); // For tests involving e.g. { // Check storage and retrieval of mapping recursion. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); QVERIFY(vi_global->mappings()->isRecursive(Mappings::NormalModeMapping, "'")); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "ihello", Mappings::NonRecursive); QVERIFY(!vi_global->mappings()->isRecursive(Mappings::NormalModeMapping, "a")); } clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello^aworld", Mappings::Recursive); DoTest("", "'", "hworldello"); // Ensure that the non-mapping logged keypresses are cleared before we execute a mapping vi_global->mappings()->add(Mappings::NormalModeMapping, "'a", "rO", Mappings::Recursive); DoTest("X", "'a", "O"); { // Check that '123 is mapped after the timeout, given that we also have mappings that // extend it (e.g. '1234, '12345, etc) and which it itself extends ('1, '12, etc). clearAllMappings(); BeginTest(""); vi_input_mode_manager->keyMapper()->setMappingTimeout(mappingTimeoutMS);; QString consectiveDigits; for (int i = 1; i < 9; i++) { consectiveDigits += QString::number(i); vi_global->mappings()->add(Mappings::NormalModeMapping, "'" + consectiveDigits, "iMapped from " + consectiveDigits + "", Mappings::Recursive); } TestPressKey("'123"); QCOMPARE(kate_document->text(), QString("")); // Shouldn't add anything until after the timeout! QTest::qWait(2 * mappingTimeoutMS); FinishTest("Mapped from 123"); } // Mappings are not "counted": any count entered applies to the first command/ motion in the mapped sequence, // and is not used to replay the entire mapped sequence times in a row. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'downmapping", "j", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmapping", "ifooibar", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmotionmapping", "lj", Mappings::Recursive); DoTest("AAAA\nXXXX\nXXXX\nXXXX\nXXXX\nBBBB\nCCCC\nDDDD", "jd3'downmapping", "AAAA\nBBBB\nCCCC\nDDDD"); DoTest("", "5'testmapping", "foofoofoofoofobaro"); DoTest("XXXX\nXXXX\nXXXX\nXXXX", "3'testmotionmappingrO", "XXXX\nXXXO\nXXXX\nXXXX"); // Regression test for a weird mistake I made: *completely* remove all counting for the // first command in the sequence; don't just set it to 1! If it is set to 1, then "%" // will mean "go to position 1 percent of the way through the document" rather than // go to matching item. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gl", "%", Mappings::Recursive); DoTest("0\n1\n2\n3\n4\n5\nfoo bar(xyz) baz", "jjjjjjwdgl", "0\n1\n2\n3\n4\n5\nfoo baz"); // Test that countable mappings work even when triggered by timeouts. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmapping", "ljrO", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmappingdummy", "dummy", Mappings::Recursive); BeginTest("XXXX\nXXXX\nXXXX\nXXXX"); vi_input_mode_manager->keyMapper()->setMappingTimeout(mappingTimeoutMS);; TestPressKey("3'testmapping"); QTest::qWait(2 * mappingTimeoutMS); FinishTest("XXXX\nXXXO\nXXXX\nXXXX"); // Test that telescoping mappings don't interfere with built-in commands. Assumes that gp // is implemented and working. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gdummy", "idummy", Mappings::Recursive); DoTest("hello", "yiwgpx", "hhellollo"); // Test that we can map a sequence of keys that extends a built-in command and use // that sequence without the built-in command firing. // Once again, assumes that gp is implemented and working. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gpa", "idummy", Mappings::Recursive); DoTest("hello", "yiwgpa", "dummyhello"); // Test that we can map a sequence of keys that extends a built-in command and still // have the original built-in command fire if we timeout after entering that command. // Once again, assumes that gp is implemented and working. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gpa", "idummy", Mappings::Recursive); BeginTest("hello"); vi_input_mode_manager->keyMapper()->setMappingTimeout(mappingTimeoutMS);; TestPressKey("yiwgp"); QTest::qWait(2 * mappingTimeoutMS); TestPressKey("x"); FinishTest("hhellollo"); // Test that something that starts off as a partial mapping following a command // (the "g" in the first "dg" is a partial mapping of "gj"), when extended to something // that is definitely not a mapping ("gg"), results in the full command being executed ("dgg"). clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gj", "aj", Mappings::Recursive); DoTest("foo\nbar\nxyz", "jjdgg", ""); // Make sure that a mapped sequence of commands is merged into a single undo-able edit. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'a", "ofooofooofoo", Mappings::Recursive); DoTest("bar", "'au", "bar"); // Make sure that a counted mapping is merged into a single undoable edit. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'a", "ofoo", Mappings::Recursive); DoTest("bar", "5'au", "bar"); // Some test setup for non-recursive mapping g -> gj (cf: bug:314415) // Firstly: work out the expected result of gj (this might be fragile as default settings // change, etc.). We use BeginTest & FinishTest for the setup and teardown etc, but this is // not an actual test - it's just computing the expected result of the real test! const QString multiVirtualLineText = "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo"; ensureKateViewVisible(); // Needs to be visible in order for virtual lines to make sense. KateViewConfig::global()->setDynWordWrap(true); BeginTest(multiVirtualLineText); TestPressKey("gjrX"); const QString expectedAfterVirtualLineDownAndChange = kate_document->text(); Q_ASSERT_X(expectedAfterVirtualLineDownAndChange.contains("X") && !expectedAfterVirtualLineDownAndChange.startsWith('X'), "setting up j->gj testcase data", "gj doesn't seem to have worked correctly!"); FinishTest(expectedAfterVirtualLineDownAndChange); // Test that non-recursive mappings are not expanded. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "j", "gj", Mappings::NonRecursive); DoTest(multiVirtualLineText, "jrX", expectedAfterVirtualLineDownAndChange); KateViewConfig::global()->setDynWordWrap(false); // Test that recursive mappings are expanded. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "X", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "X", "rx", Mappings::Recursive); DoTest("foo", "la", "fxo"); // Test that the flag that stops mappings being expanded is reset after the mapping has been executed. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "j", "gj", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "X", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "X", "rx", Mappings::Recursive); DoTest("foo", "jla", "fxo"); // Even if we start with a recursive mapping, as soon as we hit one that is not recursive, we should stop // expanding. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "X", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "X", "r.", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "i", "a", Mappings::Recursive); DoTest("foo", "li", "oo"); // Regression test: Using a mapping may trigger a call to updateSelection(), which can change the mode // from VisualLineMode to plain VisualMode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "gA", "%", Mappings::NonRecursive); DoTest("xyz\nfoo\n{\nbar\n}", "jVjgAdgglP", "foo\n{\nbar\n}\nxyz"); // Piggy back on the previous test with a regression test for issue where, if gA is mapped to %, vgly // will yank one more character than it should. DoTest("foo(bar)X", "vgAyp", "ffoo(bar)oo(bar)X"); // Make sure that a successful mapping does not break the "if we select stuff externally in Normal mode, // we should switch to Visual Mode" thing. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gA", "%", Mappings::NonRecursive); BeginTest("foo bar xyz()"); TestPressKey("gAr."); kate_view->setSelection(Range(0, 1, 0, 4)); // Actually selects "oo " (i.e. without the "b"). TestPressKey("d"); FinishTest("fbar xyz(."); // Regression tests for BUG:260655 clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "f", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "adr.", "foo .ar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "F", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "$adr.", "foo .ar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "t", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "adr.", "foo.dar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "T", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "$adr.", "foo d.r"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "r", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "ad", "doo dar"); // Feel free to map the keypress after that, though. DoTest("foo dar", "addber\\esc", "berdoo dar"); // Also, be careful about how we interpret "waiting for find char/ replace char" DoTest("foo dar", "ffas", "soo dar"); // Ignore raw "Ctrl", "Shift", "Meta" and "Alt" keys, which will almost certainly end up being pressed as // we try to trigger mappings that contain these keys. clearAllMappings(); { // Ctrl. vi_global->mappings()->add(Mappings::NormalModeMapping, "", "ictrl", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-b"); FinishTest("ctrl"); } { // Shift. vi_global->mappings()->add(Mappings::NormalModeMapping, "C", "ishift", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); QKeyEvent *shiftKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), shiftKeyDown); QApplication::sendPostedEvents(); TestPressKey("C"); FinishTest("shift"); } { // Alt. vi_global->mappings()->add(Mappings::NormalModeMapping, "", "ialt", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); QKeyEvent *altKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Alt, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\alt-b"); FinishTest("alt"); } { // Meta. vi_global->mappings()->add(Mappings::NormalModeMapping, "", "imeta", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); QKeyEvent *metaKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Meta, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), metaKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\meta-b"); FinishTest("meta"); } { // Can have mappings in Visual mode, distinct from Normal mode.. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "3l", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "inose", Mappings::NonRecursive); DoTest("0123456", "lvad", "056"); // The recursion in Visual Mode is distinct from that of Normal mode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "b", "", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "b", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "b", Mappings::Recursive); DoTest("XXX\nXXX", "lvajd", "XXX"); clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "b", "", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "b", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "b", Mappings::NonRecursive); DoTest("XXX\nXXX", "lvajd", "XXX\nXXX"); // A Visual mode mapping applies to all Visual modes (line, block, etc). clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "2j", Mappings::NonRecursive); DoTest("123\n456\n789", "lvad", "19"); DoTest("123\n456\n789", "l\\ctrl-vad", "13\n46\n79"); DoTest("123\n456\n789", "lVad", ""); // Same for recursion. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "b", "2j", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "b", Mappings::Recursive); DoTest("123\n456\n789", "lvad", "19"); DoTest("123\n456\n789", "l\\ctrl-vad", "13\n46\n79"); DoTest("123\n456\n789", "lVad", ""); // Can clear Visual mode mappings. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "l", Mappings::Recursive); vi_global->mappings()->clear(Mappings::VisualModeMapping); DoTest("123\n456\n789", "lvhd", "3\n456\n789"); DoTest("123\n456\n789", "l\\ctrl-vhd", "3\n456\n789"); DoTest("123\n456\n789", "lVhd", "456\n789"); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "l", Mappings::Recursive); vi_global->mappings()->clear(Mappings::VisualModeMapping); DoTest("123\n456\n789", "lvhd", "3\n456\n789"); DoTest("123\n456\n789", "l\\ctrl-vhd", "3\n456\n789"); DoTest("123\n456\n789", "lVhd", "456\n789"); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "l", Mappings::Recursive); vi_global->mappings()->clear(Mappings::VisualModeMapping); DoTest("123\n456\n789", "lvhd", "3\n456\n789"); DoTest("123\n456\n789", "l\\ctrl-vhd", "3\n456\n789"); DoTest("123\n456\n789", "lVhd", "456\n789"); } { // Can have mappings in Insert mode. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "xyz", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "inose", Mappings::NonRecursive); DoTest("foo", "ia\\esc", "xyzfoo"); // Recursion for Insert mode. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "b", "c", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "b", Mappings::NonRecursive); DoTest("", "ia\\esc", "b"); clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "b", "c", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "b", Mappings::Recursive); DoTest("", "ia\\esc", "c"); clearAllMappings(); // Clear mappings for Insert mode. vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "b", Mappings::NonRecursive); vi_global->mappings()->clear(Mappings::InsertModeMapping); DoTest("", "ia\\esc", "a"); } { EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // Can have mappings in Emulated Command Bar. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "a", "xyz", Mappings::NonRecursive); DoTest(" a xyz", "/a\\enterrX", " a Xyz"); // Use mappings from Normal mode as soon as we exit command bar via Enter. vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "ixyz", Mappings::NonRecursive); DoTest(" a xyz", "/a\\entera", " a xyzxyz"); // Multiple mappings. vi_global->mappings()->add(Mappings::CommandModeMapping, "b", "123", Mappings::NonRecursive); DoTest(" xyz123", "/ab\\enterrX", " Xyz123"); // Recursive mappings. vi_global->mappings()->add(Mappings::CommandModeMapping, "b", "a", Mappings::Recursive); DoTest(" xyz", "/b\\enterrX", " Xyz"); // Can clear all. vi_global->mappings()->clear(Mappings::CommandModeMapping); DoTest(" ab xyz xyz123", "/ab\\enterrX", " Xb xyz xyz123"); } // Test that not *both* of the mapping and the mapped keys are logged for repetition via "." clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "ixyz", "iabc", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "gl", "%", Mappings::NonRecursive); DoTest("", "ixyz\\esc.", "ababcc"); DoTest("foo()X\nbarxyz()Y", "cglbaz\\escggj.", "bazX\nbazY"); // Regression test for a crash when executing a mapping that switches to Normal mode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "d", Mappings::Recursive); DoTest("foo", "vlh", "o"); { // Test that we can set/ unset mappings from the command-line. clearAllMappings(); DoTest("", "\\:nn foo ibar\\foo", "bar"); // "nn" is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:nn foo l\\foorX", "xXx"); // "no" is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:no foo l\\foorX", "xXx"); // "noremap" is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:noremap foo l\\foorX", "xXx"); // "nm" is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:nm foo l\\foorX", "abXxxx"); // "nmap" is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:nmap foo l\\foorX", "abXxxx"); // Unfortunately, "map" is a reserved word :/ clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:map foo l\\foorX", "abXxxx", ShouldFail, "'map' is reserved for other stuff in Kate command line"); // nunmap works in normal mode. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "w", "ciwabc", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "b", "ciwxyz", Mappings::NonRecursive); DoTest(" 123 456 789", "\\:nunmap b\\WWwbrX", " 123 Xbc 789"); // nmap and nunmap whose "from" is a complex encoded expression. clearAllMappings(); BeginTest("123"); TestPressKey("\\:nmap ciwxyz\\"); TestPressKey("\\ctrl-9"); FinishTest("xyz"); BeginTest("123"); TestPressKey("\\:nunmap \\"); TestPressKey("\\ctrl-9"); FinishTest("123"); // vmap works in Visual mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vmap foo l\\v\\rightfoogU", "co"); // vmap does not work in Normal mode. clearAllMappings(); DoTest("xxx", "\\:vmap foo l\\foorX", "xxx\nrX"); // vm works in Visual mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vm foo l\\v\\rightfoogU", "co"); // vn works in Visual mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vn foo l\\v\\rightfoogU", "ABCo"); // vnoremap works in Visual mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vnoremap foo l\\v\\rightfoogU", "ABCo"); // vunmap works in Visual Mode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "w", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "gU", "2b", Mappings::NonRecursive); DoTest("foo bar xyz", "\\:vunmap gU\\wvlgUd", "foo BAR Xyz"); // imap works in Insert mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:imap foo l\\ifoo\\esc", "d"); // im works in Insert mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:im foo l\\ifoo\\esc", "d"); // ino works in Insert mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:ino foo l\\ifoo\\esc", "l"); // inoremap works in Insert mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:inoremap foo l\\ifoo\\esc", "l"); // iunmap works in Insert mode. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::InsertModeMapping, "m", "e", Mappings::NonRecursive); DoTest("", "\\:iunmap l\\ilm\\esc", "le"); { EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // cmap works in emulated command bar and is recursive. // NOTE: need to do the cmap call using the direct execution (i.e. \\:cmap blah blah\\), *not* using // the emulated command bar (:cmap blah blah\\enter), as this will be subject to mappings, which // can interfere with the tests! clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cmap foo l\\/foo\\enterrX", " l X foo"); // cm works in emulated command bar and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cm foo l\\/foo\\enterrX", " l X foo"); // cnoremap works in emulated command bar and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cnoremap foo l\\/foo\\enterrX", " X d foo"); // cno works in emulated command bar and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cno foo l\\/foo\\enterrX", " X d foo"); // cunmap works in emulated command bar. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::CommandModeMapping, "m", "e", Mappings::NonRecursive); DoTest(" de le", "\\:cunmap l\\/lm\\enterrX", " de Xe"); } // Can use to signify a space. clearAllMappings(); DoTest("", "\\:nn h iab\\h ", " a b"); } // More recursion tests - don't lose characters from a Recursive mapping if it looks like they might // be part of a different mapping (but end up not being so). // (Here, the leading "i" in "irecursive" could be part of the mapping "ihello"). clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "ihello", "irecursive", Mappings::Recursive); DoTest("", "'", "recursive"); // Capslock in insert mode is not handled by Vim nor by KateViewInternal, and ends up // being sent to KateViInputModeManager::handleKeypress twice (it could be argued that this is // incorrect behaviour on the part of KateViewInternal), which can cause infinite // recursion if we are not careful about identifying replayed rejected keypresses. BeginTest("foo bar"); TestPressKey("i"); QKeyEvent *capslockKeyPress = new QKeyEvent(QEvent::KeyPress, Qt::Key_CapsLock, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), capslockKeyPress); QApplication::sendPostedEvents(); FinishTest("foo bar"); // Mapping the u and the U commands to other keys. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "t", "u", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "r", "U", Mappings::Recursive); DoTest("", "ihello\\esct", ""); DoTest("", "ihello\\esctr", "hello"); // clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "", Mappings::Recursive); DoTest("Hello", "lrr", "rello"); clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "", Mappings::Recursive); DoTest("Hello", "sl\\esc", "ello"); clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "abc", Mappings::Recursive); DoTest("Hello", "sl\\esc", "abcello"); // Clear mappings for subsequent tests. clearAllMappings(); } void KeysTest::LeaderTests() { // Clean slate. KateViewConfig::global()->setViInputModeStealKeys(true); clearAllMappings(); // By default the backslash character is the leader. The default leader // is picked from the config. If we don't want to mess this from other // tests, it's better if we mock the config. const QString viTestKConfigFileName = QStringLiteral("vimodetest-leader-katevimoderc"); KConfig viTestKConfig(viTestKConfigFileName); vi_global->mappings()->setLeader(QChar()); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->add(Mappings::NormalModeMapping, "i", "ii", Mappings::Recursive); DoTest("", "\\\\i", "i"); // We can change the leader and it will work. clearAllMappings(); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->setLeader(QChar::fromLatin1(',')); vi_global->mappings()->add(Mappings::NormalModeMapping, "i", "ii", Mappings::Recursive); DoTest("", ",i", "i"); // Mixing up the with its value. clearAllMappings(); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->setLeader(QChar::fromLatin1(',')); vi_global->mappings()->add(Mappings::NormalModeMapping, ",", "ii", Mappings::Recursive); DoTest("", ",,", "i"); vi_global->mappings()->add(Mappings::NormalModeMapping, ",", "ii", Mappings::Recursive); DoTest("", ",,", "i"); // It doesn't work outside normal mode. clearAllMappings(); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->setLeader(QChar::fromLatin1(',')); vi_global->mappings()->add(Mappings::InsertModeMapping, "i", "ii", Mappings::Recursive); DoTest("", "i,ii", ",ii"); // Clear mappings for subsequent tests. clearAllMappings(); } void KeysTest::ParsingTests() { // BUG #298726 const QChar char_o_diaeresis(246); // Test that we can correctly translate finnish key ö QKeyEvent *k = new QKeyEvent(QEvent::KeyPress, 214, Qt::NoModifier, 47, 246, 16400, char_o_diaeresis); QCOMPARE(KeyParser::self()->KeyEventToQChar(*k), QChar(246)); // Test that it can be used in mappings clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, char_o_diaeresis, "ifoo", Mappings::Recursive); DoTest("hello", QString("ll%1bar").arg(char_o_diaeresis), "hefoobarllo"); // Test that is parsed like QCOMPARE(KeyParser::self()->vi2qt("cr"), int(Qt::Key_Enter)); const QString &enter = KeyParser::self()->encodeKeySequence(QLatin1String("")); QCOMPARE(KeyParser::self()->decodeKeySequence(enter), QLatin1String("")); } void KeysTest::AltGr() { QKeyEvent *altGrDown; QKeyEvent *altGrUp; // Test Alt-gr still works - this isn't quite how things work in "real-life": in real-life, something like // Alt-gr+7 would be a "{", but I don't think this can be reproduced without sending raw X11 // keypresses to Qt, so just duplicate the keypress events we would receive if we pressed // Alt-gr+7 (that is: Alt-gr down; "{"; Alt-gr up). BeginTest(""); TestPressKey("i"); altGrDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrDown); QApplication::sendPostedEvents(); // Not really Alt-gr and 7, but this is the key event that is reported by Qt if we press that. QKeyEvent *altGrAnd7 = new QKeyEvent(QEvent::KeyPress, Qt::Key_BraceLeft, Qt::GroupSwitchModifier, "{"); QApplication::postEvent(kate_view->focusProxy(), altGrAnd7); QApplication::sendPostedEvents(); altGrUp = new QKeyEvent(QEvent::KeyRelease, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrUp); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-c"); FinishTest("{"); // French Bepo keyabord AltGr + Shift + s = Ù = Unicode(0x00D9); const QString ugrave = QString(QChar(0x00D9)); BeginTest(""); TestPressKey("i"); altGrDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrDown); QApplication::sendPostedEvents(); altGrDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::ShiftModifier | Qt::GroupSwitchModifier); QApplication::postEvent(kate_view->focusProxy(), altGrDown); QApplication::sendPostedEvents(); QKeyEvent *altGrAndUGrave = new QKeyEvent(QEvent::KeyPress, Qt::Key_Ugrave, Qt::ShiftModifier | Qt::GroupSwitchModifier, ugrave); qDebug() << QString("%1").arg(altGrAndUGrave->modifiers(), 10, 16); QApplication::postEvent(kate_view->focusProxy(), altGrAndUGrave); QApplication::sendPostedEvents(); altGrUp = new QKeyEvent(QEvent::KeyRelease, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrUp); QApplication::sendPostedEvents(); FinishTest(ugrave); } void KeysTest::MacroTests() { // Update the status on qa. const QString macroIsRecordingStatus = QLatin1String("(") + i18n("recording") + QLatin1String(")"); clearAllMacros(); BeginTest(""); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("qa"); QVERIFY(kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("q"); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); FinishTest(""); // The closing "q" is not treated as the beginning of a new "begin recording macro" command. clearAllMacros(); BeginTest("foo"); TestPressKey("qaqa"); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("xyz\\esc"); FinishTest("fxyzoo"); // Record and playback a single keypress into macro register "a". clearAllMacros(); DoTest("foo bar", "qawqgg@arX", "foo Xar"); // Two macros - make sure the old one is cleared. clearAllMacros(); DoTest("123 foo bar xyz", "qawqqabqggww@arX", "123 Xoo bar xyz"); // Update the status on qb. clearAllMacros(); BeginTest(""); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("qb"); QVERIFY(kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("q"); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); FinishTest(""); // Record and playback a single keypress into macro register "b". clearAllMacros(); DoTest("foo bar", "qbwqgg@brX", "foo Xar"); // More complex macros. clearAllMacros(); DoTest("foo", "qcrXql@c", "XXo"); // Re-recording a macro should only clear that macro. clearAllMacros(); DoTest("foo 123", "qaraqqbrbqqbrBqw@a", "Boo a23"); // Empty macro clears it. clearAllMacros(); DoTest("", "qaixyz\\ctrl-cqqaq@a", "xyz"); // Hold two macros in memory simultanenously so both can be played. clearAllMacros(); DoTest("foo 123", "qaraqqbrbqw@al@b", "boo ab3"); // Do more complex things, including switching modes and using ctrl codes. clearAllMacros(); DoTest("foo bar", "qainose\\ctrl-c~qw@a", "nosEfoo nosEbar"); clearAllMacros(); DoTest("foo bar", "qayiwinose\\ctrl-r0\\ctrl-c~qw@a", "nosefoOfoo nosebaRbar"); clearAllMacros(); DoTest("foo bar", "qavldqw@a", "o r"); // Make sure we can use "q" in insert mode while recording a macro. clearAllMacros(); DoTest("foo bar", "qaiqueequeg\\ctrl-cqw@a", "queequegfoo queequegbar"); // Can invoke a macro in Visual Mode. clearAllMacros(); DoTest("foo bar", "qa~qvlll@a", "FOO Bar"); // Invoking a macro in Visual Mode does not exit Visual Mode. clearAllMacros(); DoTest("foo bar", "qallqggv@a~", "FOO bar");; // Can record & macros in Visual Mode for playback in Normal Mode. clearAllMacros(); DoTest("foo bar", "vqblq\\ctrl-c@b~", "foO bar"); // Recording a macro in Visual Mode does not exit Visual Mode. clearAllMacros(); DoTest("foo bar", "vqblql~", "FOO bar"); // Recognize correctly numbered registers clearAllMacros(); DoTest("foo", "q1iX\\escq@1", "XXfoo"); { // Ensure that we can call emulated command bar searches, and that we don't record // synthetic keypresses. EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); clearAllMacros(); DoTest("foo bar\nblank line", "qa/bar\\enterqgg@arX", "foo Xar\nblank line"); // More complex searching stuff. clearAllMacros(); DoTest("foo 123foo123\nbar 123bar123", "qayiw/\\ctrl-r0\\enterrXqggj@a", "foo 123Xoo123\nbar 123Xar123"); } // Expand mappings, but don't do *both* original keypresses and executed keypresses. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); clearAllMacros(); DoTest("", "qa'q@a", "hellhelloo"); // Actually, just do the mapped keypresses, not the executed mappings (like Vim). clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); clearAllMacros(); BeginTest(""); TestPressKey("qa'q"); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "igoodbye", Mappings::Recursive); TestPressKey("@a"); FinishTest("hellgoodbyeo"); // Clear the "stop recording macro keypresses because we're executing a mapping" when the mapping has finished // executing. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); clearAllMacros(); DoTest("", "qa'ixyz\\ctrl-cq@a", "hellxyhellxyzozo"); // ... make sure that *all* mappings have finished, though: take into account recursion. clearAllMappings(); clearAllMacros(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "ihello", "irecursive", Mappings::Recursive); DoTest("", "qa'q@a", "recursivrecursivee"); clearAllMappings(); clearAllMacros(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihelloixyz", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "ihello", "irecursive", Mappings::Recursive); DoTest("", "qa'q@a", "recursivxyrecursivxyzeze"); clearAllMappings(); clearAllMacros(); // Don't save the trailing "q" with macros, and also test that we can call one macro from another, // without one of the macros being repeated. DoTest("", "qaixyz\\ctrl-cqqb@aq@b", "xyxyxyzzz"); clearAllMappings(); clearAllMacros(); // More stringent test that macros called from another macro aren't repeated - requires more nesting // of macros ('a' calls 'b' calls 'c'). DoTest("", "qciC\\ctrl-cq" "qb@ciB\\ctrl-cq" "qa@biA\\ctrl-cq" "dd@a", "ABC"); // Don't crash if we invoke a non-existent macro. clearAllMacros(); DoTest("", "@x", ""); // Make macros "counted". clearAllMacros(); DoTest("XXXX\nXXXX\nXXXX\nXXXX", "qarOljq3@a", "OXXX\nXOXX\nXXOX\nXXXO"); // A macro can be undone with one undo. clearAllMacros(); DoTest("foo bar", "qaciwxyz\\ctrl-ci123\\ctrl-cqw@au", "xy123z bar"); // As can a counted macro. clearAllMacros(); DoTest("XXXX\nXXXX\nXXXX\nXXXX", "qarOljq3@au", "OXXX\nXXXX\nXXXX\nXXXX"); { EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // Make sure we can macro-ise an interactive sed replace. clearAllMacros(); DoTest("foo foo foo foo\nfoo foo foo foo", "qa:s/foo/bar/gc\\enteryynyAdone\\escqggj@a", "bar bar foo bardone\nbar bar foo bardone"); // Make sure the closing "q" in the interactive sed replace isn't mistaken for a macro's closing "q". clearAllMacros(); DoTest("foo foo foo foo\nfoo foo foo foo", "qa:s/foo/bar/gc\\enteryyqAdone\\escqggj@a", "bar bar foo foodone\nbar bar foo foodone"); clearAllMacros(); DoTest("foo foo foo foo\nfoo foo foo foo", "qa:s/foo/bar/gc\\enteryyqqAdone\\escggj@aAdone\\esc", "bar bar foo foodone\nbar bar foo foodone"); } clearAllMappings(); clearAllMacros(); // Expand mapping in an executed macro, if the invocation of the macro "@a" is a prefix of a mapping M, and // M ends up not being triggered. vi_global->mappings()->add(Mappings::NormalModeMapping, "@aaaa", "idummy", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "S", "ixyz", Mappings::Recursive); DoTest("", "qaSq@abrX", "Xyxyzz"); clearAllMappings(); // Can't play old version of macro while recording new version. clearAllMacros(); DoTest("", "qaiaaa\\ctrl-cqqa@aq", "aaa"); // Can't play the macro while recording it. clearAllMacros(); DoTest("", "qaiaaa\\ctrl-c@aq", "aaa"); // "@@" plays back macro "a" if "a" was the last macro we played back. clearAllMacros(); DoTest("", "qaia\\ctrl-cq@adiw@@", "a"); // "@@" plays back macro "b" if "b" was the last macro we played back. clearAllMacros(); DoTest("", "qbib\\ctrl-cq@bdiw@@", "b"); // "@@" does nothing if no macro was previously played. clearAllMacros(); DoTest("", "qaia\\ctrl-cq@@", "a"); // Nitpick: "@@" replays the last played back macro, even if that macro had not been defined // when it was first played back. clearAllMacros(); DoTest("", "@aqaia\\ctrl-cq@@", "aa"); // "@@" is counted. clearAllMacros(); DoTest("", "qaia\\ctrl-cq@adiw5@@", "aaaaa"); // Test that we can save and restore a single macro. const QString viTestKConfigFileName = "vimodetest-katevimoderc"; { clearAllMacros(); KConfig viTestKConfig(viTestKConfigFileName); BeginTest(""); TestPressKey("qaia\\ctrl-cq"); vi_global->writeConfig(&viTestKConfig); viTestKConfig.sync(); // Overwrite macro "a", and clear the document. TestPressKey("qaidummy\\ctrl-cqdd"); vi_global->readConfig(&viTestKConfig); TestPressKey("@a"); FinishTest("a"); } { // Test that we can save and restore several macros. clearAllMacros(); const QString viTestKConfigFileName = "vimodetest-katevimoderc"; KConfig viTestKConfig(viTestKConfigFileName); BeginTest(""); TestPressKey("qaia\\ctrl-cqqbib\\ctrl-cq"); vi_global->writeConfig(&viTestKConfig); viTestKConfig.sync(); // Overwrite macros "a" & "b", and clear the document. TestPressKey("qaidummy\\ctrl-cqqbidummy\\ctrl-cqdd"); vi_global->readConfig(&viTestKConfig); TestPressKey("@a@b"); FinishTest("ba"); } // Ensure that we don't crash when a "repeat change" occurs in a macro we execute. clearAllMacros(); DoTest("", "qqixyz\\ctrl-c.q@qdd", ""); // Don't record both the "." *and* the last-change keypresses when recording a macro; // just record the "." clearAllMacros(); DoTest("", "ixyz\\ctrl-cqq.qddi123\\ctrl-c@q", "121233"); // Test dealing with auto-completion. FakeCodeCompletionTestModel *fakeCodeCompletionModel = new FakeCodeCompletionTestModel(kate_view); kate_view->registerCompletionModel(fakeCodeCompletionModel); // Completion tests require a visible kate_view. ensureKateViewVisible(); // Want Vim mode to intercept ctrl-p, ctrl-n shortcuts, etc. const bool oldStealKeys = KateViewConfig::global()->viInputModeStealKeys(); KateViewConfig::global()->setViInputModeStealKeys(true); // Don't invoke completion via ctrl-space when replaying a macro. clearAllMacros(); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); fakeCodeCompletionModel->setFailTestOnInvocation(false); BeginTest(""); TestPressKey("qqico\\ctrl- \\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("ccoo"); // Don't invoke completion via ctrl-p when replaying a macro. clearAllMacros(); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); fakeCodeCompletionModel->setFailTestOnInvocation(false); BeginTest(""); TestPressKey("qqico\\ctrl-p\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("ccoo"); // Don't invoke completion via ctrl-n when replaying a macro. clearAllMacros(); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); fakeCodeCompletionModel->setFailTestOnInvocation(false); BeginTest(""); TestPressKey("qqico\\ctrl-n\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("ccoo"); // An "enter" in insert mode when no completion is activated (so, a newline) // is treated as a newline when replayed as a macro, even if completion is // active when the "enter" is replayed. clearAllMacros(); fakeCodeCompletionModel->setCompletions(QStringList()); // Prevent any completions. fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->clearWasInvoked(); BeginTest(""); TestPressKey("qqicompl\\enterX\\ctrl-cqdddd"); QVERIFY(!fakeCodeCompletionModel->wasInvoked()); // Error in test setup! fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); fakeCodeCompletionModel->forceInvocationIfDocTextIs("compl"); fakeCodeCompletionModel->clearWasInvoked(); TestPressKey("@q"); QVERIFY(fakeCodeCompletionModel->wasInvoked()); // Error in test setup! fakeCodeCompletionModel->doNotForceInvocation(); FinishTest("compl\nX"); // Same for "return". clearAllMacros(); fakeCodeCompletionModel->setCompletions(QStringList()); // Prevent any completions. fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->clearWasInvoked(); BeginTest(""); TestPressKey("qqicompl\\returnX\\ctrl-cqdddd"); QVERIFY(!fakeCodeCompletionModel->wasInvoked()); // Error in test setup! fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); fakeCodeCompletionModel->forceInvocationIfDocTextIs("compl"); fakeCodeCompletionModel->clearWasInvoked(); TestPressKey("@q"); QVERIFY(fakeCodeCompletionModel->wasInvoked()); // Error in test setup! fakeCodeCompletionModel->doNotForceInvocation(); FinishTest("compl\nX"); // If we do a plain-text completion in a macro, this should be repeated when we replay it. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicompl\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("completionA"); // Should replace only the current word when we repeat the completion. clearAllMacros(); BeginTest("compl"); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfla\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(compl)"); TestPressKey("gg@q"); FinishTest("(completionA)"); // Tail-clearing completions should be undoable with one undo. clearAllMacros(); BeginTest("compl"); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfla\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(compl)"); TestPressKey("gg@qu"); FinishTest("(compl)"); // Should be able to store multiple completions. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicom\\ctrl-p\\enter com\\ctrl-p\\ctrl-p\\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("completionC completionB"); // Clear the completions for a macro when we start recording. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "completionOrig"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicom\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setCompletions(QStringList() << "completionSecond"); TestPressKey("ddqqicom\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("completionSecond"); // Completions are per macro. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qaicom\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setCompletions(QStringList() << "completionB"); TestPressKey("ddqbicom\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@aA\\enter\\ctrl-c@b"); FinishTest("completionA\ncompletionB"); // Make sure completions work with recursive macros. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA1" << "completionA2"); fakeCodeCompletionModel->setFailTestOnInvocation(false); // Record 'a', which calls the (non-yet-existent) macro 'b'. TestPressKey("qaicom\\ctrl- \\enter\\ctrl-cA\\enter\\ctrl-c@bA\\enter\\ctrl-cicom\\ctrl- \\ctrl-p\\enter\\ctrl-cq"); // Clear document and record 'b'. fakeCodeCompletionModel->setCompletions(QStringList() << "completionB"); TestPressKey("ggdGqbicom\\ctrl- \\enter\\ctrl-cq"); TestPressKey("dd@a"); FinishTest("completionA1\ncompletionB\ncompletionA2"); // Test that non-tail-removing completions are respected. // Note that there is no way (in general) to determine if a completion was // non-tail-removing, so we explicitly set the config to false. const bool oldRemoveTailOnCompletion = KateViewConfig::global()->wordCompletionRemoveTail(); KateViewConfig::global()->setWordCompletionRemoveTail(false); const bool oldReplaceTabsDyn = kate_document->config()->replaceTabsDyn(); kate_document->config()->setReplaceTabsDyn(false); fakeCodeCompletionModel->setRemoveTailOnComplete(false); clearAllMacros(); BeginTest("compTail"); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("compTail"); TestPressKey("gg@q"); FinishTest("completionATail"); // A "word" consists of letters & numbers, plus "_". clearAllMacros(); BeginTest("(123_compTail"); fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(123_compTail"); TestPressKey("gg@q"); FinishTest("(123_completionATail"); // Correctly remove word if we are set to remove tail. KateViewConfig::global()->setWordCompletionRemoveTail(true); clearAllMacros(); BeginTest("(123_compTail)"); fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(123_compTail)"); TestPressKey("gg@q"); FinishTest("(123_completionA)"); // Again, a "word" consists of letters & numbers & underscores. clearAllMacros(); BeginTest("(123_compTail_456)"); fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionA)"); // Actually, let whether the tail is swallowed or not depend on the value when the // completion occurred, not when we replay it. clearAllMacros(); BeginTest("(123_compTail_456)"); fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); KateViewConfig::global()->setWordCompletionRemoveTail(false); kate_document->setText("(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionA)"); clearAllMacros(); BeginTest("(123_compTail_456)"); fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); kate_document->setText("(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionATail_456)"); // Can have remove-tail *and* non-remove-tail completions in one macro. clearAllMacros(); BeginTest("(123_compTail_456)\n(123_compTail_456)"); fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-c"); fakeCodeCompletionModel->setRemoveTailOnComplete(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("j^fTi\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("(123_compTail_456)\n(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionA)\n(123_completionATail_456)"); // Can repeat plain-text completions when there is no word to the left of the cursor. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqi\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); TestPressKey("gg@q"); FinishTest("123_completionA"); // Shouldn't swallow the letter under the cursor if we're not swallowing tails. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("qqi\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("oldwordshouldbeuntouched"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("123_completionAoldwordshouldbeuntouched"); // ... but do if we are swallowing tails. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); TestPressKey("qqi\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("oldwordshouldbedeleted"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("123_completionA"); // Completion of functions. // Currently, not removing the tail on function completion is not supported. fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); // A completed, no argument function "function()" is repeated correctly. BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function()"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function()"); // Cursor is placed after the closing bracket when completion a no-arg function. BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function()"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter.something();\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function().something();"); // A function taking some arguments, repeated where there is no opening bracket to // merge with, is repeated as "function()"). BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function()"); // A function taking some arguments, repeated where there is no opening bracket to // merge with, places the cursor after the opening bracket. BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function(firstArg)"); // A function taking some arguments, recorded where there was an opening bracket to merge // with but repeated where there is no such bracket, is repeated as "function()" and the // cursor placed appropriately. BeginTest("(<-Mergeable opening bracket)"); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function(firstArg)"); // A function taking some arguments, recorded where there was no opening bracket to merge // with but repeated where there is such a bracket, is repeated as "function" and the // cursor moved to after the merged opening bracket. BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function(firstArg<-firstArg goes here)"); // A function taking some arguments, recorded where there was an opening bracket to merge // with and repeated where there is also such a bracket, is repeated as "function" and the // cursor moved to after the merged opening bracket. BeginTest("(<-mergeablebracket)"); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function(firstArg<-firstArg goes here)"); // The mergeable bracket can be separated by whitespace; the cursor is still placed after the // opening bracket. BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText(" \t (<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function \t (firstArg<-firstArg goes here)"); // Whitespace only, though! BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("| \t ()"); TestPressKey("gg@q"); FinishTest("function(firstArg)| \t ()"); // The opening bracket can actually be after the current word (with optional whitespace). // Note that this wouldn't be the case if we weren't swallowing tails when completion functions, // but this is not currently supported. BeginTest("function"); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfta\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("functxyz (<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function (firstArg<-firstArg goes here)"); // Regression test for weird issue with replaying completions when the character to the left of the cursor // is not a word char. BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqciw\\ctrl- \\enter\\ctrl-cq"); TestPressKey("ddi.xyz\\enter123\\enter456\\ctrl-cggl"); // Position cursor just after the "." TestPressKey("@q"); FinishTest(".completionA\n123\n456"); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqciw\\ctrl- \\enter\\ctrl-cq"); TestPressKey("ddi.xyz.abc\\enter123\\enter456\\ctrl-cggl"); // Position cursor just after the "." TestPressKey("@q"); FinishTest(".completionA.abc\n123\n456"); // Functions taking no arguments are never bracket-merged. BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function()"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter.something();\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(<-don't merge this bracket)"); TestPressKey("gg@q"); FinishTest("function().something();(<-don't merge this bracket)"); // Not-removing-tail when completing functions is not currently supported, // so ignore the "do-not-remove-tail" settings when we try this. BeginTest("funct"); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); fakeCodeCompletionModel->setFailTestOnInvocation(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("qqfta\\ctrl- \\enterfirstArg\\ctrl-cq"); kate_document->setText("functxyz"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(firstArg)"); BeginTest("funct"); fakeCodeCompletionModel->setCompletions(QStringList() << "function()"); fakeCodeCompletionModel->setFailTestOnInvocation(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("qqfta\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("functxyz"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function()"); KateViewConfig::global()->setWordCompletionRemoveTail(true); // Deal with cases where completion ends with ";". BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function();"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();"); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function();"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();X"); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...);"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();"); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...);"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(X);"); // Tests for completions ending in ";" where bracket merging should happen on replay. // NB: bracket merging when recording is impossible with completions that end in ";". BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...);"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("(<-mergeable bracket"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(<-mergeable bracket"); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function(...);"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->setText("(<-mergeable bracket"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(X<-mergeable bracket"); // Don't merge no arg functions. BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "function();"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->setText("(<-mergeable bracket"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();X(<-mergeable bracket"); { const QString viTestKConfigFileName = "vimodetest-katevimoderc"; KConfig viTestKConfig(viTestKConfigFileName); // Test loading and saving of macro completions. clearAllMacros(); BeginTest("funct\nnoa\ncomtail\ncomtail\ncom"); fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "functionwithargs(...)" << "noargfunction()"); fakeCodeCompletionModel->setFailTestOnInvocation(false); // Record 'a'. TestPressKey("qafta\\ctrl- \\enterfirstArg\\ctrl-c"); // Function with args. TestPressKey("\\enterea\\ctrl- \\enter\\ctrl-c"); // Function no args. fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); TestPressKey("\\enterfti\\ctrl- \\enter\\ctrl-c"); // Cut off tail. fakeCodeCompletionModel->setRemoveTailOnComplete(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("\\enterfti\\ctrl- \\enter\\ctrl-cq"); // Don't cut off tail. fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); // Record 'b'. fakeCodeCompletionModel->setCompletions(QStringList() << "completionB" << "semicolonfunctionnoargs();" << "semicolonfunctionwithargs(...);"); TestPressKey("\\enterqbea\\ctrl- \\enter\\ctrl-cosemicolonfunctionw\\ctrl- \\enterX\\ctrl-cosemicolonfunctionn\\ctrl- \\enterX\\ctrl-cq"); // Save. vi_global->writeConfig(&viTestKConfig); viTestKConfig.sync(); // Overwrite 'a' and 'b' and their completions. fakeCodeCompletionModel->setCompletions(QStringList() << "blah1"); kate_document->setText(""); TestPressKey("ggqaiblah\\ctrl- \\enter\\ctrl-cq"); TestPressKey("ddqbiblah\\ctrl- \\enter\\ctrl-cq"); // Reload. vi_global->readConfig(&viTestKConfig); // Replay reloaded. fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("funct\nnoa\ncomtail\ncomtail\ncom"); TestPressKey("gg@a\\enter@b"); FinishTest("functionwithargs(firstArg)\nnoargfunction()\ncompletionA\ncompletionAtail\ncompletionB\nsemicolonfunctionwithargs(X);\nsemicolonfunctionnoargs();X"); } // Check that undo/redo operations work properly with macros. { clearAllMacros(); BeginTest(""); TestPressKey("ihello\\ctrl-cqauq"); TestPressKey("@a\\enter"); FinishTest(""); } { clearAllMacros(); BeginTest(""); TestPressKey("ihello\\ctrl-cui.bye\\ctrl-cu"); TestPressKey("qa\\ctrl-r\\enterq"); TestPressKey("@a\\enter"); FinishTest(".bye"); } // When replaying a last change in the process of replaying a macro, take the next completion // event from the last change completions log, rather than the macro completions log. // Ensure that the last change completions log is kept up to date even while we're replaying the macro. clearAllMacros(); BeginTest(""); fakeCodeCompletionModel->setCompletions(QStringList() << "completionMacro" << "completionRepeatLastChange"); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicompletionM\\ctrl- \\enter\\ctrl-c"); TestPressKey("a completionRep\\ctrl- \\enter\\ctrl-c"); TestPressKey(".q"); qDebug() << "text: " << kate_document->text(); kate_document->clear(); TestPressKey("gg@q"); FinishTest("completionMacro completionRepeatLastChange completionRepeatLastChange"); KateViewConfig::global()->setWordCompletionRemoveTail(oldRemoveTailOnCompletion); kate_document->config()->setReplaceTabsDyn(oldReplaceTabsDyn); kate_view->unregisterCompletionModel(fakeCodeCompletionModel); delete fakeCodeCompletionModel; - fakeCodeCompletionModel = 0; + fakeCodeCompletionModel = nullptr; // Hide the kate_view for subsequent tests. kate_view->hide(); mainWindow->hide(); KateViewConfig::global()->setViInputModeStealKeys(oldStealKeys); } void KeysTest::MarkTests() { // Difference between ` and ' DoTest(" a\n b", "jmak'aii", " a\n ib"); DoTest(" a\n b", "jmak`aii", " a\ni b"); // Last edit markers. DoTest("foo", "ean\\escgg`.r.", "foo."); DoTest("foo", "ean\\escgg`[r[", "foo["); DoTest("foo", "ean\\escgg`]r]", "foo]"); DoTest("foo bar", "ean\\escgg`]r]", "foon]bar"); DoTest("", "ibar\\escgg`.r.", "ba."); DoTest("", "ibar\\escgggUiw`.r.", ".AR"); DoTest("", "2ibar\\escgg`]r]", "barba]"); DoTest("", "2ibar\\escgg`[r[", "[arbar"); DoTest("", "3ibar\\escgg`.r.", "barbar.ar"); // Vim is weird. DoTest("", "abar\\esc.gg`]r]", "barba]"); DoTest("foo bar", "wgUiwgg`]r]", "foo BA]"); DoTest("foo bar", "wgUiwgg`.r.", "foo .AR"); DoTest("foo bar", "gUiwgg`]r.", "FO. bar"); DoTest("foo bar", "wdiwgg`[r[", "foo["); DoTest("foo bar", "wdiwgg`]r]", "foo]"); DoTest("foo bar", "wdiwgg`.r.", "foo."); DoTest("foo bar", "wciwnose\\escgg`.r.", "foo nos."); DoTest("foo bar", "wciwnose\\escgg`[r[", "foo [ose"); DoTest("foo bar", "wciwnose\\escgg`]r]", "foo nos]"); DoTest("foo", "~ibar\\escgg`[r[", "F[aroo"); DoTest("foo bar", "lragg`.r.", "f.o bar"); DoTest("foo bar", "lragg`[r[", "f[o bar"); DoTest("foo bar", "lragg`]r]", "f]o bar"); DoTest("", "ifoo\\ctrl-hbar\\esc`[r[", "[obar"); DoTest("", "ifoo\\ctrl-wbar\\esc`[r[", "[ar"); DoTest("", "if\\ctrl-hbar\\esc`[r[", "[ar"); DoTest("", "5ofoo\\escgg`[r[", "\n[oo\nfoo\nfoo\nfoo\nfoo"); DoTest("", "5ofoo\\escgg`]r]", "\nfoo\nfoo\nfoo\nfoo\nfo]"); DoTest("", "5ofoo\\escgg`.r.", "\nfoo\nfoo\nfoo\nfoo\n.oo"); DoTest("foo", "yyp`[r[", "foo\n[oo"); DoTest("xyz\nfoo", "ja\\returnbar\\esc`[r[", "xyz\n[\nbaroo"); DoTest("foo", "lrayypgg`[r[", "fao\n[ao"); DoTest("foo", "l~u`[r[", "[oo"); DoTest("foo", "l~u`.r.", ".oo"); DoTest("foo", "l~u`]r]", "]oo"); DoTest("foo", "lia\\escu`[r[", "[oo"); DoTest("foo", "lia\\escu`.r.", ".oo"); DoTest("foo", "lia\\escu`]r]", "]oo"); DoTest("foo", "l~u~`[r[", "f[o"); DoTest("foo\nbar\nxyz", "jyypu`[r[", "foo\nbar\n[yz"); DoTest("foo\nbar\nxyz", "jyypu`.r.", "foo\nbar\n.yz"); DoTest("foo\nbar\nxyz", "jyypu`]r]", "foo\nbar\n]yz"); DoTest("foo\nbar\nxyz\n123", "jdju`[r[", "foo\n[ar\nxyz\n123"); DoTest("foo\nbar\nxyz\n123", "jdju`.r.", "foo\n.ar\nxyz\n123"); DoTest("foo\nbar\nxyz\n123", "jdju`]r]", "foo\nbar\n]yz\n123"); DoTest("foo\nbar\nxyz\n123", "jVj~u\\esc`[r[", "foo\n[ar\nxyz\n123", ShouldFail, "Vim is weird."); } //END: KeysTest diff --git a/autotests/src/wordcompletiontest.cpp b/autotests/src/wordcompletiontest.cpp index d3e6dca7..9d48ea95 100644 --- a/autotests/src/wordcompletiontest.cpp +++ b/autotests/src/wordcompletiontest.cpp @@ -1,116 +1,116 @@ /* * * Copyright 2013 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 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 "wordcompletiontest.h" #include #include #include #include #include #include QTEST_MAIN(WordCompletionTest) // was 500000, but that takes 30 seconds on build.kde.org, removed two 0 ;) static const int count = 5000; using namespace KTextEditor; void WordCompletionTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); Editor *editor = KTextEditor::Editor::instance(); QVERIFY(editor); m_doc = editor->createDocument(this); QVERIFY(m_doc); } void WordCompletionTest::cleanupTestCase() { } void WordCompletionTest::init() { m_doc->clear(); } void WordCompletionTest::cleanup() { } void WordCompletionTest::benchWordRetrievalMixed() { const int distinctWordRatio = 100; QStringList s; s.reserve(count); for (int i = 0; i < count; i++) { s.append(QLatin1String("HelloWorld") + QString::number(i / distinctWordRatio)); } s.prepend("\n"); m_doc->setText(s); // creating the view only after inserting the text makes test execution much faster - QSharedPointer v(m_doc->createView(0)); + QSharedPointer v(m_doc->createView(nullptr)); QBENCHMARK { - KateWordCompletionModel m(0); + KateWordCompletionModel m(nullptr); QCOMPARE(m.allMatches(v.data(), KTextEditor::Range()).size(), count / distinctWordRatio); } } void WordCompletionTest::benchWordRetrievalSame() { QStringList s; s.reserve(count); // add a number so the words have roughly the same length as in the other tests const QString str = QLatin1String("HelloWorld") + QString::number(count); for (int i = 0; i < count; i++) { s.append(str); } s.prepend("\n"); m_doc->setText(s); - QSharedPointer v(m_doc->createView(0)); + QSharedPointer v(m_doc->createView(nullptr)); QBENCHMARK { - KateWordCompletionModel m(0); + KateWordCompletionModel m(nullptr); QCOMPARE(m.allMatches(v.data(), KTextEditor::Range()).size(), 1); } } void WordCompletionTest::benchWordRetrievalDistinct() { QStringList s; s.reserve(count); for (int i = 0; i < count; i++) { s.append(QLatin1String("HelloWorld") + QString::number(i)); } s.prepend("\n"); m_doc->setText(s); - QSharedPointer v(m_doc->createView(0)); + QSharedPointer v(m_doc->createView(nullptr)); QBENCHMARK { - KateWordCompletionModel m(0); + KateWordCompletionModel m(nullptr); QCOMPARE(m.allMatches(v.data(), KTextEditor::Range()).size(), count); } } diff --git a/cmake/FindEditorConfig.cmake b/cmake/FindEditorConfig.cmake new file mode 100644 index 00000000..9d5905e2 --- /dev/null +++ b/cmake/FindEditorConfig.cmake @@ -0,0 +1,63 @@ +#.rest: +# FindEditorConfig +# -------------- +# +# Try to find EditorConfig on this system. +# +# This will define the following variables: +# +# ``EditorConfig_FOUND`` +# True if inotify is available +# ``EditorConfig_LIBRARIES`` +# This has to be passed to target_link_libraries() +# ``EditorConfig_INCLUDE_DIRS`` +# This has to be passed to target_include_directories() + +#============================================================================= +# Copyright 2017 Christoph Cullmann +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +find_path(EditorConfig_INCLUDE_DIRS editorconfig/editorconfig.h) + +if(EditorConfig_INCLUDE_DIRS) + find_library(EditorConfig_LIBRARIES NAMES editorconfig) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(EditorConfig + FOUND_VAR + EditorConfig_FOUND + REQUIRED_VARS + EditorConfig_LIBRARIES + EditorConfig_INCLUDE_DIRS + ) + mark_as_advanced(EditorConfig_LIBRARIES EditorConfig_INCLUDE_DIRS) + include(FeatureSummary) + set_package_properties(EditorConfig PROPERTIES + URL "http://editorconfig.org/" + DESCRIPTION "EditorConfig editor configuration file support." + ) +else() + set(EditorConfig_FOUND FALSE) +endif() + +mark_as_advanced(EditorConfig_LIBRARIES EditorConfig_INCLUDE_DIRS) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c49fc8da..689a80ff 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,329 +1,377 @@ # handle data files, .desktop & .cmake add_subdirectory(data) # jscripts for the part add_subdirectory( script/data ) # set right defines for libgit2 usage if(LIBGIT2_FOUND) add_definitions(-DLIBGIT2_FOUND) SET (CMAKE_REQUIRED_LIBRARIES LibGit2::LibGit2) set (KTEXTEDITOR_OPTIONAL_LIBS ${KTEXTEDITOR_OPTIONAL_LIBS} LibGit2::LibGit2) endif() +if(EditorConfig_FOUND) + add_definitions(-DEDITORCONFIG_FOUND) + SET (CMAKE_REQUIRED_LIBRARIES editorconfig) + set (KTEXTEDITOR_OPTIONAL_LIBS ${KTEXTEDITOR_OPTIONAL_LIBS} editorconfig) +endif() + # handle include files, both normal ones and generated ones add_subdirectory(include) # includes include_directories( # for config.h ${CMAKE_BINARY_DIR} # for generated ktexteditor headers ${CMAKE_CURRENT_BINARY_DIR}/include # for normal sources ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/include/ktexteditor ${CMAKE_CURRENT_SOURCE_DIR}/buffer ${CMAKE_CURRENT_SOURCE_DIR}/completion ${CMAKE_CURRENT_SOURCE_DIR}/dialogs ${CMAKE_CURRENT_SOURCE_DIR}/document ${CMAKE_CURRENT_SOURCE_DIR}/script ${CMAKE_CURRENT_SOURCE_DIR}/mode ${CMAKE_CURRENT_SOURCE_DIR}/render ${CMAKE_CURRENT_SOURCE_DIR}/search ${CMAKE_CURRENT_SOURCE_DIR}/syntax ${CMAKE_CURRENT_SOURCE_DIR}/schema ${CMAKE_CURRENT_SOURCE_DIR}/undo ${CMAKE_CURRENT_SOURCE_DIR}/utils ${CMAKE_CURRENT_SOURCE_DIR}/inputmode ${CMAKE_CURRENT_SOURCE_DIR}/view ${CMAKE_CURRENT_SOURCE_DIR}/swapfile ${CMAKE_CURRENT_SOURCE_DIR}/variableeditor) # KTextEditor interface sources set(ktexteditor_LIB_SRCS # text buffer & buffer helpers +buffer/katesecuretextbuffer.cpp buffer/katetextbuffer.cpp buffer/katetextblock.cpp buffer/katetextline.cpp buffer/katetextcursor.cpp buffer/katetextrange.cpp buffer/katetexthistory.cpp buffer/katetextfolding.cpp # completion (widget, model, delegate, ...) completion/katecompletionwidget.cpp completion/katecompletionmodel.cpp completion/katecompletiontree.cpp completion/katecompletionconfig.cpp completion/kateargumenthinttree.cpp completion/kateargumenthintmodel.cpp completion/katecompletiondelegate.cpp completion/expandingtree/expandingwidgetmodel.cpp completion/expandingtree/expandingdelegate.cpp completion/expandingtree/expandingtree.cpp # simple internal word completion completion/katewordcompletion.cpp # internal syntax-file based keyword completion completion/katekeywordcompletion.cpp # dialogs dialogs/kateconfigpage.cpp dialogs/katedialogs.cpp dialogs/kateconfigpage.cpp # document (THE document, buffer, lines/cursors/..., CORE STUFF) document/katedocument.cpp document/katebuffer.cpp # undo undo/kateundo.cpp undo/katemodifiedundo.cpp undo/kateundomanager.cpp # scripting script/katescript.cpp script/kateindentscript.cpp script/katecommandlinescript.cpp script/katescriptmanager.cpp script/katescriptaction.cpp # scripting wrappers script/katescriptdocument.cpp script/katescriptview.cpp script/katescripthelpers.cpp # mode (modemanager and co) mode/katemodemanager.cpp mode/katemodeconfigpage.cpp mode/katemodemenu.cpp mode/katewildcardmatcher.cpp # modeline variable editor variableeditor/variablelineedit.cpp variableeditor/variablelistview.cpp variableeditor/variableeditor.cpp variableeditor/variableitem.cpp variableeditor/katehelpbutton.cpp # printing classes printing/kateprinter.cpp printing/printpainter.cpp printing/printconfigwidgets.cpp # rendering stuff (katerenderer and helpers) render/katerenderer.cpp render/katerenderrange.cpp render/katelayoutcache.cpp render/katetextlayout.cpp render/katelinelayout.cpp # search stuff search/kateregexp.cpp search/kateplaintextsearch.cpp search/kateregexpsearch.cpp search/katematch.cpp search/katesearchbar.cpp # syntax related stuff (highlighting, xml file parsing, ...) syntax/katesyntaxmanager.cpp syntax/katehighlight.cpp syntax/katehighlighthelpers.cpp syntax/katehighlightmenu.cpp syntax/katesyntaxdocument.cpp syntax/katehighlightingcmds.cpp # view stuff (THE view and its helpers) view/kateview.cpp view/kateviewinternal.cpp view/kateviewhelpers.cpp view/katemessagewidget.cpp view/katefadeeffect.cpp view/kateanimation.cpp view/katetextanimation.cpp view/katetextpreview.cpp view/katestatusbar.cpp view/wordcounter.cpp view/katemulticursor.cpp view/katemulticlipboard.cpp # spell checking spellcheck/prefixstore.h spellcheck/prefixstore.cpp spellcheck/ontheflycheck.h spellcheck/ontheflycheck.cpp spellcheck/spellcheck.h spellcheck/spellcheck.cpp spellcheck/spellcheckdialog.h spellcheck/spellcheckdialog.cpp spellcheck/spellcheckbar.cpp spellcheck/spellingmenu.h spellcheck/spellingmenu.cpp # generic stuff, unsorted... utils/katecmds.cpp utils/kateconfig.cpp utils/katebookmarks.cpp utils/kateautoindent.cpp utils/katetemplatehandler.cpp utils/kateglobal.cpp utils/katecmd.cpp utils/ktexteditor.cpp utils/document.cpp utils/range.cpp utils/documentcursor.cpp utils/attribute.cpp utils/codecompletioninterface.cpp utils/codecompletionmodel.cpp utils/codecompletionmodelcontrollerinterface.cpp utils/configinterface.cpp utils/movinginterface.cpp utils/movingcursor.cpp utils/movingrange.cpp utils/movingrangefeedback.cpp utils/messageinterface.cpp utils/application.cpp utils/mainwindow.cpp utils/katedefaultcolors.cpp utils/katecommandrangeexpressionparser.cpp utils/katesedcmd.cpp # schema schema/kateschema.cpp schema/kateschemaconfig.cpp schema/katestyletreewidget.cpp schema/katecolortreewidget.cpp schema/katecategorydrawer.cpp # swapfile swapfile/kateswapdiffcreator.cpp swapfile/kateswapfile.cpp # export as HTML export/exporter.cpp export/htmlexporter.cpp # input modes inputmode/kateabstractinputmode.cpp inputmode/kateabstractinputmodefactory.cpp inputmode/katenormalinputmode.cpp inputmode/katenormalinputmodefactory.cpp ) +# optionally compile with EditorConfig support +if(EditorConfig_FOUND) + set(ktexteditor_LIB_SRCS ${ktexteditor_LIB_SRCS} document/editorconfig.cpp) +endif() + ki18n_wrap_ui(ktexteditor_LIB_SRCS dialogs/textareaappearanceconfigwidget.ui dialogs/bordersappearanceconfigwidget.ui dialogs/commandmenuconfigwidget.ui dialogs/commandmenueditwidget.ui dialogs/completionconfigtab.ui dialogs/navigationconfigwidget.ui dialogs/editconfigwidget.ui dialogs/filetypeconfigwidget.ui dialogs/indentationconfigwidget.ui dialogs/opensaveconfigwidget.ui dialogs/opensaveconfigadvwidget.ui dialogs/completionconfigwidget.ui search/searchbarincremental.ui search/searchbarpower.ui spellcheck/spellcheckbar.ui dialogs/spellcheckconfigwidget.ui schema/howtoimportschema.ui ) # add the resource files, the one with mascot + ui file and the generated ones qt5_add_resources( ktexteditor_LIB_SRCS data/ktexteditor.qrc "${CMAKE_CURRENT_BINARY_DIR}/script/data/script.qrc") if (BUILD_VIMODE) ki18n_wrap_ui(ktexteditor_LIB_SRCS vimode/config/configwidget.ui) set(ktexteditor_LIB_SRCS ${ktexteditor_LIB_SRCS} inputmode/kateviinputmode.cpp inputmode/kateviinputmodefactory.cpp # vi input mode vimode/config/configtab.cpp vimode/modes/insertvimode.cpp vimode/modes/modebase.cpp vimode/modes/normalvimode.cpp vimode/modes/replacevimode.cpp vimode/modes/visualvimode.cpp vimode/appcommands.cpp vimode/cmds.cpp vimode/inputmodemanager.cpp vimode/command.cpp vimode/motion.cpp vimode/range.cpp vimode/keyparser.cpp vimode/globalstate.cpp vimode/emulatedcommandbar/emulatedcommandbar.cpp vimode/emulatedcommandbar/matchhighlighter.cpp vimode/emulatedcommandbar/completer.cpp vimode/emulatedcommandbar/activemode.cpp vimode/emulatedcommandbar/interactivesedreplacemode.cpp vimode/emulatedcommandbar/searchmode.cpp vimode/emulatedcommandbar/commandmode.cpp vimode/commandrangeexpressionparser.cpp vimode/keymapper.cpp vimode/marks.cpp vimode/jumps.cpp vimode/history.cpp vimode/macros.cpp vimode/mappings.cpp vimode/registers.cpp vimode/searcher.cpp vimode/completion.cpp vimode/completionrecorder.cpp vimode/completionreplayer.cpp vimode/macrorecorder.cpp vimode/lastchangerecorder.cpp ) endif() add_library(KF5TextEditor ${ktexteditor_LIB_SRCS} ${KTEXTEDITOR_PUBLIC_HEADERS}) generate_export_header(KF5TextEditor BASE_NAME KTextEditor) add_library(KF5::TextEditor ALIAS KF5TextEditor) target_include_directories(KF5TextEditor INTERFACE "$") # API is more or less KParts++, other stuff is used only internally target_link_libraries(KF5TextEditor PUBLIC KF5::Parts PRIVATE Qt5::Script Qt5::PrintSupport KF5::I18n KF5::Archive KF5::GuiAddons KF5::IconThemes KF5::ItemViews KF5::SonnetCore KF5::SyntaxHighlighting ${KTEXTEDITOR_OPTIONAL_LIBS} ) set_target_properties(KF5TextEditor PROPERTIES VERSION ${KTEXTEDITOR_VERSION_STRING} SOVERSION ${KTEXTEDITOR_SOVERSION} EXPORT_NAME "TextEditor" ) install(TARGETS KF5TextEditor EXPORT KF5TextEditorTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ktexteditor_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KTextEditor COMPONENT Devel ) +if(BUILD_QCH) + ecm_add_qch( + KF5TextEditor_QCH + NAME KTextEditor + BASE_NAME KF5TextEditor + VERSION ${KF5_VERSION} + ORG_DOMAIN org.kde + SOURCES # using only public headers, to cover only public API + ${KTEXTEDITOR_PUBLIC_HEADERS} + "${CMAKE_SOURCE_DIR}/docs/apidocs-groups.dox" + "${CMAKE_SOURCE_DIR}/docs/coding-guidelines.dox" + "${CMAKE_SOURCE_DIR}/docs/design.dox" + "${CMAKE_SOURCE_DIR}/docs/porting.dox" + MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" + IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics" + LINK_QCHS + KF5Parts_QCH + BLANK_MACROS + KTEXTEDITOR_EXPORT + KTEXTEDITOR_DEPRECATED + KTEXTEDITOR_DEPRECATED_EXPORT + TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + COMPONENT Devel + ) +endif() + include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KTextEditor LIB_NAME KF5TextEditor DEPS "KParts" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KTextEditor) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) + +add_executable(kauth_ktexteditor_helper buffer/katesecuretextbuffer.cpp) +target_link_libraries(kauth_ktexteditor_helper + KF5::Auth +) +install(TARGETS kauth_ktexteditor_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR} ) +kauth_install_helper_files(kauth_ktexteditor_helper org.kde.ktexteditor.katetextbuffer root) +kauth_install_actions(org.kde.ktexteditor.katetextbuffer buffer/org.kde.ktexteditor.katetextbuffer.actions) + # add part add_subdirectory(part) diff --git a/src/buffer/katesecuretextbuffer.cpp b/src/buffer/katesecuretextbuffer.cpp new file mode 100644 index 00000000..98b96cef --- /dev/null +++ b/src/buffer/katesecuretextbuffer.cpp @@ -0,0 +1,166 @@ +/* This file is part of the KTextEditor project. + * + * Copyright (C) 2017 KDE Developers + * + * 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 "katesecuretextbuffer_p.h" + +#ifndef Q_OS_WIN +#include +#include +#endif + +#include +#include +#include +#include +#include + +KAUTH_HELPER_MAIN("org.kde.ktexteditor.katetextbuffer", SecureTextBuffer) + +ActionReply SecureTextBuffer::savefile(const QVariantMap &args) +{ + const QString sourceFile = args[QLatin1String("sourceFile")].toString(); + const QString targetFile = args[QLatin1String("targetFile")].toString(); + const QByteArray checksum = args[QLatin1String("checksum")].toByteArray(); + const uint ownerId = (uint) args[QLatin1String("ownerId")].toInt(); + const uint groupId = (uint) args[QLatin1String("groupId")].toInt(); + + if (saveFileInternal(sourceFile, targetFile, checksum, ownerId, groupId)) { + return ActionReply::SuccessReply(); + } + + return ActionReply::HelperErrorReply(); +} + +bool SecureTextBuffer::saveFileInternal(const QString &sourceFile, const QString &targetFile, + const QByteArray &checksum, const uint ownerId, const uint groupId) +{ + QFileInfo targetFileInfo(targetFile); + if (!QDir::setCurrent(targetFileInfo.dir().path())) { + return false; + } + + // get information about target file + const QString targetFileName = targetFileInfo.fileName(); + targetFileInfo.setFile(targetFileName); + const bool newFile = !targetFileInfo.exists(); + + // open source and target file + QFile readFile(sourceFile); + //TODO use QSaveFile for saving contents and automatic atomic move on commit() when QSaveFile's security problem + // (default temporary file permissions) is fixed + // + // We will first generate temporary filename and then use it relatively to prevent an attacker + // to trick us to write contents to a different file by changing underlying directory. + QTemporaryFile tempFile(targetFileName); + if (!tempFile.open()) { + return false; + } + tempFile.close(); + QString tempFileName = QFileInfo(tempFile).fileName(); + tempFile.setFileName(tempFileName); + if (!readFile.open(QIODevice::ReadOnly) || !tempFile.open()) { + return false; + } + const int tempFileDescriptor = tempFile.handle(); + + // prepare checksum maker + QCryptographicHash cryptographicHash(checksumAlgorithm); + + // copy contents + char buffer[bufferLength]; + qint64 read = -1; + while ((read = readFile.read(buffer, bufferLength)) > 0) { + cryptographicHash.addData(buffer, read); + if (tempFile.write(buffer, read) == -1) { + return false; + } + } + + // check that copying was successful and checksum matched + QByteArray localChecksum = cryptographicHash.result(); + if (read == -1 || localChecksum != checksum || !tempFile.flush()) { + return false; + } + + tempFile.close(); + + if (newFile) { + // ensure new file is readable by anyone + tempFile.setPermissions(tempFile.permissions() | QFile::Permission::ReadGroup | QFile::Permission::ReadOther); + } else { + // ensure the same file permissions + tempFile.setPermissions(targetFileInfo.permissions()); + // ensure file has the same owner and group as before + setOwner(tempFileDescriptor, ownerId, groupId); + } + + // rename temporary file to the target file + if (moveFile(tempFileName, targetFileName)) { + // temporary file was renamed, there is nothing to remove anymore + tempFile.setAutoRemove(false); + return true; + } + return false; +} + +void SecureTextBuffer::setOwner(const int filedes, const uint ownerId, const uint groupId) +{ +#ifndef Q_OS_WIN + if (ownerId != (uint)-2 && groupId != (uint)-2) { + const int result = fchown(filedes, ownerId, groupId); + // set at least correct group if owner cannot be changed + if (result != 0 && errno == EPERM) { + fchown(filedes, getuid(), groupId); + } + } +#else + // no-op for windows +#endif +} + +bool SecureTextBuffer::moveFile(const QString &sourceFile, const QString &targetFile) +{ +#ifndef Q_OS_WIN + const int result = std::rename(QFile::encodeName(sourceFile).constData(), QFile::encodeName(targetFile).constData()); + if (result == 0) { + syncToDisk(QFile(targetFile).handle()); + return true; + } + return false; +#else + // use racy fallback for windows + QFile::remove(targetFile); + return QFile::rename(sourceFile, targetFile); +#endif +} + +void SecureTextBuffer::syncToDisk(const int fd) +{ +#ifndef Q_OS_WIN +#ifdef HAVE_FDATASYNC + fdatasync(fd); +#else + fsync(fd); +#endif +#else + // no-op for windows +#endif +} + diff --git a/src/buffer/katesecuretextbuffer_p.h b/src/buffer/katesecuretextbuffer_p.h new file mode 100644 index 00000000..a38285b6 --- /dev/null +++ b/src/buffer/katesecuretextbuffer_p.h @@ -0,0 +1,80 @@ +/* This file is part of the KTextEditor project. + * + * Copyright (C) 2017 KDE Developers + * + * 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_SECURE_TEXTBUFFER_P_H +#define KATE_SECURE_TEXTBUFFER_P_H + +#include +#include +#include + +#include + +using namespace KAuth; + +/** + * Class used as KAuth helper binary. + * It is supposed to be called through KAuth action. + * + * It also contains couple of common methods intended to be used + * directly by TextBuffer as well as from helper binary. + * + * This class should only be used by TextBuffer. + */ +class SecureTextBuffer : public QObject +{ + Q_OBJECT + +public: + + SecureTextBuffer() {} + + ~SecureTextBuffer() {} + + /** + * Common helper method + */ + static void setOwner(const int filedes, const uint ownerId, const uint groupId); + + static const QCryptographicHash::Algorithm checksumAlgorithm = QCryptographicHash::Algorithm::Sha512; + +private: + static const qint64 bufferLength = 4096; + + /** + * Saves file contents using sets permissions. + */ + static bool saveFileInternal(const QString &sourceFile, const QString &targetFile, + const QByteArray &checksum, const uint ownerId, const uint groupId); + + static bool moveFile(const QString &sourceFile, const QString &targetFile); + + static void syncToDisk(const int fd); + +public Q_SLOTS: + /** + * KAuth action to perform both prepare or move work based on given parameters. + * We keep this code in one method to prevent multiple KAuth user queries during one save action. + */ + static ActionReply savefile(const QVariantMap &args); + +}; + +#endif diff --git a/src/buffer/katetextbuffer.cpp b/src/buffer/katetextbuffer.cpp index 7cb92b68..aa5a4555 100644 --- a/src/buffer/katetextbuffer.cpp +++ b/src/buffer/katetextbuffer.cpp @@ -1,962 +1,1045 @@ /* This file is part of the Kate project. * * 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 "config.h" +#include "kateglobal.h" #include "katetextbuffer.h" +#include "katesecuretextbuffer_p.h" #include "katetextloader.h" // this is unfortunate, but needed for performance #include "katedocument.h" #include "kateview.h" #include "katepartdebug.h" #ifndef Q_OS_WIN #include - -// needed for umask application -#include -#include #endif #include +#include +#include +#include +#include #if 0 #define BUFFER_DEBUG qCDebug(LOG_KTE) #else #define BUFFER_DEBUG if (0) qCDebug(LOG_KTE) #endif namespace Kate { -TextBuffer::TextBuffer(KTextEditor::DocumentPrivate *parent, int blockSize) +TextBuffer::TextBuffer(KTextEditor::DocumentPrivate *parent, int blockSize, bool alwaysUseKAuth) : QObject(parent) , m_document(parent) , m_history(*this) , m_blockSize(blockSize) , m_lines(0) , m_lastUsedBlock(0) , m_revision(0) , m_editingTransactions(0) , m_editingLastRevision(0) , m_editingLastLines(0) , m_editingMinimalLineChanged(-1) , m_editingMaximalLineChanged(-1) , m_encodingProberType(KEncodingProber::Universal) - , m_fallbackTextCodec(0) - , m_textCodec(0) + , m_fallbackTextCodec(nullptr) + , m_textCodec(nullptr) , m_generateByteOrderMark(false) , m_endOfLineMode(eolUnix) , m_newLineAtEof(false) , m_lineLengthLimit(4096) + , m_alwaysUseKAuthForSave(alwaysUseKAuth) { // minimal block size must be > 0 Q_ASSERT(m_blockSize > 0); // create initial state clear(); } TextBuffer::~TextBuffer() { // remove document pointer, this will avoid any notifyAboutRangeChange to have a effect - m_document = 0; + m_document = nullptr; // not allowed during editing Q_ASSERT(m_editingTransactions == 0); // kill all ranges, work on copy, they will remove themself from the hash QSet copyRanges = m_ranges; qDeleteAll(copyRanges); Q_ASSERT(m_ranges.empty()); // clean out all cursors and lines, only cursors belonging to range will survive foreach (TextBlock *block, m_blocks) { block->deleteBlockContent(); } // delete all blocks, now that all cursors are really deleted // else asserts in destructor of blocks will fail! qDeleteAll(m_blocks); m_blocks.clear(); // kill all invalid cursors, do this after block deletion, to uncover if they might be still linked in blocks QSet copyCursors = m_invalidCursors; qDeleteAll(copyCursors); Q_ASSERT(m_invalidCursors.empty()); } void TextBuffer::invalidateRanges() { // invalidate all ranges, work on copy, they might delete themself... QSet copyRanges = m_ranges; foreach (TextRange *range, copyRanges) { range->setRange(KTextEditor::Cursor::invalid(), KTextEditor::Cursor::invalid()); } } void TextBuffer::clear() { // not allowed during editing Q_ASSERT(m_editingTransactions == 0); invalidateRanges(); // new block for empty buffer TextBlock *newBlock = new TextBlock(this, 0); newBlock->appendLine(QString()); // clean out all cursors and lines, either move them to newBlock or invalidate them, if belonging to a range foreach (TextBlock *block, m_blocks) { block->clearBlockContent(newBlock); } // kill all buffer blocks qDeleteAll(m_blocks); m_blocks.clear(); // insert one block with one empty line m_blocks.append(newBlock); // reset lines and last used block m_lines = 1; m_lastUsedBlock = 0; // reset revision m_revision = 0; // reset bom detection m_generateByteOrderMark = false; // reset the filter device m_mimeTypeForFilterDev = QStringLiteral("text/plain"); // clear edit history m_history.clear(); // we got cleared emit cleared(); } TextLine TextBuffer::line(int line) const { // get block, this will assert on invalid line int blockIndex = blockForLine(line); // get line return m_blocks.at(blockIndex)->line(line); } QString TextBuffer::text() const { QString text; // combine all blocks foreach (TextBlock *block, m_blocks) { block->text(text); } // return generated string return text; } bool TextBuffer::startEditing() { // increment transaction counter ++m_editingTransactions; // if not first running transaction, do nothing if (m_editingTransactions > 1) { return false; } // reset information about edit... m_editingLastRevision = m_revision; m_editingLastLines = m_lines; m_editingMinimalLineChanged = -1; m_editingMaximalLineChanged = -1; // transaction has started emit editingStarted(); if (m_document) emit m_document->KTextEditor::Document::editingStarted(m_document); // first transaction started return true; } bool TextBuffer::finishEditing() { // only allowed if still transactions running Q_ASSERT(m_editingTransactions > 0); // decrement counter --m_editingTransactions; // if not last running transaction, do nothing if (m_editingTransactions > 0) { return false; } // assert that if buffer changed, the line ranges are set and valid! Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged != -1 && m_editingMaximalLineChanged != -1)); Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged <= m_editingMaximalLineChanged)); Q_ASSERT(!editingChangedBuffer() || (m_editingMinimalLineChanged >= 0 && m_editingMinimalLineChanged < m_lines)); Q_ASSERT(!editingChangedBuffer() || (m_editingMaximalLineChanged >= 0 && m_editingMaximalLineChanged < m_lines)); // transaction has finished emit editingFinished(); if (m_document) emit m_document->KTextEditor::Document::editingFinished(m_document); // last transaction finished return true; } void TextBuffer::wrapLine(const KTextEditor::Cursor &position) { // debug output for REAL low-level debugging BUFFER_DEBUG << "wrapLine" << position; // only allowed if editing transaction running Q_ASSERT(m_editingTransactions > 0); // get block, this will assert on invalid line int blockIndex = blockForLine(position.line()); /** * let the block handle the wrapLine * this can only lead to one more line in this block * no other blocks will change * this call will trigger fixStartLines */ ++m_lines; // first alter the line counter, as functions called will need the valid one m_blocks.at(blockIndex)->wrapLine(position, blockIndex); // remember changes ++m_revision; // update changed line interval if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) { m_editingMinimalLineChanged = position.line(); } if (position.line() <= m_editingMaximalLineChanged) { ++m_editingMaximalLineChanged; } else { m_editingMaximalLineChanged = position.line() + 1; } // balance the changed block if needed balanceBlock(blockIndex); // emit signal about done change emit lineWrapped(position); if (m_document) emit m_document->KTextEditor::Document::lineWrapped(m_document, position); } void TextBuffer::unwrapLine(int line) { // debug output for REAL low-level debugging BUFFER_DEBUG << "unwrapLine" << line; // only allowed if editing transaction running Q_ASSERT(m_editingTransactions > 0); // line 0 can't be unwrapped Q_ASSERT(line > 0); // get block, this will assert on invalid line int blockIndex = blockForLine(line); // is this the first line in the block? bool firstLineInBlock = (line == m_blocks.at(blockIndex)->startLine()); /** * let the block handle the unwrapLine * this can either lead to one line less in this block or the previous one * the previous one could even end up with zero lines * this call will trigger fixStartLines */ - m_blocks.at(blockIndex)->unwrapLine(line, (blockIndex > 0) ? m_blocks.at(blockIndex - 1) : 0, firstLineInBlock ? (blockIndex - 1) : blockIndex); + m_blocks.at(blockIndex)->unwrapLine(line, (blockIndex > 0) ? m_blocks.at(blockIndex - 1) : nullptr, firstLineInBlock ? (blockIndex - 1) : blockIndex); --m_lines; // decrement index for later fixup, if we modified the block in front of the found one if (firstLineInBlock) { --blockIndex; } // remember changes ++m_revision; // update changed line interval if ((line - 1) < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) { m_editingMinimalLineChanged = line - 1; } if (line <= m_editingMaximalLineChanged) { --m_editingMaximalLineChanged; } else { m_editingMaximalLineChanged = line - 1; } // balance the changed block if needed balanceBlock(blockIndex); // emit signal about done change emit lineUnwrapped(line); if (m_document) emit m_document->KTextEditor::Document::lineUnwrapped(m_document, line); } void TextBuffer::insertText(const KTextEditor::Cursor &position, const QString &text) { // debug output for REAL low-level debugging BUFFER_DEBUG << "insertText" << position << text; // only allowed if editing transaction running Q_ASSERT(m_editingTransactions > 0); // skip work, if no text to insert if (text.isEmpty()) { return; } // get block, this will assert on invalid line int blockIndex = blockForLine(position.line()); // let the block handle the insertText m_blocks.at(blockIndex)->insertText(position, text); // remember changes ++m_revision; // update changed line interval if (position.line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) { m_editingMinimalLineChanged = position.line(); } if (position.line() > m_editingMaximalLineChanged) { m_editingMaximalLineChanged = position.line(); } // emit signal about done change emit textInserted(position, text); if (m_document) emit m_document->KTextEditor::Document::textInserted(m_document, position, text); } void TextBuffer::removeText(const KTextEditor::Range &range) { // debug output for REAL low-level debugging BUFFER_DEBUG << "removeText" << range; // only allowed if editing transaction running Q_ASSERT(m_editingTransactions > 0); // only ranges on one line are supported Q_ASSERT(range.start().line() == range.end().line()); // start column <= end column and >= 0 Q_ASSERT(range.start().column() <= range.end().column()); Q_ASSERT(range.start().column() >= 0); // skip work, if no text to remove if (range.isEmpty()) { return; } // get block, this will assert on invalid line int blockIndex = blockForLine(range.start().line()); // let the block handle the removeText, retrieve removed text QString text; m_blocks.at(blockIndex)->removeText(range, text); // remember changes ++m_revision; // update changed line interval if (range.start().line() < m_editingMinimalLineChanged || m_editingMinimalLineChanged == -1) { m_editingMinimalLineChanged = range.start().line(); } if (range.start().line() > m_editingMaximalLineChanged) { m_editingMaximalLineChanged = range.start().line(); } // emit signal about done change emit textRemoved(range, text); if (m_document) emit m_document->KTextEditor::Document::textRemoved(m_document, range, text); } int TextBuffer::blockForLine(int line) const { // only allow valid lines if ((line < 0) || (line >= lines())) { qFatal("out of range line requested in text buffer (%d out of [0, %d[)", line, lines()); } // we need blocks and last used block should not be negative Q_ASSERT(!m_blocks.isEmpty()); Q_ASSERT(m_lastUsedBlock >= 0); /** * shortcut: try last block first */ if (m_lastUsedBlock < m_blocks.size()) { /** * check if block matches * if yes, just return again this block */ TextBlock *block = m_blocks[m_lastUsedBlock]; const int start = block->startLine(); const int lines = block->lines(); if (start <= line && line < (start + lines)) { return m_lastUsedBlock; } } /** * search for right block * use binary search * if we leave this loop not by returning the found element we have an error */ int blockStart = 0; int blockEnd = m_blocks.size() - 1; while (blockEnd >= blockStart) { // get middle and ensure it is OK int middle = blockStart + ((blockEnd - blockStart) / 2); Q_ASSERT(middle >= 0); Q_ASSERT(middle < m_blocks.size()); // facts bout this block TextBlock *block = m_blocks[middle]; const int start = block->startLine(); const int lines = block->lines(); // right block found, remember it and return it if (start <= line && line < (start + lines)) { m_lastUsedBlock = middle; return middle; } // half our stuff ;) if (line < start) { blockEnd = middle - 1; } else { blockStart = middle + 1; } } // we should always find a block qFatal("line requested in text buffer (%d out of [0, %d[), no block found", line, lines()); return -1; } void TextBuffer::fixStartLines(int startBlock) { // only allow valid start block Q_ASSERT(startBlock >= 0); Q_ASSERT(startBlock < m_blocks.size()); // new start line for next block TextBlock *block = m_blocks.at(startBlock); int newStartLine = block->startLine() + block->lines(); // fixup block for (int index = startBlock + 1; index < m_blocks.size(); ++index) { // set new start line block = m_blocks.at(index); block->setStartLine(newStartLine); // calculate next start line newStartLine += block->lines(); } } void TextBuffer::balanceBlock(int index) { /** * two cases, too big or too small block */ TextBlock *blockToBalance = m_blocks.at(index); // first case, too big one, split it if (blockToBalance->lines() >= 2 * m_blockSize) { // half the block int halfSize = blockToBalance->lines() / 2; // create and insert new block behind current one, already set right start line TextBlock *newBlock = blockToBalance->splitBlock(halfSize); Q_ASSERT(newBlock); m_blocks.insert(m_blocks.begin() + index + 1, newBlock); // split is done return; } // second case: possibly too small block // if only one block, no chance to unite // same if this is first block, we always append to previous one if (index == 0) { return; } // block still large enough, do nothing if (2 * blockToBalance->lines() > m_blockSize) { return; } // unite small block with predecessor TextBlock *targetBlock = m_blocks.at(index - 1); // merge block blockToBalance->mergeBlock(targetBlock); // delete old block delete blockToBalance; m_blocks.erase(m_blocks.begin() + index); } void TextBuffer::debugPrint(const QString &title) const { // print header with title printf("%s (lines: %d bs: %d)\n", qPrintable(title), m_lines, m_blockSize); // print all blocks for (int i = 0; i < m_blocks.size(); ++i) { m_blocks.at(i)->debugPrint(i); } } bool TextBuffer::load(const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, int &longestLineLoaded, bool enforceTextCodec) { // fallback codec must exist Q_ASSERT(m_fallbackTextCodec); // codec must be set! Q_ASSERT(m_textCodec); /** * first: clear buffer in any case! */ clear(); /** * construct the file loader for the given file, with correct prober type */ Kate::TextLoader file(filename, m_encodingProberType); /** * triple play, maximal three loading rounds * 0) use the given encoding, be done, if no encoding errors happen - * 1) use BOM to decided if unicode or if that fails, use encoding prober, if no encoding errors happen, be done + * 1) use BOM to decided if Unicode or if that fails, use encoding prober, if no encoding errors happen, be done * 2) use fallback encoding, be done, if no encoding errors happen * 3) use again given encoding, be done in any case */ for (int i = 0; i < (enforceTextCodec ? 1 : 4); ++i) { /** * kill all blocks beside first one */ for (int b = 1; b < m_blocks.size(); ++b) { TextBlock *block = m_blocks.at(b); block->clearLines(); delete block; } m_blocks.resize(1); /** * remove lines in first block */ m_blocks.last()->clearLines(); m_lines = 0; /** * try to open file, with given encoding * in round 0 + 3 use the given encoding from user * in round 1 use 0, to trigger detection * in round 2 use fallback */ QTextCodec *codec = m_textCodec; if (i == 1) { - codec = 0; + codec = nullptr; } else if (i == 2) { codec = m_fallbackTextCodec; } if (!file.open(codec)) { // create one dummy textline, in any case m_blocks.last()->appendLine(QString()); m_lines++; return false; } // read in all lines... encodingErrors = false; while (!file.eof()) { // read line int offset = 0, length = 0; bool currentError = !file.readLine(offset, length); encodingErrors = encodingErrors || currentError; // bail out on encoding error, if not last round! if (encodingErrors && i < (enforceTextCodec ? 0 : 3)) { BUFFER_DEBUG << "Failed try to load file" << filename << "with codec" << (file.textCodec() ? file.textCodec()->name() : "(null)"); break; } - // get unicode data for this line + // get Unicode data for this line const QChar *unicodeData = file.unicode() + offset; if (longestLineLoaded < length) longestLineLoaded=length; /** * split lines, if too large */ do { /** * calculate line length */ int lineLength = length; if ((m_lineLengthLimit > 0) && (lineLength > m_lineLengthLimit)) { /** * search for place to wrap */ int spacePosition = m_lineLengthLimit - 1; for (int testPosition = m_lineLengthLimit - 1; (testPosition >= 0) && (testPosition >= (m_lineLengthLimit - (m_lineLengthLimit / 10))); --testPosition) { /** * wrap place found? */ if (unicodeData[testPosition].isSpace() || unicodeData[testPosition].isPunct()) { spacePosition = testPosition; break; } } /** * wrap the line */ lineLength = spacePosition + 1; length -= lineLength; tooLongLinesWrapped = true; } else { /** * be done after this round */ length = 0; } /** * construct new text line with content from file * move data pointer */ QString textLine(unicodeData, lineLength); unicodeData += lineLength; /** * ensure blocks aren't too large */ if (m_blocks.last()->lines() >= m_blockSize) { m_blocks.append(new TextBlock(this, m_blocks.last()->startLine() + m_blocks.last()->lines())); } /** * append line to last block */ m_blocks.last()->appendLine(textLine); ++m_lines; } while (length > 0); } // if no encoding error, break out of reading loop if (!encodingErrors) { // remember used codec, might change bom setting setTextCodec(file.textCodec()); break; } } // save checksum of file on disk setDigest(file.digest()); // remember if BOM was found if (file.byteOrderMarkFound()) { setGenerateByteOrderMark(true); } // remember eol mode, if any found in file if (file.eol() != eolUnknown) { setEndOfLineMode(file.eol()); } // remember mime type for filter device m_mimeTypeForFilterDev = file.mimeTypeForFilterDev(); // assert that one line is there! Q_ASSERT(m_lines > 0); // report CODEC + ERRORS BUFFER_DEBUG << "Loaded file " << filename << "with codec" << m_textCodec->name() << (encodingErrors ? "with" : "without") << "encoding errors"; // report BOM BUFFER_DEBUG << (file.byteOrderMarkFound() ? "Found" : "Didn't find") << "byte order mark"; // report filter device mime-type BUFFER_DEBUG << "used filter device for mime-type" << m_mimeTypeForFilterDev; // emit success emit loaded(filename, encodingErrors); // file loading worked, modulo encoding problems return true; } const QByteArray &TextBuffer::digest() const { return m_digest; } void TextBuffer::setDigest(const QByteArray &checksum) { m_digest = checksum; } void TextBuffer::setTextCodec(QTextCodec *codec) { m_textCodec = codec; // enforce bom for some encodings int mib = m_textCodec->mibEnum(); if (mib == 1013 || mib == 1014 || mib == 1015) { // utf16 setGenerateByteOrderMark(true); } if (mib == 1017 || mib == 1018 || mib == 1019) { // utf32 setGenerateByteOrderMark(true); } } bool TextBuffer::save(const QString &filename) { // codec must be set! Q_ASSERT(m_textCodec); -#ifndef Q_OS_WIN const bool newFile = !QFile::exists(filename); -#endif /** - * use QSaveFile for save write + rename + * Memorize owner and group. Due to design of QSaveFile we will have to re-set them after save is complete. */ - QSaveFile saveFile(filename); - saveFile.setDirectWriteFallback(true); + uint ownerId = -2; + uint groupId = -2; + if (!newFile) { + QFileInfo fileInfo(filename); + ownerId = fileInfo.ownerId(); + groupId = fileInfo.groupId(); + } - if (!saveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - return false; + /** + * use QSaveFile for file saving + */ + QScopedPointer saveFile(new QSaveFile(filename)); + static_cast(saveFile.data())->setDirectWriteFallback(true); + + bool usingTemporaryBuffer = false; + + // open QSaveFile for write + if (m_alwaysUseKAuthForSave || !saveFile->open(QIODevice::WriteOnly)) { + + // if that fails we need more privileges to save this file + // -> we write to a temporary file and then send its path to KAuth action for privileged save + + usingTemporaryBuffer = true; + + // we are now saving to a temporary buffer + saveFile.reset(new QBuffer()); + + // open buffer for write and read (read is used for checksum computing and writing to temporary file) + if (!saveFile->open(QIODevice::ReadWrite)) { + return false; + } } /** * construct correct filter device and try to open */ KCompressionDevice::CompressionType type = KFilterDev::compressionTypeForMimeType(m_mimeTypeForFilterDev); - KCompressionDevice file(&saveFile, false, type); + KCompressionDevice file(saveFile.data(), false, type); if (!file.open(QIODevice::WriteOnly)) { return false; } /** * construct stream + disable Unicode headers */ QTextStream stream(&file); stream.setCodec(QTextCodec::codecForName("UTF-16")); // set the correct codec stream.setCodec(m_textCodec); // generate byte order mark? stream.setGenerateByteOrderMark(generateByteOrderMark()); // our loved eol string ;) QString eol = QStringLiteral("\n"); //m_doc->config()->eolString (); if (endOfLineMode() == eolDos) { eol = QStringLiteral("\r\n"); } else if (endOfLineMode() == eolMac) { eol = QStringLiteral("\r"); } // just dump the lines out ;) for (int i = 0; i < m_lines; ++i) { // get line to save Kate::TextLine textline = line(i); stream << textline->text(); // append correct end of line string if ((i + 1) < m_lines) { stream << eol; } } if (m_newLineAtEof) { Q_ASSERT(m_lines > 0); // see .h file const Kate::TextLine lastLine = line(m_lines - 1); const int firstChar = lastLine->firstChar(); if (firstChar > -1 || lastLine->length() > 0) { stream << eol; } } // flush stream stream.flush(); // close and delete file file.close(); // flush file - if (!saveFile.flush()) { - return false; + if (!usingTemporaryBuffer) { + static_cast(saveFile.data())->flush(); } -#ifndef Q_OS_WIN - // ensure that the file is written to disk -#ifdef HAVE_FDATASYNC - fdatasync(saveFile.handle()); -#else - fsync(saveFile.handle()); -#endif -#endif - // did save work? // only finalize if stream status == OK - bool ok = (stream.status() == QTextStream::Ok) && saveFile.commit(); + bool ok = (stream.status() == QTextStream::Ok); - // remember this revision as last saved if we had success! + // commit changes if (ok) { - m_history.setLastSavedRevision(); - } -#ifndef Q_OS_WIN - if (ok && newFile) { // QTemporaryFile sets permissions to 0600, so fixing this - const mode_t mask = umask(0); - umask(mask); + if (usingTemporaryBuffer) { + + // temporary buffer was used to save the file + // -> computing checksum + // -> saving to temporary file + // -> copying the temporary file to the original file location with KAuth action - const mode_t fileMode = 0666 & ~mask; - chmod(QFile::encodeName(filename).constData(), fileMode); + QTemporaryFile tempFile; + if (!tempFile.open()) { + return false; + } + QCryptographicHash cryptographicHash(SecureTextBuffer::checksumAlgorithm); + + // go to QBuffer start + saveFile->seek(0); + + // read contents of QBuffer and add them to checksum utility as well as to QTemporaryFile + char buffer[bufferLength]; + qint64 read = -1; + while ((read = saveFile->read(buffer, bufferLength)) > 0) { + cryptographicHash.addData(buffer, read); + if (tempFile.write(buffer, read) == -1) { + return false; + } + } + if (!tempFile.flush()) { + return false; + } + + // compute checksum + QByteArray checksum = cryptographicHash.result(); + + // prepare data for KAuth action + QVariantMap kAuthActionArgs; + kAuthActionArgs.insert(QLatin1String("sourceFile"), tempFile.fileName()); + kAuthActionArgs.insert(QLatin1String("targetFile"), filename); + kAuthActionArgs.insert(QLatin1String("checksum"), checksum); + kAuthActionArgs.insert(QLatin1String("ownerId"), ownerId); + kAuthActionArgs.insert(QLatin1String("groupId"), groupId); + + // call save with elevated privileges + if (KTextEditor::EditorPrivate::unitTestMode()) { + + // unit testing purposes only + ok = SecureTextBuffer::savefile(kAuthActionArgs).succeeded(); + + } else { + + KAuth::Action kAuthSaveAction(QLatin1String("org.kde.ktexteditor.katetextbuffer.savefile")); + kAuthSaveAction.setHelperId(QLatin1String("org.kde.ktexteditor.katetextbuffer")); + kAuthSaveAction.setArguments(kAuthActionArgs); + KAuth::ExecuteJob *job = kAuthSaveAction.execute(); + ok = job->exec(); + + } + + } else { + + // standard save without elevated privileges + + QSaveFile *saveFileLocal = static_cast(saveFile.data()); + + if (!newFile) { + // ensure correct owner + SecureTextBuffer::setOwner(saveFileLocal->handle(), ownerId, groupId); + } + + ok = saveFileLocal->commit(); + + } + } + + // remember this revision as last saved if we had success! + if (ok) { + m_history.setLastSavedRevision(); } -#endif // report CODEC + ERRORS BUFFER_DEBUG << "Saved file " << filename << "with codec" << m_textCodec->name() << (ok ? "without" : "with") << "errors"; if (ok) { markModifiedLinesAsSaved(); } // emit signal on success if (ok) { emit saved(filename); } // return success or not return ok; } void TextBuffer::notifyAboutRangeChange(KTextEditor::View *view, int startLine, int endLine, bool rangeWithAttribute) { /** * ignore calls if no document is around */ if (!m_document) { return; } /** * update all views, this IS ugly and could be a signal, but I profiled and a signal is TOO slow, really * just create 20k ranges in a go and you wait seconds on a decent machine */ const QList &views = m_document->views(); foreach (KTextEditor::View *curView, views) { // filter wrong views if (view && view != curView) { continue; } // notify view, it is really a kate view static_cast(curView)->notifyAboutRangeChange(startLine, endLine, rangeWithAttribute); } } void TextBuffer::markModifiedLinesAsSaved() { foreach (TextBlock *block, m_blocks) { block->markModifiedLinesAsSaved(); } } QList TextBuffer::rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const { // get block, this will assert on invalid line const int blockIndex = blockForLine(line); // get the ranges of the right block QList rightRanges; foreach (const QSet &ranges, m_blocks.at(blockIndex)->rangesForLine(line)) { foreach (TextRange *const range, ranges) { /** * we want only ranges with attributes, but this one has none */ if (rangesWithAttributeOnly && !range->hasAttribute()) { continue; } /** * we want ranges for no view, but this one's attribute is only valid for views */ if (!view && range->attributeOnlyForViews()) { continue; } /** * the range's attribute is not valid for this view */ if (range->view() && range->view() != view) { continue; } /** * if line is in the range, ok */ if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal()) { rightRanges.append(range); } } } // return right ranges return rightRanges; } } diff --git a/src/buffer/katetextbuffer.h b/src/buffer/katetextbuffer.h index 296a647a..f8912f24 100644 --- a/src/buffer/katetextbuffer.h +++ b/src/buffer/katetextbuffer.h @@ -1,651 +1,661 @@ /* This file is part of the Kate project. * * 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. */ #ifndef KATE_TEXTBUFFER_H #define KATE_TEXTBUFFER_H #include #include #include #include #include #include #include "katedocument.h" #include #include "katetextblock.h" #include "katetextcursor.h" #include "katetextrange.h" #include "katetexthistory.h" // encoding prober #include namespace Kate { /** * Class representing a text buffer. * The interface is line based, internally the text will be stored in blocks of text lines. */ class KTEXTEDITOR_EXPORT TextBuffer : public QObject { friend class TextCursor; friend class TextRange; friend class TextBlock; Q_OBJECT public: /** * End of line mode */ enum EndOfLineMode { eolUnknown = -1 - , eolUnix = 0 - , eolDos = 1 - , eolMac = 2 + , eolUnix = 0 + , eolDos = 1 + , eolMac = 2 }; /** * Construct an empty text buffer. * Empty means one empty line in one block. * @param parent parent qobject * @param blockSize block size in lines the buffer should try to hold, default 64 lines */ - TextBuffer(KTextEditor::DocumentPrivate *parent, int blockSize = 64); + TextBuffer(KTextEditor::DocumentPrivate *parent, int blockSize = 64, bool alwaysUseKAuth = false); /** * Destruct the text buffer * Virtual, we allow inheritance */ virtual ~TextBuffer(); /** * Clears the buffer, reverts to initial empty state. * Empty means one empty line in one block. * Virtual, can be overwritten. */ virtual void clear(); /** * Set encoding prober type for this buffer to use for load. * @param proberType prober type to use for encoding */ void setEncodingProberType(KEncodingProber::ProberType proberType) { m_encodingProberType = proberType; } /** * Get encoding prober type for this buffer * @return currently in use prober type of this buffer */ KEncodingProber::ProberType encodingProberType() const { return m_encodingProberType; } /** * Set fallback codec for this buffer to use for load. * @param codec fallback QTextCodec to use for encoding */ void setFallbackTextCodec(QTextCodec *codec) { m_fallbackTextCodec = codec; } /** * Get fallback codec for this buffer * @return currently in use fallback codec of this buffer */ QTextCodec *fallbackTextCodec() const { return m_fallbackTextCodec; } /** * Set codec for this buffer to use for load/save. * Loading might overwrite this, if it encounters problems and finds a better codec. * Might change BOM setting. * @param codec QTextCodec to use for encoding */ void setTextCodec(QTextCodec *codec); /** * Get codec for this buffer * @return currently in use codec of this buffer */ QTextCodec *textCodec() const { return m_textCodec; } /** * Generate byte order mark on save. * Loading might overwrite this setting, if there is a BOM found inside the file. * @param generateByteOrderMark should BOM be generated? */ void setGenerateByteOrderMark(bool generateByteOrderMark) { m_generateByteOrderMark = generateByteOrderMark; } /** * Generate byte order mark on save? * @return should BOM be generated? */ bool generateByteOrderMark() const { return m_generateByteOrderMark; } /** * Set end of line mode for this buffer, not allowed to be set to unknown. * Loading might overwrite this setting, if there is a eol found inside the file. * @param endOfLineMode new eol mode */ void setEndOfLineMode(EndOfLineMode endOfLineMode) { Q_ASSERT(endOfLineMode != eolUnknown); m_endOfLineMode = endOfLineMode; } /** * Get end of line mode * @return end of line mode */ EndOfLineMode endOfLineMode() const { return m_endOfLineMode; } /** * Set whether to insert a newline character on save at the end of the file * @param newlineAtEof should newline be added if non-existing */ void setNewLineAtEof(bool newlineAtEof) { m_newLineAtEof = newlineAtEof; } /** * Set line length limit * @param lineLengthLimit new line length limit */ void setLineLengthLimit(int lineLengthLimit) { m_lineLengthLimit = lineLengthLimit; } /** * Load the given file. This will first clear the buffer and then load the file. * Even on error during loading the buffer will still be cleared. * Before calling this, setTextCodec must have been used to set codec! * @param filename file to open * @param encodingErrors were there problems occurred while decoding the file? * @param tooLongLinesWrapped were too long lines found and wrapped? * @param longestLineLoaded the longest line in the file (before wrapping) * @param enforceTextCodec enforce to use only the set text codec * @return success, the file got loaded, perhaps with encoding errors * Virtual, can be overwritten. */ virtual bool load(const QString &filename, bool &encodingErrors, bool &tooLongLinesWrapped, int &longestLineLoaded, bool enforceTextCodec); /** * Save the current buffer content to the given file. * Before calling this, setTextCodec and setFallbackTextCodec must have been used to set codec! * @param filename file to save * @return success * Virtual, can be overwritten. */ virtual bool save(const QString &filename); /** * Lines currently stored in this buffer. * This is never 0, even clear will let one empty line remain. */ int lines() const { Q_ASSERT(m_lines > 0); return m_lines; } /** * Revision of this buffer. Is set to 0 on construction, clear() (load will trigger clear()). * Is incremented on each change to the buffer. * @return current revision */ qint64 revision() const { return m_revision; } /** * Retrieve a text line. * @param line wanted line number * @return text line */ TextLine line(int line) const; /** * Retrieve text of complete buffer. * @return text for this buffer, lines separated by '\n' */ QString text() const; /** * Start an editing transaction, the wrapLine/unwrapLine/insertText and removeText functions * are only allowed to be called inside a editing transaction. * Editing transactions can stack. The number of startEdit and endEdit calls must match. * @return returns true, if no transaction was already running * Virtual, can be overwritten. */ virtual bool startEditing(); /** * Finish an editing transaction. Only allowed to be called if editing transaction is started. * @return returns true, if this finished last running transaction * Virtual, can be overwritten. */ virtual bool finishEditing(); /** * Query the number of editing transactions running atm. * @return number of running transactions */ int editingTransactions() const { return m_editingTransactions; } /** * Query the revsion of this buffer before the ongoing editing transactions. * @return revision of buffer before current editing transaction altered it */ qint64 editingLastRevision() const { return m_editingLastRevision; } /** * Query the number of lines of this buffer before the ongoing editing transactions. * @return number of lines of buffer before current editing transaction altered it */ int editingLastLines() const { return m_editingLastLines; } /** * Query information from the last editing transaction: was the content of the buffer changed? * This is checked by comparing the editingLastRevision() with the current revision(). * @return content of buffer was changed in last transaction? */ bool editingChangedBuffer() const { return editingLastRevision() != revision(); } /** * Query information from the last editing transaction: was the number of lines of the buffer changed? * This is checked by comparing the editingLastLines() with the current lines(). * @return content of buffer was changed in last transaction? */ bool editingChangedNumberOfLines() const { return editingLastLines() != lines(); } /** * Get minimal line number changed by last editing transaction * @return maximal line number changed by last editing transaction, or -1, if none changed */ int editingMinimalLineChanged() const { return m_editingMinimalLineChanged; } /** * Get maximal line number changed by last editing transaction * @return maximal line number changed by last editing transaction, or -1, if none changed */ int editingMaximalLineChanged() const { return m_editingMaximalLineChanged; } /** * Wrap line at given cursor position. * @param position line/column as cursor where to wrap * Virtual, can be overwritten. */ virtual void wrapLine(const KTextEditor::Cursor &position); /** * Unwrap given line. * @param line line to unwrap * Virtual, can be overwritten. */ virtual void unwrapLine(int line); /** * Insert text at given cursor position. Does nothing if text is empty, beside some consistency checks. * @param position position where to insert text * @param text text to insert * Virtual, can be overwritten. */ virtual void insertText(const KTextEditor::Cursor &position, const QString &text); /** * Remove text at given range. Does nothing if range is empty, beside some consistency checks. * @param range range of text to remove, must be on one line only. * Virtual, can be overwritten. */ virtual void removeText(const KTextEditor::Range &range); /** * TextHistory of this buffer * @return text history for this buffer */ TextHistory &history() { return m_history; } Q_SIGNALS: /** * Buffer got cleared. This is emitted when constructor or load have called clear() internally, * or when the user of the buffer has called clear() itself. */ void cleared(); /** * Buffer loaded successfully a file * @param filename file which was loaded * @param encodingErrors were there problems occurred while decoding the file? */ void loaded(const QString &filename, bool encodingErrors); /** * Buffer saved successfully a file * @param filename file which was saved */ void saved(const QString &filename); /** * Editing transaction has started. */ void editingStarted(); /** * Editing transaction has finished. */ void editingFinished(); /** * A line got wrapped. * @param position position where the wrap occurred */ void lineWrapped(const KTextEditor::Cursor &position); /** * A line got unwrapped. * @param line line where the unwrap occurred */ void lineUnwrapped(int line); /** * Text got inserted. * @param position position where the insertion occurred * @param text inserted text */ void textInserted(const KTextEditor::Cursor &position, const QString &text); /** * Text got removed. * @param range range where the removal occurred * @param text removed text */ void textRemoved(const KTextEditor::Range &range, const QString &text); private: /** * Find block containing given line. * @param line we want to find block for this line * @return index of found block */ int blockForLine(int line) const; /** * Fix start lines of all blocks after the given one * @param startBlock index of block from which we start to fix */ void fixStartLines(int startBlock); /** * Balance the given block. Look if it is too small or too large. * @param index block to balance */ void balanceBlock(int index); /** * Block for given index in block list. * @param index block index * @return block matching this index */ TextBlock *blockForIndex(int index) { return m_blocks[index]; } /** * A range changed, notify the views, in case of attributes or feedback. * @param view which view is affected? 0 for all views * @param startLine start line of change * @param endLine end line of change * @param rangeWithAttribute attribute changed or is active, this will perhaps lead to repaints */ void notifyAboutRangeChange(KTextEditor::View *view, int startLine, int endLine, bool rangeWithAttribute); /** * Mark all modified lines as lines saved on disk (modified line system). */ void markModifiedLinesAsSaved(); public: /** * Gets the document to which this buffer is bound. * \return a pointer to the document */ KTextEditor::DocumentPrivate *document() const { return m_document; } /** * Debug output, print whole buffer content with line numbers and line length * @param title title for this output */ void debugPrint(const QString &title) const; /** * Return the ranges which affect the given line. * @param line line to look at * @param view only return ranges associated with given view * @param rangesWithAttributeOnly only return ranges which have a attribute set * @return list of ranges affecting this line */ QList rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const; /** * Check if the given range pointer is still valid. * @return range pointer still belongs to range for this buffer */ bool rangePointerValid(TextRange *range) const { return m_ranges.contains(range); } /** * Invalidate all ranges in this buffer. */ void invalidateRanges(); // // checksum handling // public: /** * Checksum of the document on disk, set either through file loading * in openFile() or in KTextEditor::DocumentPrivate::saveFile() * @return git compatible sha1 checksum for this document */ const QByteArray &digest() const; /** * Set the checksum of this buffer. Make sure this checksum is up-to-date * when reading digest(). * @param checksum git compatible sha1 digest for the document on disk */ void setDigest(const QByteArray &checksum); private: QByteArray m_digest; private: /** * parent document */ KTextEditor::DocumentPrivate *m_document; /** * text history */ TextHistory m_history; /** * block size in lines the buffer will try to hold */ const int m_blockSize; /** * List of blocks which contain the lines of this buffer */ QVector m_blocks; /** * Number of lines in buffer */ int m_lines; /** * Last used block in the buffer. Is used for speeding up blockForLine. * May contain invalid index, must be checked before using. */ mutable int m_lastUsedBlock; /** * Revision of the buffer. */ qint64 m_revision; /** * Current number of running edit transactions */ int m_editingTransactions; /** * Revision remembered at start of current editing transaction */ qint64 m_editingLastRevision; /** * Number of lines remembered at start of current editing transaction */ int m_editingLastLines; /** * minimal line number changed by last editing transaction */ int m_editingMinimalLineChanged; /** * maximal line number changed by last editing transaction */ int m_editingMaximalLineChanged; /** * Set of invalid cursors for this whole buffer. * Valid cursors are inside the block the belong to. */ QSet m_invalidCursors; /** * Set of ranges of this whole buffer. */ QSet m_ranges; /** * Encoding prober type to use */ KEncodingProber::ProberType m_encodingProberType; /** * Fallback text codec to use */ QTextCodec *m_fallbackTextCodec; /** * Text codec to use */ QTextCodec *m_textCodec; /** * Mime-Type used for transparent compression/decompression support * Set by load(), reset by clear() */ QString m_mimeTypeForFilterDev; /** * Should byte order mark be created? */ bool m_generateByteOrderMark; /** * End of line mode, default is Unix */ EndOfLineMode m_endOfLineMode; /** * Insert newline character at the end of the file? */ bool m_newLineAtEof; /** * Limit for line length, longer lines will be wrapped on load */ int m_lineLengthLimit; + + /** + * For unit-testing purposes only. + */ + bool m_alwaysUseKAuthForSave; + + /** + * For copying QBuffer -> QTemporaryFile while saving document in privileged mode + */ + static const qint64 bufferLength = 4096; }; } #endif diff --git a/src/buffer/katetextcursor.cpp b/src/buffer/katetextcursor.cpp index 8ba17a64..d91dac1c 100644 --- a/src/buffer/katetextcursor.cpp +++ b/src/buffer/katetextcursor.cpp @@ -1,147 +1,147 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 "katetextcursor.h" #include "katetextbuffer.h" namespace Kate { TextCursor::TextCursor(TextBuffer &buffer, const KTextEditor::Cursor &position, InsertBehavior insertBehavior) : m_buffer(buffer) - , m_range(0) - , m_block(0) + , m_range(nullptr) + , m_block(nullptr) , m_line(-1) , m_column(-1) , m_moveOnInsert(insertBehavior == MoveOnInsert) { // init position setPosition(position, true); } TextCursor::TextCursor(TextBuffer &buffer, TextRange *range, const KTextEditor::Cursor &position, InsertBehavior insertBehavior) : m_buffer(buffer) , m_range(range) - , m_block(0) + , m_block(nullptr) , m_line(-1) , m_column(-1) , m_moveOnInsert(insertBehavior == MoveOnInsert) { // init position setPosition(position, true); } TextCursor::~TextCursor() { // remove cursor from block or buffer if (m_block) { m_block->removeCursor(this); } // only cursors without range are here! else if (!m_range) { m_buffer.m_invalidCursors.remove(this); } } void TextCursor::setPosition(const TextCursor &position) { if (m_block && m_block != position.m_block) { m_block->removeCursor(this); } m_line = position.m_line; m_column = position.m_column; m_block = position.m_block; if (m_block) { m_block->insertCursor(this); } } void TextCursor::setPosition(const KTextEditor::Cursor &position, bool init) { // any change or init? else do nothing if (!init && position.line() == line() && position.column() == m_column) { return; } // remove cursor from old block in any case if (m_block) { m_block->removeCursor(this); } // first: validate the line and column, else invalid if (position.column() < 0 || position.line() < 0 || position.line() >= m_buffer.lines()) { if (!m_range) { m_buffer.m_invalidCursors.insert(this); } - m_block = 0; + m_block = nullptr; m_line = m_column = -1; return; } // else, find block TextBlock *block = m_buffer.blockForIndex(m_buffer.blockForLine(position.line())); Q_ASSERT(block); // if cursor was invalid before, remove it from invalid cursor list if (!m_range && !m_block && !init) { Q_ASSERT(m_buffer.m_invalidCursors.contains(this)); m_buffer.m_invalidCursors.remove(this); } // else: valid cursor m_block = block; m_line = position.line() - m_block->startLine(); m_column = position.column(); m_block->insertCursor(this); } void TextCursor::setPosition(const KTextEditor::Cursor &position) { setPosition(position, false); } int TextCursor::line() const { // invalid cursor have no block if (!m_block) { return -1; } // else, calculate real line return m_block->startLine() + m_line; } KTextEditor::Document *Kate::TextCursor::document() const { return m_buffer.document(); } KTextEditor::MovingRange *Kate::TextCursor::range() const { return m_range; } } diff --git a/src/buffer/katetextfolding.cpp b/src/buffer/katetextfolding.cpp index e0666d90..f75794d9 100644 --- a/src/buffer/katetextfolding.cpp +++ b/src/buffer/katetextfolding.cpp @@ -1,1030 +1,1030 @@ /* This file is part of the Kate project. * * Copyright (C) 2013 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 "katetextfolding.h" #include "katetextbuffer.h" #include "katetextrange.h" #include "documentcursor.h" #include namespace Kate { TextFolding::FoldingRange::FoldingRange(TextBuffer &buffer, const KTextEditor::Range &range, FoldingRangeFlags _flags) : start(new TextCursor(buffer, range.start(), KTextEditor::MovingCursor::MoveOnInsert)) , end(new TextCursor(buffer, range.end(), KTextEditor::MovingCursor::MoveOnInsert)) - , parent(0) + , parent(nullptr) , flags(_flags) , id(-1) { } TextFolding::FoldingRange::~FoldingRange() { /** * kill all our data! * this will recurse all sub-structures! */ delete start; delete end; qDeleteAll(nestedRanges); } TextFolding::TextFolding(TextBuffer &buffer) : QObject() , m_buffer(buffer) , m_idCounter(-1) { /** * connect needed signals from buffer */ connect(&m_buffer, SIGNAL(cleared()), SLOT(clear())); } TextFolding::~TextFolding() { /** * only delete the folding ranges, the folded ranges and mapped ranges are the same objects */ qDeleteAll(m_foldingRanges); } void TextFolding::clear() { /** * reset counter */ m_idCounter = -1; /** * no ranges, no work */ if (m_foldingRanges.isEmpty()) { /** * assert all stuff is consistent and return! */ Q_ASSERT(m_idToFoldingRange.isEmpty()); Q_ASSERT(m_foldedFoldingRanges.isEmpty()); return; } /** * cleanup */ m_idToFoldingRange.clear(); m_foldedFoldingRanges.clear(); qDeleteAll(m_foldingRanges); m_foldingRanges.clear(); /** * folding changed! */ emit foldingRangesChanged(); } qint64 TextFolding::newFoldingRange(const KTextEditor::Range &range, FoldingRangeFlags flags) { /** * sort out invalid and empty ranges * that makes no sense, they will never grow again! */ if (!range.isValid() || range.isEmpty()) { return -1; } /** * create new folding region that we want to insert * this will internally create moving cursors! */ FoldingRange *newRange = new FoldingRange(m_buffer, range, flags); /** * the construction of the text cursors might have invalidated this * check and bail out if that happens * bail out, too, if it can't be inserted! */ if (!newRange->start->isValid() || !newRange->end->isValid() - || !insertNewFoldingRange(0 /* no parent here */, m_foldingRanges, newRange)) { + || !insertNewFoldingRange(nullptr /* no parent here */, m_foldingRanges, newRange)) { /** * cleanup and be done */ delete newRange; return -1; } /** * set id, catch overflows, even if they shall not happen */ newRange->id = ++m_idCounter; if (newRange->id < 0) { newRange->id = m_idCounter = 0; } /** * remember the range */ m_idToFoldingRange.insert(newRange->id, newRange); /** * update our folded ranges vector! */ bool updated = updateFoldedRangesForNewRange(newRange); /** * emit that something may have changed * do that only, if updateFoldedRangesForNewRange did not already do the job! */ if (!updated) { emit foldingRangesChanged(); } /** * all went fine, newRange is now registered internally! */ return newRange->id; } KTextEditor::Range TextFolding::foldingRange(qint64 id) const { FoldingRange *range = m_idToFoldingRange.value(id); if (!range) { return KTextEditor::Range::invalid(); } return KTextEditor::Range(range->start->toCursor(), range->end->toCursor()); } bool TextFolding::foldRange(qint64 id) { /** * try to find the range, else bail out */ FoldingRange *range = m_idToFoldingRange.value(id); if (!range) { return false; } /** * already folded? nothing to do */ if (range->flags & Folded) { return true; } /** * fold and be done */ range->flags |= Folded; updateFoldedRangesForNewRange(range); return true; } bool TextFolding::unfoldRange(qint64 id, bool remove) { /** * try to find the range, else bail out */ FoldingRange *range = m_idToFoldingRange.value(id); if (!range) { return false; } /** * nothing to do? * range is already unfolded and we need not to remove it! */ if (!remove && !(range->flags & Folded)) { return true; } /** * do we need to delete the range? */ const bool deleteRange = remove || !(range->flags & Persistent); /** * first: remove the range, if forced or non-persistent! */ if (deleteRange) { /** * remove from outside visible mapping! */ m_idToFoldingRange.remove(id); /** * remove from folding vectors! * FIXME: OPTIMIZE */ FoldingRange::Vector &parentVector = range->parent ? range->parent->nestedRanges : m_foldingRanges; FoldingRange::Vector newParentVector; Q_FOREACH (FoldingRange *curRange, parentVector) { /** * insert our nested ranges and reparent them */ if (curRange == range) { Q_FOREACH (FoldingRange *newRange, range->nestedRanges) { newRange->parent = range->parent; newParentVector.push_back(newRange); } continue; } /** * else just transfer elements */ newParentVector.push_back(curRange); } parentVector = newParentVector; } /** * second: unfold the range, if needed! */ bool updated = false; if (range->flags & Folded) { range->flags &= ~Folded; updated = updateFoldedRangesForRemovedRange(range); } /** * emit that something may have changed * do that only, if updateFoldedRangesForRemoveRange did not already do the job! */ if (!updated) { emit foldingRangesChanged(); } /** * really delete the range, if needed! */ if (deleteRange) { /** * clear ranges first, they got moved! */ range->nestedRanges.clear(); delete range; } /** * be done ;) */ return true; } bool TextFolding::isLineVisible(int line, qint64 *foldedRangeId) const { /** * skip if nothing folded */ if (m_foldedFoldingRanges.isEmpty()) { return true; } /** * search upper bound, index to item with start line higher than our one */ FoldingRange::Vector::const_iterator upperBound = qUpperBound(m_foldedFoldingRanges.begin(), m_foldedFoldingRanges.end(), line, compareRangeByStartWithLine); if (upperBound != m_foldedFoldingRanges.begin()) { --upperBound; } /** * check if we overlap with the range in front of us */ const bool hidden = (((*upperBound)->end->line() >= line) && (line > (*upperBound)->start->line())); /** * fill in folded range id, if needed */ if (foldedRangeId) { (*foldedRangeId) = hidden ? (*upperBound)->id : -1; } /** * visible == !hidden */ return !hidden; } void TextFolding::ensureLineIsVisible(int line) { /** * skip if nothing folded */ if (m_foldedFoldingRanges.isEmpty()) { return; } /** * while not visible, unfold */ qint64 foldedRangeId = -1; while (!isLineVisible(line, &foldedRangeId)) { /** * id should be valid! */ Q_ASSERT(foldedRangeId >= 0); /** * unfold shall work! */ const bool unfolded = unfoldRange(foldedRangeId); (void) unfolded; Q_ASSERT(unfolded); } } int TextFolding::visibleLines() const { /** * start with all lines we have */ int visibleLines = m_buffer.lines(); /** * skip if nothing folded */ if (m_foldedFoldingRanges.isEmpty()) { return visibleLines; } /** * count all folded lines and subtract them from visible lines */ Q_FOREACH (FoldingRange *range, m_foldedFoldingRanges) { visibleLines -= (range->end->line() - range->start->line()); } /** * be done, assert we did no trash */ Q_ASSERT(visibleLines > 0); return visibleLines; } int TextFolding::lineToVisibleLine(int line) const { /** * valid input needed! */ Q_ASSERT(line >= 0); /** * start with identity */ int visibleLine = line; /** * skip if nothing folded or first line */ if (m_foldedFoldingRanges.isEmpty() || (line == 0)) { return visibleLine; } /** * walk over all folded ranges until we reach the line * keep track of seen visible lines, for the case we want to convert a hidden line! */ int seenVisibleLines = 0; int lastLine = 0; Q_FOREACH (FoldingRange *range, m_foldedFoldingRanges) { /** * abort if we reach our line! */ if (range->start->line() >= line) { break; } /** * count visible lines */ seenVisibleLines += (range->start->line() - lastLine); lastLine = range->end->line(); /** * we might be contained in the region, then we return last visible line */ if (line <= range->end->line()) { return seenVisibleLines; } /** * subtrace folded lines */ visibleLine -= (range->end->line() - range->start->line()); } /** * be done, assert we did no trash */ Q_ASSERT(visibleLine >= 0); return visibleLine; } int TextFolding::visibleLineToLine(int visibleLine) const { /** * valid input needed! */ Q_ASSERT(visibleLine >= 0); /** * start with identity */ int line = visibleLine; /** * skip if nothing folded or first line */ if (m_foldedFoldingRanges.isEmpty() || (visibleLine == 0)) { return line; } /** * last visible line seen, as line in buffer */ int seenVisibleLines = 0; int lastLine = 0; int lastLineVisibleLines = 0; Q_FOREACH (FoldingRange *range, m_foldedFoldingRanges) { /** * else compute visible lines and move last seen */ lastLineVisibleLines = seenVisibleLines; seenVisibleLines += (range->start->line() - lastLine); /** * bail out if enough seen */ if (seenVisibleLines >= visibleLine) { break; } lastLine = range->end->line(); } /** * check if still no enough visible! */ if (seenVisibleLines < visibleLine) { lastLineVisibleLines = seenVisibleLines; } /** * compute visible line */ line = (lastLine + (visibleLine - lastLineVisibleLines)); Q_ASSERT(line >= 0); return line; } QVector > TextFolding::foldingRangesStartingOnLine(int line) const { /** * results vector */ QVector > results; /** * recursively do binary search */ foldingRangesStartingOnLine(results, m_foldingRanges, line); /** * return found results */ return results; } void TextFolding::foldingRangesStartingOnLine(QVector > &results, const TextFolding::FoldingRange::Vector &ranges, int line) const { /** * early out for no folds */ if (ranges.isEmpty()) { return; } /** * first: lower bound of start */ FoldingRange::Vector::const_iterator lowerBound = qLowerBound(ranges.begin(), ranges.end(), line, compareRangeByLineWithStart); /** * second: upper bound of end */ FoldingRange::Vector::const_iterator upperBound = qUpperBound(ranges.begin(), ranges.end(), line, compareRangeByStartWithLine); /** * we may need to go one to the left, if not already at the begin, as we might overlap with the one in front of us! */ if ((lowerBound != ranges.begin()) && ((*(lowerBound - 1))->end->line() >= line)) { --lowerBound; } /** * for all of them, check if we start at right line and recurse */ for (FoldingRange::Vector::const_iterator it = lowerBound; it != upperBound; ++it) { /** * this range already ok? add it to results */ if ((*it)->start->line() == line) { results.push_back(qMakePair((*it)->id, (*it)->flags)); } /** * recurse anyway */ foldingRangesStartingOnLine(results, (*it)->nestedRanges, line); } } QVector > TextFolding::foldingRangesForParentRange(qint64 parentRangeId) const { /** * toplevel ranges requested or real parent? */ const FoldingRange::Vector *ranges = nullptr; if (parentRangeId == -1) { ranges = &m_foldingRanges; } else if (FoldingRange *range = m_idToFoldingRange.value(parentRangeId)) { ranges = &range->nestedRanges; } /** * no ranges => nothing to do */ QVector > results; if (!ranges) return results; /** * else convert ranges to id + flags and pass that back */ for (FoldingRange::Vector::const_iterator it = ranges->begin(); it != ranges->end(); ++it) { results.append(qMakePair((*it)->id, (*it)->flags)); } return results; } QString TextFolding::debugDump() const { /** * dump toplevel ranges recursively */ return QStringLiteral("tree %1 - folded %2").arg(debugDump(m_foldingRanges, true), debugDump(m_foldedFoldingRanges, false)); } void TextFolding::debugPrint(const QString &title) const { // print title + content printf("%s\n %s\n", qPrintable(title), qPrintable(debugDump())); } QString TextFolding::debugDump(const TextFolding::FoldingRange::Vector &ranges, bool recurse) { /** * dump all ranges recursively */ QString dump; Q_FOREACH (FoldingRange *range, ranges) { if (!dump.isEmpty()) { dump += QLatin1Char(' '); } const QString persistent = (range->flags & Persistent) ? QStringLiteral("p") : QString(); const QString folded = (range->flags & Folded) ? QStringLiteral("f") : QString(); dump += QString::fromLatin1("[%1:%2 %3%4 ").arg(range->start->line()).arg(range->start->column()).arg(persistent, folded); /** * recurse */ if (recurse) { QString inner = debugDump(range->nestedRanges, recurse); if (!inner.isEmpty()) { dump += inner + QLatin1Char(' '); } } dump += QString::fromLatin1("%1:%2]").arg(range->end->line()).arg(range->end->column()); } return dump; } bool TextFolding::insertNewFoldingRange(FoldingRange *parent, FoldingRange::Vector &existingRanges, FoldingRange *newRange) { /** * existing ranges are non-overlapping and sorted * that means, we can search for lower bound of start of range and upper bound of end of range to find all "overlapping" ranges. */ /** * first: lower bound of start */ FoldingRange::Vector::iterator lowerBound = qLowerBound(existingRanges.begin(), existingRanges.end(), newRange, compareRangeByStart); /** * second: upper bound of end */ FoldingRange::Vector::iterator upperBound = qUpperBound(existingRanges.begin(), existingRanges.end(), newRange, compareRangeByEnd); /** * we may need to go one to the left, if not already at the begin, as we might overlap with the one in front of us! */ if ((lowerBound != existingRanges.begin()) && ((*(lowerBound - 1))->end->toCursor() > newRange->start->toCursor())) { --lowerBound; } /** * now: first case, we overlap with nothing or hit exactly one range! */ if (lowerBound == upperBound) { /** * nothing we overlap with? * then just insert and be done! */ if ((lowerBound == existingRanges.end()) || (newRange->start->toCursor() >= (*lowerBound)->end->toCursor()) || (newRange->end->toCursor() <= (*lowerBound)->start->toCursor())) { /** * insert + fix parent */ existingRanges.insert(lowerBound, newRange); newRange->parent = parent; /** * all done */ return true; } /** * we are contained in this one range? * then recurse! */ if ((newRange->start->toCursor() >= (*lowerBound)->start->toCursor()) && (newRange->end->toCursor() <= (*lowerBound)->end->toCursor())) { return insertNewFoldingRange((*lowerBound), (*lowerBound)->nestedRanges, newRange); } /** * else: we might contain at least this fold, or many more, if this if block is not taken at all * use the general code that checks for "we contain stuff" below! */ } /** * check if we contain other folds! */ FoldingRange::Vector::iterator it = lowerBound; bool includeUpperBound = false; FoldingRange::Vector nestedRanges; while (it != existingRanges.end()) { /** * do we need to take look at upper bound, too? * if not break */ if (it == upperBound) { if (newRange->end->toCursor() <= (*upperBound)->start->toCursor()) { break; } else { includeUpperBound = true; } } /** * if one region is not contained in the new one, abort! * then this is not well nested! */ if (!((newRange->start->toCursor() <= (*it)->start->toCursor()) && (newRange->end->toCursor() >= (*it)->end->toCursor()))) { return false; } /** * include into new nested ranges */ nestedRanges.push_back((*it)); /** * end reached */ if (it == upperBound) { break; } /** * else increment */ ++it; } /** * if we arrive here, all is nicely nested into our new range * remove the contained ones here, insert new range with new nested ranges we already constructed */ it = existingRanges.erase(lowerBound, includeUpperBound ? (upperBound + 1) : upperBound); existingRanges.insert(it, newRange); newRange->nestedRanges = nestedRanges; /** * correct parent mapping! */ newRange->parent = parent; Q_FOREACH (FoldingRange *range, newRange->nestedRanges) { range->parent = newRange; } /** * all nice */ return true; } bool TextFolding::compareRangeByStart(FoldingRange *a, FoldingRange *b) { return a->start->toCursor() < b->start->toCursor(); } bool TextFolding::compareRangeByEnd(FoldingRange *a, FoldingRange *b) { return a->end->toCursor() < b->end->toCursor(); } bool TextFolding::compareRangeByStartWithLine(int line, FoldingRange *range) { return (line < range->start->line()); } bool TextFolding::compareRangeByLineWithStart(FoldingRange *range, int line) { return (range->start->line() < line); } bool TextFolding::updateFoldedRangesForNewRange(TextFolding::FoldingRange *newRange) { /** * not folded? not interesting! we don't need to touch our m_foldedFoldingRanges vector */ if (!(newRange->flags & Folded)) { return false; } /** * any of the parents folded? not interesting, too! */ TextFolding::FoldingRange *parent = newRange->parent; while (parent) { /** * parent folded => be done */ if (parent->flags & Folded) { return false; } /** * walk up */ parent = parent->parent; } /** * ok, if we arrive here, we are a folded range and we have no folded parent * we now want to add this range to the m_foldedFoldingRanges vector, just removing any ranges that is included in it! * TODO: OPTIMIZE */ FoldingRange::Vector newFoldedFoldingRanges; bool newRangeInserted = false; Q_FOREACH (FoldingRange *range, m_foldedFoldingRanges) { /** * contained? kill */ if ((newRange->start->toCursor() <= range->start->toCursor()) && (newRange->end->toCursor() >= range->end->toCursor())) { continue; } /** * range is behind newRange? * insert newRange if not already done */ if (!newRangeInserted && (range->start->toCursor() >= newRange->end->toCursor())) { newFoldedFoldingRanges.push_back(newRange); newRangeInserted = true; } /** * just transfer range */ newFoldedFoldingRanges.push_back(range); } /** * last: insert new range, if not done */ if (!newRangeInserted) { newFoldedFoldingRanges.push_back(newRange); } /** * fixup folded ranges */ m_foldedFoldingRanges = newFoldedFoldingRanges; /** * folding changed! */ emit foldingRangesChanged(); /** * all fine, stuff done, signal emitted */ return true; } bool TextFolding::updateFoldedRangesForRemovedRange(TextFolding::FoldingRange *oldRange) { /** * folded? not interesting! we don't need to touch our m_foldedFoldingRanges vector */ if (oldRange->flags & Folded) { return false; } /** * any of the parents folded? not interesting, too! */ TextFolding::FoldingRange *parent = oldRange->parent; while (parent) { /** * parent folded => be done */ if (parent->flags & Folded) { return false; } /** * walk up */ parent = parent->parent; } /** * ok, if we arrive here, we are a unfolded range and we have no folded parent * we now want to remove this range from the m_foldedFoldingRanges vector and include our nested folded ranges! * TODO: OPTIMIZE */ FoldingRange::Vector newFoldedFoldingRanges; Q_FOREACH (FoldingRange *range, m_foldedFoldingRanges) { /** * right range? insert folded nested ranges */ if (range == oldRange) { appendFoldedRanges(newFoldedFoldingRanges, oldRange->nestedRanges); continue; } /** * just transfer range */ newFoldedFoldingRanges.push_back(range); } /** * fixup folded ranges */ m_foldedFoldingRanges = newFoldedFoldingRanges; /** * folding changed! */ emit foldingRangesChanged(); /** * all fine, stuff done, signal emitted */ return true; } void TextFolding::appendFoldedRanges(TextFolding::FoldingRange::Vector &newFoldedFoldingRanges, const TextFolding::FoldingRange::Vector &ranges) const { /** * search for folded ranges and append them */ Q_FOREACH (FoldingRange *range, ranges) { /** * itself folded? append */ if (range->flags & Folded) { newFoldedFoldingRanges.push_back(range); continue; } /** * else: recurse! */ appendFoldedRanges(newFoldedFoldingRanges, range->nestedRanges); } } QJsonDocument TextFolding::exportFoldingRanges() const { QJsonArray array; exportFoldingRanges(m_foldingRanges, array); QJsonDocument folds; folds.setArray(array); return folds; } void TextFolding::exportFoldingRanges(const TextFolding::FoldingRange::Vector &ranges, QJsonArray &folds) { /** * dump all ranges recursively */ Q_FOREACH (FoldingRange *range, ranges) { /** * construct one range and dump to folds */ QJsonObject rangeMap; rangeMap[QStringLiteral("startLine")] = range->start->line(); rangeMap[QStringLiteral("startColumn")] = range->start->column(); rangeMap[QStringLiteral("endLine")] = range->end->line(); rangeMap[QStringLiteral("endColumn")] = range->end->column(); rangeMap[QStringLiteral("flags")] = (int)range->flags; folds.append(rangeMap); /** * recurse */ exportFoldingRanges(range->nestedRanges, folds); } } void TextFolding::importFoldingRanges(const QJsonDocument &folds) { Q_FOREACH (FoldingRange *range, m_foldingRanges) { unfoldRange(range->id); } /** * try to create all folding ranges */ Q_FOREACH (const QJsonValue &rangeVariant, folds.array()) { /** * get map */ QJsonObject rangeMap = rangeVariant.toObject(); /** * construct range start/end */ const KTextEditor::Cursor start(rangeMap[QStringLiteral("startLine")].toInt(), rangeMap[QStringLiteral("startColumn")].toInt()); const KTextEditor::Cursor end(rangeMap[QStringLiteral("endLine")].toInt(), rangeMap[QStringLiteral("endColumn")].toInt()); /** * check validity (required when loading a possibly broken folding state from disk) */ if (start >= end || (m_buffer.document() && // <-- unit test katetextbuffertest does not have a KTE::Document assigned (!KTextEditor::DocumentCursor(m_buffer.document(), start).isValidTextPosition() || !KTextEditor::DocumentCursor(m_buffer.document(), end).isValidTextPosition())) ) { continue; } /** * get flags */ const int rawFlags = rangeMap[QStringLiteral("flags")].toInt(); FoldingRangeFlags flags; if (rawFlags & Persistent) { flags = Persistent; } if (rawFlags & Folded) { flags = Folded; } /** * create folding range */ newFoldingRange(KTextEditor::Range(start, end), flags); } } } diff --git a/src/buffer/katetextfolding.h b/src/buffer/katetextfolding.h index 3daf4e04..434e3af9 100644 --- a/src/buffer/katetextfolding.h +++ b/src/buffer/katetextfolding.h @@ -1,390 +1,390 @@ /* This file is part of the Kate project. * * Copyright (C) 2013 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. */ #ifndef KATE_TEXTFOLDING_H #define KATE_TEXTFOLDING_H #include #include "ktexteditor/range.h" #include #include #include #include namespace Kate { class TextBuffer; class TextCursor; /** * Class representing the folding information for a TextBuffer. * The interface allows to arbitrary fold given regions of a buffer as long * as they are well nested. * Multiple instances of this class can exist for the same buffer. */ class KTEXTEDITOR_EXPORT TextFolding : public QObject { Q_OBJECT public: /** * Create folding object for given buffer. * @param buffer text buffer we want to provide folding info for */ explicit TextFolding(TextBuffer &buffer); /** * Cleanup */ ~TextFolding(); /** * Folding state of a range */ enum FoldingRangeFlag { /** * Range is persistent, e.g. it should not auto-delete after unfolding! */ Persistent = 0x1, /** * Range is folded away */ Folded = 0x2 }; Q_DECLARE_FLAGS(FoldingRangeFlags, FoldingRangeFlag) /** * Create a new folding range. * @param range folding range * @param flags initial flags for the new folding range * @return on success, id of new range >= 0, else -1, we return no pointer as folding ranges might be auto-deleted internally! * the ids are stable for one Kate::TextFolding, e.g. you can rely in unit tests that you get 0,1,.... for successfully created ranges! */ qint64 newFoldingRange(const KTextEditor::Range &range, FoldingRangeFlags flags = FoldingRangeFlags()); /** * Returns the folding range associated with @p id. * If @p id is not a valid id, the returned range matches KTextEditor::Range::invalid(). * @note This works for either persistend ranges or folded ranges. * Note, that the highlighting does not add folds unless text is folded. * * @return the folding range for @p id */ KTextEditor::Range foldingRange(qint64 id) const; /** * Fold the given range. * @param id id of the range to fold * @return success */ bool foldRange(qint64 id); /** * Unfold the given range. * In addition it can be forced to remove the region, even if it is persistent. * @param id id of the range to unfold * @param remove should the range be removed from the folding after unfolding? ranges that are not persistent auto-remove themself on unfolding * @return success */ bool unfoldRange(qint64 id, bool remove = false); /** * Query if a given line is visible. * Very fast, if nothing is folded, else does binary search * log(n) for n == number of folded ranges * @param line real line to query * @param foldedRangeId if the line is not visible and that pointer is not 0, will be filled with id of range hiding the line or -1 * @return is that line visible? */ - bool isLineVisible(int line, qint64 *foldedRangeId = 0) const; + bool isLineVisible(int line, qint64 *foldedRangeId = nullptr) const; /** * Ensure that a given line will be visible. * Potentially unfold recursively all folds hiding this line, else just returns. * @param line line to make visible */ void ensureLineIsVisible(int line); /** * Query number of visible lines. * Very fast, if nothing is folded, else walks over all folded regions * O(n) for n == number of folded ranges */ int visibleLines() const; /** * Convert a text buffer line to a visible line number. * Very fast, if nothing is folded, else walks over all folded regions * O(n) for n == number of folded ranges * @param line line index in the text buffer * @return index in visible lines */ int lineToVisibleLine(int line) const; /** * Convert a visible line number to a line number in the text buffer. * Very fast, if nothing is folded, else walks over all folded regions * O(n) for n == number of folded ranges * @param visibleLine visible line index * @return index in text buffer lines */ int visibleLineToLine(int visibleLine) const; /** * Queries which folding ranges start at the given line and returns the id + flags for all * of them. Very fast if nothing is folded, else binary search. * @param line line to query starting folding ranges * @return vector of id's + flags */ QVector > foldingRangesStartingOnLine(int line) const; /** * Query child folding ranges for given range id. To query the toplevel * ranges pass id -1 * @param parentRangeId id of parent range, pass -1 to query top level ranges * @return vector of id's + flags for child ranges */ QVector > foldingRangesForParentRange(qint64 parentRangeId = -1) const; /** * Return the current known folding ranges a QJsonDocument to store in configs. * @return current folds as variant list */ QJsonDocument exportFoldingRanges() const; /** * Import the folding ranges given as a QJsonDocument like read from configs. * @param folds list of folds to import */ void importFoldingRanges(const QJsonDocument &folds); /** * Dump folding state as string, for unit testing and debugging * @return current state as text */ QString debugDump() const; /** * Print state to stdout for testing */ void debugPrint(const QString &title) const; public Q_SLOTS: /** * Clear the complete folding. * This is automatically triggered if the buffer is cleared. */ void clear(); Q_SIGNALS: /** * If the folding state of existing ranges changes or * ranges are added/removed, this signal is emitted. */ void foldingRangesChanged(); private: /** * Data holder for text folding range and its nested children */ class FoldingRange { public: /** * Construct new one * @param buffer text buffer to use * @param range folding range * @param flags flags for the new folding range */ FoldingRange(TextBuffer &buffer, const KTextEditor::Range &range, FoldingRangeFlags flags); /** * Cleanup */ ~FoldingRange(); /** * Vector of range pointers */ typedef QVector Vector; /** * start moving cursor * NO range to be more efficient */ Kate::TextCursor *start; /** * end moving cursor * NO range to be more efficient */ Kate::TextCursor *end; /** * parent range, if any */ FoldingRange *parent; /** * nested ranges, if any * this will always be sorted and non-overlapping * nested ranges are inside these ranges */ FoldingRange::Vector nestedRanges; /** * Folding range flags */ FoldingRangeFlags flags; /** * id of this range */ qint64 id; }; /** * Fill known folding ranges in a QVariantList to store in configs. * @param ranges ranges vector to dump * @param folds current folds as variant list, will be filled */ static void exportFoldingRanges(const TextFolding::FoldingRange::Vector &ranges, QJsonArray &folds); /** * Dump folding state of given vector as string, for unit testing and debugging. * Will recurse if wanted. * @param ranges ranges vector to dump * @param recurse recurse to nestedRanges? * @return current state as text */ static QString debugDump(const TextFolding::FoldingRange::Vector &ranges, bool recurse); /** * Helper to insert folding range into existing ones. * Might fail, if not correctly nested. * Then the outside must take care of the passed pointer, e.g. delete it. * Will sanitize the ranges vectors, purge invalid/empty ranges. * @param parent parent folding range if any * @param existingRanges ranges into which we want to insert the new one * @param newRange new folding range * @return success, if false, newRange should be deleted afterwards, else it is registered internally */ bool insertNewFoldingRange(FoldingRange *parent, TextFolding::FoldingRange::Vector &existingRanges, TextFolding::FoldingRange *newRange); /** * Helper to update the folded ranges if we insert a new range into the tree. * @param newRange new folding range that was inserted, will already contain its new nested ranges, if any! * @return any updated done? if yes, the foldingRangesChanged() signal got emitted! */ bool updateFoldedRangesForNewRange(TextFolding::FoldingRange *newRange); /** * Helper to update the folded ranges if we remove a new range from the tree. * @param oldRange new folding range that is removed, will still contain its new nested ranges, if any! * @return any updated done? if yes, the foldingRangesChanged() signal got emitted! */ bool updateFoldedRangesForRemovedRange(TextFolding::FoldingRange *oldRange); /** * Helper to append recursively topmost folded ranges from input to output vector. * @param newFoldedFoldingRanges output vector for folded ranges * @param ranges input vector to search recursively folded ranges inside */ void appendFoldedRanges(TextFolding::FoldingRange::Vector &newFoldedFoldingRanges, const TextFolding::FoldingRange::Vector &ranges) const; /** * Compare two ranges by their start cursor. * @param a first range * @param b second range */ static bool compareRangeByStart(FoldingRange *a, FoldingRange *b); /** * Compare two ranges by their end cursor. * @param a first range * @param b second range */ static bool compareRangeByEnd(FoldingRange *a, FoldingRange *b); /** * Compare range start with line * @param line line * @param range range */ static bool compareRangeByStartWithLine(int line, FoldingRange *range); /** * Compare range start with line * @param range range * @param line line */ static bool compareRangeByLineWithStart(FoldingRange *range, int line); /** * Internal helper that queries which folding ranges start at the given line and returns the id + flags for all * of them. Will recursively dive down starting with given vector * @param results vector that is filled with id's + flags * @param ranges ranges vector to search in * @param line line to query starting folding ranges */ void foldingRangesStartingOnLine(QVector > &results, const TextFolding::FoldingRange::Vector &ranges, int line) const; private: /** * parent text buffer * is a reference, and no pointer, as this must always exist and can't change * can't be const, as we create text cursors! */ TextBuffer &m_buffer; /** * toplevel folding ranges * this will always be sorted and non-overlapping * nested ranges are inside these ranges */ FoldingRange::Vector m_foldingRanges; /** * folded folding ranges * this is a sorted vector of ranges * all non-overlapping */ FoldingRange::Vector m_foldedFoldingRanges; /** * global id counter for the created ranges */ qint64 m_idCounter; /** * mapping: id => range */ QHash m_idToFoldingRange; }; } Q_DECLARE_OPERATORS_FOR_FLAGS(Kate::TextFolding::FoldingRangeFlags) #endif diff --git a/src/buffer/katetextloader.h b/src/buffer/katetextloader.h index f8da7958..c0b1b4b3 100644 --- a/src/buffer/katetextloader.h +++ b/src/buffer/katetextloader.h @@ -1,414 +1,414 @@ /* This file is part of the Kate project. * * 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. */ #ifndef KATE_TEXTLOADER_H #define KATE_TEXTLOADER_H #include #include #include #include // on the fly compression #include namespace Kate { /** * loader block size, load 256 kb at once per default * if file size is smaller, fall back to file size * must be a multiple of 2 */ static const qint64 KATE_FILE_LOADER_BS = 256 * 1024; /** * File Loader, will handle reading of files + detecting encoding */ class TextLoader { public: /** * Construct file loader for given file. * @param filename file to open * @param proberType prober type */ TextLoader(const QString &filename, KEncodingProber::ProberType proberType) - : m_codec(0) + : m_codec(nullptr) , m_eof(false) // default to not eof , m_lastWasEndOfLine(true) // at start of file, we had a virtual newline , m_lastWasR(false) // we have not found a \r as last char , m_position(0) , m_lastLineStart(0) , m_eol(TextBuffer::eolUnknown) // no eol type detected atm , m_buffer(KATE_FILE_LOADER_BS, 0) , m_digest(QCryptographicHash::Sha1) - , m_converterState(0) + , m_converterState(nullptr) , m_bomFound(false) , m_firstRead(true) , m_proberType(proberType) , m_fileSize(0) { // try to get mimetype for on the fly decompression, don't rely on filename! QFile testMime(filename); m_mimeType = QMimeDatabase().mimeTypeForFileNameAndData(filename, &testMime).name(); m_fileSize = testMime.size(); // construct filter device KCompressionDevice::CompressionType compressionType = KFilterDev::compressionTypeForMimeType(m_mimeType); m_file = new KCompressionDevice(filename, compressionType); } /** * Destructor */ ~TextLoader() { delete m_file; delete m_converterState; } /** * open file with given codec * @param codec codec to use, if 0, will do some auto-dectect or fallback * @return success */ bool open(QTextCodec *codec) { m_codec = codec; m_eof = false; m_lastWasEndOfLine = true; m_lastWasR = false; m_position = 0; m_lastLineStart = 0; m_eol = TextBuffer::eolUnknown; m_text.clear(); delete m_converterState; m_converterState = new QTextCodec::ConverterState(QTextCodec::ConvertInvalidToNull); m_bomFound = false; m_firstRead = true; // init the hash with the git header const QString header = QStringLiteral("blob %1").arg(m_fileSize); m_digest.reset(); m_digest.addData(header.toLatin1() + '\0'); // if already opened, close the file... if (m_file->isOpen()) { m_file->close(); } return m_file->open(QIODevice::ReadOnly); } /** * end of file reached? * @return end of file reached */ bool eof() const { return m_eof && !m_lastWasEndOfLine && (m_lastLineStart == m_text.length()); } /** * Detected end of line mode for this file. * Detected during reading, is valid after complete file is read. * @return eol mode of this file */ TextBuffer::EndOfLineMode eol() const { return m_eol; } /** * BOM found? * @return byte order mark found? */ bool byteOrderMarkFound() const { return m_bomFound; } /** * mime type used to create filter dev * @return mime-type of filter device */ const QString &mimeTypeForFilterDev() const { return m_mimeType; } /** - * internal unicode data array - * @return internal unicode data + * internal Unicode data array + * @return internal Unicode data */ const QChar *unicode() const { return m_text.unicode(); } /** * Get codec for this loader * @return currently in use codec of this loader */ QTextCodec *textCodec() const { return m_codec; } /** - * read a line, return length + offset in unicode data - * @param offset offset into internal unicode data for read line + * read a line, return length + offset in Unicode data + * @param offset offset into internal Unicode data for read line * @param length length of read line * @return true if no encoding errors occurred */ bool readLine(int &offset, int &length) { length = 0; offset = 0; bool encodingError = false; static const QLatin1Char cr(QLatin1Char('\r')); static const QLatin1Char lf(QLatin1Char('\n')); /** * did we read two time but got no stuff? encoding error * fixes problem with one character latin-1 files, which lead to crash otherwise! * bug 272579 */ bool failedToConvertOnce = false; /** * reading loop */ while (m_position <= m_text.length()) { if (m_position == m_text.length()) { // try to load more text if something is around if (!m_eof) { // kill the old lines... m_text.remove(0, m_lastLineStart); // try to read new data const int c = m_file->read(m_buffer.data(), m_buffer.size()); // if any text is there, append it.... if (c > 0) { // update hash sum m_digest.addData(m_buffer.data(), c); - // detect byte order marks & codec for byte order markers on first read + // detect byte order marks & codec for byte order marks on first read int bomBytes = 0; if (m_firstRead) { // use first 16 bytes max to allow BOM detection of codec QByteArray bom(m_buffer.data(), qMin(16, c)); - QTextCodec *codecForByteOrderMark = QTextCodec::codecForUtfText(bom, 0); + QTextCodec *codecForByteOrderMark = QTextCodec::codecForUtfText(bom, nullptr); // if codec != null, we found a BOM! if (codecForByteOrderMark) { m_bomFound = true; // eat away the different boms! int mib = codecForByteOrderMark->mibEnum(); if (mib == 106) { // utf8 bomBytes = 3; } if (mib == 1013 || mib == 1014 || mib == 1015) { // utf16 bomBytes = 2; } if (mib == 1017 || mib == 1018 || mib == 1019) { // utf32 bomBytes = 4; } } /** * if no codec given, do autodetection */ if (!m_codec) { /** * byte order said something about encoding? */ if (codecForByteOrderMark) { m_codec = codecForByteOrderMark; } else { /** - * no unicode BOM found, trigger prober + * no Unicode BOM found, trigger prober */ /** * first: try to get HTML header encoding */ - if (QTextCodec *codecForHtml = QTextCodec::codecForHtml (m_buffer, 0)) { + if (QTextCodec *codecForHtml = QTextCodec::codecForHtml (m_buffer, nullptr)) { m_codec = codecForHtml; } /** * else: use KEncodingProber */ else { KEncodingProber prober(m_proberType); prober.feed(m_buffer.constData(), c); // we found codec with some confidence? if (prober.confidence() > 0.5) { m_codec = QTextCodec::codecForName(prober.encoding()); } } // no codec, no chance, encoding error if (!m_codec) { return false; } } } m_firstRead = false; } Q_ASSERT(m_codec); QString unicode = m_codec->toUnicode(m_buffer.constData() + bomBytes, c - bomBytes, m_converterState); // detect broken encoding for (int i = 0; i < unicode.size(); ++i) { if (unicode[i] == 0) { encodingError = true; break; } } m_text.append(unicode); } // is file completely read ? m_eof = (c == -1) || (c == 0); // recalc current pos and last pos m_position -= m_lastLineStart; m_lastLineStart = 0; } // oh oh, end of file, escape ! if (m_eof && (m_position == m_text.length())) { m_lastWasEndOfLine = false; // line data offset = m_lastLineStart; length = m_position - m_lastLineStart; m_lastLineStart = m_position; return !encodingError && !failedToConvertOnce; } // empty? try again if (m_position == m_text.length()) { failedToConvertOnce = true; continue; } } if (m_text.at(m_position) == lf) { m_lastWasEndOfLine = true; if (m_lastWasR) { m_lastLineStart++; m_lastWasR = false; m_eol = TextBuffer::eolDos; } else { // line data offset = m_lastLineStart; length = m_position - m_lastLineStart; m_lastLineStart = m_position + 1; m_position++; // only win, if not dos! if (m_eol != TextBuffer::eolDos) { m_eol = TextBuffer::eolUnix; } return !encodingError; } } else if (m_text.at(m_position) == cr) { m_lastWasEndOfLine = true; m_lastWasR = true; // line data offset = m_lastLineStart; length = m_position - m_lastLineStart; m_lastLineStart = m_position + 1; m_position++; // should only win of first time! if (m_eol == TextBuffer::eolUnknown) { m_eol = TextBuffer::eolMac; } return !encodingError; } else if (m_text.at(m_position) == QChar::LineSeparator) { m_lastWasEndOfLine = true; // line data offset = m_lastLineStart; length = m_position - m_lastLineStart; m_lastLineStart = m_position + 1; m_position++; return !encodingError; } else { m_lastWasEndOfLine = false; m_lastWasR = false; } m_position++; } return !encodingError; } QByteArray digest() { return m_digest.result(); } private: QTextCodec *m_codec; bool m_eof; bool m_lastWasEndOfLine; bool m_lastWasR; int m_position; int m_lastLineStart; TextBuffer::EndOfLineMode m_eol; QString m_mimeType; QIODevice *m_file; QByteArray m_buffer; QCryptographicHash m_digest; QString m_text; QTextCodec::ConverterState *m_converterState; bool m_bomFound; bool m_firstRead; KEncodingProber::ProberType m_proberType; quint64 m_fileSize; }; } #endif diff --git a/src/buffer/katetextrange.cpp b/src/buffer/katetextrange.cpp index 9907477f..b4403de0 100644 --- a/src/buffer/katetextrange.cpp +++ b/src/buffer/katetextrange.cpp @@ -1,356 +1,356 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 "katetextrange.h" #include "katetextbuffer.h" namespace Kate { TextRange::TextRange(TextBuffer &buffer, const KTextEditor::Range &range, InsertBehaviors insertBehavior, EmptyBehavior emptyBehavior) : m_buffer(buffer) , m_start(buffer, this, range.start(), (insertBehavior &ExpandLeft) ? Kate::TextCursor::StayOnInsert : Kate::TextCursor::MoveOnInsert) , m_end(buffer, this, range.end(), (insertBehavior &ExpandRight) ? Kate::TextCursor::MoveOnInsert : Kate::TextCursor::StayOnInsert) - , m_view(0) - , m_feedback(0) + , m_view(nullptr) + , m_feedback(nullptr) , m_zDepth(0.0) , m_attributeOnlyForViews(false) , m_invalidateIfEmpty(emptyBehavior == InvalidateIfEmpty) { // remember this range in buffer m_buffer.m_ranges.insert(this); // check if range now invalid, there can happen no feedback, as m_feedback == 0 checkValidity(); } TextRange::~TextRange() { /** * reset feedback, don't want feedback during destruction */ - m_feedback = 0; + m_feedback = nullptr; // remove range from m_ranges fixLookup(m_start.line(), m_end.line(), -1, -1); // remove this range from the buffer m_buffer.m_ranges.remove(this); /** * trigger update, if we have attribute * notify right view * here we can ignore feedback, even with feedback, we want none if the range is deleted! */ if (m_attribute) { m_buffer.notifyAboutRangeChange(m_view, m_start.line(), m_end.line(), true /* we have a attribute */); } } void TextRange::setInsertBehaviors(InsertBehaviors _insertBehaviors) { /** * nothing to do? */ if (_insertBehaviors == insertBehaviors()) { return; } /** * modify cursors */ m_start.setInsertBehavior((_insertBehaviors & ExpandLeft) ? KTextEditor::MovingCursor::StayOnInsert : KTextEditor::MovingCursor::MoveOnInsert); m_end.setInsertBehavior((_insertBehaviors & ExpandRight) ? KTextEditor::MovingCursor::MoveOnInsert : KTextEditor::MovingCursor::StayOnInsert); /** * notify world */ if (m_attribute || m_feedback) { m_buffer.notifyAboutRangeChange(m_view, m_start.line(), m_end.line(), true /* we have a attribute */); } } KTextEditor::MovingRange::InsertBehaviors TextRange::insertBehaviors() const { InsertBehaviors behaviors = DoNotExpand; if (m_start.insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) { behaviors |= ExpandLeft; } if (m_end.insertBehavior() == KTextEditor::MovingCursor::MoveOnInsert) { behaviors |= ExpandRight; } return behaviors; } void TextRange::setEmptyBehavior(EmptyBehavior emptyBehavior) { /** * nothing to do? */ if (m_invalidateIfEmpty == (emptyBehavior == InvalidateIfEmpty)) { return; } /** * remember value */ m_invalidateIfEmpty = (emptyBehavior == InvalidateIfEmpty); /** * invalidate range? */ if (end() <= start()) { setRange(KTextEditor::Range::invalid()); } } void TextRange::setRange(const KTextEditor::Range &range) { // avoid work if nothing changed! if (range == toRange()) { return; } // remember old line range int oldStartLine = m_start.line(); int oldEndLine = m_end.line(); // change start and end cursor m_start.setPosition(range.start()); m_end.setPosition(range.end()); // check if range now invalid, don't emit feedback here, will be handled below // otherwise you can't delete ranges in feedback! checkValidity(oldStartLine, oldEndLine, false); // no attribute or feedback set, be done if (!m_attribute && !m_feedback) { return; } // get full range int startLineMin = oldStartLine; if (oldStartLine == -1 || (m_start.line() != -1 && m_start.line() < oldStartLine)) { startLineMin = m_start.line(); } int endLineMax = oldEndLine; if (oldEndLine == -1 || m_end.line() > oldEndLine) { endLineMax = m_end.line(); } /** * notify buffer about attribute change, it will propagate the changes * notify right view */ m_buffer.notifyAboutRangeChange(m_view, startLineMin, endLineMax, m_attribute); // perhaps need to notify stuff! if (m_feedback) { // do this last: may delete this range if (!toRange().isValid()) { m_feedback->rangeInvalid(this); } else if (toRange().isEmpty()) { m_feedback->rangeEmpty(this); } } } void TextRange::checkValidity(int oldStartLine, int oldEndLine, bool notifyAboutChange) { /** * check if any cursor is invalid or the range is zero size and it should be invalidated then */ if (!m_start.isValid() || !m_end.isValid() || (m_invalidateIfEmpty && m_end <= m_start)) { m_start.setPosition(-1, -1); m_end.setPosition(-1, -1); } /** * for ranges which are allowed to become empty, normalize them, if the end has moved to the front of the start */ if (!m_invalidateIfEmpty && m_end < m_start) { m_end.setPosition(m_start); } // fix lookup fixLookup(oldStartLine, oldEndLine, m_start.line(), m_end.line()); // perhaps need to notify stuff! if (notifyAboutChange && m_feedback) { m_buffer.notifyAboutRangeChange(m_view, m_start.line(), m_end.line(), false /* attribute not interesting here */); // do this last: may delete this range if (!toRange().isValid()) { m_feedback->rangeInvalid(this); } else if (toRange().isEmpty()) { m_feedback->rangeEmpty(this); } } } void TextRange::fixLookup(int oldStartLine, int oldEndLine, int startLine, int endLine) { // nothing changed? if (oldStartLine == startLine && oldEndLine == endLine) { return; } // now, not both can be invalid Q_ASSERT(oldStartLine >= 0 || startLine >= 0); Q_ASSERT(oldEndLine >= 0 || endLine >= 0); // get full range int startLineMin = oldStartLine; if (oldStartLine == -1 || (startLine != -1 && startLine < oldStartLine)) { startLineMin = startLine; } int endLineMax = oldEndLine; if (oldEndLine == -1 || endLine > oldEndLine) { endLineMax = endLine; } // get start block int blockIndex = m_buffer.blockForLine(startLineMin); Q_ASSERT(blockIndex >= 0); // remove this range from m_ranges for (; blockIndex < m_buffer.m_blocks.size(); ++blockIndex) { // get block TextBlock *block = m_buffer.m_blocks[blockIndex]; // either insert or remove range if ((endLine < block->startLine()) || (startLine >= (block->startLine() + block->lines()))) { block->removeRange(this); } else { block->updateRange(this); } // ok, reached end block if (endLineMax < (block->startLine() + block->lines())) { return; } } // we should not be here, really, then endLine is wrong Q_ASSERT(false); } void TextRange::setView(KTextEditor::View *view) { /** * nothing changes, nop */ if (view == m_view) { return; } /** * remember the new attribute */ m_view = view; /** * notify buffer about attribute change, it will propagate the changes * notify all views (can be optimized later) */ if (m_attribute || m_feedback) { - m_buffer.notifyAboutRangeChange(0, m_start.line(), m_end.line(), m_attribute); + m_buffer.notifyAboutRangeChange(nullptr, m_start.line(), m_end.line(), m_attribute); } } void TextRange::setAttribute(KTextEditor::Attribute::Ptr attribute) { /** * remember the new attribute */ m_attribute = attribute; /** * notify buffer about attribute change, it will propagate the changes * notify right view */ m_buffer.notifyAboutRangeChange(m_view, m_start.line(), m_end.line(), m_attribute); } void TextRange::setFeedback(KTextEditor::MovingRangeFeedback *feedback) { /** * nothing changes, nop */ if (feedback == m_feedback) { return; } /** * remember the new feedback object */ m_feedback = feedback; /** * notify buffer about feedback change, it will propagate the changes * notify right view */ m_buffer.notifyAboutRangeChange(m_view, m_start.line(), m_end.line(), m_attribute); } void TextRange::setAttributeOnlyForViews(bool onlyForViews) { /** * just set the value, no need to trigger updates, printing is not interruptable */ m_attributeOnlyForViews = onlyForViews; } void TextRange::setZDepth(qreal zDepth) { /** * nothing changes, nop */ if (zDepth == m_zDepth) { return; } /** * remember the new attribute */ m_zDepth = zDepth; /** * notify buffer about attribute change, it will propagate the changes */ if (m_attribute) { m_buffer.notifyAboutRangeChange(m_view, m_start.line(), m_end.line(), m_attribute); } } KTextEditor::Document *Kate::TextRange::document() const { return m_buffer.document(); } } diff --git a/src/buffer/katetextrange.h b/src/buffer/katetextrange.h index 77c0cbf1..74383c57 100644 --- a/src/buffer/katetextrange.h +++ b/src/buffer/katetextrange.h @@ -1,363 +1,363 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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_TEXTRANGE_H #define KATE_TEXTRANGE_H #include #include #include #include #include "katetextcursor.h" namespace Kate { class TextBuffer; /** * Class representing a 'clever' text range. * It will automagically move if the text inside the buffer it belongs to is modified. * By intention no subclass of KTextEditor::Range, must be converted manually. * A TextRange is allowed to be empty. If you call setInvalidateIfEmpty(true), * a TextRange will become automatically invalid as soon as start() == end() * position holds. */ class KTEXTEDITOR_EXPORT TextRange : public KTextEditor::MovingRange { // this is a friend, block changes might invalidate ranges... friend class TextBlock; public: /** * Construct a text range. * A TextRange is not allowed to be empty, as soon as start == end position, it will become * automatically invalid! * @param buffer parent text buffer * @param range The initial text range assumed by the new range. * @param insertBehavior Define whether the range should expand when text is inserted adjacent to the range. * @param emptyBehavior Define whether the range should invalidate itself on becoming empty. */ TextRange(TextBuffer &buffer, const KTextEditor::Range &range, InsertBehaviors insertBehavior, EmptyBehavior emptyBehavior = AllowEmpty); /** * Destruct the text block */ ~TextRange(); /** * Set insert behaviors. * @param insertBehaviors new insert behaviors */ void setInsertBehaviors(InsertBehaviors insertBehaviors) Q_DECL_OVERRIDE; /** * Get current insert behaviors. * @return current insert behaviors */ InsertBehaviors insertBehaviors() const Q_DECL_OVERRIDE; /** * Set if this range will invalidate itself if it becomes empty. * @param emptyBehavior behavior on becoming empty */ void setEmptyBehavior(EmptyBehavior emptyBehavior) Q_DECL_OVERRIDE; /** * Will this range invalidate itself if it becomes empty? * @return behavior on becoming empty */ EmptyBehavior emptyBehavior() const Q_DECL_OVERRIDE { return m_invalidateIfEmpty ? InvalidateIfEmpty : AllowEmpty; } /** * Gets the document to which this range is bound. * \return a pointer to the document */ KTextEditor::Document *document() const Q_DECL_OVERRIDE; /** * Set the range of this range. * A TextRange is not allowed to be empty, as soon as start == end position, it will become * automatically invalid! * @param range new range for this clever range */ void setRange(const KTextEditor::Range &range) Q_DECL_OVERRIDE; /** * \overload * Set the range of this range * A TextRange is not allowed to be empty, as soon as start == end position, it will become * automatically invalid! * @param start new start for this clever range * @param end new end for this clever range */ void setRange(const KTextEditor::Cursor &start, const KTextEditor::Cursor &end) { KTextEditor::MovingRange::setRange(start, end); } /** * Retrieve start cursor of this range, read-only. * @return start cursor */ const KTextEditor::MovingCursor &start() const Q_DECL_OVERRIDE { return m_start; } /** * Non-virtual version of start(), which is faster. * @return start cursor */ const TextCursor &startInternal() const { return m_start; } /** * Retrieve end cursor of this range, read-only. * @return end cursor */ const KTextEditor::MovingCursor &end() const Q_DECL_OVERRIDE { return m_end; } /** * Nonvirtual version of end(), which is faster. * @return end cursor */ const TextCursor &endInternal() const { return m_end; } /** * Convert this clever range into a dumb one. * @return normal range */ const KTextEditor::Range toRange() const { return KTextEditor::Range(start().toCursor(), end().toCursor()); } /** * Convert this clever range into a dumb one. Equal to toRange, allowing to use implicit conversion. * @return normal range */ operator KTextEditor::Range() const { return KTextEditor::Range(start().toCursor(), end().toCursor()); } /** * Gets the active view for this range. Might be already invalid, internally only used for pointer comparisons. * * \return a pointer to the active view */ KTextEditor::View *view() const Q_DECL_OVERRIDE { return m_view; } /** * Sets the currently active view for this range. * This will trigger update of the relevant view parts, if the view changed. * Set view before the attribute, that will avoid not needed redraws. * - * \param attribute View to assign to this range. If null, simply + * \param view View to assign to this range. If null, simply * removes the previous view. */ void setView(KTextEditor::View *view) Q_DECL_OVERRIDE; /** * Gets the active Attribute for this range. * * \return a pointer to the active attribute */ KTextEditor::Attribute::Ptr attribute() const Q_DECL_OVERRIDE { return m_attribute; } /** * \return whether a nonzero attribute is set. This is faster than checking attribute(), * because the reference-counting is omitted. */ bool hasAttribute() const { return m_attribute.constData(); } /** * Sets the currently active attribute for this range. * This will trigger update of the relevant view parts. * * \param attribute Attribute to assign to this range. If null, simply * removes the previous Attribute. */ void setAttribute(KTextEditor::Attribute::Ptr attribute) Q_DECL_OVERRIDE; /** * Gets the active MovingRangeFeedback for this range. * * \return a pointer to the active MovingRangeFeedback */ KTextEditor::MovingRangeFeedback *feedback() const Q_DECL_OVERRIDE { return m_feedback; } /** * Sets the currently active MovingRangeFeedback for this range. * This will trigger evaluation if feedback must be send again (for example if mouse is already inside range). * - * \param attribute MovingRangeFeedback to assign to this range. If null, simply + * \param feedback MovingRangeFeedback to assign to this range. If null, simply * removes the previous MovingRangeFeedback. */ void setFeedback(KTextEditor::MovingRangeFeedback *feedback) Q_DECL_OVERRIDE; /** * Is this range's attribute only visible in views, not for example prints? * Default is false. * @return range visible only for views */ bool attributeOnlyForViews() const Q_DECL_OVERRIDE { return m_attributeOnlyForViews; } /** * Set if this range's attribute is only visible in views, not for example prints. * @param onlyForViews attribute only valid for views */ void setAttributeOnlyForViews(bool onlyForViews) Q_DECL_OVERRIDE; /** * Gets the current Z-depth of this range. * Ranges with smaller Z-depth than others will win during rendering. * Default is 0.0. * * \return current Z-depth of this range */ qreal zDepth() const Q_DECL_OVERRIDE { return m_zDepth; } /** * Set the current Z-depth of this range. * Ranges with smaller Z-depth than others will win during rendering. * This will trigger update of the relevant view parts, if the depth changed. * Set depth before the attribute, that will avoid not needed redraws. * Default is 0.0. * * \param zDepth new Z-depth of this range */ void setZDepth(qreal zDepth) Q_DECL_OVERRIDE; private: /** * no copy constructor, don't allow this to be copied. */ TextRange(const TextRange &); /** * no assignment operator, no copying around. */ TextRange &operator= (const TextRange &); /** * Check if range is valid, used by constructor and setRange. * If at least one cursor is invalid, both will set to invalid. * Same if range itself is invalid (start >= end). * @param oldStartLine old start line of this range before changing of cursors, needed to add/remove range from m_ranges in blocks * @param oldEndLine old end line of this range * @param notifyAboutChange should feedback be emitted or not? */ void checkValidity(int oldStartLine = -1, int oldEndLine = -1, bool notifyAboutChange = true); /** * Add/Remove range from the lookup m_ranges hash of each block * @param oldStartLine old start line of this range before changing of cursors, needed to add/remove range from m_ranges in blocks * @param oldEndLine old end line of this range * @param startLine start line to start looking for the range to remove * @param endLine end line of this range */ void fixLookup(int oldStartLine, int oldEndLine, int startLine, int endLine); private: /** * parent text buffer * is a reference, and no pointer, as this must always exist and can't change */ TextBuffer &m_buffer; /** * Start cursor for this range, is a clever cursor */ TextCursor m_start; /** * End cursor for this range, is a clever cursor */ TextCursor m_end; /** * The view for which the attribute is valid, 0 means any view */ KTextEditor::View *m_view; /** * This range's current attribute. */ KTextEditor::Attribute::Ptr m_attribute; /** * pointer to the active MovingRangeFeedback */ KTextEditor::MovingRangeFeedback *m_feedback; /** * Z-depth of this range for rendering */ qreal m_zDepth; /** * Is this range's attribute only visible in views, not for example prints? */ bool m_attributeOnlyForViews; /** * Will this range invalidate itself if it becomes empty? */ bool m_invalidateIfEmpty; }; } #endif diff --git a/src/buffer/org.kde.ktexteditor.katetextbuffer.actions b/src/buffer/org.kde.ktexteditor.katetextbuffer.actions new file mode 100644 index 00000000..b52e74a8 --- /dev/null +++ b/src/buffer/org.kde.ktexteditor.katetextbuffer.actions @@ -0,0 +1,85 @@ +[Domain] +Name=Document Actions +Name[ca]=Accions de document +Name[ca@valencia]=Accions de document +Name[cs]=Činnosti dokumentu +Name[de]=Dokument-Aktionen +Name[en_GB]=Document Actions +Name[es]=Acciones de documento +Name[fi]=Tiedosto-toiminnot +Name[fr]=Actions du document +Name[ia]=Actiones de documento +Name[it]=Azioni documenti +Name[nl]=Documentacties +Name[pl]=Działania na dokumentach +Name[pt]=Acções do Documento +Name[sl]=Dejanja dokumenta +Name[sr]=Радње над документом +Name[sr@ijekavian]=Радње над документом +Name[sr@ijekavianlatin]=Radnje nad dokumentom +Name[sr@latin]=Radnje nad dokumentom +Name[sv]=Dokumentåtgärder +Name[tr]=Belge İşlemleri +Name[uk]=Дії над документом +Name[x-test]=xxDocument Actionsxx +Name[zh_CN]=文档动作 +Name[zh_TW]=文件動作 +Policy=auth_admin +Persistence=session + +[org.kde.ktexteditor.katetextbuffer.savefile] +Name=Save Document +Name[ca]=Desa el document +Name[ca@valencia]=Guarda el document +Name[cs]=Uložit dokument +Name[de]=Dokument speichern +Name[en_GB]=Save Document +Name[es]=Guardar documento +Name[fi]=Tallenna tiedosto +Name[fr]=Enregistrer le document +Name[ia]=Salveguarda documento +Name[it]=Salva documento +Name[nl]=Document opslaan +Name[nn]=Lagra dokument +Name[pl]=Zapisz dokument +Name[pt]=Gravar o Documento +Name[sl]=Shrani dokument +Name[sr]=Сачувај документ +Name[sr@ijekavian]=Сачувај документ +Name[sr@ijekavianlatin]=Sačuvaj dokument +Name[sr@latin]=Sačuvaj dokument +Name[sv]=Spara dokument +Name[tr]=Belgeyi Kaydet +Name[uk]=Зберегти документ +Name[x-test]=xxSave Documentxx +Name[zh_CN]=保存文档 +Name[zh_TW]=儲存文件 +Description=Root privileges are needed to save this document +Description[ast]=Precísense privilexos root pa guardar esti documentu +Description[ca]=Es requereixen privilegis d'administrador per desar aquest document +Description[ca@valencia]=Es requereixen privilegis d'administrador per guardar este document +Description[cs]=K uložení dokumentu je potřeba práva uživatele root +Description[de]=Rechte als Systemverwalter sind für das Speichern diese Dokuments erforderlich +Description[en_GB]=Root privileges are needed to save this document +Description[es]=Se necesitan permisos de «root» para guardar este documento +Description[fi]=Tämän tiedoston tallentamiseen tarvitaan pääkäyttäjän oikeudet +Description[fr]=Vous devez disposer des privilèges de superutilisateur pour enregistrer ce document +Description[ia]=Privilegios de radice root es necessari per salveguardar iste documento +Description[it]=Sono necessari i privilegi di root per salvare questo documento +Description[nl]=Om dit document op te slaan zijn root-rechten nodig +Description[nn]=Du må ha rotløyve for å kunna lagra dokumentet +Description[pl]=Wymagane są uprawnienia administratora do zapisania tego dokumentu +Description[pt]=São necessários privilégios de 'root' para gravar este documento +Description[sl]=Za shranjevanje tega dokumenta so zahtevana skrbniška dovoljenja +Description[sr]=Уписивање овог документа захтева корена овлашћења +Description[sr@ijekavian]=Уписивање овог документа захтева корена овлашћења +Description[sr@ijekavianlatin]=Upisivanje ovog dokumenta zahteva korena ovlašćenja +Description[sr@latin]=Upisivanje ovog dokumenta zahteva korena ovlašćenja +Description[sv]=Systemadministratörsprivilegier krävs för att spara dokumentet +Description[tr]=Bu belgeyi kaydetmek için yönetici ayrıcalıkları gereklidir +Description[uk]=Для збереження документа потрібні права доступу користувача root +Description[x-test]=xxRoot privileges are needed to save this documentxx +Description[zh_CN]=保存文档需要超级用户权限 +Description[zh_TW]=需要 root 權限以儲存此文件 +Policy=auth_admin +Persistence=session diff --git a/src/completion/expandingtree/expandingdelegate.h b/src/completion/expandingtree/expandingdelegate.h index 5ac07cf5..3395b380 100644 --- a/src/completion/expandingtree/expandingdelegate.h +++ b/src/completion/expandingtree/expandingdelegate.h @@ -1,86 +1,86 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 ExpandingDelegate_H #define ExpandingDelegate_H #include #include #include class ExpandingWidgetModel; class QVariant; class QStyleOptionViewItem; /** * This is a delegate that cares, together with ExpandingWidgetModel, about embedded widgets in tree-view. * */ class ExpandingDelegate : public QItemDelegate { Q_OBJECT public: - explicit ExpandingDelegate(ExpandingWidgetModel *model, QObject *parent = 0L); + explicit ExpandingDelegate(ExpandingWidgetModel *model, QObject *parent = nullptr); // Overridden to create highlighting for current index void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; // Returns the basic size-hint as reported by QItemDelegate QSize basicSizeHint(const QModelIndex &index) const; ExpandingWidgetModel *model() const; protected: //Called right before paint to allow last-minute changes to the style virtual void adjustStyle(const QModelIndex &index, QStyleOptionViewItem &option) const; void drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QString &text) const Q_DECL_OVERRIDE; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) Q_DECL_OVERRIDE; virtual void drawBackground(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; void drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QPixmap &pixmap) const Q_DECL_OVERRIDE; //option can be changed virtual QList createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const; void adjustRect(QRect &rect) const; /** * Creates a list of FormatRanges as should be returned by createHighlighting from a list of QVariants as described in the kde header ktexteditor/codecompletionmodel.h * */ QList highlightingFromVariantList(const QList &customHighlights) const; //Called when an item was expanded/unexpanded and the height changed virtual void heightChanged() const; //Initializes the style options from the index void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const; mutable int m_currentColumnStart; //Text-offset for custom highlighting, will be applied to m_cachedHighlights(Only highlights starting after this will be used). Shoult be zero of the highlighting is not taken from kate. mutable QList m_currentColumnStarts; mutable QList m_cachedHighlights; mutable Qt::Alignment m_cachedAlignment; mutable QColor m_backgroundColor; mutable QModelIndex m_currentIndex; private: ExpandingWidgetModel *m_model; }; #endif diff --git a/src/completion/expandingtree/expandingwidgetmodel.cpp b/src/completion/expandingtree/expandingwidgetmodel.cpp index 1e126a98..3b38d24a 100644 --- a/src/completion/expandingtree/expandingwidgetmodel.cpp +++ b/src/completion/expandingtree/expandingwidgetmodel.cpp @@ -1,560 +1,560 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 "expandingwidgetmodel.h" #include #include #include #include #include #include #include #include "kcolorutils.h" #include "expandingdelegate.h" #include "katepartdebug.h" using namespace KTextEditor; inline QModelIndex firstColumn(const QModelIndex &index) { return index.sibling(index.row(), 0); } ExpandingWidgetModel::ExpandingWidgetModel(QWidget *parent) : QAbstractItemModel(parent) { } ExpandingWidgetModel::~ExpandingWidgetModel() { clearExpanding(); } static QColor doAlternate(QColor color) { QColor background = QApplication::palette().background().color(); return KColorUtils::mix(color, background, 0.15); } uint ExpandingWidgetModel::matchColor(const QModelIndex &index) const { int matchQuality = contextMatchQuality(index.sibling(index.row(), 0)); if (matchQuality > 0) { bool alternate = index.row() & 1; QColor badMatchColor(0xff00aa44); //Blue-ish green QColor goodMatchColor(0xff00ff00); //Green QColor background = treeView()->palette().light().color(); QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, ((float)matchQuality) / 10.0); if (alternate) { totalColor = doAlternate(totalColor); } const qreal dynamicTint = 0.2; const qreal minimumTint = 0.2; qreal tintStrength = (dynamicTint * matchQuality) / 10; if (tintStrength) { tintStrength += minimumTint; //Some minimum tinting strength, else it's not visible any more } return KColorUtils::tint(background, totalColor, tintStrength).rgb(); } else { return 0; } } QVariant ExpandingWidgetModel::data(const QModelIndex &index, int role) const { switch (role) { case Qt::BackgroundRole: { if (index.column() == 0) { //Highlight by match-quality uint color = matchColor(index); if (color) { return QBrush(color); } } //Use a special background-color for expanded items if (isExpanded(index)) { if (index.row() & 1) { return doAlternate(treeView()->palette().toolTipBase().color()); } else { return treeView()->palette().toolTipBase(); } } } } return QVariant(); } void ExpandingWidgetModel::clearMatchQualities() { m_contextMatchQualities.clear(); } QModelIndex ExpandingWidgetModel::partiallyExpandedRow() const { if (m_partiallyExpanded.isEmpty()) { return QModelIndex(); } else { return m_partiallyExpanded.constBegin().key(); } } void ExpandingWidgetModel::clearExpanding() { clearMatchQualities(); QMap oldExpandState = m_expandState; foreach (const QPointer &widget, m_expandingWidgets) if (widget) { widget->deleteLater(); // By using deleteLater, we prevent crashes when an action within a widget makes the completion cancel } m_expandingWidgets.clear(); m_expandState.clear(); m_partiallyExpanded.clear(); for (QMap::const_iterator it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it) if (it.value() == Expanded) { emit dataChanged(it.key(), it.key()); } } ExpandingWidgetModel::ExpansionType ExpandingWidgetModel::isPartiallyExpanded(const QModelIndex &index) const { if (m_partiallyExpanded.contains(firstColumn(index))) { return m_partiallyExpanded[firstColumn(index)]; } else { return NotExpanded; } } void ExpandingWidgetModel::partiallyUnExpand(const QModelIndex &idx_) { QModelIndex index(firstColumn(idx_)); m_partiallyExpanded.remove(index); m_partiallyExpanded.remove(idx_); } int ExpandingWidgetModel::partiallyExpandWidgetHeight() const { return 60; ///@todo use font-metrics text-height*2 for 2 lines } void ExpandingWidgetModel::rowSelected(const QModelIndex &idx_) { QModelIndex idx(firstColumn(idx_)); if (!m_partiallyExpanded.contains(idx)) { QModelIndex oldIndex = partiallyExpandedRow(); //Unexpand the previous partially expanded row if (!m_partiallyExpanded.isEmpty()) { ///@todo allow multiple partially expanded rows while (!m_partiallyExpanded.isEmpty()) { m_partiallyExpanded.erase(m_partiallyExpanded.begin()); } //partiallyUnExpand( m_partiallyExpanded.begin().key() ); } //Notify the underlying models that the item was selected, and eventually get back the text for the expanding widget. if (!idx.isValid()) { //All items have been unselected if (oldIndex.isValid()) { emit dataChanged(oldIndex, oldIndex); } } else { QVariant variant = data(idx, CodeCompletionModel::ItemSelected); if (!isExpanded(idx) && variant.type() == QVariant::String) { //Either expand upwards or downwards, choose in a way that //the visible fields of the new selected entry are not moved. if (oldIndex.isValid() && (oldIndex < idx || (!(oldIndex < idx) && oldIndex.parent() < idx.parent()))) { m_partiallyExpanded.insert(idx, ExpandUpwards); } else { m_partiallyExpanded.insert(idx, ExpandDownwards); } //Say that one row above until one row below has changed, so no items will need to be moved(the space that is taken from one item is given to the other) if (oldIndex.isValid() && oldIndex < idx) { emit dataChanged(oldIndex, idx); if (treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem) { //Qt fails to correctly scroll in ScrollPerItem mode, so the selected index is completely visible, //so we do the scrolling by hand. QRect selectedRect = treeView()->visualRect(idx); QRect frameRect = treeView()->frameRect(); if (selectedRect.bottom() > frameRect.bottom()) { int diff = selectedRect.bottom() - frameRect.bottom(); //We need to scroll down QModelIndex newTopIndex = idx; QModelIndex nextTopIndex = idx; QRect nextRect = treeView()->visualRect(nextTopIndex); while (nextTopIndex.isValid() && nextRect.isValid() && nextRect.top() >= diff) { newTopIndex = nextTopIndex; nextTopIndex = treeView()->indexAbove(nextTopIndex); if (nextTopIndex.isValid()) { nextRect = treeView()->visualRect(nextTopIndex); } } treeView()->scrollTo(newTopIndex, QAbstractItemView::PositionAtTop); } } //This is needed to keep the item we are expanding completely visible. Qt does not scroll the view to keep the item visible. //But we must make sure that it isn't too expensive. //We need to make sure that scrolling is efficient, and the whole content is not repainted. //Since we are scrolling anyway, we can keep the next line visible, which might be a cool feature. //Since this also doesn't work smoothly, leave it for now //treeView()->scrollTo( nextLine, QAbstractItemView::EnsureVisible ); } else if (oldIndex.isValid() && idx < oldIndex) { emit dataChanged(idx, oldIndex); //For consistency with the down-scrolling, we keep one additional line visible above the current visible. //Since this also doesn't work smoothly, leave it for now /* QModelIndex prevLine = idx.sibling(idx.row()-1, idx.column()); if( prevLine.isValid() ) treeView()->scrollTo( prevLine );*/ } else { emit dataChanged(idx, idx); } } else if (oldIndex.isValid()) { //We are not partially expanding a new row, but we previously had a partially expanded row. So signalize that it has been unexpanded. emit dataChanged(oldIndex, oldIndex); } } } else { qCDebug(LOG_KTE) << "ExpandingWidgetModel::rowSelected: Row is already partially expanded"; } } QString ExpandingWidgetModel::partialExpandText(const QModelIndex &idx) const { if (!idx.isValid()) { return QString(); } return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString(); } QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex &idx_) const { QModelIndex idx(firstColumn(idx_)); if (!idx.isValid()) { return QRect(); } ExpansionType expansion = ExpandDownwards; if (m_partiallyExpanded.find(idx) != m_partiallyExpanded.constEnd()) { expansion = m_partiallyExpanded[idx]; } //Get the whole rectangle of the row: QModelIndex rightMostIndex = idx; QModelIndex tempIndex = idx; while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) { rightMostIndex = tempIndex; } QRect rect = treeView()->visualRect(idx); QRect rightMostRect = treeView()->visualRect(rightMostIndex); rect.setLeft(rect.left() + 20); rect.setRight(rightMostRect.right() - 5); //These offsets must match exactly those used in ExpandingDelegate::sizeHint() int top = rect.top() + 5; int bottom = rightMostRect.bottom() - 5; if (expansion == ExpandDownwards) { top += basicRowHeight(idx); } else { bottom -= basicRowHeight(idx); } rect.setTop(top); rect.setBottom(bottom); return rect; } bool ExpandingWidgetModel::isExpandable(const QModelIndex &idx_) const { QModelIndex idx(firstColumn(idx_)); if (!m_expandState.contains(idx)) { m_expandState.insert(idx, NotExpandable); QVariant v = data(idx, CodeCompletionModel::IsExpandable); if (v.canConvert() && v.toBool()) { m_expandState[idx] = Expandable; } } return m_expandState[idx] != NotExpandable; } bool ExpandingWidgetModel::isExpanded(const QModelIndex &idx_) const { QModelIndex idx(firstColumn(idx_)); return m_expandState.contains(idx) && m_expandState[idx] == Expanded; } void ExpandingWidgetModel::setExpanded(QModelIndex idx_, bool expanded) { QModelIndex idx(firstColumn(idx_)); //qCDebug(LOG_KTE) << "Setting expand-state of row " << idx.row() << " to " << expanded; if (!idx.isValid()) { return; } if (isExpandable(idx)) { if (!expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx]) { m_expandingWidgets[idx]->hide(); } m_expandState[idx] = expanded ? Expanded : Expandable; if (expanded) { partiallyUnExpand(idx); } if (expanded && !m_expandingWidgets.contains(idx)) { QVariant v = data(idx, CodeCompletionModel::ExpandingWidget); if (v.canConvert()) { m_expandingWidgets[idx] = v.value(); } else if (v.canConvert()) { //Create a html widget that shows the given string KTextEdit *edit = new KTextEdit(v.toString()); edit->setReadOnly(true); edit->resize(200, 50); //Make the widget small so it embeds nicely. m_expandingWidgets[idx] = edit; } else { - m_expandingWidgets[idx] = 0; + m_expandingWidgets[idx] = nullptr; } } //Eventually partially expand the row if (!expanded && firstColumn(treeView()->currentIndex()) == idx && !isPartiallyExpanded(idx)) { rowSelected(idx); //Partially expand the row. } emit dataChanged(idx, idx); if (treeView()) { treeView()->scrollTo(idx); } } } int ExpandingWidgetModel::basicRowHeight(const QModelIndex &idx_) const { QModelIndex idx(firstColumn(idx_)); ExpandingDelegate *delegate = dynamic_cast(treeView()->itemDelegate(idx)); if (!delegate || !idx.isValid()) { qCDebug(LOG_KTE) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate"; return 15; } return delegate->basicSizeHint(idx).height(); } void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex &idx_) { QModelIndex idx(firstColumn(idx_)); - QWidget *w = 0; + QWidget *w = nullptr; if (m_expandingWidgets.contains(idx)) { w = m_expandingWidgets[idx]; } if (w && isExpanded(idx)) { if (!idx.isValid()) { return; } QRect rect = treeView()->visualRect(idx); if (!rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height()) { //The item is currently not visible w->hide(); return; } QModelIndex rightMostIndex = idx; QModelIndex tempIndex = idx; while ((tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column() + 1)).isValid()) { rightMostIndex = tempIndex; } QRect rightMostRect = treeView()->visualRect(rightMostIndex); //Find out the basic height of the row rect.setLeft(rect.left() + 20); rect.setRight(rightMostRect.right() - 5); //These offsets must match exactly those used in KateCompletionDeleage::sizeHint() rect.setTop(rect.top() + basicRowHeight(idx) + 5); rect.setHeight(w->height()); if (w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible()) { w->setParent(treeView()->viewport()); w->setGeometry(rect); w->show(); } } } void ExpandingWidgetModel::placeExpandingWidgets() { for (QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { placeExpandingWidget(it.key()); } } int ExpandingWidgetModel::expandingWidgetsHeight() const { int sum = 0; for (QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { if (isExpanded(it.key()) && (*it)) { sum += (*it)->height(); } } return sum; } QWidget *ExpandingWidgetModel::expandingWidget(const QModelIndex &idx_) const { QModelIndex idx(firstColumn(idx_)); if (m_expandingWidgets.contains(idx)) { return m_expandingWidgets[idx]; } else { - return 0; + return nullptr; } } void ExpandingWidgetModel::cacheIcons() const { if (m_expandedIcon.isNull()) { m_expandedIcon = QIcon::fromTheme(QStringLiteral("arrow-down")); } if (m_collapsedIcon.isNull()) { m_collapsedIcon = QIcon::fromTheme(QStringLiteral("arrow-right")); } } QList mergeCustomHighlighting(int leftSize, const QList &left, int rightSize, const QList &right) { QList ret = left; if (left.isEmpty()) { ret << QVariant(0); ret << QVariant(leftSize); ret << QTextFormat(QTextFormat::CharFormat); } if (right.isEmpty()) { ret << QVariant(leftSize); ret << QVariant(rightSize); ret << QTextFormat(QTextFormat::CharFormat); } else { QList::const_iterator it = right.constBegin(); while (it != right.constEnd()) { { QList::const_iterator testIt = it; for (int a = 0; a < 2; a++) { ++testIt; if (testIt == right.constEnd()) { qCWarning(LOG_KTE) << "Length of input is not multiple of 3"; break; } } } ret << QVariant((*it).toInt() + leftSize); ++it; ret << QVariant((*it).toInt()); ++it; ret << *it; if (!(*it).value().isValid()) { qCDebug(LOG_KTE) << "Text-format is invalid"; } ++it; } } return ret; } //It is assumed that between each two strings, one space is inserted QList mergeCustomHighlighting(QStringList strings, QList highlights, int grapBetweenStrings) { if (strings.isEmpty()) { qCWarning(LOG_KTE) << "List of strings is empty"; return QList(); } if (highlights.isEmpty()) { qCWarning(LOG_KTE) << "List of highlightings is empty"; return QList(); } if (strings.count() != highlights.count()) { qCWarning(LOG_KTE) << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same"; return QList(); } //Merge them together QString totalString = strings[0]; QVariantList totalHighlighting = highlights[0]; strings.pop_front(); highlights.pop_front(); while (!strings.isEmpty()) { totalHighlighting = mergeCustomHighlighting(totalString.length(), totalHighlighting, strings[0].length(), highlights[0]); totalString += strings[0]; for (int a = 0; a < grapBetweenStrings; a++) { totalString += QLatin1Char(' '); } strings.pop_front(); highlights.pop_front(); } //Combine the custom-highlightings return totalHighlighting; } diff --git a/src/completion/kateargumenthinttree.cpp b/src/completion/kateargumenthinttree.cpp index bd2db8eb..b7f650d9 100644 --- a/src/completion/kateargumenthinttree.cpp +++ b/src/completion/kateargumenthinttree.cpp @@ -1,316 +1,316 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 "kateargumenthinttree.h" #include #include #include #include #include "kateargumenthintmodel.h" #include "katecompletionwidget.h" #include "expandingtree/expandingwidgetmodel.h" #include "katecompletiondelegate.h" #include "kateview.h" #include -KateArgumentHintTree::KateArgumentHintTree(KateCompletionWidget *parent) : ExpandingTree(0), m_parent(parent) //Do not use the completion-widget as widget-parent, because the argument-hint-tree will be rendered separately +KateArgumentHintTree::KateArgumentHintTree(KateCompletionWidget *parent) : ExpandingTree(nullptr), m_parent(parent) //Do not use the completion-widget as widget-parent, because the argument-hint-tree will be rendered separately { setFrameStyle(QFrame::Box | QFrame::Plain); setLineWidth(1); connect(parent, SIGNAL(destroyed(QObject*)), this, SLOT(deleteLater())); setFrameStyle(QFrame::NoFrame); setFrameStyle(QFrame::Box | QFrame::Plain); setFocusPolicy(Qt::NoFocus); setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); setUniformRowHeights(false); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); header()->hide(); setRootIsDecorated(false); setIndentation(0); setAllColumnsShowFocus(true); setAlternatingRowColors(true); setItemDelegate(new KateCompletionDelegate(parent->argumentHintModel(), parent)); } void KateArgumentHintTree::clearCompletion() { setCurrentIndex(QModelIndex()); } KateArgumentHintModel *KateArgumentHintTree::model() const { return m_parent->argumentHintModel(); } void KateArgumentHintTree::paintEvent(QPaintEvent *event) { QTreeView::paintEvent(event); updateGeometry(); ///@todo delay this. It is needed here, because visualRect(...) returns an invalid rect in updateGeometry before the content is painted } void KateArgumentHintTree::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles) { Q_UNUSED(roles) QTreeView::dataChanged(topLeft, bottomRight); //updateGeometry(); } void KateArgumentHintTree::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { /* qCDebug(LOG_KTE) << "currentChanged()";*/ static_cast(model())->rowSelected(current); QTreeView::currentChanged(current, previous); } void KateArgumentHintTree::rowsInserted(const QModelIndex &parent, int start, int end) { QTreeView::rowsInserted(parent, start, end); updateGeometry(); } int KateArgumentHintTree::sizeHintForColumn(int column) const { return QTreeView::sizeHintForColumn(column); } unsigned int KateArgumentHintTree::rowHeight(const QModelIndex &index) const { uint max = sizeHintForIndex(index).height(); for (int a = 0; a < index.model()->columnCount(index.parent()); ++a) { QModelIndex i = index.sibling(index.row(), a); uint cSize = sizeHintForIndex(i).height(); if (cSize > max) { max = cSize; } } return max; } void KateArgumentHintTree::updateGeometry(QRect geom) { //Avoid recursive calls of updateGeometry static bool updatingGeometry = false; if (updatingGeometry) { return; } updatingGeometry = true; if (model()->rowCount(QModelIndex()) == 0) { /* qCDebug(LOG_KTE) << "KateArgumentHintTree:: empty model";*/ hide(); setGeometry(geom); updatingGeometry = false; return; } int bottom = geom.bottom(); int totalWidth = resizeColumns(); int totalHeight = 0; for (int a = 0; a < model()->rowCount(QModelIndex()); ++a) { QModelIndex index(model()->index(a, 0)); totalHeight += rowHeight(index); for (int b = 0; b < model()->rowCount(index); ++b) { QModelIndex childIndex = index.child(b, 0); totalHeight += rowHeight(childIndex); } } totalHeight += frameWidth() * 2; geom.setHeight(totalHeight); geom.moveBottom(bottom); // if( totalWidth > geom.width() ) geom.setWidth(totalWidth); bool enableScrollBars = false; //Resize and move so it fits the screen horizontally int maxWidth = (QApplication::desktop()->screenGeometry(m_parent->view()).width() * 3) / 4; if (geom.width() > maxWidth) { geom.setWidth(maxWidth); geom.setHeight(geom.height() + horizontalScrollBar()->height() + 2); geom.moveBottom(bottom); enableScrollBars = true; } if (geom.right() > QApplication::desktop()->screenGeometry(m_parent->view()).right()) { geom.moveRight(QApplication::desktop()->screenGeometry(m_parent->view()).right()); } if (geom.left() < QApplication::desktop()->screenGeometry(m_parent->view()).left()) { geom.moveLeft(QApplication::desktop()->screenGeometry(m_parent->view()).left()); } //Resize and move so it fits the screen vertically bool resized = false; if (geom.top() < QApplication::desktop()->screenGeometry(this).top()) { int offset = QApplication::desktop()->screenGeometry(this).top() - geom.top(); geom.setBottom(geom.bottom() - offset); geom.moveTo(geom.left(), QApplication::desktop()->screenGeometry(this).top()); resized = true; } if (geom != geometry()) { setUpdatesEnabled(false); setAnimated(false); setHorizontalScrollBarPolicy(enableScrollBars ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); /* qCDebug(LOG_KTE) << "KateArgumentHintTree::updateGeometry: updating geometry to " << geom;*/ setGeometry(geom); if (resized && currentIndex().isValid()) { scrollTo(currentIndex()); } setUpdatesEnabled(true); } updatingGeometry = false; } int KateArgumentHintTree::resizeColumns() { int totalSize = 0; for (int a = 0; a < header()->count(); a++) { int columnSize = sizeHintForColumn(a); setColumnWidth(a, columnSize); totalSize += columnSize; } return totalSize; } void KateArgumentHintTree::updateGeometry() { updateGeometry(geometry()); } bool KateArgumentHintTree::nextCompletion() { QModelIndex current; QModelIndex firstCurrent = currentIndex(); do { QModelIndex oldCurrent = currentIndex(); current = moveCursor(MoveDown, Qt::NoModifier); if (current != oldCurrent && current.isValid()) { setCurrentIndex(current); } else { if (firstCurrent.isValid()) { setCurrentIndex(firstCurrent); } return false; } } while (!model()->indexIsItem(current)); return true; } bool KateArgumentHintTree::previousCompletion() { QModelIndex current; QModelIndex firstCurrent = currentIndex(); do { QModelIndex oldCurrent = currentIndex(); current = moveCursor(MoveUp, Qt::NoModifier); if (current != oldCurrent && current.isValid()) { setCurrentIndex(current); } else { if (firstCurrent.isValid()) { setCurrentIndex(firstCurrent); } return false; } } while (!model()->indexIsItem(current)); return true; } bool KateArgumentHintTree::pageDown() { QModelIndex old = currentIndex(); QModelIndex current = moveCursor(MovePageDown, Qt::NoModifier); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) if (!nextCompletion()) { previousCompletion(); } } return current != old; } bool KateArgumentHintTree::pageUp() { QModelIndex old = currentIndex(); QModelIndex current = moveCursor(MovePageUp, Qt::NoModifier); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) if (!previousCompletion()) { nextCompletion(); } } return current != old; } void KateArgumentHintTree::top() { QModelIndex current = moveCursor(MoveHome, Qt::NoModifier); setCurrentIndex(current); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) { nextCompletion(); } } } void KateArgumentHintTree::bottom() { QModelIndex current = moveCursor(MoveEnd, Qt::NoModifier); setCurrentIndex(current); if (current.isValid()) { setCurrentIndex(current); if (!model()->indexIsItem(current)) { previousCompletion(); } } } diff --git a/src/completion/katecompletionconfig.cpp b/src/completion/katecompletionconfig.cpp index 2178c45e..4ebd13f5 100644 --- a/src/completion/katecompletionconfig.cpp +++ b/src/completion/katecompletionconfig.cpp @@ -1,436 +1,436 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 "katecompletionconfig.h" #include "katecompletionmodel.h" #include "kateglobal.h" #include "ui_completionconfigwidget.h" #include #include #include #include #include #include #include using namespace KTextEditor; KateCompletionConfig::KateCompletionConfig(KateCompletionModel *model, QWidget *parent) : QDialog(parent) , ui(new Ui::CompletionConfigWidget()) , m_model(model) { setWindowTitle(i18n("Code Completion Configuration")); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QWidget *mw = new QWidget(this); mainLayout->addWidget(mw); ui->setupUi(mw); // Sorting ui->sorting->setChecked(m_model->isSortingEnabled()); ui->sortingAlphabetical->setChecked(m_model->isSortingAlphabetical()); ui->sortingCaseSensitive->setChecked(m_model->sortingCaseSensitivity() == Qt::CaseSensitive); ui->groupingOrderUp->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); ui->groupingOrderDown->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); connect(ui->groupingOrderUp, SIGNAL(pressed()), SLOT(moveGroupingOrderUp())); connect(ui->groupingOrderDown, SIGNAL(pressed()), SLOT(moveGroupingOrderDown())); // Filtering ui->filtering->setChecked(m_model->isFilteringEnabled()); ui->filteringContextMatchOnly->setChecked(m_model->filterContextMatchesOnly()); ui->filteringHideAttributes->setChecked(m_model->filterByAttribute()); for (CodeCompletionModel::CompletionProperty i = CodeCompletionModel::FirstProperty; i <= CodeCompletionModel::LastProperty; i = static_cast(i << 1)) { QListWidgetItem *item = new QListWidgetItem(m_model->propertyName(i), ui->filteringAttributesList, i); item->setCheckState((m_model->filterAttributes() & i) ? Qt::Checked : Qt::Unchecked); } ui->filteringMaximumInheritanceDepth->setValue(m_model->maximumInheritanceDepth()); // Grouping ui->grouping->setChecked(m_model->isGroupingEnabled()); ui->groupingUp->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); ui->groupingDown->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); m_groupingScopeType = ui->groupingMethods->topLevelItem(0); m_groupingScopeType->setCheckState(0, (m_model->groupingMethod() & KateCompletionModel::ScopeType) ? Qt::Checked : Qt::Unchecked); m_groupingScope = ui->groupingMethods->topLevelItem(1); m_groupingScope->setCheckState(0, (m_model->groupingMethod() & KateCompletionModel::Scope) ? Qt::Checked : Qt::Unchecked); m_groupingAccessType = ui->groupingMethods->topLevelItem(2); m_groupingAccessType->setCheckState(0, (m_model->groupingMethod() & KateCompletionModel::AccessType) ? Qt::Checked : Qt::Unchecked); m_groupingItemType = ui->groupingMethods->topLevelItem(3); m_groupingItemType->setCheckState(0, (m_model->groupingMethod() & KateCompletionModel::ItemType) ? Qt::Checked : Qt::Unchecked); ui->accessConst->setChecked(m_model->accessIncludeConst()); ui->accessStatic->setChecked(m_model->accessIncludeStatic()); ui->accessSignalSlot->setChecked(m_model->accessIncludeSignalSlot()); for (int i = 0; i < 4; ++i) { ui->groupingMethods->topLevelItem(i)->setCheckState(0, Qt::Unchecked); } connect(ui->groupingUp, SIGNAL(pressed()), SLOT(moveGroupingUp())); connect(ui->groupingDown, SIGNAL(pressed()), SLOT(moveGroupingDown())); // Column merging ui->columnMerging->setChecked(m_model->isColumnMergingEnabled()); ui->columnUp->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); ui->columnDown->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); connect(ui->columnUp, SIGNAL(pressed()), SLOT(moveColumnUp())); connect(ui->columnDown, SIGNAL(pressed()), SLOT(moveColumnDown())); QList mergedColumns; if (!m_model->columnMerges().isEmpty()) { foreach (const QList &list, m_model->columnMerges()) { bool first = true; foreach (int column, list) { QTreeWidgetItem *item = new QTreeWidgetItem(ui->columnMergeTree, column); item->setText(0, KateCompletionModel::columnName(column) + QString::fromLatin1(" %1").arg(column)); item->setCheckState(1, first ? Qt::Unchecked : Qt::Checked); if (column == KTextEditor::CodeCompletionModel::Name) { item->setText(2, i18n("Always")); } else { item->setCheckState(2, Qt::Checked); } first = false; mergedColumns << column; } } for (int column = 0; column < KTextEditor::CodeCompletionModel::ColumnCount; ++column) { if (!mergedColumns.contains(column)) { QTreeWidgetItem *item = new QTreeWidgetItem(ui->columnMergeTree, column); item->setText(0, KateCompletionModel::columnName(column) + QString::fromLatin1(" %1").arg(column)); item->setCheckState(1, Qt::Unchecked); Q_ASSERT(column != KTextEditor::CodeCompletionModel::Name); item->setCheckState(2, Qt::Unchecked); } } } else { for (int column = 0; column < KTextEditor::CodeCompletionModel::ColumnCount; ++column) { QTreeWidgetItem *item = new QTreeWidgetItem(ui->columnMergeTree, column); item->setText(0, KateCompletionModel::columnName(column) + QString::fromLatin1(" %1").arg(column)); item->setCheckState(1, Qt::Unchecked); if (column == KTextEditor::CodeCompletionModel::Name) { item->setText(2, i18n("Always")); } else { item->setCheckState(2, Qt::Checked); } } } // init with defaults from config or really hardcoded ones KConfigGroup config(KTextEditor::EditorPrivate::config(), "Code Completion"); readConfig(config); // buttons QDialogButtonBox *buttons = new QDialogButtonBox(this); mainLayout->addWidget(buttons); QPushButton *okButton = new QPushButton(this); okButton->setDefault(true); KGuiItem::assign(okButton, KStandardGuiItem::ok()); buttons->addButton(okButton, QDialogButtonBox::AcceptRole); connect(okButton, SIGNAL(clicked()), this, SLOT(apply())); QPushButton *cancelButton = new QPushButton(this); KGuiItem::assign(cancelButton, KStandardGuiItem::cancel()); buttons->addButton(okButton, QDialogButtonBox::RejectRole); connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); } KateCompletionConfig::~ KateCompletionConfig() { delete ui; } void KateCompletionConfig::readConfig(const KConfigGroup &config) { configStart(); // Sorting ui->sorting->setChecked(config.readEntry("Sorting Enabled", true)); ui->sortingAlphabetical->setChecked(config.readEntry("Sort Alphabetically", true)); ui->sortingCaseSensitive->setChecked(config.readEntry("Case Sensitive Sort", false)); ui->sortingInheritanceDepth->setChecked(config.readEntry("Sort by Inheritance Depth", true)); // Filtering ui->filtering->setChecked(config.readEntry("Filtering Enabled", false)); ui->filteringContextMatchOnly->setChecked(config.readEntry("Filter by Context Match Only", false)); ui->filteringHideAttributes->setChecked(config.readEntry("Hide Completions by Attribute", false)); int attributes = config.readEntry("Filter Attribute Mask", 0); for (int i = 0; i < ui->filteringAttributesList->count(); ++i) { QListWidgetItem *item = ui->filteringAttributesList->item(i); item->setCheckState(((1 << (i - 1)) & attributes) ? Qt::Checked : Qt::Unchecked); } ui->filteringMaximumInheritanceDepth->setValue(config.readEntry("Filter by Maximum Inheritance Depth", 0)); // Grouping ui->grouping->setChecked(config.readEntry("Grouping Enabled", true)); m_groupingScopeType->setCheckState(0, config.readEntry("Group by Scope Type", true) ? Qt::Checked : Qt::Unchecked); m_groupingScope->setCheckState(0, config.readEntry("Group by Scope", false) ? Qt::Checked : Qt::Unchecked); m_groupingAccessType->setCheckState(0, config.readEntry("Group by Access Type", true) ? Qt::Checked : Qt::Unchecked); m_groupingItemType->setCheckState(0, config.readEntry("Group by Item Type", false) ? Qt::Checked : Qt::Unchecked); ui->accessConst->setChecked(config.readEntry("Group by Const", false)); ui->accessStatic->setChecked(config.readEntry("Group by Static", false)); ui->accessSignalSlot->setChecked(config.readEntry("Group by Signals and Slots", false)); // Column merging ui->columnMerging->setChecked(config.readEntry("Column Merging Enabled", true)); for (int i = 0; i < ui->columnMergeTree->topLevelItemCount(); ++i) { QTreeWidgetItem *item = ui->columnMergeTree->topLevelItem(i); ///Initialize a standard column-merging: Merge Scope, Name, Arguments and Postfix item->setCheckState(1, config.readEntry(QStringLiteral("Column %1 Merge").arg(i), (i == CodeCompletionModel::Scope || i == CodeCompletionModel::Name || i == CodeCompletionModel::Arguments)) ? Qt::Checked : Qt::Unchecked); item->setCheckState(2, config.readEntry(QStringLiteral("Column %1 Show").arg(i), true) ? Qt::Checked : Qt::Unchecked); } applyInternal(); configEnd(); } void KateCompletionConfig::writeConfig(KConfigGroup &config) { // Sorting config.writeEntry("Sorting Enabled", ui->sorting->isChecked()); config.writeEntry("Sort Alphabetically", ui->sortingAlphabetical->isChecked()); config.writeEntry("Case Sensitive Sort", ui->sortingCaseSensitive->isChecked()); config.writeEntry("Sort by Inheritance Depth", ui->sortingInheritanceDepth->isChecked()); // Filtering config.writeEntry("Filtering Enabled", ui->filtering->isChecked()); config.writeEntry("Filter by Context Match Only", ui->filteringContextMatchOnly->isChecked()); config.writeEntry("Hide Completions by Attribute", ui->filteringHideAttributes->isChecked()); int attributes = 0; for (int i = 0; i < ui->filteringAttributesList->count(); ++i) { QListWidgetItem *item = ui->filteringAttributesList->item(i); if (item->checkState() == Qt::Checked) { attributes |= 1 << (i - 1); } } config.writeEntry("Filter Attribute Mask", attributes); config.writeEntry("Filter by Maximum Inheritance Depth", ui->filteringMaximumInheritanceDepth->value()); // Grouping config.writeEntry("Grouping Enabled", ui->grouping->isChecked()); config.writeEntry("Group by Scope Type", m_groupingScopeType->checkState(0) == Qt::Checked ? true : false); config.writeEntry("Group by Scope", m_groupingScope->checkState(0) == Qt::Checked ? true : false); config.writeEntry("Group by Access Type", m_groupingAccessType->checkState(0) == Qt::Checked ? true : false); config.writeEntry("Group by Item Type", m_groupingItemType->checkState(0) == Qt::Checked ? true : false); config.writeEntry("Group by Const", ui->accessConst->isChecked()); config.writeEntry("Group by Static", ui->accessStatic->isChecked()); config.writeEntry("Group by Signals and Slots", ui->accessSignalSlot->isChecked()); // Column merging config.writeEntry("Column Merging Enabled", ui->columnMerging->isChecked()); for (int i = 0; i < ui->columnMergeTree->topLevelItemCount(); ++i) { QTreeWidgetItem *item = ui->columnMergeTree->topLevelItem(i); config.writeEntry(QStringLiteral("Column %1 Merge").arg(i), item->checkState(1) == Qt::Checked ? true : false); config.writeEntry(QStringLiteral("Column %1 Show").arg(i), item->checkState(2) == Qt::Checked ? true : false); } config.sync(); } void KateCompletionConfig::updateConfig() { // Ah, nothing to do, I think...? } void KateCompletionConfig::moveColumnUp() { QTreeWidgetItem *item = ui->columnMergeTree->currentItem(); if (item) { int index = ui->columnMergeTree->indexOfTopLevelItem(item); if (index > 0) { ui->columnMergeTree->takeTopLevelItem(index); ui->columnMergeTree->insertTopLevelItem(index - 1, item); ui->columnMergeTree->setCurrentItem(item); } } } void KateCompletionConfig::moveColumnDown() { QTreeWidgetItem *item = ui->columnMergeTree->currentItem(); if (item) { int index = ui->columnMergeTree->indexOfTopLevelItem(item); if (index < ui->columnMergeTree->topLevelItemCount() - 1) { ui->columnMergeTree->takeTopLevelItem(index); ui->columnMergeTree->insertTopLevelItem(index + 1, item); ui->columnMergeTree->setCurrentItem(item); } } } void KateCompletionConfig::apply() { applyInternal(); KConfigGroup config(KTextEditor::EditorPrivate::config(), "Code Completion"); writeConfig(config); } void KateCompletionConfig::applyInternal() { // Sorting m_model->setSortingEnabled(ui->sorting->isChecked()); m_model->setSortingAlphabetical(ui->sortingAlphabetical->isChecked()); m_model->setSortingCaseSensitivity(ui->sortingCaseSensitive->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive); m_model->setSortingByInheritanceDepth(ui->sortingInheritanceDepth->isChecked()); // Filtering m_model->setFilteringEnabled(ui->filtering->isChecked()); m_model->setFilterContextMatchesOnly(ui->filteringContextMatchOnly->isChecked()); m_model->setFilterByAttribute(ui->filteringHideAttributes->isChecked()); - CodeCompletionModel::CompletionProperties attributes = 0; + CodeCompletionModel::CompletionProperties attributes = CodeCompletionModel::NoProperty; for (int i = 0; i < ui->filteringAttributesList->count(); ++i) { QListWidgetItem *item = ui->filteringAttributesList->item(i); if (item->checkState() == Qt::Checked) { attributes |= static_cast(item->type()); } } m_model->setFilterAttributes(attributes); m_model->setMaximumInheritanceDepth(ui->filteringMaximumInheritanceDepth->value()); // Grouping m_model->setGroupingEnabled(ui->grouping->isChecked()); - KateCompletionModel::GroupingMethods groupingMethod = 0; + KateCompletionModel::GroupingMethods groupingMethod = KateCompletionModel::GroupingMethods(); if (m_groupingScopeType->checkState(0) == Qt::Checked) { groupingMethod = KateCompletionModel::ScopeType; } if (m_groupingScope->checkState(0) == Qt::Checked) { groupingMethod |= KateCompletionModel::Scope; } if (m_groupingAccessType->checkState(0) == Qt::Checked) { groupingMethod |= KateCompletionModel::AccessType; } if (m_groupingItemType->checkState(0) == Qt::Checked) { groupingMethod |= KateCompletionModel::ItemType; } m_model->setGroupingMethod(groupingMethod); m_model->setAccessIncludeConst(ui->accessConst->isChecked()); m_model->setAccessIncludeStatic(ui->accessStatic->isChecked()); m_model->setAccessIncludeSignalSlot(ui->accessSignalSlot->isChecked()); // Column merging m_model->setColumnMergingEnabled(ui->columnMerging->isChecked()); QList< QList > mergedColumns; QList oneMerge; for (int i = 0; i < ui->columnMergeTree->topLevelItemCount(); ++i) { QTreeWidgetItem *item = ui->columnMergeTree->topLevelItem(i); if (item->type() != KTextEditor::CodeCompletionModel::Name && item->checkState(2) == Qt::Unchecked) { continue; } if (item->checkState(1) == Qt::Unchecked) { if (!oneMerge.isEmpty()) { mergedColumns.append(oneMerge); } oneMerge.clear(); } oneMerge.append(item->type()); } if (!oneMerge.isEmpty()) { mergedColumns.append(oneMerge); } m_model->setColumnMerges(mergedColumns); } void KateCompletionConfig::moveGroupingUp() { QTreeWidgetItem *item = ui->groupingMethods->currentItem(); if (item) { int index = ui->groupingMethods->indexOfTopLevelItem(item); if (index > 0) { ui->groupingMethods->takeTopLevelItem(index); ui->groupingMethods->insertTopLevelItem(index - 1, item); ui->groupingMethods->setCurrentItem(item); } } } void KateCompletionConfig::moveGroupingDown() { QTreeWidgetItem *item = ui->groupingMethods->currentItem(); if (item) { int index = ui->groupingMethods->indexOfTopLevelItem(item); if (index < ui->groupingMethods->topLevelItemCount() - 1) { ui->groupingMethods->takeTopLevelItem(index); ui->groupingMethods->insertTopLevelItem(index + 1, item); ui->groupingMethods->setCurrentItem(item); } } } void KateCompletionConfig::moveGroupingOrderUp() { QListWidgetItem *item = ui->sortGroupingOrder->currentItem(); int index = ui->sortGroupingOrder->currentRow(); if (index > 0) { ui->sortGroupingOrder->takeItem(index); ui->sortGroupingOrder->insertItem(index - 1, item); ui->sortGroupingOrder->setCurrentItem(item); } } void KateCompletionConfig::moveGroupingOrderDown() { QListWidgetItem *item = ui->sortGroupingOrder->currentItem(); int index = ui->sortGroupingOrder->currentRow(); if (index < ui->sortGroupingOrder->count() - 1) { ui->sortGroupingOrder->takeItem(index); ui->sortGroupingOrder->insertItem(index + 1, item); ui->sortGroupingOrder->setCurrentItem(item); } } diff --git a/src/completion/katecompletionconfig.h b/src/completion/katecompletionconfig.h index f73388fe..3999ba46 100644 --- a/src/completion/katecompletionconfig.h +++ b/src/completion/katecompletionconfig.h @@ -1,83 +1,83 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 KATECOMPLETIONCONFIG_H #define KATECOMPLETIONCONFIG_H #include #include "kateconfig.h" namespace Ui { class CompletionConfigWidget; } class QTreeWidgetItem; class KateCompletionModel; /** * @author Hamish Rodda */ class KateCompletionConfig : public QDialog, public KateConfig { Q_OBJECT public: - explicit KateCompletionConfig(KateCompletionModel *model, QWidget *parent = 0L); + explicit KateCompletionConfig(KateCompletionModel *model, QWidget *parent = nullptr); virtual ~KateCompletionConfig(); /** * Read config from object */ void readConfig(const KConfigGroup &config); /** * Write config to object */ void writeConfig(KConfigGroup &config); public Q_SLOTS: void apply(); protected: void updateConfig() Q_DECL_OVERRIDE; private Q_SLOTS: void moveColumnUp(); void moveColumnDown(); void moveGroupingUp(); void moveGroupingDown(); void moveGroupingOrderUp(); void moveGroupingOrderDown(); private: void applyInternal(); Ui::CompletionConfigWidget *ui; KateCompletionModel *m_model; QTreeWidgetItem *m_groupingScopeType; QTreeWidgetItem *m_groupingScope; QTreeWidgetItem *m_groupingAccessType; QTreeWidgetItem *m_groupingItemType; }; #endif diff --git a/src/completion/katecompletionmodel.cpp b/src/completion/katecompletionmodel.cpp index 5e860457..366e3a8b 100644 --- a/src/completion/katecompletionmodel.cpp +++ b/src/completion/katecompletionmodel.cpp @@ -1,2419 +1,2419 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005-2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * 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 "katecompletionmodel.h" #include "katecompletionwidget.h" #include "katecompletiontree.h" #include "katecompletiondelegate.h" #include "kateargumenthintmodel.h" #include "kateview.h" #include "katerenderer.h" #include "kateconfig.h" #include #include "katepartdebug.h" #include #include #include #include #include #include using namespace KTextEditor; ///A helper-class for handling completion-models with hierarchical grouping/optimization class HierarchicalModelHandler { public: explicit HierarchicalModelHandler(CodeCompletionModel *model); void addValue(CodeCompletionModel::ExtraItemDataRoles role, const QVariant &value); //Walks the index upwards and collects all defined completion-roles on the way void collectRoles(const QModelIndex &index); void takeRole(const QModelIndex &index); CodeCompletionModel *model() const; //Assumes that index is a sub-index of the indices where role-values were taken QVariant getData(CodeCompletionModel::ExtraItemDataRoles role, const QModelIndex &index) const; bool hasHierarchicalRoles() const; int inheritanceDepth(const QModelIndex &i) const; QString customGroup() const { return m_customGroup; } int customGroupingKey() const { return m_groupSortingKey; } private: typedef QMap RoleMap; RoleMap m_roleValues; QString m_customGroup; int m_groupSortingKey; CodeCompletionModel *m_model; }; CodeCompletionModel *HierarchicalModelHandler::model() const { return m_model; } bool HierarchicalModelHandler::hasHierarchicalRoles() const { return !m_roleValues.isEmpty(); } void HierarchicalModelHandler::collectRoles(const QModelIndex &index) { if (index.parent().isValid()) { collectRoles(index.parent()); } if (m_model->rowCount(index) != 0) { takeRole(index); } } int HierarchicalModelHandler::inheritanceDepth(const QModelIndex &i) const { return getData(CodeCompletionModel::InheritanceDepth, i).toInt(); } void HierarchicalModelHandler::takeRole(const QModelIndex &index) { QVariant v = index.data(CodeCompletionModel::GroupRole); if (v.isValid() && v.canConvert(QVariant::Int)) { QVariant value = index.data(v.toInt()); if (v.toInt() == Qt::DisplayRole) { m_customGroup = index.data(Qt::DisplayRole).toString(); QVariant sortingKey = index.data(CodeCompletionModel::InheritanceDepth); if (sortingKey.canConvert(QVariant::Int)) { m_groupSortingKey = sortingKey.toInt(); } } else { m_roleValues[(CodeCompletionModel::ExtraItemDataRoles)v.toInt()] = value; } } else { qCDebug(LOG_KTE) << "Did not return valid GroupRole in hierarchical completion-model"; } } QVariant HierarchicalModelHandler::getData(CodeCompletionModel::ExtraItemDataRoles role, const QModelIndex &index) const { RoleMap::const_iterator it = m_roleValues.find(role); if (it != m_roleValues.end()) { return *it; } else { return index.data(role); } } HierarchicalModelHandler::HierarchicalModelHandler(CodeCompletionModel *model) : m_groupSortingKey(-1), m_model(model) { } void HierarchicalModelHandler::addValue(CodeCompletionModel::ExtraItemDataRoles role, const QVariant &value) { m_roleValues[role] = value; } KateCompletionModel::KateCompletionModel(KateCompletionWidget *parent) : ExpandingWidgetModel(parent) , m_hasGroups(false) , m_matchCaseSensitivity(Qt::CaseInsensitive) , m_ungrouped(new Group({}, 0, this)) , m_argumentHints(new Group(i18n("Argument-hints"), -1, this)) , m_bestMatches(new Group(i18n("Best matches"), BestMatchesProperty, this)) , m_sortingEnabled(false) , m_sortingAlphabetical(false) , m_isSortingByInheritance(false) , m_sortingCaseSensitivity(Qt::CaseInsensitive) , m_filteringEnabled(false) , m_filterContextMatchesOnly(false) , m_filterByAttribute(false) , m_filterAttributes(KTextEditor::CodeCompletionModel::NoProperty) , m_maximumInheritanceDepth(0) , m_groupingEnabled(false) , m_accessConst(false) , m_accessStatic(false) , m_accesSignalSlot(false) , m_columnMergingEnabled(false) // , m_haveExactMatch(false) { m_emptyGroups.append(m_ungrouped); m_emptyGroups.append(m_argumentHints); m_emptyGroups.append(m_bestMatches); m_updateBestMatchesTimer = new QTimer(this); m_updateBestMatchesTimer->setSingleShot(true); connect(m_updateBestMatchesTimer, SIGNAL(timeout()), this, SLOT(updateBestMatches())); m_groupHash.insert(0, m_ungrouped); m_groupHash.insert(-1, m_argumentHints); m_groupHash.insert(BestMatchesProperty, m_argumentHints); } KateCompletionModel::~KateCompletionModel() { clearCompletionModels(); delete m_argumentHints; delete m_ungrouped; delete m_bestMatches; } QTreeView *KateCompletionModel::treeView() const { return view()->completionWidget()->treeView(); } QVariant KateCompletionModel::data(const QModelIndex &index, int role) const { if (!hasCompletionModel() || !index.isValid()) { return QVariant(); } if (role == Qt::DecorationRole && index.column() == KTextEditor::CodeCompletionModel::Prefix && isExpandable(index)) { cacheIcons(); if (!isExpanded(index)) { return QVariant(m_collapsedIcon); } else { return QVariant(m_expandedIcon); } } //groupOfParent returns a group when the index is a member of that group, but not the group head/label. if (!hasGroups() || groupOfParent(index)) { if ( role == Qt::TextAlignmentRole ) { if (isColumnMergingEnabled() && !m_columnMerges.isEmpty()) { int c = 0; foreach (const QList &list, m_columnMerges) { if (index.column() < c + list.size()) { c += list.size(); continue; } else if (list.count() == 1 && list.first() == CodeCompletionModel::Scope) { return Qt::AlignRight; } else { return QVariant(); } } } else if ((!isColumnMergingEnabled() || m_columnMerges.isEmpty()) && index.column() == CodeCompletionModel::Scope) { return Qt::AlignRight; } } // Merge text for column merging if (role == Qt::DisplayRole && !m_columnMerges.isEmpty() && isColumnMergingEnabled()) { QString text; foreach (int column, m_columnMerges[index.column()]) { QModelIndex sourceIndex = mapToSource(createIndex(index.row(), column, index.internalPointer())); text.append(sourceIndex.data(role).toString()); } return text; } if (role == CodeCompletionModel::HighlightingMethod) { //Return that we are doing custom-highlighting of one of the sub-strings does it. Unfortunately internal highlighting does not work for the other substrings. foreach (int column, m_columnMerges[index.column()]) { QModelIndex sourceIndex = mapToSource(createIndex(index.row(), column, index.internalPointer())); QVariant method = sourceIndex.data(CodeCompletionModel::HighlightingMethod); if (method.type() == QVariant::Int && method.toInt() == CodeCompletionModel::CustomHighlighting) { return QVariant(CodeCompletionModel::CustomHighlighting); } } return QVariant(); } if (role == CodeCompletionModel::CustomHighlight) { //Merge custom highlighting if multiple columns were merged QStringList strings; //Collect strings foreach (int column, m_columnMerges[index.column()]) { strings << mapToSource(createIndex(index.row(), column, index.internalPointer())).data(Qt::DisplayRole).toString(); } QList highlights; //Collect custom-highlightings foreach (int column, m_columnMerges[index.column()]) { highlights << mapToSource(createIndex(index.row(), column, index.internalPointer())).data(CodeCompletionModel::CustomHighlight).toList(); } return mergeCustomHighlighting(strings, highlights, 0); } QVariant v = mapToSource(index).data(role); if (v.isValid()) { return v; } else { return ExpandingWidgetModel::data(index, role); } } //Returns a nonzero group if this index is the head of a group(A Label in the list) Group *g = groupForIndex(index); if (g && (!g->isEmpty)) { switch (role) { case Qt::DisplayRole: if (!index.column()) { return g->title; } break; case Qt::FontRole: if (!index.column()) { QFont f = view()->renderer()->config()->font(); f.setBold(true); return f; } break; case Qt::ForegroundRole: return QApplication::palette().toolTipText().color(); case Qt::BackgroundRole: return QApplication::palette().toolTipBase().color(); } } return QVariant(); } int KateCompletionModel::contextMatchQuality(const QModelIndex &index) const { if (!index.isValid()) { return 0; } Group *g = groupOfParent(index); if (!g || g->filtered.size() < index.row()) { return 0; } return contextMatchQuality(g->filtered[index.row()].sourceRow()); } int KateCompletionModel::contextMatchQuality(const ModelRow &source) const { QModelIndex realIndex = source.second; int bestMatch = -1; //Iterate through all argument-hints and find the best match-quality foreach (const Item &item, m_argumentHints->filtered) { const ModelRow &row(item.sourceRow()); if (realIndex.model() != row.first) { continue; //We can only match within the same source-model } QModelIndex hintIndex = row.second; QVariant depth = hintIndex.data(CodeCompletionModel::ArgumentHintDepth); if (!depth.isValid() || depth.type() != QVariant::Int || depth.toInt() != 1) { continue; //Only match completion-items to argument-hints of depth 1(the ones the item will be given to as argument) } hintIndex.data(CodeCompletionModel::SetMatchContext); QVariant matchQuality = realIndex.data(CodeCompletionModel::MatchQuality); if (matchQuality.isValid() && matchQuality.type() == QVariant::Int) { int m = matchQuality.toInt(); if (m > bestMatch) { bestMatch = m; } } } if (m_argumentHints->filtered.isEmpty()) { QVariant matchQuality = realIndex.data(CodeCompletionModel::MatchQuality); if (matchQuality.isValid() && matchQuality.type() == QVariant::Int) { int m = matchQuality.toInt(); if (m > bestMatch) { bestMatch = m; } } } return bestMatch; } Qt::ItemFlags KateCompletionModel::flags(const QModelIndex &index) const { if (!hasCompletionModel() || !index.isValid()) { - return 0; + return Qt::NoItemFlags; } if (!hasGroups() || groupOfParent(index)) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } return Qt::ItemIsEnabled; } KateCompletionWidget *KateCompletionModel::widget() const { return static_cast(QObject::parent()); } KTextEditor::ViewPrivate *KateCompletionModel::view() const { return widget()->view(); } void KateCompletionModel::setMatchCaseSensitivity(Qt::CaseSensitivity cs) { m_matchCaseSensitivity = cs; } int KateCompletionModel::columnCount(const QModelIndex &) const { return isColumnMergingEnabled() && !m_columnMerges.isEmpty() ? m_columnMerges.count() : KTextEditor::CodeCompletionModel::ColumnCount; } KateCompletionModel::ModelRow KateCompletionModel::modelRowPair(const QModelIndex &index) const { return qMakePair(static_cast(const_cast(index.model())), index); } bool KateCompletionModel::hasChildren(const QModelIndex &parent) const { if (!hasCompletionModel()) { return false; } if (!parent.isValid()) { if (hasGroups()) { return true; } return !m_ungrouped->filtered.isEmpty(); } if (parent.column() != 0) { return false; } if (!hasGroups()) { return false; } if (Group *g = groupForIndex(parent)) { return !g->filtered.isEmpty(); } return false; } QModelIndex KateCompletionModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column < 0 || column >= columnCount(QModelIndex())) { return QModelIndex(); } if (parent.isValid() || !hasGroups()) { if (parent.isValid() && parent.column() != 0) { return QModelIndex(); } Group *g = groupForIndex(parent); if (!g) { return QModelIndex(); } if (row >= g->filtered.count()) { //qCWarning(LOG_KTE) << "Invalid index requested: row " << row << " beyond indivdual range in group " << g; return QModelIndex(); } //qCDebug(LOG_KTE) << "Returning index for child " << row << " of group " << g; return createIndex(row, column, g); } if (row >= m_rowTable.count()) { //qCWarning(LOG_KTE) << "Invalid index requested: row " << row << " beyond group range."; return QModelIndex(); } //qCDebug(LOG_KTE) << "Returning index for group " << m_rowTable[row]; return createIndex(row, column, quintptr(0)); } /*QModelIndex KateCompletionModel::sibling( int row, int column, const QModelIndex & index ) const { if (row < 0 || column < 0 || column >= columnCount(QModelIndex())) return QModelIndex(); if (!index.isValid()) { } if (Group* g = groupOfParent(index)) { if (row >= g->filtered.count()) return QModelIndex(); return createIndex(row, column, g); } if (hasGroups()) return QModelIndex(); if (row >= m_ungrouped->filtered.count()) return QModelIndex(); return createIndex(row, column, m_ungrouped); }*/ bool KateCompletionModel::hasIndex(int row, int column, const QModelIndex &parent) const { if (row < 0 || column < 0 || column >= columnCount(QModelIndex())) { return false; } if (parent.isValid() || !hasGroups()) { if (parent.isValid() && parent.column() != 0) { return false; } Group *g = groupForIndex(parent); if (row >= g->filtered.count()) { return false; } return true; } if (row >= m_rowTable.count()) { return false; } return true; } QModelIndex KateCompletionModel::indexForRow(Group *g, int row) const { if (row < 0 || row >= g->filtered.count()) { return QModelIndex(); } return createIndex(row, 0, g); } QModelIndex KateCompletionModel::indexForGroup(Group *g) const { if (!hasGroups()) { return QModelIndex(); } int row = m_rowTable.indexOf(g); if (row == -1) { return QModelIndex(); } return createIndex(row, 0, quintptr(0)); } void KateCompletionModel::clearGroups() { clearExpanding(); m_ungrouped->clear(); m_argumentHints->clear(); m_bestMatches->clear(); // Don't bother trying to work out where it is m_rowTable.removeAll(m_ungrouped); m_emptyGroups.removeAll(m_ungrouped); m_rowTable.removeAll(m_argumentHints); m_emptyGroups.removeAll(m_argumentHints); m_rowTable.removeAll(m_bestMatches); m_emptyGroups.removeAll(m_bestMatches); qDeleteAll(m_rowTable); qDeleteAll(m_emptyGroups); m_rowTable.clear(); m_emptyGroups.clear(); m_groupHash.clear(); m_customGroupHash.clear(); m_emptyGroups.append(m_ungrouped); m_groupHash.insert(0, m_ungrouped); m_emptyGroups.append(m_argumentHints); m_groupHash.insert(-1, m_argumentHints); m_emptyGroups.append(m_bestMatches); m_groupHash.insert(BestMatchesProperty, m_bestMatches); } QSet KateCompletionModel::createItems(const HierarchicalModelHandler &_handler, const QModelIndex &i, bool notifyModel) { HierarchicalModelHandler handler(_handler); QSet ret; if (handler.model()->rowCount(i) == 0) { //Leaf node, create an item ret.insert(createItem(handler, i, notifyModel)); } else { //Non-leaf node, take the role from the node, and recurse to the sub-nodes handler.takeRole(i); for (int a = 0; a < handler.model()->rowCount(i); a++) { ret += createItems(handler, i.child(a, 0), notifyModel); } } return ret; } QSet KateCompletionModel::deleteItems(const QModelIndex &i) { QSet ret; if (i.model()->rowCount(i) == 0) { //Leaf node, delete the item Group *g = groupForIndex(mapFromSource(i)); ret.insert(g); g->removeItem(ModelRow(const_cast(static_cast(i.model())), i)); } else { //Non-leaf node for (int a = 0; a < i.model()->rowCount(i); a++) { ret += deleteItems(i.child(a, 0)); } } return ret; } void KateCompletionModel::createGroups() { beginResetModel(); //After clearing the model, it has to be reset, else we will be in an invalid state while inserting //new groups. clearGroups(); bool has_groups = false; foreach (CodeCompletionModel *sourceModel, m_completionModels) { has_groups |= sourceModel->hasGroups(); for (int i = 0; i < sourceModel->rowCount(); ++i) { createItems(HierarchicalModelHandler(sourceModel), sourceModel->index(i, 0)); } } m_hasGroups = has_groups; //debugStats(); foreach (Group *g, m_rowTable) { hideOrShowGroup(g); } foreach (Group *g, m_emptyGroups) { hideOrShowGroup(g); } makeGroupItemsUnique(); updateBestMatches(); endResetModel(); } KateCompletionModel::Group *KateCompletionModel::createItem(const HierarchicalModelHandler &handler, const QModelIndex &sourceIndex, bool notifyModel) { //QModelIndex sourceIndex = sourceModel->index(row, CodeCompletionModel::Name, QModelIndex()); int completionFlags = handler.getData(CodeCompletionModel::CompletionRole, sourceIndex).toInt(); //Scope is expensive, should not be used with big models QString scopeIfNeeded = (groupingMethod() & Scope) ? sourceIndex.sibling(sourceIndex.row(), CodeCompletionModel::Scope).data(Qt::DisplayRole).toString() : QString(); int argumentHintDepth = handler.getData(CodeCompletionModel::ArgumentHintDepth, sourceIndex).toInt(); Group *g; if (argumentHintDepth) { g = m_argumentHints; } else { QString customGroup = handler.customGroup(); if (!customGroup.isNull() && m_hasGroups) { if (m_customGroupHash.contains(customGroup)) { g = m_customGroupHash[customGroup]; } else { g = new Group(customGroup, 0, this); g->customSortingKey = handler.customGroupingKey(); m_emptyGroups.append(g); m_customGroupHash.insert(customGroup, g); } } else { g = fetchGroup(completionFlags, scopeIfNeeded, handler.hasHierarchicalRoles()); } } Item item = Item(g != m_argumentHints, this, handler, ModelRow(handler.model(), sourceIndex)); if (g != m_argumentHints) { item.match(); } g->addItem(item, notifyModel); return g; } void KateCompletionModel::slotRowsInserted(const QModelIndex &parent, int start, int end) { QSet affectedGroups; HierarchicalModelHandler handler(static_cast(sender())); if (parent.isValid()) { handler.collectRoles(parent); } for (int i = start; i <= end; ++i) { affectedGroups += createItems(handler, parent.isValid() ? parent.child(i, 0) : handler.model()->index(i, 0), true); } foreach (Group *g, affectedGroups) { hideOrShowGroup(g, true); } } void KateCompletionModel::slotRowsRemoved(const QModelIndex &parent, int start, int end) { CodeCompletionModel *source = static_cast(sender()); QSet affectedGroups; for (int i = start; i <= end; ++i) { QModelIndex index = parent.isValid() ? parent.child(i, 0) : source->index(i, 0); affectedGroups += deleteItems(index); } foreach (Group *g, affectedGroups) { hideOrShowGroup(g, true); } } KateCompletionModel::Group *KateCompletionModel::fetchGroup(int attribute, const QString &scope, bool forceGrouping) { Q_UNUSED(forceGrouping); ///@todo use forceGrouping if (!hasGroups()) { return m_ungrouped; } int groupingAttribute = groupingAttributes(attribute); //qCDebug(LOG_KTE) << attribute << " " << groupingAttribute; if (m_groupHash.contains(groupingAttribute)) { if (groupingMethod() & Scope) { for (QHash::ConstIterator it = m_groupHash.constFind(groupingAttribute); it != m_groupHash.constEnd() && it.key() == groupingAttribute; ++it) if (it.value()->scope == scope) { return it.value(); } } else { return m_groupHash.value(groupingAttribute); } } QString st, at, it; QString title; if (groupingMethod() & ScopeType) { if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) { st = QStringLiteral("Global"); } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) { st = QStringLiteral("Namespace"); } else if (attribute & KTextEditor::CodeCompletionModel::LocalScope) { st = QStringLiteral("Local"); } title = st; } if (groupingMethod() & Scope) { if (!title.isEmpty()) { title.append(QLatin1String(" ")); } title.append(scope); } if (groupingMethod() & AccessType) { if (attribute & KTextEditor::CodeCompletionModel::Public) { at = QStringLiteral("Public"); } else if (attribute & KTextEditor::CodeCompletionModel::Protected) { at = QStringLiteral("Protected"); } else if (attribute & KTextEditor::CodeCompletionModel::Private) { at = QStringLiteral("Private"); } if (accessIncludeStatic() && attribute & KTextEditor::CodeCompletionModel::Static) { at.append(QLatin1String(" Static")); } if (accessIncludeConst() && attribute & KTextEditor::CodeCompletionModel::Const) { at.append(QLatin1String(" Const")); } if (!at.isEmpty()) { if (!title.isEmpty()) { title.append(QLatin1String(", ")); } title.append(at); } } if (groupingMethod() & ItemType) { if (attribute & CodeCompletionModel::Namespace) { it = i18n("Namespaces"); } else if (attribute & CodeCompletionModel::Class) { it = i18n("Classes"); } else if (attribute & CodeCompletionModel::Struct) { it = i18n("Structs"); } else if (attribute & CodeCompletionModel::Union) { it = i18n("Unions"); } else if (attribute & CodeCompletionModel::Function) { it = i18n("Functions"); } else if (attribute & CodeCompletionModel::Variable) { it = i18n("Variables"); } else if (attribute & CodeCompletionModel::Enum) { it = i18n("Enumerations"); } if (!it.isEmpty()) { if (!title.isEmpty()) { title.append(QLatin1String(" ")); } title.append(it); } } Group *ret = new Group(title, attribute, this); ret->scope = scope; m_emptyGroups.append(ret); m_groupHash.insert(groupingAttribute, ret); return ret; } bool KateCompletionModel::hasGroups() const { //qCDebug(LOG_KTE) << "m_groupHash.size()"<= m_rowTable.count()) { return m_ungrouped; } return m_rowTable[index.row()]; } /*QMap< int, QVariant > KateCompletionModel::itemData( const QModelIndex & index ) const { if (!hasGroups() || groupOfParent(index)) { QModelIndex index = mapToSource(index); if (index.isValid()) return index.model()->itemData(index); } return QAbstractItemModel::itemData(index); }*/ QModelIndex KateCompletionModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } if (Group *g = groupOfParent(index)) { if (!hasGroups()) { Q_ASSERT(g == m_ungrouped); return QModelIndex(); } int row = m_rowTable.indexOf(g); if (row == -1) { qCWarning(LOG_KTE) << "Couldn't find parent for index" << index; return QModelIndex(); } return createIndex(row, 0, quintptr(0)); } return QModelIndex(); } int KateCompletionModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { if (hasGroups()) { //qCDebug(LOG_KTE) << "Returning row count for toplevel " << m_rowTable.count(); return m_rowTable.count(); } else { //qCDebug(LOG_KTE) << "Returning ungrouped row count for toplevel " << m_ungrouped->filtered.count(); return m_ungrouped->filtered.count(); } } if (parent.column() > 0) { // only the first column has children return 0; } Group *g = groupForIndex(parent); // This is not an error, seems you don't have to check hasChildren() if (!g) { return 0; } //qCDebug(LOG_KTE) << "Returning row count for group " << g << " as " << g->filtered.count(); return g->filtered.count(); } void KateCompletionModel::sort(int column, Qt::SortOrder order) { Q_UNUSED(column) Q_UNUSED(order) } QModelIndex KateCompletionModel::mapToSource(const QModelIndex &proxyIndex) const { if (!proxyIndex.isValid()) { return QModelIndex(); } if (Group *g = groupOfParent(proxyIndex)) { if (proxyIndex.row() >= 0 && proxyIndex.row() < g->filtered.count()) { ModelRow source = g->filtered[proxyIndex.row()].sourceRow(); return source.second.sibling(source.second.row(), proxyIndex.column()); } else { qCDebug(LOG_KTE) << "Invalid proxy-index"; } } return QModelIndex(); } QModelIndex KateCompletionModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!sourceIndex.isValid()) { return QModelIndex(); } if (!hasGroups()) { return index(m_ungrouped->rowOf(modelRowPair(sourceIndex)), sourceIndex.column(), QModelIndex()); } foreach (Group *g, m_rowTable) { int row = g->rowOf(modelRowPair(sourceIndex)); if (row != -1) { return index(row, sourceIndex.column(), indexForGroup(g)); } } // Copied from above foreach (Group *g, m_emptyGroups) { int row = g->rowOf(modelRowPair(sourceIndex)); if (row != -1) { return index(row, sourceIndex.column(), indexForGroup(g)); } } return QModelIndex(); } void KateCompletionModel::setCurrentCompletion(KTextEditor::CodeCompletionModel *model, const QString &completion) { if (m_currentMatch[model] == completion) { return; } if (!hasCompletionModel()) { m_currentMatch[model] = completion; return; } changeTypes changeType = Change; if (m_currentMatch[model].length() > completion.length() && m_currentMatch[model].startsWith(completion, m_matchCaseSensitivity)) { // Filter has been broadened changeType = Broaden; } else if (m_currentMatch[model].length() < completion.length() && completion.startsWith(m_currentMatch[model], m_matchCaseSensitivity)) { // Filter has been narrowed changeType = Narrow; } //qCDebug(LOG_KTE) << model << "Old match: " << m_currentMatch[model] << ", new: " << completion << ", type: " << changeType; m_currentMatch[model] = completion; const bool resetModel = (changeType != Narrow); if (resetModel) { beginResetModel(); } if (!hasGroups()) { changeCompletions(m_ungrouped, changeType, !resetModel); } else { foreach (Group *g, m_rowTable) { if (g != m_argumentHints) { changeCompletions(g, changeType, !resetModel); } } foreach (Group *g, m_emptyGroups) { if (g != m_argumentHints) { changeCompletions(g, changeType, !resetModel); } } } // NOTE: best matches are also updated in resort resort(); if (resetModel) { endResetModel(); } clearExpanding(); //We need to do this, or be aware of expanding-widgets while filtering. emit layoutChanged(); } QString KateCompletionModel::commonPrefixInternal(const QString &forcePrefix) const { QString commonPrefix; // isNull() = true QList< Group * > groups = m_rowTable; groups += m_ungrouped; foreach (Group *g, groups) { foreach (const Item &item, g->filtered) { uint startPos = m_currentMatch[item.sourceRow().first].length(); const QString candidate = item.name().mid(startPos); if (!candidate.startsWith(forcePrefix)) { continue; } if (commonPrefix.isNull()) { commonPrefix = candidate; //Replace QString::null prefix with QString(), so we won't initialize it again if (commonPrefix.isNull()) { commonPrefix = QString(); // isEmpty() = true, isNull() = false } } else { commonPrefix = commonPrefix.left(candidate.length()); for (int a = 0; a < commonPrefix.length(); ++a) { if (commonPrefix[a] != candidate[a]) { commonPrefix = commonPrefix.left(a); break; } } } } } return commonPrefix; } QString KateCompletionModel::commonPrefix(QModelIndex selectedIndex) const { QString commonPrefix = commonPrefixInternal(QString()); if (commonPrefix.isEmpty() && selectedIndex.isValid()) { Group *g = m_ungrouped; if (hasGroups()) { g = groupOfParent(selectedIndex); } if (g && selectedIndex.row() < g->filtered.size()) { //Follow the path of the selected item, finding the next non-empty common prefix Item item = g->filtered[selectedIndex.row()]; int matchLength = m_currentMatch[item.sourceRow().first].length(); commonPrefix = commonPrefixInternal(item.name().mid(matchLength).left(1)); } } return commonPrefix; } void KateCompletionModel::changeCompletions(Group *g, changeTypes changeType, bool notifyModel) { if (changeType != Narrow) { g->filtered = g->prefilter; //In the "Broaden" or "Change" case, just re-filter everything, //and don't notify the model. The model is notified afterwards through a reset(). } //This code determines what of the filtered items still fit, and computes the ranges that were removed, giving //them to beginRemoveRows(..) in batches QList newFiltered; int deleteUntil = -1; //In each state, the range [currentRow+1, deleteUntil] needs to be deleted for (int currentRow = g->filtered.count() - 1; currentRow >= 0; --currentRow) { if (g->filtered[currentRow].match()) { //This row does not need to be deleted, which means that currentRow+1 to deleteUntil need to be deleted now if (deleteUntil != -1 && notifyModel) { beginRemoveRows(indexForGroup(g), currentRow + 1, deleteUntil); endRemoveRows(); } deleteUntil = -1; newFiltered.prepend(g->filtered[currentRow]); } else { if (deleteUntil == -1) { deleteUntil = currentRow; //Mark that this row needs to be deleted } } } if (deleteUntil != -1 && notifyModel) { beginRemoveRows(indexForGroup(g), 0, deleteUntil); endRemoveRows(); } g->filtered = newFiltered; hideOrShowGroup(g, notifyModel); } int KateCompletionModel::Group::orderNumber() const { if (this == model->m_ungrouped) { return 700; } if (customSortingKey != -1) { return customSortingKey; } if (attribute & BestMatchesProperty) { return 1; } if (attribute & KTextEditor::CodeCompletionModel::LocalScope) { return 100; } else if (attribute & KTextEditor::CodeCompletionModel::Public) { return 200; } else if (attribute & KTextEditor::CodeCompletionModel::Protected) { return 300; } else if (attribute & KTextEditor::CodeCompletionModel::Private) { return 400; } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) { return 500; } else if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) { return 600; } return 700; } bool KateCompletionModel::Group::orderBefore(Group *other) const { return orderNumber() < other->orderNumber(); } void KateCompletionModel::hideOrShowGroup(Group *g, bool notifyModel) { if (g == m_argumentHints) { emit argumentHintsChanged(); m_updateBestMatchesTimer->start(200); //We have new argument-hints, so we have new best matches return; //Never show argument-hints in the normal completion-list } if (!g->isEmpty) { if (g->filtered.isEmpty()) { // Move to empty group list g->isEmpty = true; int row = m_rowTable.indexOf(g); if (row != -1) { if (hasGroups() && notifyModel) { beginRemoveRows(QModelIndex(), row, row); } m_rowTable.removeAt(row); if (hasGroups() && notifyModel) { endRemoveRows(); } m_emptyGroups.append(g); } else { qCWarning(LOG_KTE) << "Group " << g << " not found in row table!!"; } } } else { if (!g->filtered.isEmpty()) { // Move off empty group list g->isEmpty = false; int row = 0; //Find row where to insert for (int a = 0; a < m_rowTable.count(); a++) { if (g->orderBefore(m_rowTable[a])) { row = a; break; } row = a + 1; } if (notifyModel) { if (hasGroups()) { beginInsertRows(QModelIndex(), row, row); } else { beginInsertRows(QModelIndex(), 0, g->filtered.count()); } } m_rowTable.insert(row, g); if (notifyModel) { endInsertRows(); } m_emptyGroups.removeAll(g); } } } bool KateCompletionModel::indexIsItem(const QModelIndex &index) const { if (!hasGroups()) { return true; } if (groupOfParent(index)) { return true; } return false; } void KateCompletionModel::slotModelReset() { createGroups(); //debugStats(); } void KateCompletionModel::debugStats() { if (!hasGroups()) { qCDebug(LOG_KTE) << "Model groupless, " << m_ungrouped->filtered.count() << " items."; } else { qCDebug(LOG_KTE) << "Model grouped (" << m_rowTable.count() << " groups):"; foreach (Group *g, m_rowTable) { qCDebug(LOG_KTE) << "Group" << g << "count" << g->filtered.count(); } } } bool KateCompletionModel::hasCompletionModel() const { return !m_completionModels.isEmpty(); } void KateCompletionModel::setFilteringEnabled(bool enable) { if (m_filteringEnabled != enable) { m_filteringEnabled = enable; } } void KateCompletionModel::setSortingEnabled(bool enable) { if (m_sortingEnabled != enable) { m_sortingEnabled = enable; beginResetModel(); resort(); endResetModel(); } } void KateCompletionModel::setGroupingEnabled(bool enable) { if (m_groupingEnabled != enable) { m_groupingEnabled = enable; } } void KateCompletionModel::setColumnMergingEnabled(bool enable) { if (m_columnMergingEnabled != enable) { m_columnMergingEnabled = enable; } } bool KateCompletionModel::isColumnMergingEnabled() const { return m_columnMergingEnabled; } bool KateCompletionModel::isGroupingEnabled() const { return m_groupingEnabled; } bool KateCompletionModel::isFilteringEnabled() const { return m_filteringEnabled; } bool KateCompletionModel::isSortingEnabled() const { return m_sortingEnabled; } QString KateCompletionModel::columnName(int column) { switch (column) { case KTextEditor::CodeCompletionModel::Prefix: return i18n("Prefix"); case KTextEditor::CodeCompletionModel::Icon: return i18n("Icon"); case KTextEditor::CodeCompletionModel::Scope: return i18n("Scope"); case KTextEditor::CodeCompletionModel::Name: return i18n("Name"); case KTextEditor::CodeCompletionModel::Arguments: return i18n("Arguments"); case KTextEditor::CodeCompletionModel::Postfix: return i18n("Postfix"); } return QString(); } const QList< QList < int > > &KateCompletionModel::columnMerges() const { return m_columnMerges; } void KateCompletionModel::setColumnMerges(const QList< QList < int > > &columnMerges) { beginResetModel(); m_columnMerges = columnMerges; endResetModel(); } int KateCompletionModel::translateColumn(int sourceColumn) const { if (m_columnMerges.isEmpty()) { return sourceColumn; } /* Debugging - dump column merge list QString columnMerge; foreach (const QList& list, m_columnMerges) { columnMerge += '['; foreach (int column, list) { columnMerge += QString::number(column) + " "; } columnMerge += "] "; } qCDebug(LOG_KTE) << k_funcinfo << columnMerge;*/ int c = 0; foreach (const QList &list, m_columnMerges) { foreach (int column, list) { if (column == sourceColumn) { return c; } } c++; } return -1; } int KateCompletionModel::groupingAttributes(int attribute) const { int ret = 0; if (m_groupingMethod & ScopeType) { if (countBits(attribute & ScopeTypeMask) > 1) { qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one scope type modifier provided."; } if (attribute & KTextEditor::CodeCompletionModel::GlobalScope) { ret |= KTextEditor::CodeCompletionModel::GlobalScope; } else if (attribute & KTextEditor::CodeCompletionModel::NamespaceScope) { ret |= KTextEditor::CodeCompletionModel::NamespaceScope; } else if (attribute & KTextEditor::CodeCompletionModel::LocalScope) { ret |= KTextEditor::CodeCompletionModel::LocalScope; } } if (m_groupingMethod & AccessType) { if (countBits(attribute & AccessTypeMask) > 1) { qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one access type modifier provided."; } if (attribute & KTextEditor::CodeCompletionModel::Public) { ret |= KTextEditor::CodeCompletionModel::Public; } else if (attribute & KTextEditor::CodeCompletionModel::Protected) { ret |= KTextEditor::CodeCompletionModel::Protected; } else if (attribute & KTextEditor::CodeCompletionModel::Private) { ret |= KTextEditor::CodeCompletionModel::Private; } if (accessIncludeStatic() && attribute & KTextEditor::CodeCompletionModel::Static) { ret |= KTextEditor::CodeCompletionModel::Static; } if (accessIncludeConst() && attribute & KTextEditor::CodeCompletionModel::Const) { ret |= KTextEditor::CodeCompletionModel::Const; } } if (m_groupingMethod & ItemType) { if (countBits(attribute & ItemTypeMask) > 1) { qCWarning(LOG_KTE) << "Invalid completion model metadata: more than one item type modifier provided."; } if (attribute & KTextEditor::CodeCompletionModel::Namespace) { ret |= KTextEditor::CodeCompletionModel::Namespace; } else if (attribute & KTextEditor::CodeCompletionModel::Class) { ret |= KTextEditor::CodeCompletionModel::Class; } else if (attribute & KTextEditor::CodeCompletionModel::Struct) { ret |= KTextEditor::CodeCompletionModel::Struct; } else if (attribute & KTextEditor::CodeCompletionModel::Union) { ret |= KTextEditor::CodeCompletionModel::Union; } else if (attribute & KTextEditor::CodeCompletionModel::Function) { ret |= KTextEditor::CodeCompletionModel::Function; } else if (attribute & KTextEditor::CodeCompletionModel::Variable) { ret |= KTextEditor::CodeCompletionModel::Variable; } else if (attribute & KTextEditor::CodeCompletionModel::Enum) { ret |= KTextEditor::CodeCompletionModel::Enum; } /* if (itemIncludeTemplate() && attribute & KTextEditor::CodeCompletionModel::Template) ret |= KTextEditor::CodeCompletionModel::Template;*/ } return ret; } void KateCompletionModel::setGroupingMethod(GroupingMethods m) { m_groupingMethod = m; createGroups(); } bool KateCompletionModel::accessIncludeConst() const { return m_accessConst; } void KateCompletionModel::setAccessIncludeConst(bool include) { if (m_accessConst != include) { m_accessConst = include; if (groupingMethod() & AccessType) { createGroups(); } } } bool KateCompletionModel::accessIncludeStatic() const { return m_accessStatic; } void KateCompletionModel::setAccessIncludeStatic(bool include) { if (m_accessStatic != include) { m_accessStatic = include; if (groupingMethod() & AccessType) { createGroups(); } } } bool KateCompletionModel::accessIncludeSignalSlot() const { return m_accesSignalSlot; } void KateCompletionModel::setAccessIncludeSignalSlot(bool include) { if (m_accesSignalSlot != include) { m_accesSignalSlot = include; if (groupingMethod() & AccessType) { createGroups(); } } } int KateCompletionModel::countBits(int value) const { int count = 0; for (int i = 1; i; i <<= 1) if (i & value) { count++; } return count; } KateCompletionModel::GroupingMethods KateCompletionModel::groupingMethod() const { return m_groupingMethod; } bool KateCompletionModel::isSortingByInheritanceDepth() const { return m_isSortingByInheritance; } void KateCompletionModel::setSortingByInheritanceDepth(bool byInheritance) { m_isSortingByInheritance = byInheritance; } bool KateCompletionModel::isSortingAlphabetical() const { return m_sortingAlphabetical; } Qt::CaseSensitivity KateCompletionModel::sortingCaseSensitivity() const { return m_sortingCaseSensitivity; } KateCompletionModel::Item::Item(bool doInitialMatch, KateCompletionModel *m, const HierarchicalModelHandler &handler, ModelRow sr) : model(m) , m_sourceRow(sr) , matchCompletion(StartsWithMatch) , matchFilters(true) , m_haveExactMatch(false) { inheritanceDepth = handler.getData(CodeCompletionModel::InheritanceDepth, m_sourceRow.second).toInt(); m_unimportant = handler.getData(CodeCompletionModel::UnimportantItemRole, m_sourceRow.second).toBool(); QModelIndex nameSibling = sr.second.sibling(sr.second.row(), CodeCompletionModel::Name); m_nameColumn = nameSibling.data(Qt::DisplayRole).toString(); if (doInitialMatch) { filter(); match(); } } bool KateCompletionModel::Item::operator <(const Item &rhs) const { int ret = 0; //qCDebug(LOG_KTE) << c1 << " c/w " << c2 << " -> " << (model->isSortingReverse() ? ret > 0 : ret < 0) << " (" << ret << ")"; if(m_unimportant && !rhs.m_unimportant){ return false; } if(!m_unimportant && rhs.m_unimportant){ return true; } if (matchCompletion < rhs.matchCompletion) { // enums are ordered in the order items should be displayed return true; } if (matchCompletion > rhs.matchCompletion) { return false; } if (model->isSortingByInheritanceDepth()) { ret = inheritanceDepth - rhs.inheritanceDepth; } if (ret == 0 && model->isSortingAlphabetical()) { // Do not use localeAwareCompare, because it is simply too slow for a list of about 1000 items ret = QString::compare(m_nameColumn, rhs.m_nameColumn, model->sortingCaseSensitivity()); } if (ret == 0) { const QString& filter = rhs.model->currentCompletion(rhs.m_sourceRow.first); if( m_nameColumn.startsWith(filter, Qt::CaseSensitive) ) { return true; } if( rhs.m_nameColumn.startsWith(filter, Qt::CaseSensitive) ) { return false; } // FIXME need to define a better default ordering for multiple model display ret = m_sourceRow.second.row() - rhs.m_sourceRow.second.row(); } return ret < 0; } void KateCompletionModel::Group::addItem(Item i, bool notifyModel) { if (isEmpty) { notifyModel = false; } QModelIndex groupIndex; if (notifyModel) { groupIndex = model->indexForGroup(this); } if (model->isSortingEnabled()) { prefilter.insert(qUpperBound(prefilter.begin(), prefilter.end(), i), i); if (i.isVisible()) { QList::iterator it = qUpperBound(filtered.begin(), filtered.end(), i); uint rowNumber = it - filtered.begin(); if (notifyModel) { model->beginInsertRows(groupIndex, rowNumber, rowNumber); } filtered.insert(it, i); } } else { if (notifyModel) { model->beginInsertRows(groupIndex, prefilter.size(), prefilter.size()); } if (i.isVisible()) { prefilter.append(i); } } if (notifyModel) { model->endInsertRows(); } } bool KateCompletionModel::Group::removeItem(const ModelRow &row) { for (int pi = 0; pi < prefilter.count(); ++pi) if (prefilter[pi].sourceRow() == row) { int index = rowOf(row); if (index != -1) { model->beginRemoveRows(model->indexForGroup(this), index, index); } filtered.removeAt(index); prefilter.removeAt(pi); if (index != -1) { model->endRemoveRows(); } return index != -1; } Q_ASSERT(false); return false; } KateCompletionModel::Group::Group(const QString& title, int attribute, KateCompletionModel *m) : model(m) , attribute(attribute) // ugly hack to add some left margin , title(QLatin1Char(' ') + title) , isEmpty(true) , customSortingKey(-1) { Q_ASSERT(model); } void KateCompletionModel::setSortingAlphabetical(bool alphabetical) { if (m_sortingAlphabetical != alphabetical) { m_sortingAlphabetical = alphabetical; beginResetModel(); resort(); endResetModel(); } } void KateCompletionModel::Group::resort() { qStableSort(filtered.begin(), filtered.end()); model->hideOrShowGroup(this); } void KateCompletionModel::setSortingCaseSensitivity(Qt::CaseSensitivity cs) { if (m_sortingCaseSensitivity != cs) { m_sortingCaseSensitivity = cs; beginResetModel(); resort(); endResetModel(); } } void KateCompletionModel::resort() { foreach (Group *g, m_rowTable) { g->resort(); } foreach (Group *g, m_emptyGroups) { g->resort(); } // call updateBestMatches here, so they are moved to the top again. updateBestMatches(); } bool KateCompletionModel::Item::isValid() const { return model && m_sourceRow.first && m_sourceRow.second.row() >= 0; } void KateCompletionModel::Group::clear() { prefilter.clear(); filtered.clear(); isEmpty = true; } bool KateCompletionModel::filterContextMatchesOnly() const { return m_filterContextMatchesOnly; } void KateCompletionModel::setFilterContextMatchesOnly(bool filter) { if (m_filterContextMatchesOnly != filter) { m_filterContextMatchesOnly = filter; refilter(); } } bool KateCompletionModel::filterByAttribute() const { return m_filterByAttribute; } void KateCompletionModel::setFilterByAttribute(bool filter) { if (m_filterByAttribute == filter) { m_filterByAttribute = filter; refilter(); } } KTextEditor::CodeCompletionModel::CompletionProperties KateCompletionModel::filterAttributes() const { return m_filterAttributes; } void KateCompletionModel::setFilterAttributes(KTextEditor::CodeCompletionModel::CompletionProperties attributes) { if (m_filterAttributes == attributes) { m_filterAttributes = attributes; refilter(); } } int KateCompletionModel::maximumInheritanceDepth() const { return m_maximumInheritanceDepth; } void KateCompletionModel::setMaximumInheritanceDepth(int maxDepth) { if (m_maximumInheritanceDepth != maxDepth) { m_maximumInheritanceDepth = maxDepth; refilter(); } } void KateCompletionModel::refilter() { beginResetModel(); m_ungrouped->refilter(); foreach (Group *g, m_rowTable) if (g != m_argumentHints) { g->refilter(); } foreach (Group *g, m_emptyGroups) if (g != m_argumentHints) { g->refilter(); } updateBestMatches(); clearExpanding(); //We need to do this, or be aware of expanding-widgets while filtering. endResetModel(); } void KateCompletionModel::Group::refilter() { filtered.clear(); foreach (const Item &i, prefilter) if (!i.isFiltered()) { filtered.append(i); } } bool KateCompletionModel::Item::filter() { matchFilters = false; if (model->isFilteringEnabled()) { QModelIndex sourceIndex = m_sourceRow.second.sibling(m_sourceRow.second.row(), CodeCompletionModel::Name); if (model->filterContextMatchesOnly()) { QVariant contextMatch = sourceIndex.data(CodeCompletionModel::MatchQuality); if (contextMatch.canConvert(QVariant::Int) && !contextMatch.toInt()) { return false; } } if (model->filterByAttribute()) { int completionFlags = sourceIndex.data(CodeCompletionModel::CompletionRole).toInt(); if (model->filterAttributes() & completionFlags) { return false; } } if (model->maximumInheritanceDepth() > 0) { int inheritanceDepth = sourceIndex.data(CodeCompletionModel::InheritanceDepth).toInt(); if (inheritanceDepth > model->maximumInheritanceDepth()) { return false; } } } matchFilters = true; return matchFilters; } uint KateCompletionModel::filteredItemCount() const { uint ret = 0; foreach (Group *group, m_rowTable) { ret += group->filtered.size(); } return ret; } bool KateCompletionModel::shouldMatchHideCompletionList() const { // @todo Make this faster bool doHide = false; - CodeCompletionModel *hideModel = 0; + CodeCompletionModel *hideModel = nullptr; foreach (Group *group, m_rowTable) foreach (const Item &item, group->filtered) if (item.haveExactMatch()) { KTextEditor::CodeCompletionModelControllerInterface *iface3 = dynamic_cast(item.sourceRow().first); bool hide = false; if (!iface3) { hide = true; } if (iface3 && iface3->matchingItem(item.sourceRow().second) == KTextEditor::CodeCompletionModelControllerInterface::HideListIfAutomaticInvocation) { hide = true; } if (hide) { doHide = true; hideModel = item.sourceRow().first; } } if (doHide) { // Check if all other visible items are from the same model foreach (Group *group, m_rowTable) foreach (const Item &item, group->filtered) if (item.sourceRow().first != hideModel) { return false; } } return doHide; } static inline bool matchesAbbreviationHelper(const QString &word, const QString &typed, const QVarLengthArray &offsets, int &depth, int atWord = -1, int i = 0) { int atLetter = 1; for (; i < typed.size(); i++) { const QChar c = typed.at(i).toLower(); bool haveNextWord = offsets.size() > atWord + 1; bool canCompare = atWord != -1 && word.size() > offsets.at(atWord) + atLetter; if (canCompare && c == word.at(offsets.at(atWord) + atLetter).toLower()) { // the typed letter matches a letter after the current word beginning if (! haveNextWord || c != word.at(offsets.at(atWord + 1)).toLower()) { // good, simple case, no conflict atLetter += 1; continue; } // For maliciously crafted data, the code used here theoretically can have very high // complexity. Thus ensure we don't run into this case, by limiting the amount of branches // we walk through to 128. depth++; if (depth > 128) { return false; } // the letter matches both the next word beginning and the next character in the word if (haveNextWord && matchesAbbreviationHelper(word, typed, offsets, depth, atWord + 1, i + 1)) { // resolving the conflict by taking the next word's first character worked, fine return true; } // otherwise, continue by taking the next letter in the current word. atLetter += 1; continue; } else if (haveNextWord && c == word.at(offsets.at(atWord + 1)).toLower()) { // the typed letter matches the next word beginning atWord++; atLetter = 1; continue; } // no match return false; } // all characters of the typed word were matched return true; } bool KateCompletionModel::matchesAbbreviation(const QString &word, const QString &typed) { // A mismatch is very likely for random even for the first letter, // thus this optimization makes sense. if (word.at(0).toLower() != typed.at(0).toLower()) { return false; } // First, check if all letters are contained in the word in the right order. int atLetter = 0; foreach (const QChar c, typed) { while (c.toLower() != word.at(atLetter).toLower()) { atLetter += 1; if (atLetter >= word.size()) { return false; } } } bool haveUnderscore = true; QVarLengthArray offsets; // We want to make "KComplM" match "KateCompletionModel"; this means we need // to allow parts of the typed text to be not part of the actual abbreviation, // which consists only of the uppercased / underscored letters (so "KCM" in this case). // However it might be ambigous whether a letter is part of such a word or part of // the following abbreviation, so we need to find all possible word offsets first, // then compare. for (int i = 0; i < word.size(); i++) { const QChar c = word.at(i); if (c == QLatin1Char('_')) { haveUnderscore = true; } else if (haveUnderscore || c.isUpper()) { offsets.append(i); haveUnderscore = false; } } int depth = 0; return matchesAbbreviationHelper(word, typed, offsets, depth); } static inline bool containsAtWordBeginning(const QString &word, const QString &typed, Qt::CaseSensitivity caseSensitive) { for (int i = 1; i < word.size(); i++) { // The current position is a word beginning if the previous character was an underscore // or if the current character is uppercase. Subsequent uppercase characters do not count, // to handle the special case of UPPER_CASE_VARS properly. const QChar c = word.at(i); const QChar prev = word.at(i - 1); if (!(prev == QLatin1Char('_') || (c.isUpper() && !prev.isUpper()))) { continue; } if (word.midRef(i).startsWith(typed, caseSensitive)) { return true; } } return false; } KateCompletionModel::Item::MatchType KateCompletionModel::Item::match() { QString match = model->currentCompletion(m_sourceRow.first); m_haveExactMatch = false; // Hehe, everything matches nothing! (ie. everything matches a blank string) if (match.isEmpty()) { return PerfectMatch; } if (m_nameColumn.isEmpty()) { return NoMatch; } matchCompletion = (m_nameColumn.startsWith(match, model->matchCaseSensitivity()) ? StartsWithMatch : NoMatch); if (matchCompletion == NoMatch) { // if no match, try for "contains" // Only match when the occurence is at a "word" beginning, marked by // an underscore or a capital. So Foo matches BarFoo and Bar_Foo, but not barfoo. // Starting at 1 saves looking at the beginning of the word, that was already checked above. if (containsAtWordBeginning(m_nameColumn, match, model->matchCaseSensitivity())) { matchCompletion = ContainsMatch; } } if (matchCompletion == NoMatch && !m_nameColumn.isEmpty() && !match.isEmpty()) { // if still no match, try abbreviation matching if (matchesAbbreviation(m_nameColumn, match)) { matchCompletion = AbbreviationMatch; } } if (matchCompletion && match.length() == m_nameColumn.length()) { matchCompletion = PerfectMatch; m_haveExactMatch = true; } return matchCompletion; } QString KateCompletionModel::propertyName(KTextEditor::CodeCompletionModel::CompletionProperty property) { switch (property) { case CodeCompletionModel::Public: return i18n("Public"); case CodeCompletionModel::Protected: return i18n("Protected"); case CodeCompletionModel::Private: return i18n("Private"); case CodeCompletionModel::Static: return i18n("Static"); case CodeCompletionModel::Const: return i18n("Constant"); case CodeCompletionModel::Namespace: return i18n("Namespace"); case CodeCompletionModel::Class: return i18n("Class"); case CodeCompletionModel::Struct: return i18n("Struct"); case CodeCompletionModel::Union: return i18n("Union"); case CodeCompletionModel::Function: return i18n("Function"); case CodeCompletionModel::Variable: return i18n("Variable"); case CodeCompletionModel::Enum: return i18n("Enumeration"); case CodeCompletionModel::Template: return i18n("Template"); case CodeCompletionModel::Virtual: return i18n("Virtual"); case CodeCompletionModel::Override: return i18n("Override"); case CodeCompletionModel::Inline: return i18n("Inline"); case CodeCompletionModel::Friend: return i18n("Friend"); case CodeCompletionModel::Signal: return i18n("Signal"); case CodeCompletionModel::Slot: return i18n("Slot"); case CodeCompletionModel::LocalScope: return i18n("Local Scope"); case CodeCompletionModel::NamespaceScope: return i18n("Namespace Scope"); case CodeCompletionModel::GlobalScope: return i18n("Global Scope"); default: return i18n("Unknown Property"); } } bool KateCompletionModel::Item::isVisible() const { return matchCompletion && matchFilters; } bool KateCompletionModel::Item::isFiltered() const { return !matchFilters; } bool KateCompletionModel::Item::isMatching() const { return matchFilters; } const KateCompletionModel::ModelRow &KateCompletionModel::Item::sourceRow() const { return m_sourceRow; } QString KateCompletionModel::currentCompletion(KTextEditor::CodeCompletionModel *model) const { return m_currentMatch.value(model); } Qt::CaseSensitivity KateCompletionModel::matchCaseSensitivity() const { return m_matchCaseSensitivity; } void KateCompletionModel::addCompletionModel(KTextEditor::CodeCompletionModel *model) { if (m_completionModels.contains(model)) { return; } m_completionModels.append(model); connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotRowsInserted(QModelIndex,int,int))); connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotRowsRemoved(QModelIndex,int,int))); connect(model, SIGNAL(modelReset()), SLOT(slotModelReset())); // This performs the reset createGroups(); } void KateCompletionModel::setCompletionModel(KTextEditor::CodeCompletionModel *model) { clearCompletionModels(); addCompletionModel(model); } void KateCompletionModel::setCompletionModels(const QList &models) { //if (m_completionModels == models) //return; clearCompletionModels(); m_completionModels = models; foreach (KTextEditor::CodeCompletionModel *model, models) { connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(slotRowsInserted(QModelIndex,int,int))); connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(slotRowsRemoved(QModelIndex,int,int))); connect(model, SIGNAL(modelReset()), SLOT(slotModelReset())); } // This performs the reset createGroups(); } QList< KTextEditor::CodeCompletionModel * > KateCompletionModel::completionModels() const { return m_completionModels; } void KateCompletionModel::removeCompletionModel(CodeCompletionModel *model) { if (!model || !m_completionModels.contains(model)) { return; } beginResetModel(); m_currentMatch.remove(model); clearGroups(); model->disconnect(this); m_completionModels.removeAll(model); endResetModel(); if (!m_completionModels.isEmpty()) { // This performs the reset createGroups(); } } void KateCompletionModel::makeGroupItemsUnique(bool onlyFiltered) { struct FilterItems { FilterItems(KateCompletionModel &model, const QVector &needShadowing) : m_model(model), m_needShadowing(needShadowing) { } QHash had; KateCompletionModel &m_model; const QVector< KTextEditor::CodeCompletionModel * > m_needShadowing; void filter(QList &items) { QList temp; foreach (const Item &item, items) { QHash::const_iterator it = had.constFind(item.name()); if (it != had.constEnd() && *it != item.sourceRow().first && m_needShadowing.contains(item.sourceRow().first)) { continue; } had.insert(item.name(), item.sourceRow().first); temp.push_back(item); } items = temp; } void filter(Group *group, bool onlyFiltered) { if (group->prefilter.size() == group->filtered.size()) { // Filter only once filter(group->filtered); if (!onlyFiltered) { group->prefilter = group->filtered; } } else { // Must filter twice filter(group->filtered); if (!onlyFiltered) { filter(group->prefilter); } } if (group->filtered.isEmpty()) { m_model.hideOrShowGroup(group); } } }; QVector needShadowing; foreach (KTextEditor::CodeCompletionModel *model, m_completionModels) { KTextEditor::CodeCompletionModelControllerInterface *v4 = dynamic_cast(model); if (v4 && v4->shouldHideItemsWithEqualNames()) { needShadowing.push_back(model); } } if (needShadowing.isEmpty()) { return; } FilterItems filter(*this, needShadowing); filter.filter(m_ungrouped, onlyFiltered); foreach (Group *group, m_rowTable) { filter.filter(group, onlyFiltered); } } //Updates the best-matches group void KateCompletionModel::updateBestMatches() { int maxMatches = 300; //We cannot do too many operations here, because they are all executed whenever a character is added. Would be nice if we could split the operations up somewhat using a timer. m_updateBestMatchesTimer->stop(); //Maps match-qualities to ModelRows paired together with the BestMatchesCount returned by the items. typedef QMultiMap > BestMatchMap; BestMatchMap matches; if (!hasGroups()) { //If there is no grouping, just change the order of the items, moving the best matching ones to the front QMultiMap rowsForQuality; int row = 0; foreach (const Item &item, m_ungrouped->filtered) { ModelRow source = item.sourceRow(); QVariant v = source.second.data(CodeCompletionModel::BestMatchesCount); if (v.type() == QVariant::Int && v.toInt() > 0) { int quality = contextMatchQuality(source); if (quality > 0) { rowsForQuality.insert(quality, row); } } ++row; --maxMatches; if (maxMatches < 0) { break; } } if (!rowsForQuality.isEmpty()) { //Rewrite m_ungrouped->filtered in a new order QSet movedToFront; QList newFiltered; for (QMultiMap::const_iterator it = rowsForQuality.constBegin(); it != rowsForQuality.constEnd(); ++it) { newFiltered.prepend(m_ungrouped->filtered[it.value()]); movedToFront.insert(it.value()); } { uint size = m_ungrouped->filtered.size(); for (uint a = 0; a < size; ++a) if (!movedToFront.contains(a)) { newFiltered.append(m_ungrouped->filtered[a]); } } m_ungrouped->filtered = newFiltered; } return; } ///@todo Cache the CodeCompletionModel::BestMatchesCount foreach (Group *g, m_rowTable) { if (g == m_bestMatches) { continue; } for (int a = 0; a < g->filtered.size(); a++) { ModelRow source = g->filtered[a].sourceRow(); QVariant v = source.second.data(CodeCompletionModel::BestMatchesCount); if (v.type() == QVariant::Int && v.toInt() > 0) { //Return the best match with any of the argument-hints int quality = contextMatchQuality(source); if (quality > 0) { matches.insert(quality, qMakePair(v.toInt(), g->filtered[a].sourceRow())); } --maxMatches; } if (maxMatches < 0) { break; } } if (maxMatches < 0) { break; } } //Now choose how many of the matches will be taken. This is done with the rule: //The count of shown best-matches should equal the average count of their BestMatchesCounts int cnt = 0; int matchesSum = 0; BestMatchMap::const_iterator it = matches.constEnd(); while (it != matches.constBegin()) { --it; ++cnt; matchesSum += (*it).first; if (cnt > matchesSum / cnt) { break; } } m_bestMatches->filtered.clear(); it = matches.constEnd(); while (it != matches.constBegin() && cnt > 0) { --it; --cnt; m_bestMatches->filtered.append(Item(true, this, HierarchicalModelHandler((*it).second.first), (*it).second)); } hideOrShowGroup(m_bestMatches); } void KateCompletionModel::rowSelected(const QModelIndex &row) { ExpandingWidgetModel::rowSelected(row); ///@todo delay this int rc = widget()->argumentHintModel()->rowCount(QModelIndex()); if (rc == 0) { return; } //For now, simply update the whole column 0 QModelIndex start = widget()->argumentHintModel()->index(0, 0); QModelIndex end = widget()->argumentHintModel()->index(rc - 1, 0); widget()->argumentHintModel()->emitDataChanged(start, end); } void KateCompletionModel::clearCompletionModels() { if (m_completionModels.isEmpty()) { return; } beginResetModel(); foreach (CodeCompletionModel *model, m_completionModels) { model->disconnect(this); } m_completionModels.clear(); m_currentMatch.clear(); clearGroups(); endResetModel(); } diff --git a/src/completion/katecompletionmodel.h b/src/completion/katecompletionmodel.h index 05686a37..36eda387 100644 --- a/src/completion/katecompletionmodel.h +++ b/src/completion/katecompletionmodel.h @@ -1,417 +1,417 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005-2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * 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 KATECOMPLETIONMODEL_H #define KATECOMPLETIONMODEL_H #include #include #include #include #include #include "expandingtree/expandingwidgetmodel.h" class KateCompletionWidget; class KateArgumentHintModel; namespace KTextEditor { class ViewPrivate; } class QWidget; class QTextEdit; class QTimer; class HierarchicalModelHandler; /** * This class has the responsibility for filtering, sorting, and manipulating * code completion data provided by a CodeCompletionModel. * * @author Hamish Rodda */ class KTEXTEDITOR_EXPORT KateCompletionModel : public ExpandingWidgetModel { Q_OBJECT public: - explicit KateCompletionModel(KateCompletionWidget *parent = 0L); + explicit KateCompletionModel(KateCompletionWidget *parent = nullptr); ~KateCompletionModel(); QList completionModels() const; void clearCompletionModels(); void addCompletionModel(KTextEditor::CodeCompletionModel *model); void setCompletionModel(KTextEditor::CodeCompletionModel *model); void setCompletionModels(const QList &models); void removeCompletionModel(KTextEditor::CodeCompletionModel *model); KTextEditor::ViewPrivate *view() const; KateCompletionWidget *widget() const; QString currentCompletion(KTextEditor::CodeCompletionModel *model) const; void setCurrentCompletion(KTextEditor::CodeCompletionModel *model, const QString &completion); Qt::CaseSensitivity matchCaseSensitivity() const; void setMatchCaseSensitivity(Qt::CaseSensitivity cs); static QString columnName(int column); int translateColumn(int sourceColumn) const; static QString propertyName(KTextEditor::CodeCompletionModel::CompletionProperty property); ///Returns a common prefix for all current visible completion entries ///If there is no common prefix, extracts the next useful prefix for the selected index QString commonPrefix(QModelIndex selectedIndex) const; void rowSelected(const QModelIndex &row) Q_DECL_OVERRIDE; bool indexIsItem(const QModelIndex &index) const Q_DECL_OVERRIDE; int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; bool hasChildren(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; virtual bool hasIndex(int row, int column, const QModelIndex &parent = QModelIndex()) const; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; // Disabled in case of bugs, reenable once fully debugged. //virtual QMap itemData ( const QModelIndex & index ) const; QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; // Disabled in case of bugs, reenable once fully debugged. //virtual QModelIndex sibling ( int row, int column, const QModelIndex & index ) const; void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) Q_DECL_OVERRIDE; ///Maps from this display-model into the appropriate source code-completion model virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const; ///Maps from an index in a source-model to the index of the item in this display-model virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; // Sorting bool isSortingEnabled() const; bool isSortingAlphabetical() const; bool isSortingByInheritanceDepth() const; void setSortingByInheritanceDepth(bool byIneritance); void setSortingAlphabetical(bool alphabetical); Qt::CaseSensitivity sortingCaseSensitivity() const; void setSortingCaseSensitivity(Qt::CaseSensitivity cs); bool isSortingReverse() const; void setSortingReverse(bool reverse); // Filtering bool isFilteringEnabled() const; bool filterContextMatchesOnly() const; void setFilterContextMatchesOnly(bool filter); bool filterByAttribute() const; void setFilterByAttribute(bool filter); KTextEditor::CodeCompletionModel::CompletionProperties filterAttributes() const; void setFilterAttributes(KTextEditor::CodeCompletionModel::CompletionProperties attributes); // A maximum depth of <= 0 equals don't filter by inheritance depth (i.e. infinity) and is default int maximumInheritanceDepth() const; void setMaximumInheritanceDepth(int maxDepth); // Grouping bool isGroupingEnabled() const; enum gm { ScopeType = 0x1, Scope = 0x2, AccessType = 0x4, ItemType = 0x8 }; enum { //An own property that will be used to mark the best-matches group internally BestMatchesProperty = 2 * KTextEditor::CodeCompletionModel::LastProperty }; Q_DECLARE_FLAGS(GroupingMethods, gm) static const int ScopeTypeMask = 0x380000; static const int AccessTypeMask = 0x7; static const int ItemTypeMask = 0xfe0; GroupingMethods groupingMethod() const; void setGroupingMethod(GroupingMethods m); bool accessIncludeConst() const; void setAccessIncludeConst(bool include); bool accessIncludeStatic() const; void setAccessIncludeStatic(bool include); bool accessIncludeSignalSlot() const; void setAccessIncludeSignalSlot(bool include); // Column merging bool isColumnMergingEnabled() const; const QList< QList > &columnMerges() const; void setColumnMerges(const QList< QList > &columnMerges); void debugStats(); ///Returns whether one of the filtered items exactly matches its completion string bool shouldMatchHideCompletionList() const; uint filteredItemCount() const; protected: int contextMatchQuality(const QModelIndex &index) const Q_DECL_OVERRIDE; Q_SIGNALS: void expandIndex(const QModelIndex &index); //Emitted whenever something has changed about the group of argument-hints void argumentHintsChanged(); public Q_SLOTS: void setSortingEnabled(bool enable); void setFilteringEnabled(bool enable); void setGroupingEnabled(bool enable); void setColumnMergingEnabled(bool enable); private Q_SLOTS: void slotRowsInserted(const QModelIndex &parent, int start, int end); void slotRowsRemoved(const QModelIndex &parent, int start, int end); void slotModelReset(); //Updates the best-matches group void updateBestMatches(); //Makes sure that the ungrouped group contains each item only once //Must only be called right after the group was created void makeGroupItemsUnique(bool onlyFiltered = false); private: typedef QPair ModelRow; virtual int contextMatchQuality(const ModelRow &sourceRow) const; QTreeView *treeView() const Q_DECL_OVERRIDE; friend class KateArgumentHintModel; ModelRow modelRowPair(const QModelIndex &index) const; // Represents a source row; provides sorting method class Item { public: Item(bool doInitialMatch, KateCompletionModel *model, const HierarchicalModelHandler &handler, ModelRow sourceRow); bool isValid() const; // Returns true if the item is not filtered and matches the current completion string bool isVisible() const; // Returns whether the item is filtered or not bool isFiltered() const; // Returns whether the item matches the current completion string bool isMatching() const; bool filter(); enum MatchType { NoMatch = 0, PerfectMatch, StartsWithMatch, AbbreviationMatch, ContainsMatch }; MatchType match(); const ModelRow &sourceRow() const; // Sorting operator bool operator<(const Item &rhs) const; bool haveExactMatch() const { return m_haveExactMatch; } void clearExactMatch() { m_haveExactMatch = false; } QString name() const { return m_nameColumn; } private: KateCompletionModel *model; ModelRow m_sourceRow; mutable QString m_nameColumn; int inheritanceDepth; // True when currently matching completion string MatchType matchCompletion; // True when passes all active filters bool matchFilters; bool m_haveExactMatch; bool m_unimportant; QString completionSortingName() const; }; public: // Grouping and sorting of rows class Group { public: explicit Group(const QString& title, int attribute, KateCompletionModel *model); void addItem(Item i, bool notifyModel = false); /// Removes the item specified by \a row. Returns true if a change was made to rows. bool removeItem(const ModelRow &row); void resort(); void refilter(); void clear(); //Returns whether this group should be ordered before other bool orderBefore(Group *other) const; //Returns a number that can be used for ordering int orderNumber() const; ///Returns the row in the this group's filtered list of the given model-row in a source-model ///-1 if the item is not in the filtered list ///@todo Implement an efficient way of doing this map, that does _not_ iterate over all items! int rowOf(ModelRow item) { for (int a = 0; a < filtered.size(); ++a) if (filtered[a].sourceRow() == item) { return a; } return -1; } KateCompletionModel *model; int attribute; QString title, scope; QList filtered; QList prefilter; bool isEmpty; //-1 if none was set int customSortingKey; }; bool hasGroups() const; private: QString commonPrefixInternal(const QString &forcePrefix) const; /// @note performs model reset void createGroups(); ///Creates all sub-items of index i, or the item corresponding to index i. Returns the affected groups. ///i must be an index in the source model QSet createItems(const HierarchicalModelHandler &, const QModelIndex &i, bool notifyModel = false); ///Deletes all sub-items of index i, or the item corresponding to index i. Returns the affected groups. ///i must be an index in the source model QSet deleteItems(const QModelIndex &i); Group *createItem(const HierarchicalModelHandler &, const QModelIndex &i, bool notifyModel = false); /// @note Make sure you're in a {begin,end}ResetModel block when calling this! void clearGroups(); void hideOrShowGroup(Group *g, bool notifyModel = false); /// When forceGrouping is enabled, all given attributes will be used for grouping, regardless of the completion settings. Group *fetchGroup(int attribute, const QString &scope = QString(), bool forceGrouping = false); //If this returns nonzero on an index, the index is the header of the returned group Group *groupForIndex(const QModelIndex &index) const; inline Group *groupOfParent(const QModelIndex &child) const { return static_cast(child.internalPointer()); } QModelIndex indexForRow(Group *g, int row) const; QModelIndex indexForGroup(Group *g) const; enum changeTypes { Broaden, Narrow, Change }; //Returns whether the model needs to be reset void changeCompletions(Group *g, changeTypes changeType, bool notifyModel); bool hasCompletionModel() const; /// Removes attributes not used in grouping from the input \a attribute int groupingAttributes(int attribute) const; int countBits(int value) const; void resort(); void refilter(); static bool matchesAbbreviation(const QString &word, const QString &typed); bool m_hasGroups; // ### Runtime state // General QList m_completionModels; QMap m_currentMatch; Qt::CaseSensitivity m_matchCaseSensitivity; // Column merging QList< QList > m_columnMerges; QTimer *m_updateBestMatchesTimer; Group *m_ungrouped; Group *m_argumentHints; //The argument-hints will be passed on to another model, to be shown in another widget Group *m_bestMatches; //A temporary group used for holding the best matches of all visible items // Storing the sorted order QList m_rowTable; QList m_emptyGroups; // Quick access to each specific group (if it exists) QMultiHash m_groupHash; // Maps custom group-names to their specific groups QHash m_customGroupHash; // ### Configurable state // Sorting bool m_sortingEnabled; bool m_sortingAlphabetical; bool m_isSortingByInheritance; Qt::CaseSensitivity m_sortingCaseSensitivity; QHash< int, QList > m_sortingGroupingOrder; // Filtering bool m_filteringEnabled; bool m_filterContextMatchesOnly; bool m_filterByAttribute; KTextEditor::CodeCompletionModel::CompletionProperties m_filterAttributes; int m_maximumInheritanceDepth; // Grouping bool m_groupingEnabled; GroupingMethods m_groupingMethod; bool m_accessConst, m_accessStatic, m_accesSignalSlot; // Column merging bool m_columnMergingEnabled/*, m_haveExactMatch*/; friend class CompletionTest; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KateCompletionModel::GroupingMethods) #endif diff --git a/src/completion/katecompletionwidget.cpp b/src/completion/katecompletionwidget.cpp index f0e50eb3..2107540f 100644 --- a/src/completion/katecompletionwidget.cpp +++ b/src/completion/katecompletionwidget.cpp @@ -1,1474 +1,1474 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005-2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * 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 "katecompletionwidget.h" #include #include "kateview.h" #include "katerenderer.h" #include "kateconfig.h" #include "katedocument.h" #include "katebuffer.h" #include "katecompletionmodel.h" #include "katecompletiontree.h" #include "katecompletionconfig.h" #include "kateargumenthinttree.h" #include "kateargumenthintmodel.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include const bool hideAutomaticCompletionOnExactMatch = true; //If this is true, the completion-list is navigated up/down when 'tab' is pressed, instead of doing partial completion const bool shellLikeTabCompletion = false; #define CALLCI(WHAT,WHATELSE,WHAT2,model,FUNC) \ {\ static KTextEditor::CodeCompletionModelControllerInterface defaultIf;\ KTextEditor::CodeCompletionModelControllerInterface* ret =\ dynamic_cast(model);\ if (!ret) {\ WHAT2 defaultIf.FUNC;\ }else \ WHAT2 ret->FUNC;\ } static KTextEditor::Range _completionRange(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Cursor &cursor) { CALLCI(return,, return, model, completionRange(view, cursor)); } static KTextEditor::Range _updateRange(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, KTextEditor::Range &range) { CALLCI(, return range, return, model, updateCompletionRange(view, range)); } static QString _filterString(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Range &range, const KTextEditor::Cursor &cursor) { CALLCI(return,, return, model, filterString(view, range, cursor)); } static bool _shouldAbortCompletion(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) { CALLCI(return,, return, model, shouldAbortCompletion(view, range, currentCompletion)); } static void _aborted(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view) { CALLCI(return,, return, model, aborted(view)); } static bool _shouldStartCompletion(KTextEditor::CodeCompletionModel *model, KTextEditor::View *view, QString m_automaticInvocationLine, bool m_lastInsertionByUser, const KTextEditor::Cursor &cursor) { CALLCI(return,, return, model, shouldStartCompletion(view, m_automaticInvocationLine, m_lastInsertionByUser, cursor)); } KateCompletionWidget::KateCompletionWidget(KTextEditor::ViewPrivate *parent) : QFrame(parent, Qt::ToolTip) , m_presentationModel(new KateCompletionModel(this)) , m_entryList(new KateCompletionTree(this)) , m_argumentHintModel(new KateArgumentHintModel(this)) , m_argumentHintTree(new KateArgumentHintTree(this)) , m_automaticInvocationDelay(100) , m_filterInstalled(false) , m_configWidget(new KateCompletionConfig(m_presentationModel, view())) , m_lastInsertionByUser(false) , m_inCompletionList(false) , m_isSuspended(false) , m_dontShowArgumentHints(false) , m_needShow(false) , m_hadCompletionNavigation(false) , m_noAutoHide(false) , m_completionEditRunning(false) , m_expandedAddedHeightBase(0) , m_lastInvocationType(KTextEditor::CodeCompletionModel::AutomaticInvocation) { connect(parent, SIGNAL(navigateAccept()), SLOT(navigateAccept())); connect(parent, SIGNAL(navigateBack()), SLOT(navigateBack())); connect(parent, SIGNAL(navigateDown()), SLOT(navigateDown())); connect(parent, SIGNAL(navigateLeft()), SLOT(navigateLeft())); connect(parent, SIGNAL(navigateRight()), SLOT(navigateRight())); connect(parent, SIGNAL(navigateUp()), SLOT(navigateUp())); setFrameStyle(QFrame::Box | QFrame::Plain); setLineWidth(1); //setWindowOpacity(0.8); m_entryList->setModel(m_presentationModel); m_entryList->setColumnWidth(0, 0); //These will be determined automatically in KateCompletionTree::resizeColumns m_entryList->setColumnWidth(1, 0); m_entryList->setColumnWidth(2, 0); m_entryList->setVerticalScrollMode(QAbstractItemView::ScrollPerItem); - m_argumentHintTree->setParent(0, Qt::ToolTip); + m_argumentHintTree->setParent(nullptr, Qt::ToolTip); m_argumentHintTree->setModel(m_argumentHintModel); // trigger completion on double click on completion list connect(m_entryList, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(execute())); connect(m_entryList->verticalScrollBar(), SIGNAL(valueChanged(int)), m_presentationModel, SLOT(placeExpandingWidgets())); connect(m_argumentHintTree->verticalScrollBar(), SIGNAL(valueChanged(int)), m_argumentHintModel, SLOT(placeExpandingWidgets())); connect(view(), SIGNAL(focusOut(KTextEditor::View*)), this, SLOT(viewFocusOut())); m_automaticInvocationTimer = new QTimer(this); m_automaticInvocationTimer->setSingleShot(true); connect(m_automaticInvocationTimer, SIGNAL(timeout()), this, SLOT(automaticInvocation())); // Keep branches expanded connect(m_presentationModel, SIGNAL(modelReset()), this, SLOT(modelReset())); connect(m_presentationModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int))); connect(m_argumentHintModel, SIGNAL(contentStateChanged(bool)), this, SLOT(argumentHintsChanged(bool))); // No smart lock, no queued connects connect(view(), SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(cursorPositionChanged())); connect(view(), SIGNAL(verticalScrollPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updatePositionSlot())); /** * connect to all possible editing primitives */ connect(&view()->doc()->buffer(), SIGNAL(lineWrapped(KTextEditor::Cursor)), this, SLOT(wrapLine(KTextEditor::Cursor))); connect(&view()->doc()->buffer(), SIGNAL(lineUnwrapped(int)), this, SLOT(unwrapLine(int))); connect(&view()->doc()->buffer(), SIGNAL(textInserted(KTextEditor::Cursor,QString)), this, SLOT(insertText(KTextEditor::Cursor,QString))); connect(&view()->doc()->buffer(), SIGNAL(textRemoved(KTextEditor::Range,QString)), this, SLOT(removeText(KTextEditor::Range))); // This is a non-focus widget, it is passed keyboard input from the view //We need to do this, because else the focus goes to nirvana without any control when the completion-widget is clicked. setFocusPolicy(Qt::ClickFocus); m_argumentHintTree->setFocusPolicy(Qt::ClickFocus); foreach (QWidget *childWidget, findChildren()) { childWidget->setFocusPolicy(Qt::NoFocus); } //Position the entry-list so a frame can be drawn around it m_entryList->move(frameWidth(), frameWidth()); } KateCompletionWidget::~KateCompletionWidget() { } void KateCompletionWidget::viewFocusOut() { if (QApplication::focusWidget() != this) { abortCompletion(); } } void KateCompletionWidget::focusOutEvent(QFocusEvent *) { abortCompletion(); } void KateCompletionWidget::modelContentChanged() { ////qCDebug(LOG_KTE)<<">>>>>>>>>>>>>>>>"; if (m_completionRanges.isEmpty()) { //qCDebug(LOG_KTE) << "content changed, but no completion active"; abortCompletion(); return; } if (!view()->hasFocus()) { //qCDebug(LOG_KTE) << "view does not have focus"; return; } if (!m_waitingForReset.isEmpty()) { //qCDebug(LOG_KTE) << "waiting for" << m_waitingForReset.size() << "completion-models to reset"; return; } int realItemCount = 0; foreach (KTextEditor::CodeCompletionModel *model, m_presentationModel->completionModels()) { realItemCount += model->rowCount(); } if (!m_isSuspended && ((isHidden() && m_argumentHintTree->isHidden()) || m_needShow) && realItemCount != 0) { m_needShow = false; updateAndShow(); } if (m_argumentHintModel->rowCount(QModelIndex()) == 0) { m_argumentHintTree->hide(); } if (m_presentationModel->rowCount(QModelIndex()) == 0) { hide(); } //With each filtering items can be added or removed, so we have to reset the current index here so we always have a selected item m_entryList->setCurrentIndex(model()->index(0, 0)); if (!model()->indexIsItem(m_entryList->currentIndex())) { QModelIndex firstIndex = model()->index(0, 0, m_entryList->currentIndex()); m_entryList->setCurrentIndex(firstIndex); //m_entryList->scrollTo(firstIndex, QAbstractItemView::PositionAtTop); } updateHeight(); //New items for the argument-hint tree may have arrived, so check whether it needs to be shown if (m_argumentHintTree->isHidden() && !m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) { m_argumentHintTree->show(); } if (!m_noAutoHide && hideAutomaticCompletionOnExactMatch && !isHidden() && m_lastInvocationType == KTextEditor::CodeCompletionModel::AutomaticInvocation && m_presentationModel->shouldMatchHideCompletionList()) { hide(); } else if (isHidden() && !m_presentationModel->shouldMatchHideCompletionList() && m_presentationModel->rowCount(QModelIndex())) { show(); } } KateArgumentHintTree *KateCompletionWidget::argumentHintTree() const { return m_argumentHintTree; } KateArgumentHintModel *KateCompletionWidget::argumentHintModel() const { return m_argumentHintModel; } const KateCompletionModel *KateCompletionWidget::model() const { return m_presentationModel; } KateCompletionModel *KateCompletionWidget::model() { return m_presentationModel; } void KateCompletionWidget::rowsInserted(const QModelIndex &parent, int rowFrom, int rowEnd) { m_entryList->setAnimated(false); if (!model()->isGroupingEnabled()) { return; } if (!parent.isValid()) for (int i = rowFrom; i <= rowEnd; ++i) { m_entryList->expand(m_presentationModel->index(i, 0, parent)); } } KTextEditor::ViewPrivate *KateCompletionWidget::view() const { return static_cast(const_cast(parent())); } void KateCompletionWidget::argumentHintsChanged(bool hasContent) { m_dontShowArgumentHints = !hasContent; if (m_dontShowArgumentHints) { m_argumentHintTree->hide(); } else { updateArgumentHintGeometry(); } } void KateCompletionWidget::startCompletion(KTextEditor::CodeCompletionModel::InvocationType invocationType, const QList &models) { if (invocationType == KTextEditor::CodeCompletionModel::UserInvocation) { abortCompletion(); } startCompletion(KTextEditor::Range(KTextEditor::Cursor(-1, -1), KTextEditor::Cursor(-1, -1)), models, invocationType); } void KateCompletionWidget::deleteCompletionRanges() { ////qCDebug(LOG_KTE); foreach (const CompletionRange &r, m_completionRanges) { delete r.range; } m_completionRanges.clear(); } void KateCompletionWidget::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model, KTextEditor::CodeCompletionModel::InvocationType invocationType) { QList models; if (model) { models << model; } else { models = m_sourceModels; } startCompletion(word, models, invocationType); } void KateCompletionWidget::startCompletion(const KTextEditor::Range &word, const QList &modelsToStart, KTextEditor::CodeCompletionModel::InvocationType invocationType) { ////qCDebug(LOG_KTE)<<"============"; m_isSuspended = false; m_inCompletionList = true; //Always start at the top of the completion-list m_needShow = true; if (m_completionRanges.isEmpty()) { m_noAutoHide = false; //Re-enable auto-hide on every clean restart of the completion } m_lastInvocationType = invocationType; disconnect(this->model(), SIGNAL(layoutChanged()), this, SLOT(modelContentChanged())); disconnect(this->model(), SIGNAL(modelReset()), this, SLOT(modelContentChanged())); m_dontShowArgumentHints = true; QList models = (modelsToStart.isEmpty() ? m_sourceModels : modelsToStart); foreach (KTextEditor::CodeCompletionModel *model, m_completionRanges.keys()) if (!models.contains(model)) { models << model; } if (!m_filterInstalled) { if (!QApplication::activeWindow()) { qCWarning(LOG_KTE) << "No active window to install event filter on!!"; return; } // Enable the cc box to move when the editor window is moved QApplication::activeWindow()->installEventFilter(this); m_filterInstalled = true; } m_presentationModel->clearCompletionModels(); if (invocationType == KTextEditor::CodeCompletionModel::UserInvocation) { deleteCompletionRanges(); } foreach (KTextEditor::CodeCompletionModel *model, models) { KTextEditor::Range range; if (word.isValid()) { range = word; //qCDebug(LOG_KTE)<<"word is used"; } else { range = _completionRange(model, view(), view()->cursorPosition()); //qCDebug(LOG_KTE)<<"completionRange has been called, cursor pos is"<cursorPosition(); } //qCDebug(LOG_KTE)<<"range is"<completionInvoked(view(), range, invocationType); disconnect(model, SIGNAL(waitForReset()), this, SLOT(waitForModelReset())); m_completionRanges[model] = CompletionRange(view()->doc()->newMovingRange(range, KTextEditor::MovingRange::ExpandRight | KTextEditor::MovingRange::ExpandLeft)); //In automatic invocation mode, hide the completion widget as soon as the position where the completion was started is passed to the left m_completionRanges[model].leftBoundary = view()->cursorPosition(); //In manual invocation mode, bound the activity either the point from where completion was invoked, or to the start of the range if (invocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation) if (range.start() < m_completionRanges[model].leftBoundary) { m_completionRanges[model].leftBoundary = range.start(); } if (!m_completionRanges[model].range->toRange().isValid()) { qCWarning(LOG_KTE) << "Could not construct valid smart-range from" << range << "instead got" << *m_completionRanges[model].range; abortCompletion(); return; } } m_presentationModel->setCompletionModels(models); cursorPositionChanged(); if (!m_completionRanges.isEmpty()) { connect(this->model(), SIGNAL(layoutChanged()), this, SLOT(modelContentChanged())); connect(this->model(), SIGNAL(modelReset()), this, SLOT(modelContentChanged())); //Now that all models have been notified, check whether the widget should be displayed instantly modelContentChanged(); } else { abortCompletion(); } } void KateCompletionWidget::waitForModelReset() { KTextEditor::CodeCompletionModel *senderModel = qobject_cast(sender()); if (!senderModel) { qCWarning(LOG_KTE) << "waitForReset signal from bad model"; return; } m_waitingForReset.insert(senderModel); } void KateCompletionWidget::updateAndShow() { //qCDebug(LOG_KTE)<<"*******************************************"; if (!view()->hasFocus()) { qCDebug(LOG_KTE) << "view does not have focus"; return; } setUpdatesEnabled(false); modelReset(); m_argumentHintModel->buildRows(); if (m_argumentHintModel->rowCount(QModelIndex()) != 0) { argumentHintsChanged(true); } // } //We do both actions twice here so they are stable, because they influence each other: //updatePosition updates the height, resizeColumns needs the correct height to decide over //how many rows it computs the column-width updatePosition(true); m_entryList->resizeColumns(true, true); updatePosition(true); m_entryList->resizeColumns(true, true); setUpdatesEnabled(true); if (m_argumentHintModel->rowCount(QModelIndex())) { updateArgumentHintGeometry(); m_argumentHintTree->show(); } else { m_argumentHintTree->hide(); } if (m_presentationModel->rowCount() && (!m_presentationModel->shouldMatchHideCompletionList() || !hideAutomaticCompletionOnExactMatch || m_lastInvocationType != KTextEditor::CodeCompletionModel::AutomaticInvocation)) { show(); } else { hide(); } } void KateCompletionWidget::updatePositionSlot() { updatePosition(); } bool KateCompletionWidget::updatePosition(bool force) { if (!force && !isCompletionActive()) { return false; } if (!completionRange()) { return false; } QPoint cursorPosition = view()->cursorToCoordinate(completionRange()->start()); if (cursorPosition == QPoint(-1, -1)) { // Start of completion range is now off-screen -> abort abortCompletion(); return false; } QPoint p = view()->mapToGlobal(cursorPosition); int x = p.x() - m_entryList->columnTextViewportPosition(m_presentationModel->translateColumn(KTextEditor::CodeCompletionModel::Name)) - 7 - (m_entryList->viewport()->pos().x()); int y = p.y(); y += view()->renderer()->config()->fontMetrics().height() + 2; bool borderHit = false; if (x + width() > QApplication::desktop()->screenGeometry(view()).right()) { x = QApplication::desktop()->screenGeometry(view()).right() - width(); borderHit = true; } if (x < QApplication::desktop()->screenGeometry(view()).left()) { x = QApplication::desktop()->screenGeometry(view()).left(); borderHit = true; } move(QPoint(x, y)); updateHeight(); updateArgumentHintGeometry(); // //qCDebug(LOG_KTE) << "updated to" << geometry() << m_entryList->geometry() << borderHit; return borderHit; } void KateCompletionWidget::updateArgumentHintGeometry() { if (!m_dontShowArgumentHints) { //Now place the argument-hint widget QRect geom = m_argumentHintTree->geometry(); geom.moveTo(pos()); geom.setWidth(width()); geom.moveBottom(pos().y() - view()->renderer()->config()->fontMetrics().height() * 2); m_argumentHintTree->updateGeometry(geom); } } //Checks whether the given model has at least "rows" rows, also searching the second level of the tree. bool hasAtLeastNRows(int rows, QAbstractItemModel *model) { int count = 0; for (int row = 0; row < model->rowCount(); ++row) { ++count; QModelIndex index(model->index(row, 0)); if (index.isValid()) { count += model->rowCount(index); } if (count > rows) { return true; } } return false; } void KateCompletionWidget::updateHeight() { QRect geom = geometry(); int minBaseHeight = 10; int maxBaseHeight = 300; int baseHeight = 0; int calculatedCustomHeight = 0; if (hasAtLeastNRows(15, m_presentationModel)) { //If we know there is enough rows, always use max-height, we don't need to calculate size-hints baseHeight = maxBaseHeight; } else { //Calculate size-hints to determine the best height for (int row = 0; row < m_presentationModel->rowCount(); ++row) { baseHeight += treeView()->sizeHintForRow(row); QModelIndex index(m_presentationModel->index(row, 0)); if (index.isValid()) { for (int row2 = 0; row2 < m_presentationModel->rowCount(index); ++row2) { int h = 0; for (int a = 0; a < m_presentationModel->columnCount(index); ++a) { int localHeight = treeView()->sizeHintForIndex(index.child(row2, a)).height(); if (localHeight > h) { h = localHeight; } } baseHeight += h; if (baseHeight > maxBaseHeight) { break; } } if (baseHeight > maxBaseHeight) { break; } } } calculatedCustomHeight = baseHeight; } baseHeight += 2 * frameWidth(); if (m_entryList->horizontalScrollBar()->isVisible()) { baseHeight += m_entryList->horizontalScrollBar()->height(); } if (baseHeight < minBaseHeight) { baseHeight = minBaseHeight; } if (baseHeight > maxBaseHeight) { baseHeight = maxBaseHeight; m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } else { //Somewhere there seems to be a bug that makes QTreeView add a scroll-bar //even if the content exactly fits in. So forcefully disable the scroll-bar in that case m_entryList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } int newExpandingAddedHeight = 0; if (baseHeight == maxBaseHeight && model()->expandingWidgetsHeight()) { //Eventually add some more height if (calculatedCustomHeight && calculatedCustomHeight > baseHeight && calculatedCustomHeight < (maxBaseHeight + model()->expandingWidgetsHeight())) { newExpandingAddedHeight = calculatedCustomHeight - baseHeight; } else { newExpandingAddedHeight = model()->expandingWidgetsHeight(); } } if (m_expandedAddedHeightBase != baseHeight && m_expandedAddedHeightBase - baseHeight > -2 && m_expandedAddedHeightBase - baseHeight < 2) { //Re-use the stored base-height if it only slightly differs from the current one. //Reason: Qt seems to apply slightly wrong sizes when the completion-widget is moved out of the screen at the bottom, // which completely breaks this algorithm. Solution: re-use the old base-size if it only slightly differs from the computed one. baseHeight = m_expandedAddedHeightBase; } int screenBottom = QApplication::desktop()->screenGeometry(view()).bottom(); //Limit the height to the bottom of the screen int bottomPosition = baseHeight + newExpandingAddedHeight + geometry().top(); if (bottomPosition > screenBottom) { newExpandingAddedHeight -= bottomPosition - (screenBottom); } int finalHeight = baseHeight + newExpandingAddedHeight; if (finalHeight < 10) { m_entryList->resize(m_entryList->width(), height() - 2 * frameWidth()); return; } m_expandedAddedHeightBase = geometry().height(); geom.setHeight(finalHeight); //Work around a crash deep within the Qt 4.5 raster engine m_entryList->setScrollingEnabled(false); if (geometry() != geom) { setGeometry(geom); } QSize entryListSize = QSize(m_entryList->width(), finalHeight - 2 * frameWidth()); if (m_entryList->size() != entryListSize) { m_entryList->resize(entryListSize); } m_entryList->setScrollingEnabled(true); } void KateCompletionWidget::cursorPositionChanged() { ////qCDebug(LOG_KTE); if (m_completionRanges.isEmpty()) { return; } QModelIndex oldCurrentSourceIndex; if (m_inCompletionList && m_entryList->currentIndex().isValid()) { oldCurrentSourceIndex = m_presentationModel->mapToSource(m_entryList->currentIndex()); } //Check the models and eventuall abort some const QList checkCompletionRanges = m_completionRanges.keys(); for (QList::const_iterator it = checkCompletionRanges.begin(); it != checkCompletionRanges.end(); ++it) { KTextEditor::CodeCompletionModel *model = *it; if (!m_completionRanges.contains(model)) { continue; } //qCDebug(LOG_KTE)<<"range before _updateRange:"<< *range; // this might invalidate the range, therefore re-check afterwards KTextEditor::Range rangeTE = m_completionRanges[model].range->toRange(); KTextEditor::Range newRange = _updateRange(model, view(), rangeTE); if (!m_completionRanges.contains(model)) { continue; } // update value m_completionRanges[model].range->setRange(newRange); //qCDebug(LOG_KTE)<<"range after _updateRange:"<< *range; QString currentCompletion = _filterString(model, view(), *m_completionRanges[model].range, view()->cursorPosition()); if (!m_completionRanges.contains(model)) { continue; } //qCDebug(LOG_KTE)<<"after _filterString, currentCompletion="<< currentCompletion; bool abort = _shouldAbortCompletion(model, view(), *m_completionRanges[model].range, currentCompletion); if (!m_completionRanges.contains(model)) { continue; } //qCDebug(LOG_KTE)<<"after _shouldAbortCompletion:abort="<cursorPosition() < m_completionRanges[model].leftBoundary) { //qCDebug(LOG_KTE) << "aborting because of boundary: cursor:"<cursorPosition()<<"completion_Range_left_boundary:"<removeCompletionModel(model); } } else { m_presentationModel->setCurrentCompletion(model, currentCompletion); } } if (oldCurrentSourceIndex.isValid()) { QModelIndex idx = m_presentationModel->mapFromSource(oldCurrentSourceIndex); if (idx.isValid()) { //qCDebug(LOG_KTE) << "setting" << idx; m_entryList->setCurrentIndex(idx.sibling(idx.row(), 0)); // m_entryList->nextCompletion(); // m_entryList->previousCompletion(); } else { //qCDebug(LOG_KTE) << "failed to map from source"; } } m_entryList->scheduleUpdate(); } bool KateCompletionWidget::isCompletionActive() const { return !m_completionRanges.isEmpty() && ((!isHidden() && isVisible()) || (!m_argumentHintTree->isHidden() && m_argumentHintTree->isVisible())); } void KateCompletionWidget::abortCompletion() { //qCDebug(LOG_KTE) ; m_isSuspended = false; bool wasActive = isCompletionActive(); if (hasFocus()) { view()->activateWindow(); view()->setFocus(); } clear(); if (!isHidden()) { hide(); } if (!m_argumentHintTree->isHidden()) { m_argumentHintTree->hide(); } if (wasActive) { view()->sendCompletionAborted(); } } void KateCompletionWidget::clear() { m_presentationModel->clearCompletionModels(); m_argumentHintTree->clearCompletion(); m_argumentHintModel->clear(); foreach (KTextEditor::CodeCompletionModel *model, m_completionRanges.keys()) { _aborted(model, view()); } deleteCompletionRanges(); } bool KateCompletionWidget::navigateAccept() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetAccept"); } QModelIndex index = selectedIndex(); if (index.isValid()) { index.data(KTextEditor::CodeCompletionModel::AccessibilityAccept); return true; } return false; } void KateCompletionWidget::execute() { //qCDebug(LOG_KTE) ; if (!isCompletionActive()) { return; } QModelIndex index = selectedIndex(); if (!index.isValid()) { return abortCompletion(); } QModelIndex toExecute; if (index.model() == m_presentationModel) { toExecute = m_presentationModel->mapToSource(index); } else { toExecute = m_argumentHintModel->mapToSource(index); } if (!toExecute.isValid()) { qCWarning(LOG_KTE) << "Could not map index" << m_entryList->selectionModel()->currentIndex() << "to source index."; return abortCompletion(); } // encapsulate all editing as being from the code completion, and undo-able in one step. view()->doc()->editStart(); m_completionEditRunning = true; // create scoped pointer, to ensure deletion of cursor QScopedPointer oldPos(view()->doc()->newMovingCursor(view()->cursorPosition(), KTextEditor::MovingCursor::StayOnInsert)); KTextEditor::CodeCompletionModel *model = static_cast(const_cast(toExecute.model())); Q_ASSERT(model); Q_ASSERT(m_completionRanges.contains(model)); KTextEditor::Cursor start = m_completionRanges[model].range->start(); model->executeCompletionItem(view(), *m_completionRanges[model].range, toExecute); view()->doc()->editEnd(); m_completionEditRunning = false; abortCompletion(); view()->sendCompletionExecuted(start, model, toExecute); KTextEditor::Cursor newPos = view()->cursorPosition(); if (newPos > *oldPos) { m_automaticInvocationAt = newPos; m_automaticInvocationLine = view()->doc()->text(KTextEditor::Range(*oldPos, newPos)); //qCDebug(LOG_KTE) << "executed, starting automatic invocation with line" << m_automaticInvocationLine; m_lastInsertionByUser = false; m_automaticInvocationTimer->start(); } } void KateCompletionWidget::resizeEvent(QResizeEvent *event) { QFrame::resizeEvent(event); } void KateCompletionWidget::showEvent(QShowEvent *event) { m_isSuspended = false; QFrame::showEvent(event); if (!m_dontShowArgumentHints && m_argumentHintModel->rowCount(QModelIndex()) != 0) { m_argumentHintTree->show(); } } KTextEditor::MovingRange *KateCompletionWidget::completionRange(KTextEditor::CodeCompletionModel *model) const { if (!model) { if (m_completionRanges.isEmpty()) { - return 0; + return nullptr; } KTextEditor::MovingRange *ret = m_completionRanges.begin()->range; foreach (const CompletionRange &range, m_completionRanges) if (range.range->start() > ret->start()) { ret = range.range; } return ret; } if (m_completionRanges.contains(model)) { return m_completionRanges[model].range; } else { - return 0; + return nullptr; } } QMap KateCompletionWidget::completionRanges() const { return m_completionRanges; } void KateCompletionWidget::modelReset() { setUpdatesEnabled(false); m_entryList->setAnimated(false); m_argumentHintTree->setAnimated(false); ///We need to do this by hand, because QTreeView::expandAll is very inefficient. ///It creates a QPersistentModelIndex for every single item in the whole tree.. for (int row = 0; row < m_argumentHintModel->rowCount(QModelIndex()); ++row) { QModelIndex index(m_argumentHintModel->index(row, 0, QModelIndex())); if (!m_argumentHintTree->isExpanded(index)) { m_argumentHintTree->expand(index); } } for (int row = 0; row < m_entryList->model()->rowCount(QModelIndex()); ++row) { QModelIndex index(m_entryList->model()->index(row, 0, QModelIndex())); if (!m_entryList->isExpanded(index)) { m_entryList->expand(index); } } setUpdatesEnabled(true); } KateCompletionTree *KateCompletionWidget::treeView() const { return m_entryList; } QModelIndex KateCompletionWidget::selectedIndex() const { if (!isCompletionActive()) { return QModelIndex(); } if (m_inCompletionList) { return m_entryList->currentIndex(); } else { return m_argumentHintTree->currentIndex(); } } bool KateCompletionWidget::navigateLeft() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetLeft"); } QModelIndex index = selectedIndex(); if (index.isValid()) { index.data(KTextEditor::CodeCompletionModel::AccessibilityPrevious); return true; } return false; } bool KateCompletionWidget::navigateRight() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { ///@todo post 4.2: Make these slots public interface, or create an interface using virtual functions QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetRight"); } QModelIndex index = selectedIndex(); if (index.isValid()) { index.data(KTextEditor::CodeCompletionModel::AccessibilityNext); return true; } return false; } bool KateCompletionWidget::hadNavigation() const { return m_hadCompletionNavigation; } void KateCompletionWidget::resetHadNavigation() { m_hadCompletionNavigation = false; } bool KateCompletionWidget::navigateBack() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetBack"); } return false; } bool KateCompletionWidget::toggleExpanded(bool forceExpand, bool forceUnExpand) { if ((canExpandCurrentItem() || forceExpand) && !forceUnExpand) { bool ret = canExpandCurrentItem(); setCurrentItemExpanded(true); return ret; } else if (canCollapseCurrentItem() || forceUnExpand) { bool ret = canCollapseCurrentItem(); setCurrentItemExpanded(false); return ret; } return false; } bool KateCompletionWidget::canExpandCurrentItem() const { if (m_inCompletionList) { if (!m_entryList->currentIndex().isValid()) { return false; } return model()->isExpandable(m_entryList->currentIndex()) && !model()->isExpanded(m_entryList->currentIndex()); } else { if (!m_argumentHintTree->currentIndex().isValid()) { return false; } return argumentHintModel()->isExpandable(m_argumentHintTree->currentIndex()) && !argumentHintModel()->isExpanded(m_argumentHintTree->currentIndex()); } } bool KateCompletionWidget::canCollapseCurrentItem() const { if (m_inCompletionList) { if (!m_entryList->currentIndex().isValid()) { return false; } return model()->isExpandable(m_entryList->currentIndex()) && model()->isExpanded(m_entryList->currentIndex()); } else { if (!m_argumentHintTree->currentIndex().isValid()) { return false; } return m_argumentHintModel->isExpandable(m_argumentHintTree->currentIndex()) && m_argumentHintModel->isExpanded(m_argumentHintTree->currentIndex()); } } void KateCompletionWidget::setCurrentItemExpanded(bool expanded) { if (m_inCompletionList) { if (!m_entryList->currentIndex().isValid()) { return; } model()->setExpanded(m_entryList->currentIndex(), expanded); updateHeight(); } else { if (!m_argumentHintTree->currentIndex().isValid()) { return; } m_argumentHintModel->setExpanded(m_argumentHintTree->currentIndex(), expanded); } } bool KateCompletionWidget::eventFilter(QObject *watched, QEvent *event) { bool ret = QFrame::eventFilter(watched, event); if (watched != this) if (event->type() == QEvent::Move) { updatePosition(); } return ret; } bool KateCompletionWidget::navigateDown() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetDown"); } return false; } bool KateCompletionWidget::navigateUp() { m_hadCompletionNavigation = true; if (currentEmbeddedWidget()) { QMetaObject::invokeMethod(currentEmbeddedWidget(), "embeddedWidgetUp"); } return false; } QWidget *KateCompletionWidget::currentEmbeddedWidget() { QModelIndex index = selectedIndex(); if (!index.isValid()) { - return 0; + return nullptr; } if (qobject_cast(index.model())) { const ExpandingWidgetModel *model = static_cast(index.model()); if (model->isExpanded(index)) { return model->expandingWidget(index); } } - return 0; + return nullptr; } void KateCompletionWidget::cursorDown() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->nextCompletion(); } else { if (!m_argumentHintTree->nextCompletion()) { switchList(); } } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::cursorUp() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { if (!m_entryList->previousCompletion()) { switchList(); } } else { m_argumentHintTree->previousCompletion(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::pageDown() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->pageDown(); } else { if (!m_argumentHintTree->pageDown()) { switchList(); } } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::pageUp() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { if (!m_entryList->pageUp()) { switchList(); } } else { m_argumentHintTree->pageUp(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::top() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->top(); } else { m_argumentHintTree->top(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::bottom() { bool wasPartiallyExpanded = model()->partiallyExpandedRow().isValid(); if (m_inCompletionList) { m_entryList->bottom(); } else { m_argumentHintTree->bottom(); } if (wasPartiallyExpanded != model()->partiallyExpandedRow().isValid()) { updateHeight(); } } void KateCompletionWidget::switchList() { if (m_inCompletionList) { if (m_argumentHintModel->rowCount(QModelIndex()) != 0) { m_entryList->setCurrentIndex(QModelIndex()); m_argumentHintTree->setCurrentIndex(m_argumentHintModel->index(m_argumentHintModel->rowCount(QModelIndex()) - 1, 0)); m_inCompletionList = false; } } else { if (m_presentationModel->rowCount(QModelIndex()) != 0) { m_argumentHintTree->setCurrentIndex(QModelIndex()); m_entryList->setCurrentIndex(m_presentationModel->index(0, 0)); if (model()->hasGroups()) { //If we have groups we have to move on, because the first item is a label m_entryList->nextCompletion(); } m_inCompletionList = true; } } } void KateCompletionWidget::showConfig() { abortCompletion(); m_configWidget->exec(); } void KateCompletionWidget::completionModelReset() { KTextEditor::CodeCompletionModel *model = qobject_cast(sender()); if (!model) { qCWarning(LOG_KTE) << "bad sender"; return; } if (!m_waitingForReset.contains(model)) { return; } m_waitingForReset.remove(model); if (m_waitingForReset.isEmpty()) { if (!isCompletionActive()) { //qCDebug(LOG_KTE) << "all completion-models we waited for are ready. Last one: " << model->objectName(); //Eventually show the completion-list if this was the last model we were waiting for //Use a queued connection once again to make sure that KateCompletionModel is notified before we are QMetaObject::invokeMethod(this, "modelContentChanged", Qt::QueuedConnection); } } } void KateCompletionWidget::modelDestroyed(QObject *model) { m_sourceModels.removeAll(static_cast(model)); abortCompletion(); } void KateCompletionWidget::registerCompletionModel(KTextEditor::CodeCompletionModel *model) { if (m_sourceModels.contains(model)) { return; } connect(model, SIGNAL(destroyed(QObject*)), SLOT(modelDestroyed(QObject*))); //This connection must not be queued connect(model, SIGNAL(modelReset()), SLOT(completionModelReset())); m_sourceModels.append(model); if (isCompletionActive()) { m_presentationModel->addCompletionModel(model); } } void KateCompletionWidget::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) { disconnect(model, SIGNAL(destroyed(QObject*)), this, SLOT(modelDestroyed(QObject*))); disconnect(model, SIGNAL(modelReset()), this, SLOT(completionModelReset())); m_sourceModels.removeAll(model); abortCompletion(); } bool KateCompletionWidget::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const { return m_sourceModels.contains(model); } int KateCompletionWidget::automaticInvocationDelay() const { return m_automaticInvocationDelay; } void KateCompletionWidget::setAutomaticInvocationDelay(int delay) { m_automaticInvocationDelay = delay; } void KateCompletionWidget::wrapLine(const KTextEditor::Cursor &) { m_lastInsertionByUser = !m_completionEditRunning; // wrap line, be done m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); } void KateCompletionWidget::unwrapLine(int) { m_lastInsertionByUser = !m_completionEditRunning; // just removal m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); } void KateCompletionWidget::insertText(const KTextEditor::Cursor &position, const QString &text) { m_lastInsertionByUser = !m_completionEditRunning; // no invoke? if (!view()->isAutomaticInvocationEnabled()) { m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); return; } if (m_automaticInvocationAt != position) { m_automaticInvocationLine.clear(); m_lastInsertionByUser = !m_completionEditRunning; } m_automaticInvocationLine += text; m_automaticInvocationAt = position; m_automaticInvocationAt.setColumn(position.column() + text.length()); if (m_automaticInvocationLine.isEmpty()) { m_automaticInvocationTimer->stop(); return; } m_automaticInvocationTimer->start(m_automaticInvocationDelay); } void KateCompletionWidget::removeText(const KTextEditor::Range &) { m_lastInsertionByUser = !m_completionEditRunning; // just removal m_automaticInvocationLine.clear(); m_automaticInvocationTimer->stop(); } void KateCompletionWidget::automaticInvocation() { //qCDebug(LOG_KTE)<<"m_automaticInvocationAt:"<cursorPosition(); if (m_automaticInvocationAt != view()->cursorPosition()) { return; } bool start = false; QList models; //qCDebug(LOG_KTE)<<"checking models"; foreach (KTextEditor::CodeCompletionModel *model, m_sourceModels) { //qCDebug(LOG_KTE)<<"m_completionRanges contains model?:"<cursorPosition()); //qCDebug(LOG_KTE)<<"start="<commonPrefix((m_inCompletionList && !shellLikeTabCompletion) ? m_entryList->currentIndex() : QModelIndex()); if (!prefix.isEmpty()) { view()->insertText(prefix); } else if (shellLikeTabCompletion) { cursorDown(); return; } } else { if (shellLikeTabCompletion) { cursorUp(); return; } //Reset left boundaries, so completion isn't stopped typedef QMap CompletionRangeMap; for (CompletionRangeMap::iterator it = m_completionRanges.begin(); it != m_completionRanges.end(); ++it) { (*it).leftBoundary = (*it).range->start(); } //Remove suffix until the completion-list filter is widened again uint itemCount = m_presentationModel->filteredItemCount(); while (view()->cursorPosition().column() > 0 && m_presentationModel->filteredItemCount() == itemCount) { KTextEditor::Range lastcharRange = KTextEditor::Range(view()->cursorPosition() - KTextEditor::Cursor(0, 1), view()->cursorPosition()); QString cursorText = view()->document()->text(lastcharRange); if (!cursorText[0].isSpace()) { view()->document()->removeText(lastcharRange); QApplication::sendPostedEvents(); } else { break; } } } } diff --git a/src/completion/katecompletionwidget.h b/src/completion/katecompletionwidget.h index d2b10565..3df2e773 100644 --- a/src/completion/katecompletionwidget.h +++ b/src/completion/katecompletionwidget.h @@ -1,247 +1,247 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005-2006 Hamish Rodda * Copyright (C) 2007-2008 David Nolden * * 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 KATECOMPLETIONWIDGET_H #define KATECOMPLETIONWIDGET_H #include #include #include #include #include #include class QToolButton; class QPushButton; class QLabel; class QTimer; namespace KTextEditor { class ViewPrivate; } class KateCompletionModel; class KateCompletionTree; class KateArgumentHintTree; class KateArgumentHintModel; namespace KTextEditor { class EmbeddedWidgetInterface; } /** * This is the code completion's main widget, and also contains the * core interface logic. * * @author Hamish Rodda */ class KTEXTEDITOR_EXPORT KateCompletionWidget : public QFrame { Q_OBJECT public: explicit KateCompletionWidget(KTextEditor::ViewPrivate *parent); ~KateCompletionWidget(); KTextEditor::ViewPrivate *view() const; KateCompletionTree *treeView() const; bool isCompletionActive() const; void startCompletion(KTextEditor::CodeCompletionModel::InvocationType invocationType, const QList &models = QList()); void startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model, KTextEditor::CodeCompletionModel::InvocationType invocationType = KTextEditor::CodeCompletionModel::ManualInvocation); void startCompletion(const KTextEditor::Range &word, const QList &models = QList(), KTextEditor::CodeCompletionModel::InvocationType invocationType = KTextEditor::CodeCompletionModel::ManualInvocation); void userInvokedCompletion(); public Q_SLOTS: //Executed when return is pressed while completion is active. void execute(); void cursorDown(); void cursorUp(); public: void tab(bool shift); ///Returns whether the current item was expanded/unexpanded bool toggleExpanded(bool forceExpand = false, bool forceUnExpand = false); const KateCompletionModel *model() const; KateCompletionModel *model(); void registerCompletionModel(KTextEditor::CodeCompletionModel *model); void unregisterCompletionModel(KTextEditor::CodeCompletionModel *model); bool isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const; int automaticInvocationDelay() const; void setAutomaticInvocationDelay(int delay); struct CompletionRange { - CompletionRange() : range(0) + CompletionRange() : range(nullptr) { } explicit CompletionRange(KTextEditor::MovingRange *r) : range(r) { } bool operator==(const CompletionRange &rhs) const { return range->toRange() == rhs.range->toRange(); } KTextEditor::MovingRange *range; //Whenever the cursor goes before this position, the completion is stopped, unless it is invalid. KTextEditor::Cursor leftBoundary; }; - KTextEditor::MovingRange *completionRange(KTextEditor::CodeCompletionModel *model = 0) const; + KTextEditor::MovingRange *completionRange(KTextEditor::CodeCompletionModel *model = nullptr) const; QMap completionRanges() const; // Navigation void pageDown(); void pageUp(); void top(); void bottom(); QWidget *currentEmbeddedWidget(); bool canExpandCurrentItem() const; bool canCollapseCurrentItem() const; void setCurrentItemExpanded(bool); //Returns true if a screen border has been hit bool updatePosition(bool force = false); bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; KateArgumentHintTree *argumentHintTree() const; KateArgumentHintModel *argumentHintModel() const; ///Called by KateViewInternal, because we need the specific information from the event. void updateHeight(); public Q_SLOTS: void waitForModelReset(); void abortCompletion(); void showConfig(); /* void viewFocusIn(); void viewFocusOut();*/ void updatePositionSlot(); void automaticInvocation(); /* void updateFocus();*/ void argumentHintsChanged(bool hasContent); bool navigateUp(); bool navigateDown(); bool navigateLeft(); bool navigateRight(); bool navigateAccept(); bool navigateBack(); bool hadNavigation() const; void resetHadNavigation(); protected: void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; void focusOutEvent(QFocusEvent * event) Q_DECL_OVERRIDE; private Q_SLOTS: void completionModelReset(); void modelDestroyed(QObject *model); void modelContentChanged(); void cursorPositionChanged(); void modelReset(); void rowsInserted(const QModelIndex &parent, int row, int rowEnd); void viewFocusOut(); void wrapLine(const KTextEditor::Cursor &position); void unwrapLine(int line); void insertText(const KTextEditor::Cursor &position, const QString &text); void removeText(const KTextEditor::Range &range); private: void updateAndShow(); void updateArgumentHintGeometry(); QModelIndex selectedIndex() const; void clear(); //Switch cursor between argument-hint list / completion-list void switchList(); KTextEditor::Range determineRange() const; void completionRangeChanged(KTextEditor::CodeCompletionModel *, const KTextEditor::Range &word); void deleteCompletionRanges(); QList m_sourceModels; KateCompletionModel *m_presentationModel; QMap m_completionRanges; QSet m_waitingForReset; KTextEditor::Cursor m_lastCursorPosition; KateCompletionTree *m_entryList; KateArgumentHintModel *m_argumentHintModel; KateArgumentHintTree *m_argumentHintTree; QTimer *m_automaticInvocationTimer; //QTimer* m_updateFocusTimer; QWidget *m_statusBar; QToolButton *m_sortButton; QLabel *m_sortText; QToolButton *m_filterButton; QLabel *m_filterText; QPushButton *m_configButton; KTextEditor::Cursor m_automaticInvocationAt; QString m_automaticInvocationLine; int m_automaticInvocationDelay; bool m_filterInstalled; class KateCompletionConfig *m_configWidget; bool m_lastInsertionByUser; bool m_inCompletionList; //Are we in the completion-list? If not, we're in the argument-hint list bool m_isSuspended; bool m_dontShowArgumentHints; //Used temporarily to prevent flashing bool m_needShow; bool m_hadCompletionNavigation; bool m_haveExactMatch; bool m_noAutoHide; /** * is a completion edit ongoing? */ bool m_completionEditRunning; int m_expandedAddedHeightBase; KTextEditor::CodeCompletionModel::InvocationType m_lastInvocationType; }; #endif diff --git a/src/completion/katewordcompletion.cpp b/src/completion/katewordcompletion.cpp index 0d845337..51cb7c89 100644 --- a/src/completion/katewordcompletion.cpp +++ b/src/completion/katewordcompletion.cpp @@ -1,583 +1,583 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003 Anders Lund * 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. */ //BEGIN includes #include "katewordcompletion.h" #include "kateview.h" #include "kateconfig.h" #include "katedocument.h" #include "kateglobal.h" #include "katepartdebug.h" #include "katedefaultcolors.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //END /// Amount of characters the document may have to enable automatic invocation (1MB) static const int autoInvocationMaxFilesize = 1000000; //BEGIN KateWordCompletionModel KateWordCompletionModel::KateWordCompletionModel(QObject *parent) : CodeCompletionModel (parent) , m_automatic(false) { setHasGroups(false); } KateWordCompletionModel::~KateWordCompletionModel() { } void KateWordCompletionModel::saveMatches(KTextEditor::View *view, const KTextEditor::Range &range) { m_matches = allMatches(view, range); m_matches.sort(); } QVariant KateWordCompletionModel::data(const QModelIndex &index, int role) const { if (role == UnimportantItemRole) { return QVariant(true); } if (role == InheritanceDepth) { return 10000; } if (!index.parent().isValid()) { //It is the group header switch (role) { case Qt::DisplayRole: return i18n("Auto Word Completion"); case GroupRole: return Qt::DisplayRole; } } if (index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole) { return m_matches.at(index.row()); } if (index.column() == KTextEditor::CodeCompletionModel::Icon && role == Qt::DecorationRole) { static QIcon icon(QIcon::fromTheme(QStringLiteral("insert-text")).pixmap(QSize(16, 16))); return icon; } return QVariant(); } QModelIndex KateWordCompletionModel::parent(const QModelIndex &index) const { if (index.internalId()) { return createIndex(0, 0, quintptr(0)); } else { return QModelIndex(); } } QModelIndex KateWordCompletionModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { if (row == 0) { return createIndex(row, column, quintptr(0)); } else { return QModelIndex(); } } else if (parent.parent().isValid()) { return QModelIndex(); } if (row < 0 || row >= m_matches.count() || column < 0 || column >= ColumnCount) { return QModelIndex(); } return createIndex(row, column, 1); } int KateWordCompletionModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid() && !m_matches.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_matches.count(); } } bool KateWordCompletionModel::shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) { if (!userInsertion) { return false; } if (insertedText.isEmpty()) { return false; } KTextEditor::ViewPrivate *v = qobject_cast (view); if (view->document()->totalCharacters() > autoInvocationMaxFilesize) { // Disable automatic invocation for files larger than 1MB (see benchmarks) return false; } const QString &text = view->document()->line(position.line()).left(position.column()); const uint check = v->config()->wordCompletionMinimalWordLength(); // Start completion immediately if min. word size is zero if (!check) { return true; } // Otherwise, check if user has typed long enough text... const int start = text.length(); const int end = start - check; if (end < 0) { return false; } for (int i = start - 1; i >= end; i--) { const QChar c = text.at(i); if (!(c.isLetter() || (c.isNumber()) || c == QLatin1Char('_'))) { return false; } } return true; } bool KateWordCompletionModel::shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) { if (m_automatic) { KTextEditor::ViewPrivate *v = qobject_cast (view); if (currentCompletion.length() < v->config()->wordCompletionMinimalWordLength()) { return true; } } return CodeCompletionModelControllerInterface::shouldAbortCompletion(view, range, currentCompletion); } void KateWordCompletionModel::completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType it) { m_automatic = it == AutomaticInvocation; saveMatches(view, range); } /** * Scan throughout the entire document for possible completions, * ignoring any dublets and words shorter than configured and/or * reasonable minimum length. */ QStringList KateWordCompletionModel::allMatches(KTextEditor::View *view, const KTextEditor::Range &range) const { QSet result; const int minWordSize = qMax(2, qobject_cast(view)->config()->wordCompletionMinimalWordLength()); const int lines = view->document()->lines(); for (int line = 0; line < lines; line++) { const QString &text = view->document()->line(line); int wordBegin = 0; int offset = 0; const int end = text.size(); const bool cursorLine = view->cursorPosition().line() == line; while (offset < end) { const QChar c = text.at(offset); // increment offset when at line end, so we take the last character too if ((! c.isLetterOrNumber() && c != QLatin1Char('_')) || (offset == end - 1 && offset++)) { if (offset - wordBegin > minWordSize && (line != range.end().line() || offset != range.end().column())) { /** * don't add the word we are inside with cursor! */ if (!cursorLine || (view->cursorPosition().column() < wordBegin || view->cursorPosition().column() > offset)) { result.insert(text.mid(wordBegin, offset - wordBegin)); } } wordBegin = offset + 1; } if (c.isSpace()) { wordBegin = offset + 1; } offset += 1; } } return result.values(); } void KateWordCompletionModel::executeCompletionItem (KTextEditor::View *view , const KTextEditor::Range &word , const QModelIndex &index ) const { KTextEditor::ViewPrivate *v = qobject_cast (view); if (v->config()->wordCompletionRemoveTail()) { int tailStart = word.end().column(); const QString &line = view->document()->line(word.end().line()); int tailEnd = line.length(); for (int i = word.end().column(); i < tailEnd; ++i) { // Letters, numbers and underscore are part of a word! /// \todo Introduce configurable \e word-separators?? if (!line[i].isLetterOrNumber() && line[i] != QLatin1Char('_')) { tailEnd = i; } } int sizeDiff = m_matches.at(index.row()).size() - (word.end().column() - word.start().column()); tailStart += sizeDiff; tailEnd += sizeDiff; KTextEditor::Range tail(KTextEditor::Cursor(word.start().line(), tailStart), KTextEditor::Cursor(word.end().line(), tailEnd)); view->document()->replaceText(word, m_matches.at(index.row())); v->doc()->editEnd(); v->doc()->editStart(); view->document()->replaceText(tail, QString()); } else { view->document()->replaceText(word, m_matches.at(index.row())); } } KTextEditor::CodeCompletionModelControllerInterface::MatchReaction KateWordCompletionModel::matchingItem(const QModelIndex & /*matched*/) { return HideListIfAutomaticInvocation; } bool KateWordCompletionModel::shouldHideItemsWithEqualNames() const { // We don't want word-completion items if the same items // are available through more sophisticated completion models return true; } // Return the range containing the word left of the cursor KTextEditor::Range KateWordCompletionModel::completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position) { int line = position.line(); int col = position.column(); KTextEditor::Document *doc = view->document(); while (col > 0) { const QChar c = (doc->characterAt(KTextEditor::Cursor(line, col - 1))); if (c.isLetterOrNumber() || c.isMark() || c == QLatin1Char('_')) { col--; continue; } break; } return KTextEditor::Range(KTextEditor::Cursor(line, col), position); } //END KateWordCompletionModel //BEGIN KateWordCompletionView struct KateWordCompletionViewPrivate { KTextEditor::MovingRange *liRange; // range containing last inserted text KTextEditor::Range dcRange; // current range to be completed by directional completion KTextEditor::Cursor dcCursor; // directional completion search cursor QRegExp re; // hrm int directionalPos; // be able to insert "" at the correct time bool isCompleting; // true when the directional completion is doing a completion }; KateWordCompletionView::KateWordCompletionView(KTextEditor::View *view, KActionCollection *ac) : QObject(view), m_view(view), m_dWCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()), d(new KateWordCompletionViewPrivate) { d->isCompleting = false; d->dcRange = KTextEditor::Range::invalid(); d->liRange = static_cast(m_view->document())->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand); const KColorScheme &colors(KTextEditor::EditorPrivate::self()->defaultColors().view()); KTextEditor::Attribute::Ptr a = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); a->setBackground(colors.background(KColorScheme::ActiveBackground)); a->setForeground(colors.foreground(KColorScheme::ActiveText)); // ### this does 0 d->liRange->setAttribute(a); QAction *action; if (qobject_cast(view)) { action = new QAction(i18n("Shell Completion"), this); ac->addAction(QStringLiteral("doccomplete_sh"), action); connect(action, SIGNAL(triggered()), this, SLOT(shellComplete())); } action = new QAction(i18n("Reuse Word Above"), this); ac->addAction(QStringLiteral("doccomplete_bw"), action); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_8); connect(action, SIGNAL(triggered()), this, SLOT(completeBackwards())); action = new QAction(i18n("Reuse Word Below"), this); ac->addAction(QStringLiteral("doccomplete_fw"), action); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_9); connect(action, SIGNAL(triggered()), this, SLOT(completeForwards())); } KateWordCompletionView::~KateWordCompletionView() { delete d; } void KateWordCompletionView::completeBackwards() { complete(false); } void KateWordCompletionView::completeForwards() { complete(); } // Pop up the editors completion list if applicable void KateWordCompletionView::popupCompletionList() { qCDebug(LOG_KTE) << "entered ..."; KTextEditor::Range r = range(); KTextEditor::CodeCompletionInterface *cci = qobject_cast(m_view); if (!cci || cci->isCompletionActive()) { return; } m_dWCompletionModel->saveMatches(m_view, r); qCDebug(LOG_KTE) << "after save matches ..."; if (! m_dWCompletionModel->rowCount(QModelIndex())) { return; } cci->startCompletion(r, m_dWCompletionModel); } // Contributed by void KateWordCompletionView::shellComplete() { KTextEditor::Range r = range(); QStringList matches = m_dWCompletionModel->allMatches(m_view, r); if (matches.size() == 0) { return; } QString partial = findLongestUnique(matches, r.columnWidth()); if (partial.isEmpty()) { popupCompletionList(); } else { m_view->document()->insertText(r.end(), partial.mid(r.columnWidth())); d->liRange->setView(m_view); d->liRange->setRange(KTextEditor::Range(r.end(), partial.length() - r.columnWidth())); connect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(slotCursorMoved())); } } // Do one completion, searching in the desired direction, // if possible void KateWordCompletionView::complete(bool fw) { KTextEditor::Range r = range(); int inc = fw ? 1 : -1; KTextEditor::Document *doc = m_view->document(); if (d->dcRange.isValid()) { //qCDebug(LOG_KTE)<<"CONTINUE "<dcRange; // this is a repeted activation // if we are back to where we started, reset. if ((fw && d->directionalPos == -1) || (!fw && d->directionalPos == 1)) { const int spansColumns = d->liRange->end().column() - d->liRange->start().column(); if (spansColumns > 0) { doc->removeText(*d->liRange); } d->liRange->setRange(KTextEditor::Range::invalid()); d->dcCursor = r.end(); d->directionalPos = 0; return; } if (fw) { const int spansColumns = d->liRange->end().column() - d->liRange->start().column(); d->dcCursor.setColumn(d->dcCursor.column() + spansColumns); } d->directionalPos += inc; } else { // new completion, reset all //qCDebug(LOG_KTE)<<"RESET FOR NEW"; d->dcRange = r; d->liRange->setRange(KTextEditor::Range::invalid()); d->dcCursor = r.start(); d->directionalPos = inc; d->liRange->setView(m_view); connect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(slotCursorMoved())); } d->re.setPattern(QLatin1String("\\b") + doc->text(d->dcRange) + QLatin1String("(\\w+)")); int pos(0); QString ln = doc->line(d->dcCursor.line()); while (true) { //qCDebug(LOG_KTE)<<"SEARCHING FOR "<re.pattern()<<" "<dcCursor; pos = fw ? d->re.indexIn(ln, d->dcCursor.column()) : d->re.lastIndexIn(ln, d->dcCursor.column()); if (pos > -1) { // we matched a word //qCDebug(LOG_KTE)<<"USABLE MATCH"; QString m = d->re.cap(1); if (m != doc->text(*d->liRange) && (d->dcCursor.line() != d->dcRange.start().line() || pos != d->dcRange.start().column())) { // we got good a match! replace text and return. d->isCompleting = true; KTextEditor::Range replaceRange(d->liRange->toRange()); if (!replaceRange.isValid()) { replaceRange.setRange(r.end(), r.end()); } doc->replaceText(replaceRange, m); d->liRange->setRange(KTextEditor::Range(d->dcRange.end(), m.length())); d->dcCursor.setColumn(pos); // for next try d->isCompleting = false; return; } // equal to last one, continue else { //qCDebug(LOG_KTE)<<"SKIPPING, EQUAL MATCH"; d->dcCursor.setColumn(pos); // for next try if (fw) { d->dcCursor.setColumn(pos + m.length()); } else { if (pos == 0) { if (d->dcCursor.line() > 0) { int l = d->dcCursor.line() + inc; ln = doc->line(l); d->dcCursor.setPosition(l, ln.length()); } else { return; } } else { d->dcCursor.setColumn(d->dcCursor.column() - 1); } } } } else { // no match //qCDebug(LOG_KTE)<<"NO MATCH"; if ((! fw && d->dcCursor.line() == 0) || (fw && d->dcCursor.line() >= doc->lines())) { return; } int l = d->dcCursor.line() + inc; ln = doc->line(l); d->dcCursor.setPosition(l, fw ? 0 : ln.length()); } } // while true } void KateWordCompletionView::slotCursorMoved() { if (d->isCompleting) { return; } d->dcRange = KTextEditor::Range::invalid(); disconnect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(slotCursorMoved())); - d->liRange->setView(0); + d->liRange->setView(nullptr); d->liRange->setRange(KTextEditor::Range::invalid()); } // Contributed by FIXME QString KateWordCompletionView::findLongestUnique(const QStringList &matches, int lead) const { QString partial = matches.first(); foreach (const QString ¤t, matches) { if (!current.startsWith(partial)) { while (partial.length() > lead) { partial.remove(partial.length() - 1, 1); if (current.startsWith(partial)) { break; } } if (partial.length() == lead) { return QString(); } } } return partial; } // Return the string to complete (the letters behind the cursor) QString KateWordCompletionView::word() const { return m_view->document()->text(range()); } // Return the range containing the word behind the cursor KTextEditor::Range KateWordCompletionView::range() const { return m_dWCompletionModel->completionRange(m_view, m_view->cursorPosition()); } //END diff --git a/src/data/katepart5ui.rc b/src/data/katepart5ui.rc index 0659d3bb..f0f68a65 100644 --- a/src/data/katepart5ui.rc +++ b/src/data/katepart5ui.rc @@ -1,165 +1,165 @@ - - + + &File &Edit Find Variants Go To &View Word Wrap Borders &Code Folding &Tools Word Completion Spelling &Settings Main Toolbar - + diff --git a/src/data/ktexteditor.desktop b/src/data/ktexteditor.desktop index b7d28ffb..830effb3 100644 --- a/src/data/ktexteditor.desktop +++ b/src/data/ktexteditor.desktop @@ -1,60 +1,60 @@ [Desktop Entry] Type=ServiceType X-KDE-ServiceType=KTextEditor/Document X-KDE-Derived=KParts/ReadWritePart Comment=Embeddable Text Editor Component (with Doc/View Separation) Comment[ar]=مكوّن محرّر نصوص يمكن تضمينه (يفصل بين Doc والمناظير) -Comment[ast]=Componente d'editor de testu empotrable (con separtación de documentos/vista) +Comment[ast]=Componente d'editor empotrable de testu (con separtación de documentos/vista) Comment[bg]=Текстов редактор (с разделение между Doc/View) Comment[bs]=Ugradiva komponenta uređivača teksta (s razdvajanjem dokumenta i pogleda) Comment[ca]=Component incrustable de l'editor de text (amb separació Doc/Vista) Comment[ca@valencia]=Component incrustable de l'editor de text (amb separació Doc/Vista) Comment[cs]=Pohltitelná komponenta textového editoru (s oddělením Doc/View) Comment[da]=Teksteditorkomponent som kan indlejres (med dok./visning-adskillelse) Comment[de]=Einbettungsfähige Editorkomponente (mit Text/Ansicht-Aufteilung) Comment[el]=Ενσωματώσιμο συστατικό επεξεργαστή κειμένου (με διαχωρισμό των δεδομένων από την προβολή τους) Comment[en_GB]=Embeddable Text Editor Component (with Doc/View Separation) Comment[es]=Componente empotrable de edición de texto (con separación documentos/vista) Comment[et]=Põimitav tekstiredaktori komponent (dokumendi/vaate eraldamisega) Comment[eu]=Testu-editore osagai kapsulagarria (dokumentu/ikuspegi bereizlearekin) Comment[fi]=Upotettava tekstimuokkauskomponentti (Tiedosto/Näkymä-jaolla) Comment[fr]=Composant intégrable d'édition de texte (avec séparation Doc / Vue) Comment[ga]=Comhpháirt eagarthóireacht téacs inleabaithe (le deighilt idir cáipéis agus amharc) Comment[gd]=Co-phàirt deasaiche teacsa a ghabhas leabachadh (le sgaradh eadar modh deasachaidh is seallaidh) Comment[gl]=Compoñente integrábel de edición de texto (cunha Separación Doc/Vista) Comment[hu]=Beágyazható szövegszerkesztő (dokumentum/nézet modellel) Comment[ia]=Componente del editor interne de texto (con separation Doc/Vista) Comment[is]=Ívafin textaritilseining (með skjal/sýn aðskilnaði) Comment[it]=Componente editor di testo integrabile (con separazione documento/vista) Comment[kk]=Ендірілетін мәтін өңдеу компоненті (Құжат/көрініс үлгіні қолдайтын) Comment[km]=សមាសភាគ​កម្មវិធី​និពន្ធ​អត្ថបទ​ដែល​អាច​បង្កប់​បាន​​ (ជា​មួយ​ការ​បំបែក Doc/View ) Comment[ko]=끼워넣을 수 있는 텍스트 편집기 구성 요소 (문서/뷰 구분) Comment[lt]=Įtraukiamas tekstų redagavimo komponentas (su dokumento/žiūrėjimo atskyrimu) Comment[lv]=Iegultā teksta redaktora komponente (ar redaktora/skatītāja atdalīšanu) Comment[mr]=अंतर्भूतयोग्य पाठ्य संपादक घटक (Doc/View विभाजनासकट) Comment[nb]=Innebygget skriveprogram-komponent (med Doc/View-skille) Comment[nds]=Inbettbor Texteditor-Komponent (mit Dokment-/Ansicht-Trennen) Comment[nl]=Ingebed tekstinvoercomponent (met scheiding van tekst/weergave) Comment[nn]=Innebyggbar skriveprogramkomponent (med Doc/View-skilje) Comment[pa]=ਇੰਬੈੱਡਯੋਗ ਟੈਕਸਟ ਐਡੀਟਰ ਭਾਗ (Doc/ਝਲਕ ਵੱਖ ਕਰਨ ਨਾਲ) Comment[pl]=Komponent osadzanego edytora tekstu (z podziałem Dokument/Widok) Comment[pt]=Componente Incorporado do Editor de Texto (com Separação entre Documentos) Comment[pt_BR]=Componente de edição de textos integrado (com separação de documentação/visualização) Comment[ro]=Componentă de Editare Text Încorporabilă (cu Separarea Documentului/Vizualizării) Comment[ru]=Встраиваемый компонент редактора текста (с поддержкой модели документ/вид) Comment[si]=තිළැලිය හැකි පෙළ සකසන සංරචකය (ලේඛන/දසුන් වෙන්කිරීම සහිතව) Comment[sk]=Vložiteľný komponent textového editora (s oddelením Doc/Pohľad) Comment[sl]=Vgradljiva enota urejevalnika besedil (z ločevanjem pogleda in dokumenta) Comment[sr]=Угнездива компонента уређивача текста (уз раздвајање документ-приказ) Comment[sr@ijekavian]=Угњездива компонента уређивача текста (уз раздвајање документ-приказ) Comment[sr@ijekavianlatin]=Ugnjezdiva komponenta uređivača teksta (uz razdvajanje dokument-prikaz) Comment[sr@latin]=Ugnezdiva komponenta uređivača teksta (uz razdvajanje dokument-prikaz) Comment[sv]=Inbäddningsbar texteditor (med dok/vyseparation) Comment[tg]=Қисми таҳриргари матнии дарунсохтшаванда (бо тақсимкунии Санад/Намоиш) Comment[tr]=Gömülebilir Metin Düzenleyici Bileşeni (Doc/View ayrımı ile) Comment[ug]=سىڭدۈرۈشچان تېكىست تەھرىرلىگۈچ بۆلىكى(پۈتۈك/كۆرۈنۈش ئايرىلىدۇ) Comment[uk]=Компонент редактора текстів, який можна вбудовувати (з розділенням документ/вигляд) Comment[wa]=Ravalé compôzant aspougneu d' tecse (avou dispårtaedje documint/vuwe) Comment[x-test]=xxEmbeddable Text Editor Component (with Doc/View Separation)xx Comment[zh_CN]=可嵌入的文本编辑器部件(文档和视图分离) Comment[zh_TW]=可嵌入的文字編輯器元件 (Doc/View 分開) diff --git a/src/data/ktexteditorplugin.desktop b/src/data/ktexteditorplugin.desktop index a0d65a05..6ed7531d 100644 --- a/src/data/ktexteditorplugin.desktop +++ b/src/data/ktexteditorplugin.desktop @@ -1,61 +1,61 @@ [Desktop Entry] Type=ServiceType X-KDE-ServiceType=KTextEditor/Plugin X-KDE-Derived= Comment=KTextEditor Plugin -Comment[ar]=ملحقة محرّر نصوصك +Comment[ar]=ملحقة «محرّر نصوصك» Comment[ast]=Complementu de KTextEditor Comment[bg]=Приставка за KTextEditor Comment[bs]=Priključak za KTextEditor Comment[ca]=Connector del KTextEditor Comment[ca@valencia]=Connector del KTextEditor Comment[cs]=Modul textového editoru Comment[da]=KTextEditor-plugin Comment[de]=KTextEditor-Modul Comment[el]=Πρόσθετο του KTextEditor Comment[en_GB]=KTextEditor Plugin Comment[es]=Complemento KTextEditor Comment[et]=KTextEditori plugin Comment[eu]=KTextEditor plugina Comment[fi]=KTextEditor-liitännäinen Comment[fr]=Module externe KTextEditor Comment[ga]=Breiseán KTextEditor Comment[gd]=Plugan KTextEditor -Comment[gl]=Engadido de KTextEditor +Comment[gl]=Complemento de KTextEditor Comment[hu]=KTextEditor-bővítmény Comment[ia]=KTextEditor Plugin Comment[is]=KTextEditor-viðbót Comment[it]=Estensione KTextEditor Comment[ja]=KTextEditor プラグイン Comment[kk]=KTextEditor плагині Comment[km]=កម្មវិធី​ជំនួយ​របស់ KTextEditor Comment[ko]=KTextEditor 플러그인 Comment[lt]=KTextEditor papildinys Comment[lv]=KTextEditor spraudnis Comment[mr]=के-टेक्स्ट-एडिटर प्लगइन Comment[nb]=Programtillegget KTextEditor Comment[nds]=KTextEditor-Moduul Comment[nl]=KTextEditor-plug-in Comment[nn]=KTextEditor-tillegg Comment[pa]=KTextEditor ਪਲੱਗਇਨ Comment[pl]=Wtyczka edytora tekstu Comment[pt]='Plugin' do KTextEditor Comment[pt_BR]=Plugin do KTextEditor Comment[ro]=Modul editor de text Comment[ru]=Расширение KTextEditor Comment[si]=Kපෙළසැකසීම ප්ලගිනය Comment[sk]=Plugin KTextEditor Comment[sl]=Vstavek za KTextEditor Comment[sr]=Прикључак за KTextEditor Comment[sr@ijekavian]=Прикључак за KTextEditor Comment[sr@ijekavianlatin]=Priključak za KTextEditor Comment[sr@latin]=Priključak za KTextEditor Comment[sv]=Insticksprogram för texteditor Comment[tg]=Плагини KTextEditor Comment[tr]=KTextEditor Eklentisi Comment[ug]=KTextEditor قىستۇرما Comment[uk]=Додаток KTextEditor Comment[wa]=Tchôke-divins KTextEditor Comment[x-test]=xxKTextEditor Pluginxx Comment[zh_CN]=KTextEditor 插件 Comment[zh_TW]=KTextEditor 外掛程式 diff --git a/src/dialogs/bordersappearanceconfigwidget.ui b/src/dialogs/bordersappearanceconfigwidget.ui index d46af095..f4ad4c2a 100644 --- a/src/dialogs/bordersappearanceconfigwidget.ui +++ b/src/dialogs/bordersappearanceconfigwidget.ui @@ -1,315 +1,322 @@ BordersAppearanceConfigWidget 0 0 375 522 Borders If this option is checked, every new view will display marks for folding. Show &folding markers Qt::Horizontal QSizePolicy::Fixed 16 16 If checked, hovering over a folded region shows a preview of the folded text in a popup. Show preview of folded code <p>If this option is checked, every new view will display an icon border on the left hand side.</p><p>The icon border shows bookmark signs, for instance.</p> Show &icon border If this option is checked, every new view will display line numbers on the left hand side. Show &line numbers If this option is checked, a small indicator for modified and saved lines is shown on the left hand side. Show line modification markers <p>If this option is checked, every new view will show marks on the vertical scrollbar.</p><p>These marks will show bookmarks, for instance.</p> Show &scrollbar marks <p>If this option is checked, hovering over the vertical scrollbar will show a preview of the text.</p> Show text &preview on scrollbar If this option is checked, every new view will show a mini map on the vertical scrollbar. Show scrollbar mini-map false false If this option is checked, every new view will show a mini map of the whole document on the vertical scrollbar. Map the whole document true - Minim&ap Width + Minim&ap width: spBoxMiniMapWidth Qt::Horizontal QSizePolicy::Fixed 16 16 Qt::Horizontal 40 20 40 100 5 60 Scro&llbars visibility: cmbShowScrollbars Always On Show When Needed Always Off + + + + Qt::Horizontal + + + Choose how the bookmarks should be ordered in the <b>Bookmarks</b> menu. Sort Bookmarks Menu Each new bookmark will be added to the bottom, independently from where it is placed in the document. B&y creation The bookmarks will be ordered by the line numbers they are placed at. By posi&tion Qt::Vertical 20 31 chkShowFoldingMarkers chkIconBorder chkLineNumbers chkShowLineModification chkScrollbarMarks chkScrollbarMiniMap chkScrollbarMiniMapAll spBoxMiniMapWidth cmbShowScrollbars rbSortBookmarksByCreation rbSortBookmarksByPosition chkScrollbarMiniMap toggled(bool) miniMapConfigs setEnabled(bool) 76 295 37 347 diff --git a/src/dialogs/editconfigwidget.ui b/src/dialogs/editconfigwidget.ui index aa95d093..44eedda4 100644 --- a/src/dialogs/editconfigwidget.ui +++ b/src/dialogs/editconfigwidget.ui @@ -1,186 +1,189 @@ EditConfigWidget 0 0 275 344 + + 0 + Static Word Wrap <p>Automatically start a new line of text when the current line exceeds the length specified by the <b>Wrap words at:</b> option.</p><p>This option does not wrap existing lines of text - use the <b>Apply Static Word Wrap</b> option in the <b>Tools</b> menu for that purpose.</p><p>If you want lines to be <i>visually wrapped</i> instead, according to the width of the view, enable <b>Dynamic Word Wrap</b> in the <b>Appearance</b> config page.</p> Enable static &word wrap <p>If this option is checked, a vertical line will be drawn at the word wrap column as defined in the <strong>Editing</strong> properties.</p><p>Note that the word wrap marker is only drawn if you use a fixed pitch font.</p> Show static word wra&p marker (if applicable) 0 0 0 0 Wra&p words at: sbWordWrap If the Word Wrap option is selected this entry determines the length (in characters) at which the editor will automatically start a new line. 20 200 76 Qt::Horizontal 1 0 Input Mode - Default input mode + Default input mode: Qt::Horizontal 40 20 Auto Brackets Enable automatic brackets Copy and Paste Copy/Cut the current line if no selection Qt::Vertical 0 1 KPluralHandlingSpinBox QSpinBox
kpluralhandlingspinbox.h
diff --git a/src/dialogs/filetypeconfigwidget.ui b/src/dialogs/filetypeconfigwidget.ui index 6a53f13a..3e52c18a 100644 --- a/src/dialogs/filetypeconfigwidget.ui +++ b/src/dialogs/filetypeconfigwidget.ui @@ -1,272 +1,272 @@ FileTypeConfigWidget 0 &Filetype: cmbFiletypes Select the filetype you want to change. Create a new file type. &New Delete the current file type. &Delete Qt::Horizontal 1 0 Properties &Name: edtName The name of the filetype will be the text of the corresponding menu item. &Section: edtSection The section name is used to organize the file types in menus. &Variables: edtVariables <p>This string allows to configure Kate's settings for the files selected by this mimetype using Kate variables. Almost any configuration option can be set, such as highlight, indent-mode, encoding, etc.</p><p>For a full list of known variables, see the manual.</p> &Highlighting: cmbHl - &Indentation Mode: + &Indentation mode: cmbIndenter File e&xtensions: edtFileExtensions The wildcards mask allows to select files by filename. A typical mask uses an asterisk and the file extension, for example <code>*.txt; *.text</code>. The string is a semicolon-separated list of masks. MIME &types: edtMimeTypes 0 The mime type mask allows to select files by mimetype. The string is a semicolon-separated list of mimetypes, for example <code>text/plain; text/english</code>. Displays a wizard that helps you easily select mimetypes. P&riority: sbPriority Sets priority for this file type. If more than one file type selects the same file, the one with the highest priority will be used. Qt::Horizontal 1 0 Download Highlighting Files... Qt::Vertical 0 1 KLineEdit QLineEdit
klineedit.h
KComboBox QComboBox
kcombobox.h
VariableLineEdit
variablelineedit.h
diff --git a/src/dialogs/kateconfigpage.h b/src/dialogs/kateconfigpage.h index 6a73e85c..17582550 100644 --- a/src/dialogs/kateconfigpage.h +++ b/src/dialogs/kateconfigpage.h @@ -1,49 +1,49 @@ /* This file is part of the KDE libraries * * 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_CONFIG_PAGE_H__ #define __KATE_CONFIG_PAGE_H__ #include class KateConfigPage : public KTextEditor::ConfigPage { Q_OBJECT public: - explicit KateConfigPage(QWidget *parent = 0, const char *name = 0); + explicit KateConfigPage(QWidget *parent = nullptr, const char *name = nullptr); virtual ~KateConfigPage(); virtual void reload() = 0; public: bool hasChanged() { return m_changed; } protected Q_SLOTS: void slotChanged(); private Q_SLOTS: void somethingHasChanged(); protected: bool m_changed; }; #endif diff --git a/src/dialogs/katedialogs.cpp b/src/dialogs/katedialogs.cpp index 30291791..6c7221b3 100644 --- a/src/dialogs/katedialogs.cpp +++ b/src/dialogs/katedialogs.cpp @@ -1,1459 +1,1459 @@ /* This file is part of the KDE libraries Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2003 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2006 Dominik Haumann Copyright (C) 2007 Mirko Stocker Copyright (C) 2009 Michel Ludwig Copyright (C) 2009 Erlend Hamberg Based on work of: 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 "katedialogs.h" #include #include #include "kateautoindent.h" #include "katebuffer.h" #include "kateconfig.h" #include "katedocument.h" #include "kateglobal.h" #include "kateschema.h" #include "katemodeconfigpage.h" #include "kateview.h" #include "spellcheck/spellcheck.h" #include "kateglobal.h" // auto generated ui files #include "ui_textareaappearanceconfigwidget.h" #include "ui_bordersappearanceconfigwidget.h" #include "ui_navigationconfigwidget.h" #include "ui_editconfigwidget.h" #include "ui_indentationconfigwidget.h" #include "ui_completionconfigtab.h" #include "ui_opensaveconfigwidget.h" #include "ui_opensaveconfigadvwidget.h" #include "ui_spellcheckconfigwidget.h" #include #include #include #include #include #include #include "katepartdebug.h" #include "kateabstractinputmodefactory.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 #include #include #include #include #include // trailing slash is important #define HLDOWNLOADPATH QStringLiteral("http://kate.kde.org/syntax/") //END //BEGIN KateIndentConfigTab KateIndentConfigTab::KateIndentConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::IndentationConfigWidget(); ui->setupUi(newWidget); ui->cmbMode->addItems(KateAutoIndent::listModes()); ui->label->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); connect(ui->label, SIGNAL(linkActivated(QString)), this, SLOT(showWhatsThis(QString))); // What's This? help can be found in the ui file reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->cmbMode, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->rbIndentWithTabs, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbIndentWithSpaces, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbIndentMixed, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbIndentWithTabs, SIGNAL(toggled(bool)), ui->sbIndentWidth, SLOT(setDisabled(bool))); connect(ui->chkKeepExtraSpaces, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkIndentPaste, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkBackspaceUnindents, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->sbTabWidth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->sbIndentWidth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->rbTabAdvances, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbTabIndents, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbTabSmart, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } KateIndentConfigTab::~KateIndentConfigTab() { delete ui; } void KateIndentConfigTab::slotChanged() { if (ui->rbIndentWithTabs->isChecked()) { ui->sbIndentWidth->setValue(ui->sbTabWidth->value()); } KateConfigPage::slotChanged(); } void KateIndentConfigTab::showWhatsThis(const QString &text) { QWhatsThis::showText(QCursor::pos(), text); } void KateIndentConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateDocumentConfig::global()->configStart(); KateDocumentConfig::global()->setKeepExtraSpaces(ui->chkKeepExtraSpaces->isChecked()); KateDocumentConfig::global()->setBackspaceIndents(ui->chkBackspaceUnindents->isChecked()); KateDocumentConfig::global()->setIndentPastedText(ui->chkIndentPaste->isChecked()); KateDocumentConfig::global()->setIndentationWidth(ui->sbIndentWidth->value()); KateDocumentConfig::global()->setIndentationMode(KateAutoIndent::modeName(ui->cmbMode->currentIndex())); KateDocumentConfig::global()->setTabWidth(ui->sbTabWidth->value()); KateDocumentConfig::global()->setReplaceTabsDyn(ui->rbIndentWithSpaces->isChecked()); if (ui->rbTabAdvances->isChecked()) { KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabInsertsTab); } else if (ui->rbTabIndents->isChecked()) { KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabIndents); } else { KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabSmart); } KateDocumentConfig::global()->configEnd(); } void KateIndentConfigTab::reload() { ui->sbTabWidth->setSuffix(ki18np(" character", " characters")); ui->sbTabWidth->setValue(KateDocumentConfig::global()->tabWidth()); ui->sbIndentWidth->setSuffix(ki18np(" character", " characters")); ui->sbIndentWidth->setValue(KateDocumentConfig::global()->indentationWidth()); ui->chkKeepExtraSpaces->setChecked(KateDocumentConfig::global()->keepExtraSpaces()); ui->chkIndentPaste->setChecked(KateDocumentConfig::global()->indentPastedText()); ui->chkBackspaceUnindents->setChecked(KateDocumentConfig::global()->backspaceIndents()); ui->rbTabAdvances->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabInsertsTab); ui->rbTabIndents->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabIndents); ui->rbTabSmart->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabSmart); ui->cmbMode->setCurrentIndex(KateAutoIndent::modeNumber(KateDocumentConfig::global()->indentationMode())); if (KateDocumentConfig::global()->replaceTabsDyn()) { ui->rbIndentWithSpaces->setChecked(true); } else { if (KateDocumentConfig::global()->indentationWidth() == KateDocumentConfig::global()->tabWidth()) { ui->rbIndentWithTabs->setChecked(true); } else { ui->rbIndentMixed->setChecked(true); } } ui->sbIndentWidth->setEnabled(!ui->rbIndentWithTabs->isChecked()); } QString KateIndentConfigTab::name() const { return i18n("Indentation"); } //END KateIndentConfigTab //BEGIN KateCompletionConfigTab KateCompletionConfigTab::KateCompletionConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::CompletionConfigTab(); ui->setupUi(newWidget); // What's This? help can be found in the ui file reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->chkAutoCompletionEnabled, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->gbWordCompletion, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->gbKeywordCompletion, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->minimalWordLength, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->removeTail, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } KateCompletionConfigTab::~KateCompletionConfigTab() { delete ui; } void KateCompletionConfigTab::showWhatsThis(const QString &text) { QWhatsThis::showText(QCursor::pos(), text); } void KateCompletionConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateViewConfig::global()->setAutomaticCompletionInvocation(ui->chkAutoCompletionEnabled->isChecked()); KateViewConfig::global()->setWordCompletion(ui->gbWordCompletion->isChecked()); KateViewConfig::global()->setWordCompletionMinimalWordLength(ui->minimalWordLength->value()); KateViewConfig::global()->setWordCompletionRemoveTail(ui->removeTail->isChecked()); KateViewConfig::global()->setKeywordCompletion(ui->gbKeywordCompletion->isChecked()); KateViewConfig::global()->configEnd(); } void KateCompletionConfigTab::reload() { ui->chkAutoCompletionEnabled->setChecked(KateViewConfig::global()->automaticCompletionInvocation()); ui->gbWordCompletion->setChecked(KateViewConfig::global()->wordCompletion()); ui->minimalWordLength->setValue(KateViewConfig::global()->wordCompletionMinimalWordLength()); ui->gbKeywordCompletion->setChecked(KateViewConfig::global()->keywordCompletion()); ui->removeTail->setChecked(KateViewConfig::global()->wordCompletionRemoveTail()); } QString KateCompletionConfigTab::name() const { return i18n("Auto Completion"); } //END KateCompletionConfigTab //BEGIN KateSpellCheckConfigTab KateSpellCheckConfigTab::KateSpellCheckConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::SpellCheckConfigWidget(); ui->setupUi(newWidget); // What's This? help can be found in the ui file reload(); // // after initial reload, connect the stuff for the changed () signal m_sonnetConfigWidget = new Sonnet::ConfigWidget(this); connect(m_sonnetConfigWidget, SIGNAL(configChanged()), this, SLOT(slotChanged())); layout->addWidget(m_sonnetConfigWidget); layout->addWidget(newWidget); setLayout(layout); } KateSpellCheckConfigTab::~KateSpellCheckConfigTab() { delete ui; } void KateSpellCheckConfigTab::showWhatsThis(const QString &text) { QWhatsThis::showText(QCursor::pos(), text); } void KateSpellCheckConfigTab::apply() { if (!hasChanged()) { // nothing changed, no need to apply stuff return; } m_changed = false; KateDocumentConfig::global()->configStart(); m_sonnetConfigWidget->save(); KateDocumentConfig::global()->configEnd(); foreach (KTextEditor::DocumentPrivate *doc, KTextEditor::EditorPrivate::self()->kateDocuments()) { doc->refreshOnTheFlyCheck(); } } void KateSpellCheckConfigTab::reload() { // does nothing } QString KateSpellCheckConfigTab::name() const { return i18n("Spellcheck"); } //END KateSpellCheckConfigTab //BEGIN KateNavigationConfigTab KateNavigationConfigTab::KateNavigationConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us having more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::NavigationConfigWidget(); ui->setupUi(newWidget); // What's This? Help is in the ui-files reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->cbTextSelectionMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); connect(ui->chkSmartHome, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkPagingMovesCursor, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->sbAutoCenterCursor, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->chkScrollPastEnd, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } KateNavigationConfigTab::~KateNavigationConfigTab() { delete ui; } void KateNavigationConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); KateDocumentConfig::global()->setSmartHome(ui->chkSmartHome->isChecked()); KateViewConfig::global()->setAutoCenterLines(qMax(0, ui->sbAutoCenterCursor->value())); KateDocumentConfig::global()->setPageUpDownMovesCursor(ui->chkPagingMovesCursor->isChecked()); KateViewConfig::global()->setPersistentSelection(ui->cbTextSelectionMode->currentIndex() == 1); KateViewConfig::global()->setScrollPastEnd(ui->chkScrollPastEnd->isChecked()); KateDocumentConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); } void KateNavigationConfigTab::reload() { ui->cbTextSelectionMode->setCurrentIndex(KateViewConfig::global()->persistentSelection() ? 1 : 0); ui->chkSmartHome->setChecked(KateDocumentConfig::global()->smartHome()); ui->chkPagingMovesCursor->setChecked(KateDocumentConfig::global()->pageUpDownMovesCursor()); ui->sbAutoCenterCursor->setValue(KateViewConfig::global()->autoCenterLines()); ui->chkScrollPastEnd->setChecked(KateViewConfig::global()->scrollPastEnd()); } QString KateNavigationConfigTab::name() const { return i18n("Text Navigation"); } //END KateNavigationConfigTab //BEGIN KateEditGeneralConfigTab KateEditGeneralConfigTab::KateEditGeneralConfigTab(QWidget *parent) : KateConfigPage(parent) { QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::EditConfigWidget(); ui->setupUi(newWidget); QList inputModes = KTextEditor::EditorPrivate::self()->inputModeFactories(); Q_FOREACH(KateAbstractInputModeFactory *fact, inputModes) { ui->cmbInputMode->addItem(fact->name(), static_cast(fact->inputMode())); } reload(); connect(ui->chkStaticWordWrap, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkShowStaticWordWrapMarker, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->sbWordWrap, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->chkAutoBrackets, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkSmartCopyCut, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->cmbInputMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); // "What's this?" help is in the ui-file layout->addWidget(newWidget); setLayout(layout); } KateEditGeneralConfigTab::~KateEditGeneralConfigTab() { delete ui; } void KateEditGeneralConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); KateDocumentConfig::global()->setWordWrapAt(ui->sbWordWrap->value()); KateDocumentConfig::global()->setWordWrap(ui->chkStaticWordWrap->isChecked()); KateRendererConfig::global()->setWordWrapMarker(ui->chkShowStaticWordWrapMarker->isChecked()); KateViewConfig::global()->setAutoBrackets(ui->chkAutoBrackets->isChecked()); KateViewConfig::global()->setSmartCopyCut(ui->chkSmartCopyCut->isChecked()); KateViewConfig::global()->setInputModeRaw(ui->cmbInputMode->currentData().toInt()); KateDocumentConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); } void KateEditGeneralConfigTab::reload() { ui->chkStaticWordWrap->setChecked(KateDocumentConfig::global()->wordWrap()); ui->chkShowStaticWordWrapMarker->setChecked(KateRendererConfig::global()->wordWrapMarker()); ui->sbWordWrap->setSuffix(ki18ncp("Wrap words at (value is at 20 or larger)", " character", " characters")); ui->sbWordWrap->setValue(KateDocumentConfig::global()->wordWrapAt()); ui->chkAutoBrackets->setChecked(KateViewConfig::global()->autoBrackets()); ui->chkSmartCopyCut->setChecked(KateViewConfig::global()->smartCopyCut()); const int id = static_cast(KateViewConfig::global()->inputMode()); ui->cmbInputMode->setCurrentIndex(ui->cmbInputMode->findData(id)); } QString KateEditGeneralConfigTab::name() const { return i18n("General"); } //END KateEditGeneralConfigTab //BEGIN KateEditConfigTab KateEditConfigTab::KateEditConfigTab(QWidget *parent) : KateConfigPage(parent) , editConfigTab(new KateEditGeneralConfigTab(this)) , navigationConfigTab(new KateNavigationConfigTab(this)) , indentConfigTab(new KateIndentConfigTab(this)) , completionConfigTab(new KateCompletionConfigTab(this)) , spellCheckConfigTab(new KateSpellCheckConfigTab(this)) { QVBoxLayout *layout = new QVBoxLayout; layout->setMargin(0); QTabWidget *tabWidget = new QTabWidget(this); // add all tabs tabWidget->insertTab(0, editConfigTab, editConfigTab->name()); tabWidget->insertTab(1, navigationConfigTab, navigationConfigTab->name()); tabWidget->insertTab(2, indentConfigTab, indentConfigTab->name()); tabWidget->insertTab(3, completionConfigTab, completionConfigTab->name()); tabWidget->insertTab(4, spellCheckConfigTab, spellCheckConfigTab->name()); connect(editConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(navigationConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(indentConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(completionConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(spellCheckConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); int i = tabWidget->count(); Q_FOREACH(KateAbstractInputModeFactory *factory, KTextEditor::EditorPrivate::self()->inputModeFactories()) { KateConfigPage *tab = factory->createConfigPage(this); if (tab) { m_inputModeConfigTabs << tab; tabWidget->insertTab(i, tab, tab->name()); connect(tab, SIGNAL(changed()), this, SLOT(slotChanged())); i++; } } layout->addWidget(tabWidget); setLayout(layout); } KateEditConfigTab::~KateEditConfigTab() { qDeleteAll(m_inputModeConfigTabs); } void KateEditConfigTab::apply() { // try to update the rest of tabs editConfigTab->apply(); navigationConfigTab->apply(); indentConfigTab->apply(); completionConfigTab->apply(); spellCheckConfigTab->apply(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->apply(); } } void KateEditConfigTab::reload() { editConfigTab->reload(); navigationConfigTab->reload(); indentConfigTab->reload(); completionConfigTab->reload(); spellCheckConfigTab->reload(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->reload(); } } void KateEditConfigTab::reset() { editConfigTab->reset(); navigationConfigTab->reset(); indentConfigTab->reset(); completionConfigTab->reset(); spellCheckConfigTab->reset(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->reset(); } } void KateEditConfigTab::defaults() { editConfigTab->defaults(); navigationConfigTab->defaults(); indentConfigTab->defaults(); completionConfigTab->defaults(); spellCheckConfigTab->defaults(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->defaults(); } } QString KateEditConfigTab::name() const { return i18n("Editing"); } QString KateEditConfigTab::fullName() const { return i18n("Editing Options"); } QIcon KateEditConfigTab::icon() const { return QIcon::fromTheme(QStringLiteral("accessories-text-editor")); } //END KateEditConfigTab //BEGIN KateViewDefaultsConfig KateViewDefaultsConfig::KateViewDefaultsConfig(QWidget *parent) : KateConfigPage(parent) , textareaUi(new Ui::TextareaAppearanceConfigWidget()) , bordersUi(new Ui::BordersAppearanceConfigWidget()) { QLayout *layout = new QVBoxLayout(this); QTabWidget *tabWidget = new QTabWidget(this); layout->addWidget(tabWidget); layout->setMargin(0); QWidget *textareaTab = new QWidget(tabWidget); textareaUi->setupUi(textareaTab); tabWidget->addTab(textareaTab, i18n("General")); QWidget *bordersTab = new QWidget(tabWidget); bordersUi->setupUi(bordersTab); tabWidget->addTab(bordersTab, i18n("Borders")); textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Off")); textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Follow Line Numbers")); textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Always On")); // What's This? help is in the ui-file reload(); // // after initial reload, connect the stuff for the changed () signal // connect(textareaUi->gbWordWrap, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->cmbDynamicWordWrapIndicator, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(textareaUi->sbDynamicWordWrapDepth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(textareaUi->chkShowTabs, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkShowSpaces, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkShowIndentationLines, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkShowWholeBracketExpression, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkAnimateBracketMatching, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkFoldFirstLine, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkIconBorder, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarMarks, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarPreview, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarMiniMap, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarMiniMapAll, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); bordersUi->chkScrollbarMiniMapAll->hide(); // this is temporary until the feature is done connect(bordersUi->spBoxMiniMapWidth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(bordersUi->chkLineNumbers, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkShowLineModification, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkShowFoldingMarkers, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkShowFoldingPreview, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->rbSortBookmarksByPosition, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->rbSortBookmarksByCreation, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->cmbShowScrollbars, SIGNAL(activated(int)), this, SLOT(slotChanged())); } KateViewDefaultsConfig::~KateViewDefaultsConfig() { delete bordersUi; delete textareaUi; } void KateViewDefaultsConfig::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateRendererConfig::global()->configStart(); KateViewConfig::global()->setDynWordWrap(textareaUi->gbWordWrap->isChecked()); KateViewConfig::global()->setDynWordWrapIndicators(textareaUi->cmbDynamicWordWrapIndicator->currentIndex()); KateViewConfig::global()->setDynWordWrapAlignIndent(textareaUi->sbDynamicWordWrapDepth->value()); KateDocumentConfig::global()->setShowTabs(textareaUi->chkShowTabs->isChecked()); KateDocumentConfig::global()->setShowSpaces(textareaUi->chkShowSpaces->isChecked()); KateViewConfig::global()->setLineNumbers(bordersUi->chkLineNumbers->isChecked()); KateViewConfig::global()->setIconBar(bordersUi->chkIconBorder->isChecked()); KateViewConfig::global()->setScrollBarMarks(bordersUi->chkScrollbarMarks->isChecked()); KateViewConfig::global()->setScrollBarPreview(bordersUi->chkScrollbarPreview->isChecked()); KateViewConfig::global()->setScrollBarMiniMap(bordersUi->chkScrollbarMiniMap->isChecked()); KateViewConfig::global()->setScrollBarMiniMapAll(bordersUi->chkScrollbarMiniMapAll->isChecked()); KateViewConfig::global()->setScrollBarMiniMapWidth(bordersUi->spBoxMiniMapWidth->value()); KateViewConfig::global()->setFoldingBar(bordersUi->chkShowFoldingMarkers->isChecked()); KateViewConfig::global()->setFoldingPreview(bordersUi->chkShowFoldingPreview->isChecked()); KateViewConfig::global()->setLineModification(bordersUi->chkShowLineModification->isChecked()); KateViewConfig::global()->setShowScrollbars(bordersUi->cmbShowScrollbars->currentIndex()); KateViewConfig::global()->setBookmarkSort(bordersUi->rbSortBookmarksByPosition->isChecked() ? 0 : 1); KateRendererConfig::global()->setShowIndentationLines(textareaUi->chkShowIndentationLines->isChecked()); KateRendererConfig::global()->setShowWholeBracketExpression(textareaUi->chkShowWholeBracketExpression->isChecked()); KateRendererConfig::global()->setAnimateBracketMatching(textareaUi->chkAnimateBracketMatching->isChecked()); KateViewConfig::global()->setFoldFirstLine(textareaUi->chkFoldFirstLine->isChecked()); KateRendererConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); } void KateViewDefaultsConfig::reload() { textareaUi->gbWordWrap->setChecked(KateViewConfig::global()->dynWordWrap()); textareaUi->cmbDynamicWordWrapIndicator->setCurrentIndex(KateViewConfig::global()->dynWordWrapIndicators()); textareaUi->sbDynamicWordWrapDepth->setValue(KateViewConfig::global()->dynWordWrapAlignIndent()); textareaUi->chkShowTabs->setChecked(KateDocumentConfig::global()->showTabs()); textareaUi->chkShowSpaces->setChecked(KateDocumentConfig::global()->showSpaces()); bordersUi->chkLineNumbers->setChecked(KateViewConfig::global()->lineNumbers()); bordersUi->chkIconBorder->setChecked(KateViewConfig::global()->iconBar()); bordersUi->chkScrollbarMarks->setChecked(KateViewConfig::global()->scrollBarMarks()); bordersUi->chkScrollbarPreview->setChecked(KateViewConfig::global()->scrollBarPreview()); bordersUi->chkScrollbarMiniMap->setChecked(KateViewConfig::global()->scrollBarMiniMap()); bordersUi->chkScrollbarMiniMapAll->setChecked(KateViewConfig::global()->scrollBarMiniMapAll()); bordersUi->spBoxMiniMapWidth->setValue(KateViewConfig::global()->scrollBarMiniMapWidth()); bordersUi->chkShowFoldingMarkers->setChecked(KateViewConfig::global()->foldingBar()); bordersUi->chkShowFoldingPreview->setChecked(KateViewConfig::global()->foldingPreview()); bordersUi->chkShowLineModification->setChecked(KateViewConfig::global()->lineModification()); bordersUi->rbSortBookmarksByPosition->setChecked(KateViewConfig::global()->bookmarkSort() == 0); bordersUi->rbSortBookmarksByCreation->setChecked(KateViewConfig::global()->bookmarkSort() == 1); bordersUi->cmbShowScrollbars->setCurrentIndex(KateViewConfig::global()->showScrollbars()); textareaUi->chkShowIndentationLines->setChecked(KateRendererConfig::global()->showIndentationLines()); textareaUi->chkShowWholeBracketExpression->setChecked(KateRendererConfig::global()->showWholeBracketExpression()); textareaUi->chkAnimateBracketMatching->setChecked(KateRendererConfig::global()->animateBracketMatching()); textareaUi->chkFoldFirstLine->setChecked(KateViewConfig::global()->foldFirstLine()); } void KateViewDefaultsConfig::reset() { ; } void KateViewDefaultsConfig::defaults() { ; } QString KateViewDefaultsConfig::name() const { return i18n("Appearance"); } QString KateViewDefaultsConfig::fullName() const { return i18n("Appearance"); } QIcon KateViewDefaultsConfig::icon() const { return QIcon::fromTheme(QStringLiteral("preferences-desktop-theme")); } //END KateViewDefaultsConfig //BEGIN KateSaveConfigTab KateSaveConfigTab::KateSaveConfigTab(QWidget *parent) : KateConfigPage(parent) , modeConfigPage(new ModeConfigPage(this)) { // FIXME: Is really needed to move all this code below to another class, // since it is another tab itself on the config dialog. This means we should // initialize, add and work with as we do with modeConfigPage (ereslibre) QVBoxLayout *layout = new QVBoxLayout; layout->setMargin(0); QTabWidget *tabWidget = new QTabWidget(this); QWidget *tmpWidget = new QWidget(tabWidget); QVBoxLayout *internalLayout = new QVBoxLayout; QWidget *newWidget = new QWidget(tabWidget); ui = new Ui::OpenSaveConfigWidget(); ui->setupUi(newWidget); QWidget *tmpWidget2 = new QWidget(tabWidget); QVBoxLayout *internalLayout2 = new QVBoxLayout; QWidget *newWidget2 = new QWidget(tabWidget); uiadv = new Ui::OpenSaveConfigAdvWidget(); uiadv->setupUi(newWidget2); // What's this help is added in ui/opensaveconfigwidget.ui reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->cmbEncoding, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->cmbEncodingDetection, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->cmbEncodingFallback, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->cmbEOL, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->chkDetectEOL, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkEnableBOM, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->lineLengthLimit, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->cbRemoveTrailingSpaces, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); connect(ui->chkNewLineAtEof, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(uiadv->chkBackupLocalFiles, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(uiadv->chkBackupRemoteFiles, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(uiadv->edtBackupPrefix, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(uiadv->edtBackupSuffix, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(uiadv->cmbSwapFileMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); connect(uiadv->cmbSwapFileMode, SIGNAL(currentIndexChanged(int)), this, SLOT(swapFileModeChanged(int))); connect(uiadv->kurlSwapDirectory, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(uiadv->spbSwapFileSync, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); internalLayout->addWidget(newWidget); tmpWidget->setLayout(internalLayout); internalLayout2->addWidget(newWidget2); tmpWidget2->setLayout(internalLayout2); // add all tabs tabWidget->insertTab(0, tmpWidget, i18n("General")); tabWidget->insertTab(1, tmpWidget2, i18n("Advanced")); tabWidget->insertTab(2, modeConfigPage, modeConfigPage->name()); connect(modeConfigPage, SIGNAL(changed()), this, SLOT(slotChanged())); layout->addWidget(tabWidget); setLayout(layout); } KateSaveConfigTab::~KateSaveConfigTab() { delete ui; } void KateSaveConfigTab::swapFileModeChanged(int idx) { const KateDocumentConfig::SwapFileMode mode = static_cast(idx); switch (mode) { case KateDocumentConfig::DisableSwapFile: uiadv->lblSwapDirectory->setEnabled(false); uiadv->kurlSwapDirectory->setEnabled(false); uiadv->lblSwapFileSync->setEnabled(false); uiadv->spbSwapFileSync->setEnabled(false); break; case KateDocumentConfig::EnableSwapFile: uiadv->lblSwapDirectory->setEnabled(false); uiadv->kurlSwapDirectory->setEnabled(false); uiadv->lblSwapFileSync->setEnabled(true); uiadv->spbSwapFileSync->setEnabled(true); break; case KateDocumentConfig::SwapFilePresetDirectory: uiadv->lblSwapDirectory->setEnabled(true); uiadv->kurlSwapDirectory->setEnabled(true); uiadv->lblSwapFileSync->setEnabled(true); uiadv->spbSwapFileSync->setEnabled(true); break; } } void KateSaveConfigTab::apply() { modeConfigPage->apply(); // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateGlobalConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); if (uiadv->edtBackupSuffix->text().isEmpty() && uiadv->edtBackupPrefix->text().isEmpty()) { KMessageBox::information( this, i18n("You did not provide a backup suffix or prefix. Using default suffix: '~'"), i18n("No Backup Suffix or Prefix") ); uiadv->edtBackupSuffix->setText(QStringLiteral("~")); } uint f(0); if (uiadv->chkBackupLocalFiles->isChecked()) { f |= KateDocumentConfig::LocalFiles; } if (uiadv->chkBackupRemoteFiles->isChecked()) { f |= KateDocumentConfig::RemoteFiles; } KateDocumentConfig::global()->setBackupFlags(f); KateDocumentConfig::global()->setBackupPrefix(uiadv->edtBackupPrefix->text()); KateDocumentConfig::global()->setBackupSuffix(uiadv->edtBackupSuffix->text()); KateDocumentConfig::global()->setSwapFileMode(uiadv->cmbSwapFileMode->currentIndex()); KateDocumentConfig::global()->setSwapDirectory(uiadv->kurlSwapDirectory->url().toLocalFile()); KateDocumentConfig::global()->setSwapSyncInterval(uiadv->spbSwapFileSync->value()); KateDocumentConfig::global()->setRemoveSpaces(ui->cbRemoveTrailingSpaces->currentIndex()); KateDocumentConfig::global()->setNewLineAtEof(ui->chkNewLineAtEof->isChecked()); // set both standard and fallback encoding KateDocumentConfig::global()->setEncoding(KCharsets::charsets()->encodingForName(ui->cmbEncoding->currentText())); KateGlobalConfig::global()->setProberType((KEncodingProber::ProberType)ui->cmbEncodingDetection->currentIndex()); KateGlobalConfig::global()->setFallbackEncoding(KCharsets::charsets()->encodingForName(ui->cmbEncodingFallback->currentText())); KateDocumentConfig::global()->setEol(ui->cmbEOL->currentIndex()); KateDocumentConfig::global()->setAllowEolDetection(ui->chkDetectEOL->isChecked()); KateDocumentConfig::global()->setBom(ui->chkEnableBOM->isChecked()); KateDocumentConfig::global()->setLineLengthLimit(ui->lineLengthLimit->value()); KateDocumentConfig::global()->configEnd(); KateGlobalConfig::global()->configEnd(); } void KateSaveConfigTab::reload() { modeConfigPage->reload(); // encodings ui->cmbEncoding->clear(); ui->cmbEncodingFallback->clear(); QStringList encodings(KCharsets::charsets()->descriptiveEncodingNames()); int insert = 0; for (int i = 0; i < encodings.count(); i++) { bool found = false; QTextCodec *codecForEnc = KCharsets::charsets()->codecForName(KCharsets::charsets()->encodingForName(encodings[i]), found); if (found) { ui->cmbEncoding->addItem(encodings[i]); ui->cmbEncodingFallback->addItem(encodings[i]); if (codecForEnc == KateDocumentConfig::global()->codec()) { ui->cmbEncoding->setCurrentIndex(insert); } if (codecForEnc == KateGlobalConfig::global()->fallbackCodec()) { // adjust index for fallback config, has no default! ui->cmbEncodingFallback->setCurrentIndex(insert); } insert++; } } // encoding detection ui->cmbEncodingDetection->clear(); bool found = false; for (int i = 0; !KEncodingProber::nameForProberType((KEncodingProber::ProberType) i).isEmpty(); ++i) { ui->cmbEncodingDetection->addItem(KEncodingProber::nameForProberType((KEncodingProber::ProberType) i)); if (i == KateGlobalConfig::global()->proberType()) { ui->cmbEncodingDetection->setCurrentIndex(ui->cmbEncodingDetection->count() - 1); found = true; } } if (!found) { ui->cmbEncodingDetection->setCurrentIndex(KEncodingProber::Universal); } // eol ui->cmbEOL->setCurrentIndex(KateDocumentConfig::global()->eol()); ui->chkDetectEOL->setChecked(KateDocumentConfig::global()->allowEolDetection()); ui->chkEnableBOM->setChecked(KateDocumentConfig::global()->bom()); ui->lineLengthLimit->setValue(KateDocumentConfig::global()->lineLengthLimit()); ui->cbRemoveTrailingSpaces->setCurrentIndex(KateDocumentConfig::global()->removeSpaces()); ui->chkNewLineAtEof->setChecked(KateDocumentConfig::global()->newLineAtEof()); // other stuff uint f(KateDocumentConfig::global()->backupFlags()); uiadv->chkBackupLocalFiles->setChecked(f & KateDocumentConfig::LocalFiles); uiadv->chkBackupRemoteFiles->setChecked(f & KateDocumentConfig::RemoteFiles); uiadv->edtBackupPrefix->setText(KateDocumentConfig::global()->backupPrefix()); uiadv->edtBackupSuffix->setText(KateDocumentConfig::global()->backupSuffix()); uiadv->cmbSwapFileMode->setCurrentIndex(KateDocumentConfig::global()->swapFileModeRaw()); uiadv->kurlSwapDirectory->setUrl(QUrl::fromLocalFile(KateDocumentConfig::global()->swapDirectory())); uiadv->spbSwapFileSync->setValue(KateDocumentConfig::global()->swapSyncInterval()); swapFileModeChanged(KateDocumentConfig::global()->swapFileMode()); } void KateSaveConfigTab::reset() { modeConfigPage->reset(); } void KateSaveConfigTab::defaults() { modeConfigPage->defaults(); ui->cbRemoveTrailingSpaces->setCurrentIndex(0); uiadv->chkBackupLocalFiles->setChecked(true); uiadv->chkBackupRemoteFiles->setChecked(false); uiadv->edtBackupPrefix->setText(QString()); uiadv->edtBackupSuffix->setText(QStringLiteral("~")); uiadv->cmbSwapFileMode->setCurrentIndex(1); uiadv->kurlSwapDirectory->setDisabled(true); uiadv->lblSwapDirectory->setDisabled(true); uiadv->spbSwapFileSync->setValue(15); } QString KateSaveConfigTab::name() const { return i18n("Open/Save"); } QString KateSaveConfigTab::fullName() const { return i18n("File Opening & Saving"); } QIcon KateSaveConfigTab::icon() const { return QIcon::fromTheme(QStringLiteral("document-save")); } //END KateSaveConfigTab //BEGIN KateHlDownloadDialog KateHlDownloadDialog::KateHlDownloadDialog(QWidget *parent, const char *name, bool modal) : QDialog(parent) { setWindowTitle(i18n("Highlight Download")); setObjectName(QString::fromUtf8(name)); setModal(modal); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QLabel *label = new QLabel(i18n("Select the syntax highlighting files you want to update:"), this); mainLayout->addWidget(label); list = new QTreeWidget(this); list->setColumnCount(4); list->setHeaderLabels(QStringList() << QString() << i18n("Name") << i18n("Installed") << i18n("Latest")); list->setSelectionMode(QAbstractItemView::MultiSelection); list->setAllColumnsShowFocus(true); list->setRootIsDecorated(false); list->setColumnWidth(0, 22); mainLayout->addWidget(list); label = new QLabel(i18n("Note: New versions are selected automatically."), this); mainLayout->addWidget(label); // buttons QDialogButtonBox *buttons = new QDialogButtonBox(this); mainLayout->addWidget(buttons); m_installButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("&Install")); m_installButton->setDefault(true); buttons->addButton(m_installButton, QDialogButtonBox::AcceptRole); connect(m_installButton, SIGNAL(clicked()), this, SLOT(slotInstall())); QPushButton *closeButton = new QPushButton; KGuiItem::assign(closeButton, KStandardGuiItem::cancel()); buttons->addButton(closeButton, QDialogButtonBox::RejectRole); connect(closeButton, SIGNAL(clicked()), this, SLOT(reject())); transferJob = KIO::get(QUrl(QStringLiteral("%1update-%2.%3.xml").arg(HLDOWNLOADPATH).arg(KTEXTEDITOR_VERSION_MAJOR).arg(KTEXTEDITOR_VERSION_MINOR)), KIO::Reload); connect(transferJob, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(listDataReceived(KIO::Job*,QByteArray))); // void data( KIO::Job *, const QByteArray &data); resize(450, 400); } KateHlDownloadDialog::~KateHlDownloadDialog() {} /// Split typical version string (\c major.minor.patch) into /// numeric components, convert 'em to \c unsigned and form a /// single value that can be compared w/ other versions /// using relation operators. /// \note It takes into account only first 3 numbers unsigned KateHlDownloadDialog::parseVersion(const QString &version_string) { unsigned vn[3] = {0, 0, 0}; unsigned idx = 0; foreach (const QString &n, version_string.split(QLatin1Char('.'))) { vn[idx++] = n.toUInt(); if (idx == sizeof(vn)) { break; } } return (((vn[0]) << 16) | ((vn[1]) << 8) | (vn[2])); } void KateHlDownloadDialog::listDataReceived(KIO::Job *, const QByteArray &data) { if (!transferJob || transferJob->isErrorPage()) { m_installButton->setEnabled(false); if (data.size() == 0) { KMessageBox::error(this, i18n("The list of highlightings could not be found on / retrieved from the server")); } return; } listData += QLatin1String(data); qCDebug(LOG_KTE) << QStringLiteral("CurrentListData: ") << listData; qCDebug(LOG_KTE) << QStringLiteral("Data length: %1").arg(data.size()); qCDebug(LOG_KTE) << QStringLiteral("listData length: %1").arg(listData.length()); if (data.size() == 0) { if (listData.length() > 0) { QString installedVersion; KateHlManager *hlm = KateHlManager::self(); QDomDocument doc; doc.setContent(listData); QDomElement DocElem = doc.documentElement(); QDomNode n = DocElem.firstChild(); - KateHighlighting *hl = 0; + KateHighlighting *hl = nullptr; if (n.isNull()) { qCDebug(LOG_KTE) << QStringLiteral("There is no usable childnode"); } while (!n.isNull()) { installedVersion = QStringLiteral(" --"); QDomElement e = n.toElement(); if (!e.isNull()) { qCDebug(LOG_KTE) << QStringLiteral("NAME: ") << e.tagName() << QStringLiteral(" - ") << e.attribute(QStringLiteral("name")); } n = n.nextSibling(); QString Name = e.attribute(QStringLiteral("name")); for (int i = 0; i < hlm->highlights(); i++) { hl = hlm->getHl(i); if (hl && hl->name() == Name) { installedVersion = QLatin1String(" ") + hl->version(); break; } else { - hl = 0; + hl = nullptr; } } // autoselect entry if new or updated. QTreeWidgetItem *entry = new QTreeWidgetItem(list); entry->setText(0, QString()); entry->setText(1, e.attribute(QStringLiteral("name"))); entry->setText(2, installedVersion); entry->setText(3, e.attribute(QStringLiteral("version"))); entry->setText(4, e.attribute(QStringLiteral("url"))); bool is_fresh = false; if (hl) { unsigned prev_version = parseVersion(hl->version()); unsigned next_version = parseVersion(e.attribute(QStringLiteral("version"))); is_fresh = prev_version < next_version; } else { is_fresh = true; } if (is_fresh) { entry->treeWidget()->setItemSelected(entry, true); entry->setIcon(0, QIcon::fromTheme((QStringLiteral("get-hot-new-stuff")))); } } list->resizeColumnToContents(1); list->sortItems(1, Qt::AscendingOrder); } } } void KateHlDownloadDialog::slotInstall() { const QString destdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/org.kde.syntax-highlighting/syntax/"); QDir(destdir).mkpath(QStringLiteral(".")); // make sure the dir is there foreach (QTreeWidgetItem *it, list->selectedItems()) { QUrl src(it->text(4)); QString filename = src.fileName(); // if there is no fileName construct at least something if (filename.isEmpty()) { filename = src.path().replace(QLatin1Char('/'), QLatin1Char('_')); } QUrl dest = QUrl::fromLocalFile(destdir + filename); KIO::FileCopyJob *job = KIO::file_copy(src, dest); KJobWidgets::setWindow(job, this); job->exec(); } } //END KateHlDownloadDialog //BEGIN KateGotoBar KateGotoBar::KateGotoBar(KTextEditor::View *view, QWidget *parent) : KateViewBarWidget(true, parent) , m_view(view) { - Q_ASSERT(m_view != 0); // this bar widget is pointless w/o a view + Q_ASSERT(m_view != nullptr); // this bar widget is pointless w/o a view QHBoxLayout *topLayout = new QHBoxLayout(centralWidget()); topLayout->setMargin(0); gotoRange = new QSpinBox(centralWidget()); QLabel *label = new QLabel(i18n("&Go to line:"), centralWidget()); label->setBuddy(gotoRange); QToolButton *btnOK = new QToolButton(centralWidget()); btnOK->setAutoRaise(true); btnOK->setIcon(QIcon::fromTheme(QStringLiteral("go-jump"))); btnOK->setText(i18n("Go")); btnOK->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); connect(btnOK, SIGNAL(clicked()), this, SLOT(gotoLine())); topLayout->addWidget(label); topLayout->addWidget(gotoRange, 1); topLayout->setStretchFactor(gotoRange, 0); topLayout->addWidget(btnOK); topLayout->addStretch(); setFocusProxy(gotoRange); } void KateGotoBar::updateData() { gotoRange->setMaximum(m_view->document()->lines()); if (!isVisible()) { gotoRange->setValue(m_view->cursorPosition().line() + 1); gotoRange->adjustSize(); // ### does not respect the range :-( } gotoRange->setFocus(Qt::OtherFocusReason); gotoRange->selectAll(); } void KateGotoBar::keyPressEvent(QKeyEvent *event) { int key = event->key(); if (key == Qt::Key_Return || key == Qt::Key_Enter) { gotoLine(); return; } KateViewBarWidget::keyPressEvent(event); } void KateGotoBar::gotoLine() { KTextEditor::ViewPrivate *kv = qobject_cast(m_view); if (kv && kv->selection() && !kv->config()->persistentSelection()) { kv->clearSelection(); } m_view->setCursorPosition(KTextEditor::Cursor(gotoRange->value() - 1, 0)); m_view->setFocus(); emit hideMe(); } //END KateGotoBar //BEGIN KateDictionaryBar KateDictionaryBar::KateDictionaryBar(KTextEditor::ViewPrivate *view, QWidget *parent) : KateViewBarWidget(true, parent) , m_view(view) { - Q_ASSERT(m_view != 0); // this bar widget is pointless w/o a view + Q_ASSERT(m_view != nullptr); // this bar widget is pointless w/o a view QHBoxLayout *topLayout = new QHBoxLayout(centralWidget()); topLayout->setMargin(0); //topLayout->setSpacing(spacingHint()); m_dictionaryComboBox = new Sonnet::DictionaryComboBox(centralWidget()); connect(m_dictionaryComboBox, SIGNAL(dictionaryChanged(QString)), this, SLOT(dictionaryChanged(QString))); connect(view->doc(), SIGNAL(defaultDictionaryChanged(KTextEditor::DocumentPrivate*)), this, SLOT(updateData())); QLabel *label = new QLabel(i18n("Dictionary:"), centralWidget()); label->setBuddy(m_dictionaryComboBox); topLayout->addWidget(label); topLayout->addWidget(m_dictionaryComboBox, 1); topLayout->setStretchFactor(m_dictionaryComboBox, 0); topLayout->addStretch(); } KateDictionaryBar::~KateDictionaryBar() { } void KateDictionaryBar::updateData() { KTextEditor::DocumentPrivate *document = m_view->doc(); QString dictionary = document->defaultDictionary(); if (dictionary.isEmpty()) { dictionary = Sonnet::Speller().defaultLanguage(); } m_dictionaryComboBox->setCurrentByDictionary(dictionary); } void KateDictionaryBar::dictionaryChanged(const QString &dictionary) { KTextEditor::Range selection = m_view->selectionRange(); if (selection.isValid() && !selection.isEmpty()) { m_view->doc()->setDictionary(dictionary, selection); } else { m_view->doc()->setDefaultDictionary(dictionary); } } //END KateGotoBar //BEGIN KateModOnHdPrompt KateModOnHdPrompt::KateModOnHdPrompt(KTextEditor::DocumentPrivate *doc, KTextEditor::ModificationInterface::ModifiedOnDiskReason modtype, const QString &reason) : QObject(doc) , m_doc(doc) , m_modtype(modtype) , m_proc(nullptr) , m_diffFile(nullptr) , m_diffAction(nullptr) { m_message = new KTextEditor::Message(reason, KTextEditor::Message::Information); m_message->setPosition(KTextEditor::Message::AboveView); m_message->setWordWrap(true); // If the file isn't deleted, present a diff button const bool onDiskDeleted = modtype == KTextEditor::ModificationInterface::OnDiskDeleted; if (!onDiskDeleted) { if (!QStandardPaths::findExecutable(QStringLiteral("diff")).isEmpty()) { m_diffAction = new QAction(i18n("View &Difference"), this); m_diffAction->setToolTip(i18n("Shows a diff of the changes")); m_message->addAction(m_diffAction, false); connect(m_diffAction, SIGNAL(triggered()), this, SLOT(slotDiff())); } QAction * aReload = new QAction(i18n("&Reload"), this); aReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); aReload->setToolTip(i18n("Reload the file from disk. Unsaved changes will be lost.")); m_message->addAction(aReload); connect(aReload, SIGNAL(triggered()), this, SIGNAL(reloadTriggered())); } else { QAction * aSaveAs = new QAction(i18n("&Save As..."), this); aSaveAs->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); aSaveAs->setToolTip(i18n("Lets you select a location and save the file again.")); m_message->addAction(aSaveAs, false); connect(aSaveAs, SIGNAL(triggered()), this, SIGNAL(saveAsTriggered())); } QAction * aIgnore = new QAction(i18n("&Ignore"), this); aIgnore->setToolTip(i18n("Ignores the changes on disk without any action.")); aIgnore->setIcon(KStandardGuiItem::overwrite().icon()); m_message->addAction(aIgnore); connect(aIgnore, SIGNAL(triggered()), this, SIGNAL(ignoreTriggered())); m_doc->postMessage(m_message); } KateModOnHdPrompt::~KateModOnHdPrompt() { delete m_proc; - m_proc = 0; + m_proc = nullptr; if (m_diffFile) { m_diffFile->setAutoRemove(true); delete m_diffFile; - m_diffFile = 0; + m_diffFile = nullptr; } delete m_message; } void KateModOnHdPrompt::slotDiff() { if (m_diffFile) { return; } m_diffFile = new QTemporaryFile(); m_diffFile->open(); // Start a KProcess that creates a diff m_proc = new KProcess(this); m_proc->setOutputChannelMode(KProcess::MergedChannels); *m_proc << QStringLiteral("diff") << QLatin1String("-u") << QStringLiteral("-") << m_doc->url().toLocalFile(); connect(m_proc, SIGNAL(readyRead()), this, SLOT(slotDataAvailable())); connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotPDone())); // disable the diff button, to hinder the user to run it twice. m_diffAction->setEnabled(false); m_proc->start(); QTextStream ts(m_proc); int lastln = m_doc->lines() - 1; for (int l = 0; l < lastln; ++l) { ts << m_doc->line(l) << '\n'; } ts << m_doc->line(lastln); ts.flush(); m_proc->closeWriteChannel(); } void KateModOnHdPrompt::slotDataAvailable() { m_diffFile->write(m_proc->readAll()); } void KateModOnHdPrompt::slotPDone() { m_diffAction->setEnabled(true); const QProcess::ExitStatus es = m_proc->exitStatus(); delete m_proc; - m_proc = 0; + m_proc = nullptr; if (es != QProcess::NormalExit) { KMessageBox::sorry(nullptr, i18n("The diff command failed. Please make sure that " "diff(1) is installed and in your PATH."), i18n("Error Creating Diff")); delete m_diffFile; - m_diffFile = 0; + m_diffFile = nullptr; return; } if (m_diffFile->size() == 0) { KMessageBox::information(nullptr, i18n("The files are identical."), i18n("Diff Output")); delete m_diffFile; - m_diffFile = 0; + m_diffFile = nullptr; return; } m_diffFile->setAutoRemove(false); QUrl url = QUrl::fromLocalFile(m_diffFile->fileName()); delete m_diffFile; - m_diffFile = 0; + m_diffFile = nullptr; // KRun::runUrl should delete the file, once the client exits KRun::runUrl(url, QStringLiteral("text/x-patch"), nullptr, true); } //END KateModOnHdPrompt diff --git a/src/dialogs/katedialogs.h b/src/dialogs/katedialogs.h index d2102a89..d122236a 100644 --- a/src/dialogs/katedialogs.h +++ b/src/dialogs/katedialogs.h @@ -1,375 +1,375 @@ /* This file is part of the KDE libraries Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2003 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2006-2016 Dominik Haumann Copyright (C) 2007 Mirko Stocker Copyright (C) 2009 Michel Ludwig 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_DIALOGS_H__ #define __KATE_DIALOGS_H__ #include "katehighlight.h" #include "kateviewhelpers.h" #include "kateconfigpage.h" #include #include #include #include #include #include #include #include #include #include class ModeConfigPage; namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } namespace KTextEditor { class Message; } namespace KIO { class Job; class TransferJob; } class KComboBox; class KShortcutsEditor; class QSpinBox; class KPluginSelector; class KPluginInfo; class KProcess; class QCheckBox; class QLabel; class QCheckBox; class QKeyEvent; class QTemporaryFile; class QTableWidget; namespace Ui { class TextareaAppearanceConfigWidget; class BordersAppearanceConfigWidget; class NavigationConfigWidget; class EditConfigWidget; class IndentationConfigWidget; class OpenSaveConfigWidget; class OpenSaveConfigAdvWidget; class CompletionConfigTab; class SpellCheckConfigWidget; } class KateGotoBar : public KateViewBarWidget { Q_OBJECT public: - explicit KateGotoBar(KTextEditor::View *view, QWidget *parent = 0); + explicit KateGotoBar(KTextEditor::View *view, QWidget *parent = nullptr); void updateData(); protected Q_SLOTS: void gotoLine(); protected: void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; private: KTextEditor::View *const m_view; QSpinBox *gotoRange; }; class KateDictionaryBar : public KateViewBarWidget { Q_OBJECT public: - explicit KateDictionaryBar(KTextEditor::ViewPrivate *view, QWidget *parent = NULL); + explicit KateDictionaryBar(KTextEditor::ViewPrivate *view, QWidget *parent = nullptr); virtual ~KateDictionaryBar(); public Q_SLOTS: void updateData(); protected Q_SLOTS: void dictionaryChanged(const QString &dictionary); private: KTextEditor::ViewPrivate *m_view; Sonnet::DictionaryComboBox *m_dictionaryComboBox; }; class KateIndentConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateIndentConfigTab(QWidget *parent); ~KateIndentConfigTab(); QString name() const Q_DECL_OVERRIDE; protected: Ui::IndentationConfigWidget *ui; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE {} void defaults() Q_DECL_OVERRIDE {} private Q_SLOTS: void slotChanged(); void showWhatsThis(const QString &text); }; class KateCompletionConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateCompletionConfigTab(QWidget *parent); ~KateCompletionConfigTab(); QString name() const Q_DECL_OVERRIDE; protected: Ui::CompletionConfigTab *ui; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE {} void defaults() Q_DECL_OVERRIDE {} private Q_SLOTS: void showWhatsThis(const QString &text); }; class KateEditGeneralConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateEditGeneralConfigTab(QWidget *parent); ~KateEditGeneralConfigTab(); QString name() const Q_DECL_OVERRIDE; private: Ui::EditConfigWidget *ui; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE {} void defaults() Q_DECL_OVERRIDE {} }; class KateNavigationConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateNavigationConfigTab(QWidget *parent); ~KateNavigationConfigTab(); QString name() const Q_DECL_OVERRIDE; private: Ui::NavigationConfigWidget *ui; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE {} void defaults() Q_DECL_OVERRIDE {} }; class KateSpellCheckConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateSpellCheckConfigTab(QWidget *parent); ~KateSpellCheckConfigTab(); QString name() const Q_DECL_OVERRIDE; protected: Ui::SpellCheckConfigWidget *ui; Sonnet::ConfigWidget *m_sonnetConfigWidget; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE {} void defaults() Q_DECL_OVERRIDE {} private Q_SLOTS: void showWhatsThis(const QString &text); }; class KateEditConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateEditConfigTab(QWidget *parent); ~KateEditConfigTab(); QString name() const Q_DECL_OVERRIDE; QString fullName() const Q_DECL_OVERRIDE; QIcon icon() const Q_DECL_OVERRIDE; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; private: KateEditGeneralConfigTab *editConfigTab; KateNavigationConfigTab *navigationConfigTab; KateIndentConfigTab *indentConfigTab; KateCompletionConfigTab *completionConfigTab; KateSpellCheckConfigTab *spellCheckConfigTab; QList m_inputModeConfigTabs; }; class KateViewDefaultsConfig : public KateConfigPage { Q_OBJECT public: explicit KateViewDefaultsConfig(QWidget *parent); ~KateViewDefaultsConfig(); QString name() const Q_DECL_OVERRIDE; QString fullName() const Q_DECL_OVERRIDE; QIcon icon() const Q_DECL_OVERRIDE; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; private: Ui::TextareaAppearanceConfigWidget *const textareaUi; Ui::BordersAppearanceConfigWidget *const bordersUi; }; class KateSaveConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateSaveConfigTab(QWidget *parent); ~KateSaveConfigTab(); QString name() const Q_DECL_OVERRIDE; QString fullName() const Q_DECL_OVERRIDE; QIcon icon() const Q_DECL_OVERRIDE; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; void swapFileModeChanged(int); protected: //why? //KComboBox *m_encoding, *m_encodingDetection, *m_eol; QCheckBox *cbLocalFiles, *cbRemoteFiles; QCheckBox *replaceTabs, *removeSpaces, *allowEolDetection; class QSpinBox *blockCount; class QLabel *blockCountLabel; private: Ui::OpenSaveConfigWidget *ui; Ui::OpenSaveConfigAdvWidget *uiadv; ModeConfigPage *modeConfigPage; }; class KateHlDownloadDialog: public QDialog { Q_OBJECT public: KateHlDownloadDialog(QWidget *parent, const char *name, bool modal); ~KateHlDownloadDialog(); private: static unsigned parseVersion(const QString &); class QTreeWidget *list; class QString listData; KIO::TransferJob *transferJob; QPushButton *m_installButton; private Q_SLOTS: void listDataReceived(KIO::Job *, const QByteArray &data); void slotInstall(); }; /** * This dialog will prompt the user for what do with a file that is * modified on disk. * If the file wasn't deleted, it has a 'diff' button, which will create * a diff file (uing diff(1)) and launch that using KRun. */ class KateModOnHdPrompt : public QObject { Q_OBJECT public: enum Status { Reload = 1, // 0 is QDialog::Rejected Save, Overwrite, Ignore, Close }; KateModOnHdPrompt(KTextEditor::DocumentPrivate *doc, KTextEditor::ModificationInterface::ModifiedOnDiskReason modtype, const QString &reason); ~KateModOnHdPrompt(); Q_SIGNALS: void saveAsTriggered(); void ignoreTriggered(); void reloadTriggered(); private Q_SLOTS: /** * Show a diff between the document text and the disk file. */ void slotDiff(); private Q_SLOTS: void slotDataAvailable(); ///< read data from the process void slotPDone(); ///< Runs the diff file when done private: KTextEditor::DocumentPrivate *m_doc; QPointer m_message; KTextEditor::ModificationInterface::ModifiedOnDiskReason m_modtype; KProcess *m_proc; QTemporaryFile *m_diffFile; QAction *m_diffAction; }; #endif diff --git a/src/dialogs/opensaveconfigadvwidget.ui b/src/dialogs/opensaveconfigadvwidget.ui index db1c183e..306f2ff7 100644 --- a/src/dialogs/opensaveconfigadvwidget.ui +++ b/src/dialogs/opensaveconfigadvwidget.ui @@ -1,230 +1,227 @@ OpenSaveConfigAdvWidget 0 0 383 480 + + 0 + <p>Backing up on save will cause Kate to copy the disk file to '&lt;prefix&gt;&lt;filename&gt;&lt;suffix&gt;' before saving changes.<p>The suffix defaults to <strong>~</strong> and prefix is empty by default. Backup on Save QFormLayout::ExpandingFieldsGrow If this option is enabled, backups for local files will be created when saving. &Local files If this option is enabled, backups for remote files will be created when saving. &Remote files &Prefix: edtBackupPrefix Enter the prefix to prepend to the backup file names. &Suffix: edtBackupSuffix Enter the suffix to append to the backup file names. 0 50 Swap File Options Swap file: cmbSwapFileMode Disable Enable Alternative Directory - Directory + Directory: kurlSwapDirectory false KFile::Directory|KFile::LocalOnly Directory for swp files Sync every: spbSwapFileSync Disabled s 0 600 10 15 Be aware, that disabling the swap file synchronization may lead to data loss in case of a system crash. Qt::AutoText true - - - - Qt::Vertical - - - - 20 - 130 - - - - + + + + Qt::Vertical + + + KLineEdit QLineEdit
klineedit.h
KUrlRequester QFrame
kurlrequester.h
chkBackupLocalFiles chkBackupRemoteFiles edtBackupPrefix edtBackupSuffix
diff --git a/src/dialogs/opensaveconfigwidget.ui b/src/dialogs/opensaveconfigwidget.ui index 02dbfcd7..894823d0 100644 --- a/src/dialogs/opensaveconfigwidget.ui +++ b/src/dialogs/opensaveconfigwidget.ui @@ -1,243 +1,243 @@ OpenSaveConfigWidget 0 File Format QFormLayout::ExpandingFieldsGrow &Encoding: cmbEncoding This defines the standard encoding to use to open/save files, if not changed in the open/save dialog or by using a command line option. - &Encoding Detection: + &Encoding detection: cmbEncodingDetection if neither the encoding chosen as standard above, nor the encoding specified in the open/save dialog, nor the encoding specified on command line match the content of the file, this detection will be run. - &Fallback Encoding: + &Fallback encoding: cmbEncodingFallback - This defines the fallback encoding to try for opening files if neither the encoding chosen as standard above, nor the encoding specified in the open/save dialog, nor the encoding specified on command line match the content of the file. Before this is used, an attempt will be made to determine the encoding to use by looking for a byte order marker at start of file: if one is found, the right unicode encoding will be chosen; otherwise encoding detection will run, if both fail fallback encoding will be tried. + This defines the fallback encoding to try for opening files if neither the encoding chosen as standard above, nor the encoding specified in the open/save dialog, nor the encoding specified on command line match the content of the file. Before this is used, an attempt will be made to determine the encoding to use by looking for a byte order mark at start of file: if one is found, the right Unicode encoding will be chosen; otherwise encoding detection will run, if both fail the fallback encoding will be tried. E&nd of line: cmbEOL UNIX DOS/Windows Macintosh If this option is enabled the editor will autodetect the end of line type. The first found end of line type will be used for the whole file. A&utomatic end of line detection - The byte order mark is a special sequence at the beginning of unicode encoded documents. It helps editors to open text documents with the correct unicode encoding. The byte order mark is not visible in the displayed document. + The byte order mark is a special sequence at the beginning of Unicode encoded documents. It helps editors to open text documents with the correct Unicode encoding. The byte order mark is not visible in the displayed document. - Enable byte order marker + Enable byte order mark (BOM) - Line Length Limit: + Line length limit: lineLengthLimit Unlimited -1 1000000 Automatic Cleanups on Save Depending on the choice, trailing spaces are removed when saving a document, either in the entire document or only of modified lines. Re&move trailing spaces: cbRemoveTrailingSpaces Depending on the choice, trailing spaces are removed when saving a document, either in the entire document or only of modified lines. Never On Modified Lines In Entire Document Qt::Horizontal 40 20 On save, a line break is appended to the document if not already present. The line break is visible after reloading the file. Append newline at end of file on save Qt::Vertical 0 1 KComboBox QComboBox
kcombobox.h
diff --git a/src/dialogs/textareaappearanceconfigwidget.ui b/src/dialogs/textareaappearanceconfigwidget.ui index e62a8612..043f3f01 100644 --- a/src/dialogs/textareaappearanceconfigwidget.ui +++ b/src/dialogs/textareaappearanceconfigwidget.ui @@ -1,211 +1,211 @@ TextareaAppearanceConfigWidget If this option is checked, the text lines will be wrapped at the view border on the screen. &Dynamic Word Wrap true Dynamic &word wrap indicators (if applicable): cmbDynamicWordWrapIndicator Choose when the Dynamic Word Wrap Indicators should be displayed. Align dynamically wrapped lines to indentation depth: sbDynamicWordWrapDepth 0 0 <p>Enables the start of dynamically wrapped lines to be aligned vertically to the indentation level of the first line. This can help to make code and markup more readable.</p><p>Additionally, this allows you to set a maximum width of the screen, as a percentage, after which dynamically wrapped lines will no longer be vertically aligned. For example, at 50%, lines whose indentation levels are deeper than 50% of the width of the screen will not have vertical alignment applied to subsequent wrapped lines.</p> Disabled % of View Width 80 10 Whitespace Highlighting The editor will display a symbol to indicate the presence of a tab in the text. &Highlight tabulators Highlight trailing &spaces Advanced If this is enabled, the editor will display vertical lines to help identify indent lines. Show i&ndentation lines If this is enabled, the range between the selected matching brackets will be highlighted. Highlight range between selected brackets Flash matching brackets If this is enabled, matching brackets are animated for better visibility. Animate bracket matching When this setting is enabled, the editor view automatically folds comment blocks that start on the first line of the document. This is helpful to hide license headers which are commonly placed at the beginning of a file. - Fold First Line + Fold first line Qt::Vertical 0 1 KComboBox QComboBox
kcombobox.h
gbWordWrap toggled(bool) cmbDynamicWordWrapIndicator setEnabled(bool) 115 7 340 44 gbWordWrap toggled(bool) sbDynamicWordWrapDepth setEnabled(bool) 59 6 385 72
diff --git a/src/document/editorconfig.cpp b/src/document/editorconfig.cpp new file mode 100644 index 00000000..14c0bef2 --- /dev/null +++ b/src/document/editorconfig.cpp @@ -0,0 +1,166 @@ +/* This file is part of the KTextEditor project. + * + * Copyright (C) 2017 Grzegorz Szymaszek + * + * 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 "editorconfig.h" + +/** + * @return whether a string value could be converted to a bool value as + * supported. The value is put in *result. + */ +static bool 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; +} + +/** + * @return whether a string value could be converted to a integer value. The + * value is put in *result. + */ +static bool checkIntValue(QString val, int *result) +{ + bool ret(false); + *result = val.toInt(&ret); + return ret; +} + +EditorConfig::EditorConfig(KTextEditor::DocumentPrivate *document) + : m_document(document) + , m_handle(editorconfig_handle_init()) +{ +} + +EditorConfig::~EditorConfig() +{ + editorconfig_handle_destroy(m_handle); +} + +int EditorConfig::parse() +{ + const int code = editorconfig_parse(m_document->url().toLocalFile().toStdString().c_str(), m_handle); + + if (code != 0) { + if (code == EDITORCONFIG_PARSE_MEMORY_ERROR) { + qCDebug(LOG_KTE) << "Failed to parse .editorconfig, memory error occurred"; + } else if (code > 0) { + qCDebug(LOG_KTE) << "Failed to parse .editorconfig, error in line" << code; + } else { + qCDebug(LOG_KTE) << "Failed to parse .editorconfig, unknown error"; + } + + return code; + } + + // count of found key-value pairs + const unsigned int count = editorconfig_handle_get_name_value_count(m_handle); + + // if indent_size=tab + bool setIndentSizeAsTabWidth = false; + + // whether corresponding fields were found in .editorconfig + bool indentSizeSet = false; + bool tabWidthSet = false; + + // the following only applies if indent_size=tab and there isn’t tab_width + int tabWidth = m_document->config()->tabWidth(); + + for (unsigned int i = 0; i < count; ++ i) { + // raw values from EditorConfig library + const char *rawKey = nullptr; + const char *rawValue = nullptr; + + // buffers for integer/boolean values + int intValue; + bool boolValue; + + // fetch next key-value pair + editorconfig_handle_get_name_value(m_handle, i, &rawKey, &rawValue); + + // and convert to Qt strings + const QLatin1String key = QLatin1String(rawKey); + const QLatin1String value = QLatin1String(rawValue); + + if (QLatin1String("charset") == key) { + m_document->setEncoding(value); + } else if (QLatin1String("end_of_line") == key) { + QStringList eols; + + // NOTE: EOLs are declared in Kate::TextBuffer::EndOfLineMode + eols << QLatin1String("lf") << QLatin1String("crlf") << QLatin1String("cr"); + + if ((intValue = eols.indexOf(value)) != -1) { + m_document->config()->setEol(intValue); + m_document->config()->setAllowEolDetection(false); + } else { + qCDebug(LOG_KTE) << "End of line in .editorconfig other than unix/dos/mac"; + } + } else if (QLatin1String("indent_size") == key) { + if (QLatin1String("tab") == value) { + setIndentSizeAsTabWidth = true; + } else if (checkIntValue(value, &intValue)) { + m_document->config()->setIndentationWidth(intValue); + indentSizeSet = true; + } else { + qCDebug(LOG_KTE) << "Indent size in .editorconfig not a number, nor tab"; + } + } else if (QLatin1String("indent_style") == key) { + if (QLatin1String("tab") == value) { + m_document->config()->setReplaceTabsDyn(false); + } else if (QLatin1String("space") == value) { + m_document->config()->setReplaceTabsDyn(true); + } else { + qCDebug(LOG_KTE) << "Indent style in .editorconfig other than tab or space"; + } + } else if (QLatin1String("insert_final_newline") == key && checkBoolValue(value, &boolValue)) { + m_document->config()->setNewLineAtEof(boolValue); + } else if (QLatin1String("max_line_length") == key && checkIntValue(value, &intValue)) { + m_document->config()->setWordWrapAt(intValue); + } else if (QLatin1String("tab_width") == key && checkIntValue(value, &intValue)) { + m_document->config()->setTabWidth(intValue); + tabWidth = intValue; + tabWidthSet = true; + } else if (QLatin1String("trim_trailing_whitespace") == key && checkBoolValue(value, &boolValue)) { + if (boolValue) { + m_document->config()->setRemoveSpaces(2); + } else { + m_document->config()->setRemoveSpaces(0); + } + } + } + + if (setIndentSizeAsTabWidth) { + m_document->config()->setIndentationWidth(tabWidth); + } else if (! tabWidthSet && indentSizeSet) { + m_document->config()->setTabWidth(m_document->config()->indentationWidth()); + } + + return 0; +} diff --git a/src/document/editorconfig.h b/src/document/editorconfig.h new file mode 100644 index 00000000..aca76c74 --- /dev/null +++ b/src/document/editorconfig.h @@ -0,0 +1,63 @@ +/* This file is part of the KTextEditor project. + * + * Copyright (C) 2017 Grzegorz Szymaszek + * + * 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 _EDITORCONFIG_H_ +#define _EDITORCONFIG_H_ + +#include + +#include +#include + +#include "kateconfig.h" +#include "katedocument.h" +#include "kateglobal.h" +#include "katepartdebug.h" + +class KateDocumentConfig; + +namespace KTextEditor { class DocumentPrivate; } + +class EditorConfig +{ +public: + EditorConfig(KTextEditor::DocumentPrivate *document); + ~EditorConfig(); + /** + * Runs EditorConfig parser and sets proper parent DocumentPrivate + * configuration. Implemented options: charset, end_of_line, indent_size, + * indent_style, insert_final_newline, max_line_length, tab_width, + * trim_trailing_whitespace. + * + * @see https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties + * + * @return 0 if EditorConfig library reported successful file parsing, + * positive integer if a parsing error occurred (the return value would be + * the line number of parsing error), negative integer if another error + * occurred. In other words, returns value returned by editorconfig_parse. + */ + int parse(); + +private: + KTextEditor::DocumentPrivate *m_document; + editorconfig_handle m_handle; +}; + +#endif diff --git a/src/document/katebuffer.cpp b/src/document/katebuffer.cpp index 41081a54..adc58f27 100644 --- a/src/document/katebuffer.cpp +++ b/src/document/katebuffer.cpp @@ -1,714 +1,714 @@ /* This file is part of the KDE libraries Copyright (c) 2000 Waldo Bastian Copyright (C) 2002-2004 Christoph Cullmann Copyright (C) 2007 Mirko Stocker 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 "katebuffer.h" #include "katedocument.h" #include "katehighlight.h" #include "kateconfig.h" #include "kateglobal.h" #include "kateautoindent.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include #include /** * Initial value for m_maxDynamicContexts */ static const int KATE_MAX_DYNAMIC_CONTEXTS = 512; /** * Create an empty buffer. (with one block with one empty line) */ KateBuffer::KateBuffer(KTextEditor::DocumentPrivate *doc) : Kate::TextBuffer(doc), m_doc(doc), m_brokenEncoding(false), m_tooLongLinesWrapped(false), m_longestLineLoaded(0), - m_highlight(0), + m_highlight(nullptr), m_tabWidth(8), m_lineHighlighted(0), m_maxDynamicContexts(KATE_MAX_DYNAMIC_CONTEXTS) { } /** * Cleanup on destruction */ KateBuffer::~KateBuffer() { } void KateBuffer::editStart() { if (!startEditing()) { return; } } void KateBuffer::editEnd() { /** * not finished, do nothing */ if (!finishEditing()) { return; } /** * nothing change, OK */ if (!editingChangedBuffer()) { return; } /** * if we arrive here, line changed should be OK */ Q_ASSERT(editingMinimalLineChanged() != -1); Q_ASSERT(editingMaximalLineChanged() != -1); Q_ASSERT(editingMinimalLineChanged() <= editingMaximalLineChanged()); /** * no highlighting, nothing to do */ if (!m_highlight) { return; } /** * if we don't touch the highlighted area => fine */ if (editingMinimalLineChanged() > m_lineHighlighted) { return; } /** * look one line too far, needed for linecontinue stuff */ const int editTagLineEnd = editingMaximalLineChanged() + 1; /** * for indentation sensitive folding, we need to redo things * one line up, to e.g. get notified about new folding starts * see bug 351496 */ int editTagLineStart = editingMinimalLineChanged(); if ((editTagLineStart > 0) && m_highlight->foldingIndentationSensitive()) --editTagLineStart; /** * really update highlighting */ doHighlight( editTagLineStart, editTagLineEnd, true); } void KateBuffer::clear() { // call original clear function Kate::TextBuffer::clear(); // reset the state m_brokenEncoding = false; m_tooLongLinesWrapped = false; m_longestLineLoaded = 0; // back to line 0 with hl m_lineHighlighted = 0; } bool KateBuffer::openFile(const QString &m_file, bool enforceTextCodec) { // first: setup fallback and normal encoding setEncodingProberType(KateGlobalConfig::global()->proberType()); setFallbackTextCodec(KateGlobalConfig::global()->fallbackCodec()); setTextCodec(m_doc->config()->codec()); // setup eol setEndOfLineMode((EndOfLineMode) m_doc->config()->eol()); // NOTE: we do not remove trailing spaces on load. This was discussed // over the years again and again. bugs: 306926, 239077, ... // line length limit setLineLengthLimit(m_doc->lineLengthLimit()); // then, try to load the file m_brokenEncoding = false; m_tooLongLinesWrapped = false; m_longestLineLoaded = 0; /** * allow non-existent files without error, if local file! * will allow to do "kate newfile.txt" without error messages but still fail if e.g. you mistype a url * and it can't be fetched via fish:// or other strange things in kio happen... * just clear() + exit with success! */ if (m_doc->url().isLocalFile() && !QFile::exists(m_file)) { clear(); KTextEditor::Message *message = new KTextEditor::Message(i18nc("short translation, user created new file", "New file"), KTextEditor::Message::Warning); message->setPosition(KTextEditor::Message::TopInView); message->setAutoHide(1000); m_doc->postMessage(message); // remember error m_doc->m_openingError = true; m_doc->m_openingErrorMessage = i18n("The file %1 does not exist.", m_doc->url().toString()); return true; } /** * check if this is a normal file or not, avoids to open char devices or directories! * else clear buffer and break out with error */ if (!QFileInfo(m_file).isFile()) { clear(); return false; } /** * try to load */ if (!load(m_file, m_brokenEncoding, m_tooLongLinesWrapped, m_longestLineLoaded, enforceTextCodec)) { return false; } // save back encoding m_doc->config()->setEncoding(QString::fromLatin1(textCodec()->name())); // set eol mode, if a eol char was found if (m_doc->config()->allowEolDetection()) { m_doc->config()->setEol(endOfLineMode()); } // generate a bom? if (generateByteOrderMark()) { m_doc->config()->setBom(true); } // okay, loading did work return true; } bool KateBuffer::canEncode() { QTextCodec *codec = m_doc->config()->codec(); - // hardcode some unicode encodings which can encode all chars + // hardcode some Unicode encodings which can encode all chars if ((QString::fromLatin1(codec->name()) == QLatin1String("UTF-8")) || (QString::fromLatin1(codec->name()) == QLatin1String("ISO-10646-UCS-2"))) { return true; } for (int i = 0; i < lines(); i++) { if (!codec->canEncode(line(i)->string())) { qCDebug(LOG_KTE) << QLatin1String("ENC NAME: ") << codec->name(); qCDebug(LOG_KTE) << QLatin1String("STRING LINE: ") << line(i)->string(); qCDebug(LOG_KTE) << QLatin1String("ENC WORKING: FALSE"); return false; } } return true; } bool KateBuffer::saveFile(const QString &m_file) { // first: setup fallback and normal encoding setEncodingProberType(KateGlobalConfig::global()->proberType()); setFallbackTextCodec(KateGlobalConfig::global()->fallbackCodec()); setTextCodec(m_doc->config()->codec()); // setup eol setEndOfLineMode((EndOfLineMode) m_doc->config()->eol()); // generate bom? setGenerateByteOrderMark(m_doc->config()->bom()); // append a newline character at the end of the file (eof) ? setNewLineAtEof(m_doc->config()->newLineAtEof()); // try to save if (!save(m_file)) { return false; } // no longer broken encoding, or we don't care m_brokenEncoding = false; m_tooLongLinesWrapped = false; m_longestLineLoaded = 0; // okay return true; } void KateBuffer::ensureHighlighted(int line, int lookAhead) { // valid line at all? if (line < 0 || line >= lines()) { return; } // already hl up-to-date for this line? if (line < m_lineHighlighted) { return; } // update hl until this line + max lookAhead int end = qMin(line + lookAhead, lines() - 1); // ensure we have enough highlighted doHighlight(m_lineHighlighted, end, false); } void KateBuffer::wrapLine(const KTextEditor::Cursor &position) { // call original Kate::TextBuffer::wrapLine(position); if (m_lineHighlighted > position.line() + 1) { m_lineHighlighted++; } } void KateBuffer::unwrapLine(int line) { // reimplemented, so first call original Kate::TextBuffer::unwrapLine(line); if (m_lineHighlighted > line) { --m_lineHighlighted; } } void KateBuffer::setTabWidth(int w) { if ((m_tabWidth != w) && (m_tabWidth > 0)) { m_tabWidth = w; if (m_highlight && m_highlight->foldingIndentationSensitive()) { invalidateHighlighting(); } } } void KateBuffer::setHighlight(int hlMode) { KateHighlighting *h = KateHlManager::self()->getHl(hlMode); // aha, hl will change if (h != m_highlight) { bool invalidate = !h->noHighlighting(); if (m_highlight) { invalidate = true; } h->use(); m_highlight = h; if (invalidate) { invalidateHighlighting(); } // inform the document that the hl was really changed // needed to update attributes and more ;) m_doc->bufferHlChanged(); // try to set indentation if (!h->indentation().isEmpty()) { m_doc->config()->setIndentationMode(h->indentation()); } } } void KateBuffer::invalidateHighlighting() { m_lineHighlighted = 0; } void KateBuffer::doHighlight(int startLine, int endLine, bool invalidate) { // no hl around, no stuff to do if (!m_highlight || m_highlight->noHighlighting()) { return; } #ifdef BUFFER_DEBUGGING QTime t; t.start(); qCDebug(LOG_KTE) << "HIGHLIGHTED START --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine; qCDebug(LOG_KTE) << "HL UNTIL LINE: " << m_lineHighlighted; qCDebug(LOG_KTE) << "HL DYN COUNT: " << KateHlManager::self()->countDynamicCtxs() << " MAX: " << m_maxDynamicContexts; #endif // see if there are too many dynamic contexts; if yes, invalidate HL of all documents if (KateHlManager::self()->countDynamicCtxs() >= m_maxDynamicContexts) { { if (KateHlManager::self()->resetDynamicCtxs()) { #ifdef BUFFER_DEBUGGING qCDebug(LOG_KTE) << "HL invalidated - too many dynamic contexts ( >= " << m_maxDynamicContexts << ")"; #endif // avoid recursive invalidation KateHlManager::self()->setForceNoDCReset(true); foreach (KTextEditor::DocumentPrivate *doc, KTextEditor::EditorPrivate::self()->kateDocuments()) { doc->makeAttribs(); } // doHighlight *shall* do his work. After invalidation, some highlight has // been recalculated, but *maybe not* until endLine ! So we shall force it manually... doHighlight(m_lineHighlighted, endLine, false); m_lineHighlighted = endLine; KateHlManager::self()->setForceNoDCReset(false); return; } else { m_maxDynamicContexts *= 2; #ifdef BUFFER_DEBUGGING qCDebug(LOG_KTE) << "New dynamic contexts limit: " << m_maxDynamicContexts; #endif } } } // if possible get previous line, otherwise create 0 line. Kate::TextLine prevLine = (startLine >= 1) ? plainLine(startLine - 1) : Kate::TextLine(); // here we are atm, start at start line in the block int current_line = startLine; int start_spellchecking = -1; int last_line_spellchecking = -1; bool ctxChanged = false; Kate::TextLine textLine = plainLine(current_line); Kate::TextLine nextLine; // loop over the lines of the block, from startline to endline or end of block // if stillcontinue forces us to do so for (; current_line < qMin(endLine + 1, lines()); ++current_line) { // get next line, if any if ((current_line + 1) < lines()) { nextLine = plainLine(current_line + 1); } else { nextLine = Kate::TextLine(new Kate::TextLineData()); } ctxChanged = false; m_highlight->doHighlight(prevLine.data(), textLine.data(), nextLine.data(), ctxChanged, tabWidth()); #ifdef BUFFER_DEBUGGING // debug stuff qCDebug(LOG_KTE) << "current line to hl: " << current_line; qCDebug(LOG_KTE) << "text length: " << textLine->length() << " attribute list size: " << textLine->attributesList().size(); const QVector &ml(textLine->attributesList()); for (int i = 2; i < ml.size(); i += 3) { qCDebug(LOG_KTE) << "start: " << ml.at(i - 2) << " len: " << ml.at(i - 1) << " at: " << ml.at(i) << " "; } qCDebug(LOG_KTE); #endif // need we to continue ? bool stillcontinue = ctxChanged; if (stillcontinue && start_spellchecking < 0) { start_spellchecking = current_line; } else if (!stillcontinue && start_spellchecking >= 0) { last_line_spellchecking = current_line; } // move around the lines prevLine = textLine; textLine = nextLine; } /** * perhaps we need to adjust the maximal highlighed line */ int oldHighlighted = m_lineHighlighted; if (ctxChanged || current_line > m_lineHighlighted) { m_lineHighlighted = current_line; } // tag the changed lines ! if (invalidate) { #ifdef BUFFER_DEBUGGING qCDebug(LOG_KTE) << "HIGHLIGHTED TAG LINES: " << startLine << current_line; #endif emit tagLines(startLine, qMax(current_line, oldHighlighted)); if (start_spellchecking >= 0 && lines() > 0) { emit respellCheckBlock(start_spellchecking, qMin(lines() - 1, (last_line_spellchecking == -1) ? qMax(current_line, oldHighlighted) : last_line_spellchecking)); } } #ifdef BUFFER_DEBUGGING qCDebug(LOG_KTE) << "HIGHLIGHTED END --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine; qCDebug(LOG_KTE) << "HL UNTIL LINE: " << m_lineHighlighted; qCDebug(LOG_KTE) << "HL DYN COUNT: " << KateHlManager::self()->countDynamicCtxs() << " MAX: " << m_maxDynamicContexts; qCDebug(LOG_KTE) << "TIME TAKEN: " << t.elapsed(); #endif } KTextEditor::Range KateBuffer::computeFoldingRangeForStartLine(int startLine) { /** * ensure valid input */ Q_ASSERT(startLine >= 0); Q_ASSERT(startLine < lines()); /** * no highlighting, no folding, ATM */ if (!m_highlight || m_highlight->noHighlighting()) { return KTextEditor::Range::invalid(); } /** * first: get the wanted start line highlighted */ ensureHighlighted(startLine); Kate::TextLine startTextLine = plainLine(startLine); /** * return if no folding start! */ if (!startTextLine->markedAsFoldingStart()) { return KTextEditor::Range::invalid(); } /** * now: decided if indentation based folding or not! */ if (startTextLine->markedAsFoldingStartIndentation()) { /** * get our start indentation level */ const int startIndentation = startTextLine->indentDepth(tabWidth()); /** * search next line with indentation level <= our one */ int lastLine = startLine + 1; for (; lastLine < lines(); ++lastLine) { /** * get line */ Kate::TextLine textLine = plainLine(lastLine); /** * indentation higher than our start line? continue */ if (startIndentation < textLine->indentDepth(tabWidth())) { continue; } /** * empty line? continue */ if (m_highlight->isEmptyLine(textLine.data())) { continue; } /** * else, break */ break; } /** * lastLine is always one too much */ --lastLine; /** * backtrack all empty lines, we don't want to add them to the fold! */ while (lastLine > startLine) { if (m_highlight->isEmptyLine(plainLine(lastLine).data())) { --lastLine; } else { break; } } /** * we shall not fold one-liners */ if (lastLine == startLine) { return KTextEditor::Range::invalid(); } /** * be done now */ return KTextEditor::Range(KTextEditor::Cursor(startLine, 0), KTextEditor::Cursor(lastLine, plainLine(lastLine)->length())); } /** * 'normal' attribute based folding, aka token based like '{' BLUB '}' */ /** * first step: search the first region type, that stays open for the start line */ short openedRegionType = 0; int openedRegionOffset = -1; { /** * mapping of type to "first" offset of it and current number of not matched openings */ QHash > foldingStartToOffsetAndCount; /** * walk over all attributes of the line and compute the matchings */ const QVector &startLineAttributes = startTextLine->attributesList(); for (int i = 0; i < startLineAttributes.size(); ++i) { /** * folding close? */ if (startLineAttributes[i].foldingValue < 0) { /** * search for this type, try to decrement counter, perhaps erase element! */ QHash >::iterator end = foldingStartToOffsetAndCount.find(-startLineAttributes[i].foldingValue); if (end != foldingStartToOffsetAndCount.end()) { if (end.value().second > 1) { --(end.value().second); } else { foldingStartToOffsetAndCount.erase(end); } } } /** * folding open? */ if (startLineAttributes[i].foldingValue > 0) { /** * search for this type, either insert it, with current offset or increment counter! */ QHash >::iterator start = foldingStartToOffsetAndCount.find(startLineAttributes[i].foldingValue); if (start != foldingStartToOffsetAndCount.end()) { ++(start.value().second); } else { foldingStartToOffsetAndCount.insert(startLineAttributes[i].foldingValue, qMakePair(startLineAttributes[i].offset, 1)); } } } /** * compute first type with offset */ QHashIterator > hashIt(foldingStartToOffsetAndCount); while (hashIt.hasNext()) { hashIt.next(); if (openedRegionOffset == -1 || hashIt.value().first < openedRegionOffset) { openedRegionType = hashIt.key(); openedRegionOffset = hashIt.value().first; } } } /** * no opening region found, bad, nothing to do */ if (openedRegionType == 0) { return KTextEditor::Range::invalid(); } /** * second step: search for matching end region marker! */ int countOfOpenRegions = 1; for (int line = startLine + 1; line < lines(); ++line) { /** * ensure line is highlighted */ ensureHighlighted(line); Kate::TextLine textLine = plainLine(line); /** * search for matching end marker */ const QVector &lineAttributes = textLine->attributesList(); for (int i = 0; i < lineAttributes.size(); ++i) { /** * matching folding close? */ if (lineAttributes[i].foldingValue == -openedRegionType) { --countOfOpenRegions; /** * end reached? * compute resulting range! */ if (countOfOpenRegions == 0) { /** * special handling of end: if end is at column 0 of a line, move it to end of previous line! * fixes folding for stuff like * #pragma mark END_OLD_AND_START_NEW_REGION */ KTextEditor::Cursor endCursor(line, lineAttributes[i].offset); if (endCursor.column() == 0 && endCursor.line() > 0) { endCursor = KTextEditor::Cursor(endCursor.line() - 1, plainLine(lines() - 1)->length()); } /** * return computed range */ return KTextEditor::Range(KTextEditor::Cursor(startLine, openedRegionOffset), endCursor); } } /** * matching folding open? */ if (lineAttributes[i].foldingValue == openedRegionType) { ++countOfOpenRegions; } } } /** * if we arrive here, the opened range spans to the end of the document! */ return KTextEditor::Range(KTextEditor::Cursor(startLine, openedRegionOffset), KTextEditor::Cursor(lines() - 1, plainLine(lines() - 1)->length())); } diff --git a/src/document/katebuffer.h b/src/document/katebuffer.h index 4956cf8c..d96003a8 100644 --- a/src/document/katebuffer.h +++ b/src/document/katebuffer.h @@ -1,294 +1,294 @@ /* This file is part of the KDE libraries Copyright (c) 2000 Waldo Bastian Copyright (C) 2002-2004 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. */ #ifndef __KATE_BUFFER_H__ #define __KATE_BUFFER_H__ #include "katetextbuffer.h" #include #include class KateLineInfo; namespace KTextEditor { class DocumentPrivate; } class KateHighlighting; /** * The KateBuffer class maintains a collections of lines. * * @author Waldo Bastian * @author Christoph Cullmann */ class KTEXTEDITOR_EXPORT KateBuffer : public Kate::TextBuffer { Q_OBJECT public: /** * Create an empty buffer. * @param doc parent document */ explicit KateBuffer(KTextEditor::DocumentPrivate *doc); /** * Goodbye buffer */ ~KateBuffer(); public: /** * start some editing action */ void editStart(); /** * finish some editing action */ void editEnd(); /** * were there changes in the current running * editing session? * @return changes done? */ inline bool editChanged() const { return editingChangedBuffer(); } /** * dirty lines start * @return start line */ inline int editTagStart() const { return editingMinimalLineChanged(); } /** * dirty lines end * @return end line */ inline int editTagEnd() const { return editingMaximalLineChanged(); } /** * line inserted/removed? * @return line inserted/removed? */ inline bool editTagFrom() const { return editingChangedNumberOfLines() != 0; } public: /** * Clear the buffer. */ void clear() Q_DECL_OVERRIDE; /** * Open a file, use the given filename * @param m_file filename to open * @param enforceTextCodec enforce to use only the set text codec * @return success */ bool openFile(const QString &m_file, bool enforceTextCodec); /** * Did encoding errors occur on load? * @return encoding errors occurred on load? */ bool brokenEncoding() const { return m_brokenEncoding; } /** * Too long lines wrapped on load? * @return too long lines wrapped on load? */ bool tooLongLinesWrapped() const { return m_tooLongLinesWrapped; } int longestLineLoaded() const { return m_longestLineLoaded; } /** * Can the current codec handle all chars * @return chars can be encoded */ bool canEncode(); /** * Save the buffer to a file, use the given filename + codec + end of line chars (internal use of qtextstream) * @param m_file filename to save to * @return success */ bool saveFile(const QString &m_file); public: /** * Return line @p lineno. * Highlighting of returned line might be out-dated, which may be sufficient * for pure text manipulation functions, like search/replace. * If you require highlighting to be up to date, call @ref ensureHighlighted * prior to this method. */ inline Kate::TextLine plainLine(int lineno) { if (lineno < 0 || lineno >= lines()) { return Kate::TextLine(); } return line(lineno); } /** * Update highlighting of given line @p line, if needed. * If @p line is already highlighted, this function does nothing. * If @p line is not highlighted, all lines up to line + lookAhead * are highlighted. * @param lookAhead also highlight these following lines */ void ensureHighlighted(int line, int lookAhead = 64); /** * Return the total number of lines in the buffer. */ inline int count() const { return lines(); } /** * Unwrap given line. * @param line line to unwrap */ void unwrapLine(int line) Q_DECL_OVERRIDE; /** * Wrap line at given cursor position. * @param position line/column as cursor where to wrap */ void wrapLine(const KTextEditor::Cursor &position) Q_DECL_OVERRIDE; public: inline int tabWidth() const { return m_tabWidth; } public: void setTabWidth(int w); /** * Use @p highlight for highlighting * * @p highlight may be 0 in which case highlighting * will be disabled. */ void setHighlight(int hlMode); KateHighlighting *highlight() { return m_highlight; } /** * Invalidate highlighting of whole buffer. */ void invalidateHighlighting(); /** * For a given line, compute the folding range that starts there * to be used to fold e.g. from the icon border * @param startLine start line * @return folding range starting at the given line or invalid range */ KTextEditor::Range computeFoldingRangeForStartLine(int startLine); private: /** * Highlight information needs to be updated. * * @param from first line in range * @param to last line in range - * @param invalidat should the rehighlighted lines be tagged ? + * @param invalidate should the rehighlighted lines be tagged? */ void doHighlight(int from, int to, bool invalidate); Q_SIGNALS: /** * Emitted when the highlighting of a certain range has * changed. */ void tagLines(int start, int end); void respellCheckBlock(int start, int end); private: /** * document we belong to */ KTextEditor::DocumentPrivate *const m_doc; /** * file loaded with encoding problems? */ bool m_brokenEncoding; /** * too long lines wrapped on load? */ bool m_tooLongLinesWrapped; /** * length of the longest line loaded */ int m_longestLineLoaded; /** * current highlighting mode or 0 */ KateHighlighting *m_highlight; // for the scrapty indent sensitive langs int m_tabWidth; /** * last line with valid highlighting */ int m_lineHighlighted; /** * number of dynamic contexts causing a full invalidation */ int m_maxDynamicContexts; }; #endif diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp index a546f42c..653f6cb4 100644 --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -1,5985 +1,6006 @@ /* 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" +#ifdef EDITORCONFIG_FOUND +#include "editorconfig.h" +#endif + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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), + m_activeView(nullptr), editSessionNumber(0), editIsRunning(false), m_undoMergeAllEdits(false), m_undoManager(new KateUndoManager(this)), m_editableMarks(markType01), - m_annotationModel(0), + m_annotationModel(nullptr), 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_prevModOnHdReason(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_onTheFlyChecker(nullptr), m_documentState(DocumentIdle), m_readWriteStateBeforeLoading(false), m_isUntitled(true), m_openingError(false) { /** * 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); + m_swapfile = (config()->swapFileMode() == KateDocumentConfig::DisableSwapFile) ? nullptr : 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() { // delete pending mod-on-hd message, if applicable delete m_modOnHdHandler; /** * we are about to delete cursors/ranges/... */ emit aboutToDeleteMovingInterfaceContent(this); // kill it early, it has ranges! delete m_onTheFlyChecker; - m_onTheFlyChecker = NULL; + m_onTheFlyChecker = nullptr; clearDictionaryRanges(); // Tell the world that we're about to close (== destruct) // Apps must receive this in a direct signal-slot connection, and prevent // any further use of interfaces once they return. emit aboutToClose(this); // remove file from dirwatch deactivateDirWatch(); // thanks for offering, KPart, but we're already self-destructing setAutoDeleteWidget(false); setAutoDeletePart(false); // clean up remaining views qDeleteAll (m_views.keys()); m_views.clear(); // cu marks for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { delete i.value(); } m_marks.clear(); delete m_config; KTextEditor::EditorPrivate::self()->deregisterDocument(this); } //END void KTextEditor::DocumentPrivate::saveEditingPositions(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; + return nullptr; } // does a widget exist already? use it! if (KTextEditor::Document::widget()) { return KTextEditor::Document::widget(); } // create and return one... - KTextEditor::View *view = (KTextEditor::View *)createView(0); + KTextEditor::View *view = (KTextEditor::View *)createView(nullptr); insertChildClient(view); view->setContextMenu(view->defaultContextMenu()); setWidget(view); return view; } //BEGIN KTextEditor::Document stuff KTextEditor::View *KTextEditor::DocumentPrivate::createView(QWidget *parent, KTextEditor::MainWindow *mainWindow) { KTextEditor::ViewPrivate *newView = new KTextEditor::ViewPrivate(this, parent, mainWindow); if (m_fileChangedDialogsActivated) { connect(newView, SIGNAL(focusIn(KTextEditor::View*)), this, SLOT(slotModifiedOnDisk())); } emit viewCreated(this, newView); // post existing messages to the new view, if no specific view is given foreach (KTextEditor::Message *message, m_messageHash.keys()) { if (!message->view()) { newView->postMessage(message, m_messageHash[message]); } } return newView; } KTextEditor::Range KTextEditor::DocumentPrivate::rangeOnLine(KTextEditor::Range range, int line) const { const int col1 = toVirtualColumn(range.start()); const int col2 = toVirtualColumn(range.end()); return KTextEditor::Range(line, fromVirtualColumn(line, col1), line, fromVirtualColumn(line, col2)); } //BEGIN KTextEditor::EditInterface stuff bool KTextEditor::DocumentPrivate::isEditingTransactionRunning() const { return editSessionNumber > 0; } QString KTextEditor::DocumentPrivate::text() const { return m_buffer->text(); } QString KTextEditor::DocumentPrivate::text(const KTextEditor::Range &range, bool blockwise) const { if (!range.isValid()) { qCWarning(LOG_KTE) << "Text requested for invalid range" << range; return QString(); } QString s; if (range.start().line() == range.end().line()) { if (range.start().column() > range.end().column()) { return QString(); } Kate::TextLine textLine = m_buffer->plainLine(range.start().line()); if (!textLine) { return QString(); } return textLine->string(range.start().column(), range.end().column() - range.start().column()); } else { for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i) { Kate::TextLine textLine = m_buffer->plainLine(i); if (!blockwise) { if (i == range.start().line()) { s.append(textLine->string(range.start().column(), textLine->length() - range.start().column())); } else if (i == range.end().line()) { s.append(textLine->string(0, range.end().column())); } else { s.append(textLine->string()); } } else { KTextEditor::Range subRange = rangeOnLine(range, i); s.append(textLine->string(subRange.start().column(), subRange.columnWidth())); } if (i < range.end().line()) { s.append(QLatin1Char('\n')); } } } return s; } QChar KTextEditor::DocumentPrivate::characterAt(const KTextEditor::Cursor &position) const { Kate::TextLine textLine = m_buffer->plainLine(position.line()); if (!textLine) { return QChar(); } return textLine->at(position.column()); } QString KTextEditor::DocumentPrivate::wordAt(const KTextEditor::Cursor &cursor) const { return text(wordRangeAt(cursor)); } KTextEditor::Range KTextEditor::DocumentPrivate::wordRangeAt(const KTextEditor::Cursor &cursor) const { // get text line const int line = cursor.line(); Kate::TextLine textLine = m_buffer->plainLine(line); if (!textLine) { return KTextEditor::Range::invalid(); } // make sure the cursor is const int lineLenth = textLine->length(); if (cursor.column() > lineLenth) { return KTextEditor::Range::invalid(); } int start = cursor.column(); int end = start; while (start > 0 && highlight()->isInWord(textLine->at(start - 1), textLine->attribute(start - 1))) { start--; } while (end < lineLenth && highlight()->isInWord(textLine->at(end), textLine->attribute(end))) { end++; } return KTextEditor::Range(line, start, line, end); } bool KTextEditor::DocumentPrivate::isValidTextPosition(const KTextEditor::Cursor& cursor) const { const int ln = cursor.line(); const int col = cursor.column(); // cursor in document range? if (ln < 0 || col < 0 || ln >= lines() || col > lineLength(ln)) { return false; } const QString str = line(ln); Q_ASSERT(str.length() >= col); // cursor at end of line? const int len = lineLength(ln); if (col == 0 || col == len) { return true; } // cursor in the middle of a valid utf32-surrogate? return (! str.at(col).isLowSurrogate()) || (! str.at(col-1).isHighSurrogate()); } QStringList KTextEditor::DocumentPrivate::textLines(const KTextEditor::Range &range, bool blockwise) const { QStringList ret; if (!range.isValid()) { qCWarning(LOG_KTE) << "Text requested for invalid range" << range; return ret; } if (blockwise && (range.start().column() > range.end().column())) { return ret; } if (range.start().line() == range.end().line()) { Q_ASSERT(range.start() <= range.end()); Kate::TextLine textLine = m_buffer->plainLine(range.start().line()); if (!textLine) { return ret; } ret << textLine->string(range.start().column(), range.end().column() - range.start().column()); } else { for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i) { Kate::TextLine textLine = m_buffer->plainLine(i); if (!blockwise) { if (i == range.start().line()) { ret << textLine->string(range.start().column(), textLine->length() - range.start().column()); } else if (i == range.end().line()) { ret << textLine->string(0, range.end().column()); } else { ret << textLine->string(); } } else { KTextEditor::Range subRange = rangeOnLine(range, i); ret << textLine->string(subRange.start().column(), subRange.columnWidth()); } } } return ret; } QString KTextEditor::DocumentPrivate::line(int line) const { Kate::TextLine l = m_buffer->plainLine(line); if (!l) { return QString(); } return l->string(); } bool KTextEditor::DocumentPrivate::setText(const QString &s) { if (!isReadWrite()) { return false; } QList msave; foreach (KTextEditor::Mark *mark, m_marks) { msave.append(*mark); } editStart(); // delete the text clear(); // insert the new text insertText(KTextEditor::Cursor(), s); editEnd(); foreach (KTextEditor::Mark mark, msave) { setMark(mark.line, mark.type); } return true; } bool KTextEditor::DocumentPrivate::setText(const QStringList &text) { if (!isReadWrite()) { return false; } QList msave; foreach (KTextEditor::Mark *mark, m_marks) { msave.append(*mark); } editStart(); // delete the text clear(); // insert the new text insertText(KTextEditor::Cursor::start(), text); editEnd(); foreach (KTextEditor::Mark mark, msave) { setMark(mark.line, mark.type); } return true; } bool KTextEditor::DocumentPrivate::clear() { if (!isReadWrite()) { return false; } foreach (KTextEditor::ViewPrivate *view, m_views) { view->clear(); view->tagAll(); view->update(); } clearMarks(); emit aboutToInvalidateMovingInterfaceContent(this); m_buffer->invalidateRanges(); emit aboutToRemoveText(documentRange()); return editRemoveLines(0, lastLine()); } bool KTextEditor::DocumentPrivate::insertText(const KTextEditor::Cursor &position, const QString &text, bool block) { if (!isReadWrite()) { return false; } if (text.isEmpty()) { return true; } editStart(); int currentLine = position.line(); int currentLineStart = 0; const int totalLength = text.length(); int insertColumn = position.column(); // pad with empty lines, if insert position is after last line if (position.line() > lines()) { int line = lines(); while (line <= position.line()) { editInsertLine(line, QString()); line++; } } const 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())); + + // restore if set by user, too! + m_fileTypeSetByUser = kconfig.readEntry("Mode Set By User", false); } } if (!flags.contains(QStringLiteral("SkipHighlighting"))) { // restore the hl stuff if (kconfig.hasKey("Highlighting")) { const int mode = KateHlManager::self()->nameFind(kconfig.readEntry("Highlighting")); if (mode >= 0) { m_buffer->setHighlight(mode); // restore if set by user, too! see bug 332605, otherwise we loose the hl later again on save m_hlSetByUser = kconfig.readEntry("Highlighting Set By User", false); } } } // indent mode config()->setIndentationMode(kconfig.readEntry("Indentation Mode", config()->indentationMode())); // Restore Bookmarks const QList marks = kconfig.readEntry("Bookmarks", QList()); for (int i = 0; i < marks.count(); i++) { addMark(marks.at(i), KTextEditor::DocumentPrivate::markType01); } } void KTextEditor::DocumentPrivate::writeSessionConfig(KConfigGroup &kconfig, const QSet &flags) { if (this->url().isLocalFile()) { const QString path = this->url().toLocalFile(); if (path.startsWith(QDir::tempPath())) { return; // inside tmp resource, do not save } } if (!flags.contains(QStringLiteral("SkipUrl"))) { // save url kconfig.writeEntry("URL", this->url().toString()); } if (!flags.contains(QStringLiteral("SkipEncoding"))) { // save encoding kconfig.writeEntry("Encoding", encoding()); } if (!flags.contains(QStringLiteral("SkipMode"))) { // save file type kconfig.writeEntry("Mode", m_fileType); + // save if set by user, too! + kconfig.writeEntry("Mode Set By User", m_fileTypeSetByUser); } if (!flags.contains(QStringLiteral("SkipHighlighting"))) { // save hl kconfig.writeEntry("Highlighting", highlight()->name()); // save if set by user, too! see bug 332605, otherwise we loose the hl later again on save kconfig.writeEntry("Highlighting Set By User", m_hlSetByUser); } // indent mode kconfig.writeEntry("Indentation Mode", config()->indentationMode()); // Save Bookmarks QList marks; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) if (i.value()->type & KTextEditor::MarkInterface::markType01) { marks << i.value()->line; } kconfig.writeEntry("Bookmarks", marks); } //END KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff uint KTextEditor::DocumentPrivate::mark(int line) { KTextEditor::Mark *m = m_marks.value(line); if (!m) { return 0; } return m->type; } void KTextEditor::DocumentPrivate::setMark(int line, uint markType) { clearMark(line); addMark(line, markType); } void KTextEditor::DocumentPrivate::clearMark(int line) { if (line < 0 || line > lastLine()) { return; } if (!m_marks.value(line)) { return; } KTextEditor::Mark *mark = m_marks.take(line); emit markChanged(this, *mark, MarkRemoved); emit marksChanged(this); delete mark; tagLines(line, line); repaintViews(true); } void KTextEditor::DocumentPrivate::addMark(int line, uint markType) { KTextEditor::Mark *mark; if (line < 0 || line > lastLine()) { return; } if (markType == 0) { return; } if ((mark = m_marks.value(line))) { // Remove bits already set markType &= ~mark->type; if (markType == 0) { return; } // Add bits mark->type |= markType; } else { mark = new KTextEditor::Mark; mark->line = line; mark->type = markType; m_marks.insert(line, mark); } // Emit with a mark having only the types added. KTextEditor::Mark temp; temp.line = line; temp.type = markType; emit markChanged(this, temp, MarkAdded); emit marksChanged(this); tagLines(line, line); repaintViews(true); } void KTextEditor::DocumentPrivate::removeMark(int line, uint markType) { if (line < 0 || line > lastLine()) { return; } KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { return; } // Remove bits not set markType &= mark->type; if (markType == 0) { return; } // Subtract bits mark->type &= ~markType; // Emit with a mark having only the types removed. KTextEditor::Mark temp; temp.line = line; temp.type = markType; emit markChanged(this, temp, MarkRemoved); if (mark->type == 0) { m_marks.remove(line); delete mark; } emit marksChanged(this); tagLines(line, line); repaintViews(true); } const QHash &KTextEditor::DocumentPrivate::marks() { return m_marks; } void KTextEditor::DocumentPrivate::requestMarkTooltip(int line, QPoint position) { KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { return; } bool handled = false; emit markToolTipRequested(this, *mark, position, handled); } bool KTextEditor::DocumentPrivate::handleMarkClick(int line) { + bool handled = false; KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { - return false; + emit markClicked(this, KTextEditor::Mark{line, 0}, handled); + } else { + emit markClicked(this, *mark, handled); } - bool handled = false; - emit markClicked(this, *mark, handled); - return handled; } bool KTextEditor::DocumentPrivate::handleMarkContextMenu(int line, QPoint position) { + bool handled = false; KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { - return false; + emit markContextMenuRequested(this, KTextEditor::Mark{line, 0}, position, handled); + } else { + emit markContextMenuRequested(this, *mark, position, handled); } - 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() { /** * collect first 4k of text * only heuristic */ QByteArray buf; for (int i = 0; (i < lines()) && (buf.size() <= 4096); ++i) { buf.append(line(i).toUtf8()); buf.append('\n'); } // use path of url, too, if set if (!url().path().isEmpty()) { return QMimeDatabase().mimeTypeForFileNameAndData(url().path(), buf).name(); } // else only use the content return QMimeDatabase().mimeTypeForData(buf).name(); } //END KTextEditor::DocumentInfoInterface //BEGIN: error void KTextEditor::DocumentPrivate::showAndSetOpeningErrorAccess() { QPointer message = new KTextEditor::Message(i18n("The file %1 could not be loaded, as it was not possible to read from it.
Check if you have read access to this file.", this->url().toDisplayString(QUrl::PreferLocalFile)), KTextEditor::Message::Error); message->setWordWrap(true); - QAction *tryAgainAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("translators: you can also translate 'Try Again' with 'Reload'", "Try Again"), 0); + QAction *tryAgainAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("translators: you can also translate 'Try Again' with 'Reload'", "Try Again"), nullptr); connect(tryAgainAction, SIGNAL(triggered()), SLOT(documentReload()), Qt::QueuedConnection); - QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), 0); + QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), nullptr); closeAction->setToolTip(i18n("Close message")); // add try again and close actions message->addAction(tryAgainAction); message->addAction(closeAction); // finally post message postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 could not be loaded, as it was not possible to read from it.\n\nCheck if you have read access to this file.", this->url().toDisplayString(QUrl::PreferLocalFile)); } //END: error void KTextEditor::DocumentPrivate::openWithLineLengthLimitOverride() { // raise line length limit to the next power of 2 const int longestLine = m_buffer->longestLineLoaded(); - int newLimit = pow(2, ceil(log(longestLine) / log(2))); // TODO: C++11: use log2(x) + int newLimit = pow(2, ceil(log2(longestLine))); if (newLimit <= longestLine) { newLimit *= 2; } // do the raise config()->setLineLengthLimit(newLimit); // just reload m_buffer->clear(); openFile(); if (!m_openingError) { setReadWrite(true); m_readWriteStateBeforeLoading = true; } } int KTextEditor::DocumentPrivate::lineLengthLimit() const { return config()->lineLengthLimit(); } //BEGIN KParts::ReadWrite stuff bool KTextEditor::DocumentPrivate::openFile() { /** * we are about to invalidate all cursors/ranges/.. => m_buffer->openFile will do so */ emit aboutToInvalidateMovingInterfaceContent(this); // no open errors until now... m_openingError = false; m_openingErrorMessage.clear (); // add new m_file to dirwatch activateDirWatch(); // remember current encoding QString currentEncoding = encoding(); // // mime type magic to get encoding right // QString mimeType = arguments().mimeType(); int pos = mimeType.indexOf(QLatin1Char(';')); if (pos != -1 && !(m_reloading && m_userSetEncodingForNextReload)) { setEncoding(mimeType.mid(pos + 1)); } // update file type, we do this here PRE-LOAD, therefore pass file name for reading from updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, localFilePath())); // read dir config (if possible and wanted) // do this PRE-LOAD to get encoding info! readDirConfig(); // perhaps we need to re-set again the user encoding if (m_reloading && m_userSetEncodingForNextReload && (currentEncoding != encoding())) { setEncoding(currentEncoding); } bool success = m_buffer->openFile(localFilePath(), (m_reloading && m_userSetEncodingForNextReload)); // // yeah, success // read variables // if (success) { readVariables(); } // // update views // foreach (KTextEditor::ViewPrivate *view, m_views) { // This is needed here because inserting the text moves the view's start position (it is a MovingCursor) view->setCursorPosition(KTextEditor::Cursor()); view->updateView(true); } // Inform that the text has changed (required as we're not inside the usual editStart/End stuff) emit textChanged(this); emit loaded(this); // // to houston, we are not modified // if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // // display errors // if (!success) { showAndSetOpeningErrorAccess(); } // warn: broken encoding if (m_buffer->brokenEncoding()) { // this file can't be saved again without killing it setReadWrite(false); m_readWriteStateBeforeLoading=false; QPointer message = new KTextEditor::Message(i18n("The file %1 was opened with %2 encoding but contained invalid characters.
" "It is set to read-only mode, as saving might destroy its content.
" "Either reopen the file with the correct encoding chosen or enable the read-write mode again in the tools menu to be able to edit it.", this->url().toDisplayString(QUrl::PreferLocalFile), QString::fromLatin1(m_buffer->textCodec()->name())), KTextEditor::Message::Warning); message->setWordWrap(true); postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 was opened with %2 encoding but contained invalid characters." " It is set to read-only mode, as saving might destroy its content." " Either reopen the file with the correct encoding chosen or enable the read-write mode again in the tools menu to be able to edit it.", this->url().toDisplayString(QUrl::PreferLocalFile), QString::fromLatin1(m_buffer->textCodec()->name())); } // warn: too long lines if (m_buffer->tooLongLinesWrapped()) { // this file can't be saved again without modifications setReadWrite(false); m_readWriteStateBeforeLoading = false; QPointer message = new KTextEditor::Message(i18n("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).
" "The longest of those lines was %3 characters long
" "Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().toDisplayString(QUrl::PreferLocalFile), config()->lineLengthLimit(), m_buffer->longestLineLoaded()), KTextEditor::Message::Warning); QAction *increaseAndReload = new QAction(i18n("Temporarily raise limit and reload file"), message); connect(increaseAndReload, SIGNAL(triggered()), this, SLOT(openWithLineLengthLimitOverride())); message->addAction(increaseAndReload, true); message->addAction(new QAction(i18n("Close"), message), true); message->setWordWrap(true); postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).
" "The longest of those lines was %3 characters long
" "Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().toDisplayString(QUrl::PreferLocalFile), config()->lineLengthLimit(),m_buffer->longestLineLoaded()); } // // return the success // return success; } bool KTextEditor::DocumentPrivate::saveFile() { // delete pending mod-on-hd message if applicable. delete m_modOnHdHandler; // some warnings, if file was changed by the outside! if (!url().isEmpty()) { if (m_fileChangedDialogsActivated && m_modOnHd) { QString str = reasonedMOHString() + QLatin1String("\n\n"); if (!isModified()) { if (KMessageBox::warningContinueCancel(dialogParent(), str + i18n("Do you really want to save this unmodified file? You could overwrite changed data in the file on disk."), i18n("Trying to Save Unmodified File"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue) { return false; } } else { if (KMessageBox::warningContinueCancel(dialogParent(), str + i18n("Do you really want to save this file? Both your open file and the file on disk were changed. There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue) { return false; } } } } // // can we encode it if we want to save it ? // if (!m_buffer->canEncode() && (KMessageBox::warningContinueCancel(dialogParent(), - i18n("The selected encoding cannot encode every unicode character in this document. Do you really want to save it? There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue)) { + i18n("The selected encoding cannot encode every Unicode character in this document. Do you really want to save it? There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue)) { return false; } /** * create a backup file or abort if that fails! * if no backup file wanted, this routine will just return true */ if (!createBackupFile()) return false; // update file type, pass no file path, read file type content from this document - 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()); + // only update file type if path has changed so that variables are not overridden on normal save + if (oldPath != localFilePath()) { + updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, QString())); - if (fo.path() != fn.path()) { + if (url().isLocalFile()) { + // if file is local then read dir config for new path readDirConfig(); } } // read our vars readVariables(); // remove file from dirwatch deactivateDirWatch(); // remove all trailing spaces in the document (as edit actions) // NOTE: we need this as edit actions, since otherwise the edit actions // in the swap file recovery may happen at invalid cursor positions removeTrailingSpaces(); // // try to save // if (!m_buffer->saveFile(localFilePath())) { // add m_file again to dirwatch activateDirWatch(oldPath); KMessageBox::error(dialogParent(), i18n("The document could not be saved, as it was not possible to write to %1.\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; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // (dominik) mark last undo group as not mergeable, otherwise the next // edit action might be merged and undo will never stop at the saved state m_undoManager->undoSafePoint(); m_undoManager->updateLineModifications(); // // return success // return true; } bool KTextEditor::DocumentPrivate::createBackupFile() { /** * backup for local or remote files wanted? */ const bool backupLocalFiles = (config()->backupFlags() & KateDocumentConfig::LocalFiles); const bool backupRemoteFiles = (config()->backupFlags() & KateDocumentConfig::RemoteFiles); /** * early out, before mount check: backup wanted at all? * => if not, all fine, just return */ if (!backupLocalFiles && !backupRemoteFiles) { return true; } /** * decide if we need backup based on locality * skip that, if we always want backups, as currentMountPoints is not that fast */ QUrl u(url()); bool needBackup = backupLocalFiles && backupRemoteFiles; if (!needBackup) { bool slowOrRemoteFile = !u.isLocalFile(); if (!slowOrRemoteFile) { // could be a mounted remote filesystem (e.g. nfs, sshfs, cifs) // we have the early out above to skip this, if we want no backup, which is the default KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByDevice(u.toLocalFile()); slowOrRemoteFile = (mountPoint && mountPoint->probablySlow()); } needBackup = (!slowOrRemoteFile && backupLocalFiles) || (slowOrRemoteFile && backupRemoteFiles); } /** * no backup needed? be done */ if (!needBackup) { return true; } /** * else: try to backup */ if (config()->backupPrefix().contains(QDir::separator())) { /** * replace complete path, as prefix is a path! */ u.setPath(config()->backupPrefix() + u.fileName() + config()->backupSuffix()); } else { /** * replace filename in url */ const QString fileName = u.fileName(); u = u.adjusted(QUrl::RemoveFilename); u.setPath(u.path() + config()->backupPrefix() + fileName + config()->backupSuffix()); } qCDebug(LOG_KTE) << "backup src file name: " << url(); qCDebug(LOG_KTE) << "backup dst file name: " << u; // handle the backup... bool backupSuccess = false; // local file mode, no kio if (u.isLocalFile()) { if (QFile::exists(url().toLocalFile())) { // first: check if backupFile is already there, if true, unlink it QFile backupFile(u.toLocalFile()); if (backupFile.exists()) { backupFile.remove(); } backupSuccess = QFile::copy(url().toLocalFile(), u.toLocalFile()); } else { backupSuccess = true; } } else { // remote file mode, kio // get the right permissions, start with safe default KIO::StatJob *statJob = KIO::stat(url(), KIO::StatJob::SourceSide, 2); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); if (!statJob->exec()) { // do a evil copy which will overwrite target if possible KFileItem item(statJob->statResult(), url()); KIO::FileCopyJob *job = KIO::file_copy(url(), u, item.permissions(), KIO::Overwrite); KJobWidgets::setWindow(job, QApplication::activeWindow()); job->exec(); backupSuccess = !job->error(); } else { backupSuccess = true; } } // backup has failed, ask user how to proceed if (!backupSuccess && (KMessageBox::warningContinueCancel(dialogParent() , i18n("For file %1 no backup copy could be created before saving." " If an error occurs while saving, you might lose the data of this file." " A reason could be that the media you write to is full or the directory of the file is read-only for you.", url().toDisplayString(QUrl::PreferLocalFile)) , i18n("Failed to create backup copy.") , KGuiItem(i18n("Try to Save Nevertheless")) , KStandardGuiItem::cancel(), QStringLiteral("Backup Failed Warning")) != KMessageBox::Continue)) { return false; } return true; } void KTextEditor::DocumentPrivate::readDirConfig() { if (!url().isLocalFile()) { return; } /** - * search .kateconfig upwards + * first search .kateconfig upwards * with recursion guard */ QSet seenDirectories; QDir dir (QFileInfo(localFilePath()).absolutePath()); while (!seenDirectories.contains (dir.absolutePath ())) { /** * fill recursion guard */ seenDirectories.insert (dir.absolutePath ()); // try to open config file in this dir QFile f(dir.absolutePath () + QLatin1String("/.kateconfig")); if (f.open(QIODevice::ReadOnly)) { QTextStream stream(&f); uint linesRead = 0; QString line = stream.readLine(); while ((linesRead < 32) && !line.isNull()) { readVariableLine(line); line = stream.readLine(); linesRead++; } - break; + return; } /** * else: cd up, if possible or abort */ if (!dir.cdUp()) { break; } } + +#ifdef EDITORCONFIG_FOUND + // if there wasn’t any .kateconfig file and KTextEditor was compiled with + // EditorConfig support, try to load document config from a .editorconfig + // file, if such is provided + EditorConfig editorConfig(this); + editorConfig.parse(); +#endif } void KTextEditor::DocumentPrivate::activateDirWatch(const QString &useFileName) { QString fileToUse = useFileName; if (fileToUse.isEmpty()) { fileToUse = localFilePath(); } QFileInfo fileInfo = QFileInfo(fileToUse); if (fileInfo.isSymLink()) { // Monitor the actual data and not the symlink fileToUse = fileInfo.canonicalFilePath(); } // same file as we are monitoring, return if (fileToUse == m_dirWatchFile) { return; } // remove the old watched file deactivateDirWatch(); // add new file if needed if (url().isLocalFile() && !fileToUse.isEmpty()) { KTextEditor::EditorPrivate::self()->dirWatch()->addFile(fileToUse); m_dirWatchFile = fileToUse; } } void KTextEditor::DocumentPrivate::deactivateDirWatch() { if (!m_dirWatchFile.isEmpty()) { KTextEditor::EditorPrivate::self()->dirWatch()->removeFile(m_dirWatchFile); } m_dirWatchFile.clear(); } bool KTextEditor::DocumentPrivate::openUrl(const QUrl &url) { + if (!m_reloading) { + // Reset filetype when opening url + m_fileTypeSetByUser = false; + } bool res = KTextEditor::Document::openUrl(normalizeUrl(url)); updateDocName(); return res; } bool KTextEditor::DocumentPrivate::closeUrl() { // // file mod on hd // if (!m_reloading && !url().isEmpty()) { if (m_fileChangedDialogsActivated && m_modOnHd) { // make sure to not forget pending mod-on-hd handler delete m_modOnHdHandler; QWidget *parentWidget(dialogParent()); if (!(KMessageBox::warningContinueCancel( parentWidget, reasonedMOHString() + QLatin1String("\n\n") + i18n("Do you really want to continue to close this file? Data loss may occur."), i18n("Possible Data Loss"), KGuiItem(i18n("Close Nevertheless")), KStandardGuiItem::cancel(), QStringLiteral("kate_close_modonhd_%1").arg(m_modOnHdReason)) == KMessageBox::Continue)) { /** * reset reloading */ m_reloading = false; return false; } } } // // first call the normal kparts implementation // if (!KParts::ReadWritePart::closeUrl()) { /** * reset reloading */ m_reloading = false; return false; } // Tell the world that we're about to go ahead with the close if (!m_reloading) { emit aboutToClose(this); } /** * delete all KTE::Messages */ if (!m_messageHash.isEmpty()) { QList keys = m_messageHash.keys(); foreach (KTextEditor::Message *message, keys) { delete message; } } /** * we are about to invalidate all cursors/ranges/.. => m_buffer->clear will do so */ emit aboutToInvalidateMovingInterfaceContent(this); // remove file from dirwatch deactivateDirWatch(); // // empty url + fileName // setUrl(QUrl()); setLocalFilePath(QString()); // we are not modified if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // remove all marks clearMarks(); // clear the buffer m_buffer->clear(); // clear undo/redo history m_undoManager->clearUndo(); m_undoManager->clearRedo(); // no, we are no longer modified setModified(false); // we have no longer any hl m_buffer->setHighlight(0); // update all our views foreach (KTextEditor::ViewPrivate *view, m_views) { view->clearSelection(); // fix bug #118588 view->clear(); } // purge swap file if (m_swapfile) { m_swapfile->fileClosed(); } // success return true; } bool KTextEditor::DocumentPrivate::isDataRecoveryAvailable() const { return m_swapfile && m_swapfile->shouldRecover(); } void KTextEditor::DocumentPrivate::recoverData() { if (isDataRecoveryAvailable()) { m_swapfile->recover(); } } void KTextEditor::DocumentPrivate::discardDataRecovery() { if (isDataRecoveryAvailable()) { m_swapfile->discard(); } } void KTextEditor::DocumentPrivate::setReadWrite(bool rw) { if (isReadWrite() == rw) { return; } KParts::ReadWritePart::setReadWrite(rw); foreach (KTextEditor::ViewPrivate *view, m_views) { view->slotUpdateUndo(); view->slotReadWriteChanged(); } emit readWriteChanged(this); } void KTextEditor::DocumentPrivate::setModified(bool m) { if (isModified() != m) { KParts::ReadWritePart::setModified(m); foreach (KTextEditor::ViewPrivate *view, m_views) { view->slotUpdateUndo(); } emit modifiedChanged(this); } m_undoManager->setModified(m); } //END //BEGIN Kate specific stuff ;) void KTextEditor::DocumentPrivate::makeAttribs(bool needInvalidate) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->renderer()->updateAttributes(); } if (needInvalidate) { m_buffer->invalidateHighlighting(); } foreach (KTextEditor::ViewPrivate *view, m_views) { view->tagAll(); view->updateView(true); } } // the attributes of a hl have changed, update void KTextEditor::DocumentPrivate::internalHlChanged() { makeAttribs(); } void KTextEditor::DocumentPrivate::addView(KTextEditor::View *view) { Q_ASSERT (!m_views.contains(view)); m_views.insert(view, static_cast(view)); // 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); + setActiveView(nullptr); } } void KTextEditor::DocumentPrivate::setActiveView(KTextEditor::View *view) { if (m_activeView == view) { return; } m_activeView = static_cast(view); } bool KTextEditor::DocumentPrivate::ownedView(KTextEditor::ViewPrivate *view) { // do we own the given view? return (m_views.contains(view)); } int KTextEditor::DocumentPrivate::toVirtualColumn(int line, int column) const { Kate::TextLine textLine = m_buffer->plainLine(line); if (textLine) { return textLine->toVirtualColumn(column, config()->tabWidth()); } else { return 0; } } int KTextEditor::DocumentPrivate::toVirtualColumn(const KTextEditor::Cursor &cursor) const { return toVirtualColumn(cursor.line(), cursor.column()); } int KTextEditor::DocumentPrivate::fromVirtualColumn(int line, int column) const { Kate::TextLine textLine = m_buffer->plainLine(line); if (textLine) { return textLine->fromVirtualColumn(column, config()->tabWidth()); } else { return 0; } } int KTextEditor::DocumentPrivate::fromVirtualColumn(const KTextEditor::Cursor &cursor) const { return fromVirtualColumn(cursor.line(), cursor.column()); } bool KTextEditor::DocumentPrivate::typeChars(KTextEditor::ViewPrivate *view, const QString &realChars) { /** * filter out non-printable chars (convert to utf-32 to support surrogate pairs) */ const auto realUcs4Chars = realChars.toUcs4(); QVector ucs4Chars; Q_FOREACH (auto c, realUcs4Chars) if (QChar::isPrint(c) || c == QChar::fromLatin1('\t') || c == QChar::fromLatin1('\n') || c == QChar::fromLatin1('\r')) { ucs4Chars.append(c); } QString chars = QString::fromUcs4(ucs4Chars.data(), ucs4Chars.size()); /** * no printable chars => nothing to insert! */ if (chars.isEmpty()) { return false; } /** * always unfreeze on typing */ view->cursors()->setSecondaryFrozen(false); /** * auto bracket handling for newly inserted text * remember if we should auto add some */ QChar closingBracket; if (view->config()->autoBrackets() && chars.size() == 1 && !view->cursors()->hasSecondaryCursors()) { /** * 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; } } /** * 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(); } auto oldCursors = view->allCursors(); if (view->currentInputMode()->overwrite()) { Q_FOREACH ( const auto& cursor, view->allCursors() ) { auto line = cursor.line(); auto virtualColumn = toVirtualColumn(view->cursorPosition()); 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 #warning fix this: backspace in overwrite mode restores characters // if (oldCur.column() < lineLength(line)) { // QChar removed = characterAt(KTextEditor::Cursor(line, column)); // view->currentInputMode()->overwrittenChar(removed); // } removeText(r); } } Q_FOREACH ( const auto& cursor, view->allCursors() ) { auto adjusted = eventuallyReplaceTabs(cursor, chars); insertText(cursor, adjusted); } /** * auto bracket handling for newly inserted text * we inserted a bracket? * => add the matching closing one to the view + input chars * try to preserve the cursor position */ bool skipAutobrace = closingBracket == QLatin1Char('\''); if ( highlight() && skipAutobrace ) { auto context = highlight()->contextForLocation(this, view->cursorPosition() - Cursor{0, 1}); // skip adding ' in spellchecked areas, because those are text skipAutobrace = !context || highlight()->attributeRequiresSpellchecking(context->attr); } if (!closingBracket.isNull() && !skipAutobrace ) { // add bracket to the view Q_FOREACH ( const auto& cursorPos, view->allCursors() ) { 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(); if ( ! view->cursors()->hasSecondaryCursors() ) { // 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 */ Q_FOREACH ( const auto& oldCur, oldCursors ) { 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 Q_FOREACH ( auto c, v->allCursors() ) { 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) { /** * always unfreeze secondary cursors on typing */ view->cursors()->setSecondaryFrozen(false); if (!view->config()->persistentSelection() && view->selection()) { auto range = view->selectionRange(); // only bail out on non-zero-width selections, the rest is handled below if ( ! view->blockSelection() || range.start().column() != range.end().column() ) { 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 #warning how do we solve this for 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; } if ( view->blockSelection() && col > textLine->length() ) { view->clearSelection(false); insertText(c, QStringLiteral(" "), true); removeText({c-KTextEditor::Cursor{0, 1}, c}, true); } 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) { Q_ASSERT(false); // TODO this function should go away static const QChar newLineChar(QLatin1Char('\n')); QString s = text; if (s.isEmpty()) { return; } int lines = s.count(newLineChar); auto paste_text_at = [this, view, s, lines](const KTextEditor::Cursor& pos) { editStart(); #warning TODO handle overwrite mode /** 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(); editStart(); if (!view->config()->persistentSelection() && view->selection()) { auto 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(); } editEnd(); Q_FOREACH ( const auto& cursor, view->allCursors() ) { paste_text_at(cursor); } 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; } editStart(); Q_FOREACH ( auto c, view->allCursors() ) { int lineLen = line(c.line()).length(); if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); } else if (view->currentInputMode()->overwrite() && c.column() < lineLen) { KTextEditor::Range r = KTextEditor::Range(c, 1); // replace mode needs to know what was removed so it can be restored with backspace QChar removed = line(c.line()).at(r.start().column()); view->currentInputMode()->overwrittenChar(removed); removeText(r); } 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())); if (change > 0) { // comment if (!hassel) { if (hasStartLineCommentMark) { addStartLineCommentToSingleLine(line, startAttrib); } else if (hasStartStopCommentMark) { addStartStopCommentToSingleLine(line, startAttrib); } } else { // anders: prefer single line comment to avoid nesting probs // If the selection starts after first char in the first line // or ends before the last char of the last line, we may use // multiline comment markers. // TODO We should try to detect nesting. // - if selection ends at col 0, most likely she wanted that // line ignored const KTextEditor::Range sel = v->selectionRange(); if (hasStartStopCommentMark && (!hasStartLineCommentMark || ( (sel.start().column() > m_buffer->plainLine(sel.start().line())->firstChar()) || (sel.end().column() > 0 && sel.end().column() < (m_buffer->plainLine(sel.end().line())->length())) ))) { addStartStopCommentToSelection(v, startAttrib); } else if (hasStartLineCommentMark) { addStartLineCommentToSelection(v, startAttrib); } } } else { // uncomment bool removed = false; if (!hassel) { removed = (hasStartLineCommentMark && removeStartLineCommentFromSingleLine(line, startAttrib)) || (hasStartStopCommentMark && removeStartStopCommentFromSingleLine(line, startAttrib)); } else { // anders: this seems like it will work with above changes :) removed = (hasStartStopCommentMark && removeStartStopCommentFromSelection(v, startAttrib)) || (hasStartLineCommentMark && removeStartLineCommentFromSelection(v, startAttrib)); } // recursive call for toggle comment if (!removed && change == 0) { comment(v, line, column, 1); } } if (skipWordWrap) { setWordWrap(true); // see begin of function ::comment (bug #105373) } } void KTextEditor::DocumentPrivate::transform(KTextEditor::ViewPrivate *v, const KTextEditor::Cursor &c, KTextEditor::DocumentPrivate::TextTransform t) { if (v->selection()) { editStart(); // cache the selection and cursor, so we can be sure to restore. KTextEditor::Range selection = v->selectionRange(); KTextEditor::Range range(selection.start(), 0); while (range.start().line() <= selection.end().line()) { int start = 0; int end = lineLength(range.start().line()); if (range.start().line() == selection.start().line() || v->blockSelection()) { start = selection.start().column(); } if (range.start().line() == selection.end().line() || v->blockSelection()) { end = selection.end().column(); } if (start > end) { int swapCol = start; start = end; end = swapCol; } range.setStart(KTextEditor::Cursor(range.start().line(), start)); range.setEnd(KTextEditor::Cursor(range.end().line(), end)); QString s = text(range); QString old = s; if (t == Uppercase) { s = s.toUpper(); } else if (t == Lowercase) { s = s.toLower(); } else { // Capitalize Kate::TextLine l = m_buffer->plainLine(range.start().line()); int p(0); while (p < s.length()) { // If bol or the character before is not in a word, up this one: // 1. if both start and p is 0, upper char. // 2. if blockselect or first line, and p == 0 and start-1 is not in a word, upper // 3. if p-1 is not in a word, upper. if ((! range.start().column() && ! p) || ((range.start().line() == selection.start().line() || v->blockSelection()) && ! p && ! highlight()->isInWord(l->at(range.start().column() - 1))) || (p && ! highlight()->isInWord(s.at(p - 1))) ) { s[p] = s.at(p).toUpper(); } p++; } } if (s != old) { removeText(range); insertText(range.start(), s); } range.setBothLines(range.start().line() + 1); } editEnd(); // restore selection & cursor v->setSelection(selection); v->setCursorPosition(c); } else { // no selection editStart(); // get cursor KTextEditor::Cursor cursor = c; QString old = text(KTextEditor::Range(cursor, 1)); QString s; switch (t) { case Uppercase: s = old.toUpper(); break; case Lowercase: s = old.toLower(); break; case Capitalize: { Kate::TextLine l = m_buffer->plainLine(cursor.line()); while (cursor.column() > 0 && highlight()->isInWord(l->at(cursor.column() - 1), l->attribute(cursor.column() - 1))) { cursor.setColumn(cursor.column() - 1); } old = text(KTextEditor::Range(cursor, 1)); s = old.toUpper(); } break; default: break; } removeText(KTextEditor::Range(cursor, 1)); insertText(cursor, s); editEnd(); } } void KTextEditor::DocumentPrivate::joinLines(uint first, uint last) { // if ( first == last ) last += 1; editStart(); int line(first); while (first < last) { // Normalize the whitespace in the joined lines by making sure there's // always exactly one space between the joined lines // This cannot be done in editUnwrapLine, because we do NOT want this // behavior when deleting from the start of a line, just when explicitly // calling the join command Kate::TextLine l = kateTextLine(line); Kate::TextLine tl = kateTextLine(line + 1); if (!l || !tl) { editEnd(); return; } int pos = tl->firstChar(); if (pos >= 0) { if (pos != 0) { editRemoveText(line + 1, 0, pos); } if (!(l->length() == 0 || l->at(l->length() - 1).isSpace())) { editInsertText(line + 1, 0, QLatin1String(" ")); } } else { // Just remove the whitespace and let Kate handle the rest editRemoveText(line + 1, 0, tl->length()); } editUnWrapLine(line); first++; } editEnd(); } void KTextEditor::DocumentPrivate::tagLines(int start, int end) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->tagLines(start, end, true); } } void KTextEditor::DocumentPrivate::repaintViews(bool paintOnlyDirty) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->repaintText(paintOnlyDirty); } } /* Bracket matching uses the following algorithm: If in overwrite mode, match the bracket currently underneath the cursor. Otherwise, if the character to the left is a bracket, match it. Otherwise if the character to the right of the cursor is a bracket, match it. Otherwise, don't match anything. */ KTextEditor::Range KTextEditor::DocumentPrivate::findMatchingBracket(const KTextEditor::Cursor &start, int maxLines) { if (maxLines < 0) { return KTextEditor::Range::invalid(); } Kate::TextLine textLine = m_buffer->plainLine(start.line()); if (!textLine) { return KTextEditor::Range::invalid(); } KTextEditor::Range range(start, start); const QChar right = textLine->at(range.start().column()); const QChar left = textLine->at(range.start().column() - 1); QChar bracket; if (config()->ovr()) { if (isBracket(right)) { bracket = right; } else { return KTextEditor::Range::invalid(); } } else if (isBracket(right)) { bracket = right; } else if (isBracket(left)) { range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1)); bracket = left; } else { return KTextEditor::Range::invalid(); } const QChar opposite = matchingBracket(bracket, 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 (url().isEmpty() || !m_modOnHd) { return; } if (!m_fileChangedDialogsActivated || m_modOnHdHandler) { return; } // don't ask the user again and again the same thing if (m_modOnHdReason == m_prevModOnHdReason) { return; } m_prevModOnHdReason = m_modOnHdReason; m_modOnHdHandler = new KateModOnHdPrompt(this, m_modOnHdReason, reasonedMOHString()); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::saveAsTriggered, this, &DocumentPrivate::onModOnHdSaveAs); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::reloadTriggered, this, &DocumentPrivate::onModOnHdReload); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::ignoreTriggered, this, &DocumentPrivate::onModOnHdIgnore); } void KTextEditor::DocumentPrivate::onModOnHdSaveAs() { m_modOnHd = false; QWidget *parentWidget(dialogParent()); const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (!res.isEmpty() && checkOverwrite(res, parentWidget)) { if (! saveAs(res)) { KMessageBox::error(parentWidget, i18n("Save failed")); m_modOnHd = true; } else { delete m_modOnHdHandler; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); } } else { // the save as dialog was canceled, we are still modified on disk m_modOnHd = true; } } void KTextEditor::DocumentPrivate::onModOnHdReload() { m_modOnHd = false; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); documentReload(); delete m_modOnHdHandler; } void KTextEditor::DocumentPrivate::onModOnHdIgnore() { // ignore as long as m_prevModOnHdReason == m_modOnHdReason delete m_modOnHdHandler; } void KTextEditor::DocumentPrivate::setModifiedOnDisk(ModifiedOnDiskReason reason) { m_modOnHdReason = reason; m_modOnHd = (reason != OnDiskUnmodified); emit modifiedOnDisk(this, (reason != OnDiskUnmodified), reason); } class KateDocumentTmpMark { public: QString line; KTextEditor::Mark mark; }; void KTextEditor::DocumentPrivate::setModifiedOnDiskWarning(bool on) { m_fileChangedDialogsActivated = on; } bool KTextEditor::DocumentPrivate::documentReload() { if (url().isEmpty()) { return false; } // typically, the message for externally modified files is visible. Since it // does not make sense showing an additional dialog, just hide the message. delete m_modOnHdHandler; 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; m_prevModOnHdReason = 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(), {}, nullptr, QFileDialog::DontConfirmOverwrite); 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(), {}, nullptr, QFileDialog::DontConfirmOverwrite); 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(), {}, nullptr, QFileDialog::DontConfirmOverwrite); 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("folding-preview") << QStringLiteral("bookmark-sorting") << QStringLiteral("auto-center-lines") << QStringLiteral("icon-bar-color") << QStringLiteral("scrollbar-minimap") << QStringLiteral("scrollbar-preview") // 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) { /** * set eol + avoid that it is overwritten by auto-detection again! * this fixes e.g. .kateconfig files with // kate: eol dos; to work, bug 365705 */ m_config->setEol(n); m_config->setAllowEolDetection(false); } - } else if (var == QLatin1String("bom") || var == QLatin1String("byte-order-marker")) { + } else if (var == QLatin1String("bom") || var == QLatin1String("byte-order-mark") || var == QLatin1String("byte-order-marker")) { if (checkBoolValue(val, &state)) { m_config->setBom(state); } } else if (var == QLatin1String("remove-trailing-spaces")) { val = val.toLower(); if (val == QLatin1String("1") || val == QLatin1String("modified") || val == QLatin1String("mod") || val == QLatin1String("+")) { m_config->setRemoveSpaces(1); } else if (val == QLatin1String("2") || val == QLatin1String("all") || val == QLatin1String("*")) { m_config->setRemoveSpaces(2); } else { m_config->setRemoveSpaces(0); } } else if (var == QLatin1String("syntax") || var == QLatin1String("hl")) { setHighlightingMode(val); } else if (var == QLatin1String("mode")) { setMode(val); } else if (var == QLatin1String("encoding")) { setEncoding(val); } else if (var == QLatin1String("default-dictionary")) { setDefaultDictionary(val); } else if (var == QLatin1String("automatic-spell-checking") && checkBoolValue(val, &state)) { onTheFlySpellCheckingEnabled(state); } // VIEW SETTINGS else if (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("auto-brackets") && checkBoolValue(val, &state)) { v->config()->setAutoBrackets(state); } else 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("folding-preview") && checkBoolValue(val, &state)) { v->config()->setFoldingPreview(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); } else if (var == QLatin1String("scrollbar-minimap") && checkBoolValue(val, &state)) { v->config()->setScrollBarMiniMap(state); } else if (var == QLatin1String("scrollbar-preview") && checkBoolValue(val, &state)) { v->config()->setScrollBarPreview(state); } // 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; m_prevModOnHdReason = 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; + git_repository *repository = nullptr; const QByteArray utf8Path = url().toLocalFile().toUtf8(); - if (git_repository_open_ext(&repository, utf8Path.constData(), 0, Q_NULLPTR) == 0) { + if (git_repository_open_ext(&repository, utf8Path.constData(), 0, nullptr) == 0) { /** * if we have repo, convert the git hash to an OID */ git_oid oid; if (git_oid_fromstr(&oid, oldDigest.toHex().data()) == 0) { /** * finally: is there a blob for this git hash? */ - git_blob *blob = Q_NULLPTR; + git_blob *blob = nullptr; if (git_blob_lookup(&blob, repository, &oid) == 0) { /** * this hash exists still in git => just reload */ m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; documentReload(); } git_blob_free(blob); } } git_repository_free(repository); } #endif } /** * emit our signal to the outside! */ emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } QByteArray KTextEditor::DocumentPrivate::checksum() const { return m_buffer->digest(); } bool KTextEditor::DocumentPrivate::createDigest() { QByteArray digest; if (url().isLocalFile()) { QFile f(url().toLocalFile()); if (f.open(QIODevice::ReadOnly)) { // init the hash with the git header QCryptographicHash crypto(QCryptographicHash::Sha1); const QString header = QStringLiteral("blob %1").arg(f.size()); crypto.addData(header.toLatin1() + '\0'); while (!f.atEnd()) { crypto.addData(f.read(256 * 1024)); } digest = crypto.result(); } } /** * set new digest */ m_buffer->setDigest(digest); return !digest.isEmpty(); } QString KTextEditor::DocumentPrivate::reasonedMOHString() const { // squeeze path const QString str = KStringHandler::csqueeze(url().toDisplayString(QUrl::PreferLocalFile)); switch (m_modOnHdReason) { case OnDiskModified: return i18n("The file '%1' was modified by another program.", str); break; case OnDiskCreated: return i18n("The file '%1' was created by another program.", str); break; case OnDiskDeleted: return i18n("The file '%1' was deleted by another program.", str); break; default: return QString(); } Q_UNREACHABLE(); return QString(); } void KTextEditor::DocumentPrivate::removeTrailingSpaces() { const int remove = config()->removeSpaces(); if (remove == 0) { return; } // temporarily disable static word wrap (see bug #328900) const bool wordWrapEnabled = config()->wordWrap(); if (wordWrapEnabled) { setWordWrap(false); } editStart(); for (int line = 0; line < lines(); ++line) { Kate::TextLine textline = plainKateTextLine(line); // remove trailing spaces in entire document, remove = 2 // remove trailing spaces of touched lines, remove = 1 // remove trailing spaces of lines saved on disk, remove = 1 if (remove == 2 || textline->markedAsModified() || textline->markedAsSavedOnDisk()) { const int p = textline->lastChar() + 1; const int l = textline->length() - p; if (l > 0) { editRemoveText(line, p, l); } } } editEnd(); // enable word wrap again, if it was enabled (see bug #328900) if (wordWrapEnabled) { setWordWrap(true); // see begin of this function } } void KTextEditor::DocumentPrivate::updateFileType(const QString &newType, bool user) { if (user || !m_fileTypeSetByUser) { if (!newType.isEmpty()) { // remember that we got set by user m_fileTypeSetByUser = user; m_fileType = newType; m_config->configStart(); 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"), QUrl(), {}, nullptr, QFileDialog::DontConfirmOverwrite); 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"), + QStringLiteral("on-the-fly-spellcheck"), }; 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(); + } else if (key == QLatin1String("on-the-fly-spellcheck")) { + return isOnTheFlySpellCheckingEnabled(); } // 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)) { + } else if (value.type() == QVariant::Bool) { const bool bValue = value.toBool(); - if (key == QLatin1String("backup-on-save-local") && value.type() == QVariant::String) { + if (key == QLatin1String("backup-on-save-local")) { 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 (key == QLatin1String("on-the-fly-spellcheck")) { + onTheFlySpellCheckingEnabled(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; } 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); + QAction *cancel = new QAction(i18n("&Abort Loading"), nullptr); connect(cancel, SIGNAL(triggered()), this, SLOT(slotAbortLoading())); m_loadingMessage->addAction(cancel); } /** * really post message */ postMessage(m_loadingMessage); } void KTextEditor::DocumentPrivate::slotAbortLoading() { /** * no job, no work */ if (!m_loadingJob) { return; } /** * abort loading if any job * signal results! */ m_loadingJob->kill(KJob::EmitResult); - m_loadingJob = 0; + m_loadingJob = nullptr; } void KTextEditor::DocumentPrivate::slotUrlChanged(const QUrl &url) { if (m_reloading) { // the URL is temporarily unset and then reset to the previous URL during reload // we do not want to notify the outside about this return; } Q_UNUSED(url); updateDocName(); emit documentUrlChanged(this); } bool KTextEditor::DocumentPrivate::save() { /** * no double save/load * we need to allow DocumentPreSavingAs here as state, as save is called in saveAs! */ if ((m_documentState != DocumentIdle) && (m_documentState != DocumentPreSavingAs)) { return false; } /** * if we are idle, we are now saving */ if (m_documentState == DocumentIdle) { m_documentState = DocumentSaving; } else { m_documentState = DocumentSavingAs; } /** * call back implementation for real work */ return KTextEditor::Document::save(); } bool KTextEditor::DocumentPrivate::saveAs(const QUrl &url) { /** * abort on bad URL * that is done in saveAs below, too * but we must check it here already to avoid messing up * as no signals will be send, then */ if (!url.isValid()) { return false; } /** * no double save/load */ if (m_documentState != DocumentIdle) { return false; } /** * we enter the pre save as phase */ m_documentState = DocumentPreSavingAs; /** * call base implementation for real work */ return KTextEditor::Document::saveAs(normalizeUrl(url)); } QString KTextEditor::DocumentPrivate::defaultDictionary() const { return m_defaultDictionary; } QList > KTextEditor::DocumentPrivate::dictionaryRanges() const { return m_dictionaryRanges; } void KTextEditor::DocumentPrivate::clearDictionaryRanges() { for (QList >::iterator i = m_dictionaryRanges.begin(); i != m_dictionaryRanges.end(); ++i) { delete(*i).first; } m_dictionaryRanges.clear(); if (m_onTheFlyChecker) { m_onTheFlyChecker->refreshSpellCheck(); } emit dictionaryRangesPresent(false); } void KTextEditor::DocumentPrivate::setDictionary(const QString &newDictionary, const KTextEditor::Range &range) { 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); + Q_ASSERT(m_onTheFlyChecker == nullptr); m_onTheFlyChecker = new KateOnTheFlyChecker(this); } else { delete m_onTheFlyChecker; - m_onTheFlyChecker = 0; + m_onTheFlyChecker = nullptr; } foreach (KTextEditor::ViewPrivate *view, m_views) { view->reflectOnTheFlySpellCheckStatus(enable); } } bool KTextEditor::DocumentPrivate::isOnTheFlySpellCheckingEnabled() const { - return m_onTheFlyChecker != 0; + return m_onTheFlyChecker != nullptr; } QString KTextEditor::DocumentPrivate::dictionaryForMisspelledRange(const KTextEditor::Range &range) const { if (!m_onTheFlyChecker) { return QString(); } else { return m_onTheFlyChecker->dictionaryForMisspelledRange(range); } } void KTextEditor::DocumentPrivate::clearMisspellingForWord(const QString &word) { if (m_onTheFlyChecker) { m_onTheFlyChecker->clearMisspellingForWord(word); } } void KTextEditor::DocumentPrivate::refreshOnTheFlyCheck(const KTextEditor::Range &range) { if (m_onTheFlyChecker) { m_onTheFlyChecker->refreshSpellCheck(range); } } void KTextEditor::DocumentPrivate::rangeInvalid(KTextEditor::MovingRange *movingRange) { deleteDictionaryRange(movingRange); } void KTextEditor::DocumentPrivate::rangeEmpty(KTextEditor::MovingRange *movingRange) { deleteDictionaryRange(movingRange); } void KTextEditor::DocumentPrivate::deleteDictionaryRange(KTextEditor::MovingRange *movingRange) { qCDebug(LOG_KTE) << "deleting" << movingRange; auto finder = [=] (const QPair& item) -> bool { return item.first == movingRange; }; auto it = std::find_if(m_dictionaryRanges.begin(), m_dictionaryRanges.end(), finder); if (it != m_dictionaryRanges.end()) { m_dictionaryRanges.erase(it); delete movingRange; } Q_ASSERT(std::find_if(m_dictionaryRanges.begin(), m_dictionaryRanges.end(), finder) == m_dictionaryRanges.end()); } bool KTextEditor::DocumentPrivate::containsCharacterEncoding(const KTextEditor::Range &range) { KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn; ++col) { int attr = textLine->attribute(col); const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr); if (!prefixStore.findPrefix(textLine, col).isEmpty()) { return true; } } } return false; } int KTextEditor::DocumentPrivate::computePositionWrtOffsets(const OffsetList &offsetList, int pos) { int previousOffset = 0; for (OffsetList::const_iterator i = offsetList.begin(); i != offsetList.end(); ++i) { if ((*i).first > pos) { break; } previousOffset = (*i).second; } return pos + previousOffset; } QString KTextEditor::DocumentPrivate::decodeCharacters(const KTextEditor::Range &range, KTextEditor::DocumentPrivate::OffsetList &decToEncOffsetList, KTextEditor::DocumentPrivate::OffsetList &encToDecOffsetList) { QString toReturn; KTextEditor::Cursor previous = range.start(); int decToEncCurrentOffset = 0, encToDecCurrentOffset = 0; int i = 0; int newI = 0; KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn;) { int attr = textLine->attribute(col); const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr); const QHash &characterEncodingsHash = highlighting->getCharacterEncodings(attr); QString matchingPrefix = prefixStore.findPrefix(textLine, col); if (!matchingPrefix.isEmpty()) { toReturn += text(KTextEditor::Range(previous, KTextEditor::Cursor(line, col))); const QChar &c = characterEncodingsHash.value(matchingPrefix); const bool isNullChar = c.isNull(); if (!c.isNull()) { toReturn += c; } i += matchingPrefix.length(); col += matchingPrefix.length(); previous = KTextEditor::Cursor(line, col); decToEncCurrentOffset = decToEncCurrentOffset - (isNullChar ? 0 : 1) + matchingPrefix.length(); encToDecCurrentOffset = encToDecCurrentOffset - matchingPrefix.length() + (isNullChar ? 0 : 1); newI += (isNullChar ? 0 : 1); decToEncOffsetList.push_back(QPair(newI, decToEncCurrentOffset)); encToDecOffsetList.push_back(QPair(i, encToDecCurrentOffset)); continue; } ++col; ++i; ++newI; } ++i; ++newI; } if (previous < range.end()) { toReturn += text(KTextEditor::Range(previous, range.end())); } return toReturn; } void KTextEditor::DocumentPrivate::replaceCharactersByEncoding(const KTextEditor::Range &range) { KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn;) { int attr = textLine->attribute(col); const QHash &reverseCharacterEncodingsHash = highlighting->getReverseCharacterEncodings(attr); QHash::const_iterator it = reverseCharacterEncodingsHash.find(textLine->at(col)); if (it != reverseCharacterEncodingsHash.end()) { replaceText(KTextEditor::Range(line, col, line, col + 1), *it); col += (*it).length(); continue; } ++col; } } } // // Highlighting information // KTextEditor::Attribute::Ptr KTextEditor::DocumentPrivate::attributeAt(const KTextEditor::Cursor &position) { KTextEditor::Attribute::Ptr attrib(new KTextEditor::Attribute()); - KTextEditor::ViewPrivate *view = m_views.empty() ? Q_NULLPTR : m_views.begin().value(); + KTextEditor::ViewPrivate *view = m_views.empty() ? nullptr : m_views.begin().value(); if (!view) { qCWarning(LOG_KTE) << "ATTENTION: cannot access lineAttributes() without any View (will be fixed eventually)"; return attrib; } Kate::TextLine kateLine = kateTextLine(position.line()); if (!kateLine) { return attrib; } *attrib = *view->renderer()->attribute(kateLine->attribute(position.column())); return attrib; } QStringList KTextEditor::DocumentPrivate::embeddedHighlightingModes() const { return highlight()->getEmbeddedHighlightingModes(); } QString KTextEditor::DocumentPrivate::highlightingModeAt(const KTextEditor::Cursor &position) { 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); + QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), nullptr); closeAction->setToolTip(i18n("Close message")); message->addAction(closeAction); } // make sure the message is registered even if no actions and no views exist m_messageHash[message] = QList >(); // reparent actions, as we want full control over when they are deleted foreach (QAction *action, message->actions()) { - action->setParent(0); + action->setParent(nullptr); m_messageHash[message].append(QSharedPointer(action)); } // post message to requested view, or to all views if (KTextEditor::ViewPrivate *view = qobject_cast(message->view())) { view->postMessage(message, m_messageHash[message]); } else { foreach (KTextEditor::ViewPrivate *view, m_views) { view->postMessage(message, m_messageHash[message]); } } // also catch if the user manually calls delete message connect(message, SIGNAL(closed(KTextEditor::Message*)), SLOT(messageDestroyed(KTextEditor::Message*))); return true; } void KTextEditor::DocumentPrivate::messageDestroyed(KTextEditor::Message *message) { // KTE:Message is already in destructor Q_ASSERT(m_messageHash.contains(message)); m_messageHash.remove(message); } //END KTextEditor::MessageInterface void KTextEditor::DocumentPrivate::closeDocumentInApplication() { KTextEditor::EditorPrivate::self()->application()->closeDocument(this); } diff --git a/src/document/katedocument.h b/src/document/katedocument.h index 639d294f..9ef90629 100644 --- a/src/document/katedocument.h +++ b/src/document/katedocument.h @@ -1,1410 +1,1410 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy Copyright (C) 2006 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KATE_DOCUMENT_H_ #define _KATE_DOCUMENT_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katetextline.h" class KateTemplateHandler; namespace KTextEditor { class Plugin; class Attribute; class TemplateScript; } namespace KIO { class TransferJob; } namespace Kate { class SwapFile; } class KateBuffer; namespace KTextEditor { class ViewPrivate; } class KateDocumentConfig; class KateHighlighting; class KateUndoManager; class KateOnTheFlyChecker; class KateDocumentTest; class KateAutoIndent; class KateModOnHdPrompt; /** * @brief Backend of KTextEditor::Document related public KTextEditor interfaces. * * @warning This file is @e private API and not part of the public * KTextEditor interfaces. */ class KTEXTEDITOR_EXPORT KTextEditor::DocumentPrivate : public KTextEditor::Document, public KTextEditor::MarkInterface, public KTextEditor::ModificationInterface, public KTextEditor::ConfigInterface, public KTextEditor::AnnotationInterface, public KTextEditor::MovingInterface, private KTextEditor::MovingRangeFeedback { Q_OBJECT Q_INTERFACES(KTextEditor::MarkInterface) Q_INTERFACES(KTextEditor::ModificationInterface) Q_INTERFACES(KTextEditor::AnnotationInterface) Q_INTERFACES(KTextEditor::ConfigInterface) Q_INTERFACES(KTextEditor::MovingInterface) friend class KTextEditor::Document; friend class ::KateDocumentTest; friend class ::KateBuffer; public: explicit DocumentPrivate(bool bSingleViewMode = false, bool bReadOnly = false, - QWidget *parentWidget = 0, QObject * = 0); + QWidget *parentWidget = nullptr, QObject * = nullptr); ~DocumentPrivate(); using ReadWritePart::closeUrl; bool closeUrl() Q_DECL_OVERRIDE; bool openUrl(const QUrl &url) Q_DECL_OVERRIDE; KTextEditor::Range rangeOnLine(KTextEditor::Range range, int line) const; private: void showAndSetOpeningErrorAccess(); /* * Overload this to have on-demand view creation */ public: /** * @return The widget defined by this part, set by setWidget(). */ QWidget *widget() Q_DECL_OVERRIDE; public: bool readOnly() const { return m_bReadOnly; } bool singleViewMode() const { return m_bSingleViewMode; } private: // only to make part work, don't change it ! const bool m_bSingleViewMode; const bool m_bReadOnly; // // KTextEditor::Document stuff // public: - KTextEditor::View *createView(QWidget *parent, KTextEditor::MainWindow *mainWindow = Q_NULLPTR) Q_DECL_OVERRIDE; + KTextEditor::View *createView(QWidget *parent, KTextEditor::MainWindow *mainWindow = nullptr) Q_DECL_OVERRIDE; QList views() const Q_DECL_OVERRIDE { return m_views.keys(); } virtual KTextEditor::View *activeView() const { return m_activeView; } private: QHash m_views; KTextEditor::View *m_activeView; // // KTextEditor::EditInterface stuff // public Q_SLOTS: bool setText(const QString &) Q_DECL_OVERRIDE; bool setText(const QStringList &text) Q_DECL_OVERRIDE; bool clear() Q_DECL_OVERRIDE; bool insertText(const KTextEditor::Cursor &position, const QString &s, bool block = false) Q_DECL_OVERRIDE; bool insertText(const KTextEditor::Cursor &position, const QStringList &text, bool block = false) Q_DECL_OVERRIDE; bool insertLine(int line, const QString &s) Q_DECL_OVERRIDE; bool insertLines(int line, const QStringList &s) Q_DECL_OVERRIDE; bool removeText(const KTextEditor::Range &range, bool block = false) Q_DECL_OVERRIDE; bool removeLine(int line) Q_DECL_OVERRIDE; bool replaceText(const KTextEditor::Range &range, const QString &s, bool block = false) Q_DECL_OVERRIDE; // unhide method... bool replaceText(const KTextEditor::Range &r, const QStringList &l, bool b) Q_DECL_OVERRIDE { return KTextEditor::Document::replaceText(r, l, b); } public: bool isEditingTransactionRunning() const Q_DECL_OVERRIDE; QString text(const KTextEditor::Range &range, bool blockwise = false) const Q_DECL_OVERRIDE; QStringList textLines(const KTextEditor::Range &range, bool block = false) const Q_DECL_OVERRIDE; QString text() const Q_DECL_OVERRIDE; QString line(int line) const Q_DECL_OVERRIDE; QChar characterAt(const KTextEditor::Cursor &position) const Q_DECL_OVERRIDE; QString wordAt(const KTextEditor::Cursor &cursor) const Q_DECL_OVERRIDE; KTextEditor::Range wordRangeAt(const KTextEditor::Cursor &cursor) const Q_DECL_OVERRIDE; bool isValidTextPosition(const KTextEditor::Cursor& cursor) const Q_DECL_OVERRIDE; int lines() const Q_DECL_OVERRIDE; bool isLineModified(int line) const Q_DECL_OVERRIDE; bool isLineSaved(int line) const Q_DECL_OVERRIDE; bool isLineTouched(int line) const Q_DECL_OVERRIDE; KTextEditor::Cursor documentEnd() const Q_DECL_OVERRIDE; int totalCharacters() const Q_DECL_OVERRIDE; int lineLength(int line) const Q_DECL_OVERRIDE; Q_SIGNALS: void charactersSemiInteractivelyInserted(const KTextEditor::Cursor &position, const QString &text); /** * The \p document emits this signal whenever text was inserted. The * insertion occurred at range.start(), and new text now occupies up to * range.end(). * \param document document which emitted this signal * \param range range that the newly inserted text occupies * \see insertText(), insertLine() */ void textInserted(KTextEditor::Document *document, const KTextEditor::Range &range); /** * The \p document emits this signal whenever \p range was removed, i.e. * text was removed. * \param document document which emitted this signal * \param range range that the removed text previously occupied * \param oldText the text that has been removed * \see removeText(), removeLine(), clear() */ void textRemoved(KTextEditor::Document *document, const KTextEditor::Range &range, const QString &oldText); public: //BEGIN editStart/editEnd (start, end, undo, cursor update, view update) /** * Enclose editor actions with @p editStart() and @p editEnd() to group * them. */ bool editStart(); /** * Alias for @p editStart() */ void editBegin() { editStart(); } /** * End a editor operation. * @see editStart() */ bool editEnd(); void pushEditState(); void popEditState(); virtual bool startEditing() { return editStart(); } virtual bool finishEditing() { return editEnd(); } //END editStart/editEnd void inputMethodStart(); void inputMethodEnd(); //BEGIN LINE BASED INSERT/REMOVE STUFF (editStart() and editEnd() included) /** * Add a string in the given line/column * @param line line number * @param col column * @param s string to be inserted * @return true on success */ bool editInsertText(int line, int col, const QString &s); /** * Remove a string in the given line/column * @param line line number * @param col column * @param len length of text to be removed * @return true on success */ bool editRemoveText(int line, int col, int len); /** * Mark @p line as @p autowrapped. This is necessary if static word warp is * enabled, because we have to know whether to insert a new line or add the * wrapped words to the followin line. * @param line line number * @param autowrapped autowrapped? * @return true on success */ bool editMarkLineAutoWrapped(int line, bool autowrapped); /** * Wrap @p line. If @p newLine is true, ignore the textline's flag * KateTextLine::flagAutoWrapped and force a new line. Whether a new line * was needed/added you can grab with @p newLineAdded. * @param line line number * @param col column * @param newLine if true, force a new line * @param newLineAdded return value is true, if new line was added (may be 0) * @return true on success */ - bool editWrapLine(int line, int col, bool newLine = true, bool *newLineAdded = 0); + bool editWrapLine(int line, int col, bool newLine = true, bool *newLineAdded = nullptr); /** * Unwrap @p line. If @p removeLine is true, we force to join the lines. If * @p removeLine is true, @p length is ignored (eg not needed). * @param line line number * @param removeLine if true, force to remove the next line * @return true on success */ bool editUnWrapLine(int line, bool removeLine = true, int length = 0); /** * Insert a string at the given line. * @param line line number * @param s string to insert * @return true on success */ bool editInsertLine(int line, const QString &s); /** * Remove a line * @param line line number * @return true on success */ bool editRemoveLine(int line); bool editRemoveLines(int from, int to); /** * Remove a line * @param startLine line to begin wrapping * @param endLine line to stop wrapping * @return true on success */ bool wrapText(int startLine, int endLine); //END LINE BASED INSERT/REMOVE STUFF Q_SIGNALS: /** * Emmitted when text from @p line was wrapped at position pos onto line @p nextLine. */ void editLineWrapped(int line, int col, int len); /** * Emitted each time text from @p nextLine was upwrapped onto @p line. */ void editLineUnWrapped(int line, int col); public: bool isEditRunning() const; void setUndoMergeAllEdits(bool merge); enum EditingPositionKind { Previous, Next }; /** *Returns the next or previous position cursor in this document from the stack depending on the argument passed. *@return cursor invalid if m_editingStack empty */ KTextEditor::Cursor lastEditingPosition(EditingPositionKind nextOrPrevious, KTextEditor::Cursor); private: int editSessionNumber; QStack editStateStack; bool editIsRunning; bool m_undoMergeAllEdits; QStack> m_editingStack; int m_editingStackPosition = -1; static const int s_editingStackSizeLimit = 32; // // KTextEditor::UndoInterface stuff // public Q_SLOTS: void undo(); void redo(); /** * Removes all the elements in m_editingStack of the respective document. */ void clearEditingPosStack(); /** * Saves the editing positions into the stack. * If the consecutive editings happens in the same line, then remove * the previous and add the new one with updated column no. */ void saveEditingPositions(KTextEditor::Document *, const KTextEditor::Range &range); public: uint undoCount() const; uint redoCount() const; KateUndoManager *undoManager() { return m_undoManager; } protected: KateUndoManager *const m_undoManager; Q_SIGNALS: void undoChanged(); public: QVector searchText( const KTextEditor::Range &range, const QString &pattern, const KTextEditor::SearchOptions options) const; private: /** * Return a widget suitable to be used as a dialog parent. */ QWidget *dialogParent(); /* * Access to the mode/highlighting subsystem */ public: /** * @copydoc KTextEditor::Document::defaultStyleAt() */ KTextEditor::DefaultStyle defaultStyleAt(const KTextEditor::Cursor &position) const Q_DECL_OVERRIDE; /** * Return the name of the currently used mode * \return name of the used mode */ QString mode() const Q_DECL_OVERRIDE; /** * Return the name of the currently used mode * \return name of the used mode */ QString highlightingMode() const Q_DECL_OVERRIDE; /** * Return a list of the names of all possible modes * \return list of mode names */ QStringList modes() const Q_DECL_OVERRIDE; /** * Return a list of the names of all possible modes * \return list of mode names */ QStringList highlightingModes() const Q_DECL_OVERRIDE; /** * Set the current mode of the document by giving its name * \param name name of the mode to use for this document * \return \e true on success, otherwise \e false */ bool setMode(const QString &name) Q_DECL_OVERRIDE; /** * Set the current mode of the document by giving its name * \param name name of the mode to use for this document * \return \e true on success, otherwise \e false */ bool setHighlightingMode(const QString &name) Q_DECL_OVERRIDE; /** - * Returns the name of the section for a highlight given its index in the highlight + * Returns the name of the section for a highlight given its @p index in the highlight * list (as returned by highlightModes()). * You can use this function to build a tree of the highlight names, organized in sections. - * \param name the name of the highlight for which to find the section name. + * \param index in the highlight list for which to find the section name. */ QString highlightingModeSection(int index) const Q_DECL_OVERRIDE; /** - * Returns the name of the section for a mode given its index in the highlight + * Returns the name of the section for a mode given its @p index in the highlight * list (as returned by modes()). * You can use this function to build a tree of the mode names, organized in sections. - * \param name the name of the highlight for which to find the section name. + * \param index index in the highlight list for which to find the section name. */ QString modeSection(int index) const Q_DECL_OVERRIDE; /* * Helpers.... */ public: void bufferHlChanged(); /** * allow to mark, that we changed hl on user wish and should not reset it * atm used for the user visible menu to select highlightings */ void setDontChangeHlOnSave(); /** * Set that the BOM marker is forced via the tool menu */ void bomSetByUser(); public: /** * Read session settings from the given \p config. * * Known flags: * "SkipUrl" => don't save/restore the file * "SkipMode" => don't save/restore the mode * "SkipHighlighting" => don't save/restore the highlighting * "SkipEncoding" => don't save/restore the encoding * * \param config read the session settings from this KConfigGroup * \param flags additional flags * \see writeSessionConfig() */ void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) Q_DECL_OVERRIDE; /** * Write session settings to the \p config. * See readSessionConfig() for more details. * * \param config write the session settings to this KConfigGroup * \param flags additional flags * \see readSessionConfig() */ void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) Q_DECL_OVERRIDE; Q_SIGNALS: void configChanged(); // // KTextEditor::MarkInterface // public Q_SLOTS: void setMark(int line, uint markType) Q_DECL_OVERRIDE; void clearMark(int line) Q_DECL_OVERRIDE; void addMark(int line, uint markType) Q_DECL_OVERRIDE; void removeMark(int line, uint markType) Q_DECL_OVERRIDE; void clearMarks() Q_DECL_OVERRIDE; void requestMarkTooltip(int line, QPoint position); ///Returns true if the click on the mark should not be further processed bool handleMarkClick(int line); ///Returns true if the context-menu event should not further be processed bool handleMarkContextMenu(int line, QPoint position); void setMarkPixmap(MarkInterface::MarkTypes, const QPixmap &) Q_DECL_OVERRIDE; void setMarkDescription(MarkInterface::MarkTypes, const QString &) Q_DECL_OVERRIDE; void setEditableMarks(uint markMask) Q_DECL_OVERRIDE; public: uint mark(int line) Q_DECL_OVERRIDE; const QHash &marks() Q_DECL_OVERRIDE; QPixmap markPixmap(MarkInterface::MarkTypes) const Q_DECL_OVERRIDE; QString markDescription(MarkInterface::MarkTypes) const Q_DECL_OVERRIDE; virtual QColor markColor(MarkInterface::MarkTypes) const; uint editableMarks() const Q_DECL_OVERRIDE; Q_SIGNALS: void markToolTipRequested(KTextEditor::Document *document, KTextEditor::Mark mark, QPoint position, bool &handled); void markContextMenuRequested(KTextEditor::Document *document, KTextEditor::Mark mark, QPoint pos, bool &handled); void markClicked(KTextEditor::Document *document, KTextEditor::Mark mark, bool &handled); void marksChanged(KTextEditor::Document *) Q_DECL_OVERRIDE; void markChanged(KTextEditor::Document *, KTextEditor::Mark, KTextEditor::MarkInterface::MarkChangeAction) Q_DECL_OVERRIDE; private: QHash m_marks; QHash m_markPixmaps; QHash m_markDescriptions; uint m_editableMarks; // KTextEditor::PrintInterface // public Q_SLOTS: bool print() Q_DECL_OVERRIDE; void printPreview() Q_DECL_OVERRIDE; // // KTextEditor::DocumentInfoInterface ( ### unfinished ) // public: /** * Tries to detect mime-type based on file name and content of buffer. * * @return the name of the mimetype for the document. */ QString mimeType() Q_DECL_OVERRIDE; // // once was KTextEditor::VariableInterface // public: /** * Returns the value for the variable @p name. * If the Document does not have a variable called @p name, * an empty QString() is returned. * * @param name variable to query * @return value of the variable @p name * @see setVariable() */ virtual QString variable(const QString &name) const; /** * Set the variable @p name to @p value. Setting and changing a variable * has immediate effect on the Document. For instance, setting the variable * @e indent-mode to @e cstyle will immediately cause the Document to load * the C Style indenter. * * @param name the variable name * @param value the value to be set * @see variable() */ virtual void setVariable(const QString &name, const QString &value); private: QMap m_storedVariables; // // MovingInterface API // public: /** * Create a new moving cursor for this document. * @param position position of the moving cursor to create * @param insertBehavior insertion behavior * @return new moving cursor for the document */ KTextEditor::MovingCursor *newMovingCursor(const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior = KTextEditor::MovingCursor::MoveOnInsert) Q_DECL_OVERRIDE; /** * Create a new moving range for this document. * @param range range of the moving range to create * @param insertBehaviors insertion behaviors * @param emptyBehavior behavior on becoming empty * @return new moving range for the document */ virtual KTextEditor::MovingRange *newMovingRange(const KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors = KTextEditor::MovingRange::DoNotExpand , KTextEditor::MovingRange::EmptyBehavior emptyBehavior = KTextEditor::MovingRange::AllowEmpty) Q_DECL_OVERRIDE; /** * Current revision * @return current revision */ qint64 revision() const Q_DECL_OVERRIDE; /** * Last revision the buffer got successful saved * @return last revision buffer got saved, -1 if none */ qint64 lastSavedRevision() const Q_DECL_OVERRIDE; /** * Lock a revision, this will keep it around until released again. * But all revisions will always be cleared on buffer clear() (and therefor load()) * @param revision revision to lock */ void lockRevision(qint64 revision) Q_DECL_OVERRIDE; /** * Release a revision. * @param revision revision to release */ void unlockRevision(qint64 revision) Q_DECL_OVERRIDE; /** * Transform a cursor from one revision to an other. * @param cursor cursor to transform * @param insertBehavior behavior of this cursor on insert of text at its position * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ void transformCursor(KTextEditor::Cursor &cursor, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1) Q_DECL_OVERRIDE; /** * Transform a cursor from one revision to an other. * @param line line number of the cursor to transform * @param column column number of the cursor to transform * @param insertBehavior behavior of this cursor on insert of text at its position * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ void transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision = -1) Q_DECL_OVERRIDE; /** * Transform a range from one revision to an other. * @param range range to transform * @param insertBehaviors behavior of this range on insert of text at its position * @param emptyBehavior behavior on becoming empty * @param fromRevision from this revision we want to transform * @param toRevision to this revision we want to transform, default of -1 is current revision */ void transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision = -1) Q_DECL_OVERRIDE; // // MovingInterface Signals // Q_SIGNALS: /** * This signal is emitted before the cursors/ranges/revisions of a document are destroyed as the document is deleted. * @param document the document which the interface belongs too which is in the process of being deleted */ void aboutToDeleteMovingInterfaceContent(KTextEditor::Document *document); /** * This signal is emitted before the ranges of a document are invalidated and the revisions are deleted as the document is cleared (for example on load/reload). * While this signal is emitted, still the old document content is around before the clear. * @param document the document which the interface belongs too which will invalidate its data */ void aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *document); // // Annotation Interface // public: void setAnnotationModel(KTextEditor::AnnotationModel *model) Q_DECL_OVERRIDE; KTextEditor::AnnotationModel *annotationModel() const Q_DECL_OVERRIDE; Q_SIGNALS: void annotationModelChanged(KTextEditor::AnnotationModel *, KTextEditor::AnnotationModel *); private: KTextEditor::AnnotationModel *m_annotationModel; // // KParts::ReadWrite stuff // public: /** * open the file obtained by the kparts framework * the framework abstracts the loading of remote files * @return success */ bool openFile() Q_DECL_OVERRIDE; /** * save the file obtained by the kparts framework * the framework abstracts the uploading of remote files * @return success */ bool saveFile() Q_DECL_OVERRIDE; void setReadWrite(bool rw = true) Q_DECL_OVERRIDE; void setModified(bool m) Q_DECL_OVERRIDE; private: void activateDirWatch(const QString &useFileName = QString()); void deactivateDirWatch(); QString m_dirWatchFile; /** * Make backup copy during saveFile, if configured that way. * @return success? else saveFile should return false and not write the file */ bool createBackupFile(); public: /** * Type chars in a view. * Will filter out non-printable chars from the realChars array before inserting. */ bool typeChars(KTextEditor::ViewPrivate *type, const QString &realChars); /** * gets the last line number (lines() - 1) */ inline int lastLine() const { return lines() - 1; } // Repaint all of all of the views void repaintViews(bool paintOnlyDirty = true); KateHighlighting *highlight() const; public Q_SLOTS: void tagLines(int start, int end); private Q_SLOTS: void internalHlChanged(); public: void addView(KTextEditor::View *); /** removes the view from the list of views. The view is *not* deleted. * That's your job. Or, easier, just delete the view in the first place. * It will remove itself. TODO: this could be converted to a private slot * connected to the view's destroyed() signal. It is not currently called * anywhere except from the KTextEditor::ViewPrivate destructor. */ void removeView(KTextEditor::View *); void setActiveView(KTextEditor::View *); bool ownedView(KTextEditor::ViewPrivate *); int toVirtualColumn(int line, int column) const; int toVirtualColumn(const KTextEditor::Cursor &) const; int fromVirtualColumn(int line, int column) const; int fromVirtualColumn(const KTextEditor::Cursor &) const; void newLine(KTextEditor::ViewPrivate *view); // Changes input void backspace(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &); void del(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &); void transpose(const KTextEditor::Cursor &); void paste(KTextEditor::ViewPrivate *view, const QString &text); public: void indent(KTextEditor::Range range, int change); void comment(KTextEditor::ViewPrivate *view, uint line, uint column, int change); void align(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range); void insertTab(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &); enum TextTransform { Uppercase, Lowercase, Capitalize }; /** Handling uppercase, lowercase and capitalize for the view. If there is a selection, that is transformed, otherwise for uppercase or lowercase the character right of the cursor is transformed, for capitalize the word under the cursor is transformed. */ void transform(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &, TextTransform); /** Unwrap a range of lines. */ void joinLines(uint first, uint last); private: bool removeStringFromBeginning(int line, const QString &str); bool removeStringFromEnd(int line, const QString &str); /** Expand tabs to spaces in typed text, if enabled. @param cursorPos The current cursor position for the inserted characters. @param str The typed characters to expand. */ QString eventuallyReplaceTabs(const KTextEditor::Cursor &cursorPos, const QString &str) const; /** Find the position (line and col) of the next char that is not a space. If found line and col point to the found character. Otherwise they have both the value -1. @param line Line of the character which is examined first. @param col Column of the character which is examined first. @return True if the specified or a following character is not a space Otherwise false. */ bool nextNonSpaceCharPos(int &line, int &col); /** Find the position (line and col) of the previous char that is not a space. If found line and col point to the found character. Otherwise they have both the value -1. @return True if the specified or a preceding character is not a space. Otherwise false. */ bool previousNonSpaceCharPos(int &line, int &col); /** * Sets a comment marker as defined by the language providing the attribute * @p attrib on the line @p line */ void addStartLineCommentToSingleLine(int line, int attrib = 0); /** * Removes a comment marker as defined by the language providing the attribute * @p attrib on the line @p line */ bool removeStartLineCommentFromSingleLine(int line, int attrib = 0); /** * @see addStartLineCommentToSingleLine. */ void addStartStopCommentToSingleLine(int line, int attrib = 0); /** *@see removeStartLineCommentFromSingleLine. */ bool removeStartStopCommentFromSingleLine(int line, int attrib = 0); /** *@see removeStartLineCommentFromSingleLine. */ bool removeStartStopCommentFromRegion(const KTextEditor::Cursor &start, const KTextEditor::Cursor &end, int attrib = 0); /** * Add a comment marker as defined by the language providing the attribute * @p attrib to each line in the selection. */ void addStartStopCommentToSelection(KTextEditor::ViewPrivate *view, int attrib = 0); /** * @see addStartStopCommentToSelection. */ void addStartLineCommentToSelection(KTextEditor::ViewPrivate *view, int attrib = 0); /** * Removes comment markers relevant to the language providing * the attribuge @p attrib from each line in the selection. * * @return whether the operation succeeded. */ bool removeStartStopCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib = 0); /** * @see removeStartStopCommentFromSelection. */ bool removeStartLineCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib = 0); public: KTextEditor::Range findMatchingBracket(const KTextEditor::Cursor & start, int maxLines); public: QString documentName() const Q_DECL_OVERRIDE { return m_docName; } private: void updateDocName(); public: /** * @return whether the document is modified on disk since last saved */ bool isModifiedOnDisc() { return m_modOnHd; } void setModifiedOnDisk(ModifiedOnDiskReason reason) Q_DECL_OVERRIDE; void setModifiedOnDiskWarning(bool on) Q_DECL_OVERRIDE; public Q_SLOTS: /** * Ask the user what to do, if the file has been modified on disk. * Reimplemented from KTextEditor::Document. */ - virtual void slotModifiedOnDisk(KTextEditor::View *v = 0); + virtual void slotModifiedOnDisk(KTextEditor::View *v = nullptr); /** * Reloads the current document from disk if possible */ bool documentReload() Q_DECL_OVERRIDE; bool documentSave() Q_DECL_OVERRIDE; bool documentSaveAs() Q_DECL_OVERRIDE; bool documentSaveAsWithEncoding(const QString &encoding); bool documentSaveCopyAs(); bool save() Q_DECL_OVERRIDE; public: bool saveAs(const QUrl &url) Q_DECL_OVERRIDE; Q_SIGNALS: /** * Indicate this file is modified on disk * @param doc the KTextEditor::Document object that represents the file on disk * @param isModified indicates the file was modified rather than created or deleted * @param reason the reason we are emitting the signal. */ void modifiedOnDisk(KTextEditor::Document *doc, bool isModified, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) Q_DECL_OVERRIDE; private: // helper to handle the embedded notification for externally modified files QPointer m_modOnHdHandler; private Q_SLOTS: void onModOnHdSaveAs(); void onModOnHdReload(); void onModOnHdIgnore(); public: bool setEncoding(const QString &e) Q_DECL_OVERRIDE; QString encoding() const Q_DECL_OVERRIDE; public Q_SLOTS: void setWordWrap(bool on); void setWordWrapAt(uint col); public: bool wordWrap() const; uint wordWrapAt() const; public Q_SLOTS: void setPageUpDownMovesCursor(bool on); public: bool pageUpDownMovesCursor() const; // code folding public: /** * Same as plainKateTextLine(), except that it is made sure * the line is highlighted. */ Kate::TextLine kateTextLine(int i); //! @copydoc KateBuffer::plainLine() Kate::TextLine plainKateTextLine(int i); Q_SIGNALS: void aboutToRemoveText(const KTextEditor::Range &); private Q_SLOTS: void slotModOnHdDirty(const QString &path); void slotModOnHdCreated(const QString &path); void slotModOnHdDeleted(const QString &path); void slotDelayedHandleModOnHd(); private: /** * Create a git compatible sha1 checksum of the file, if it is a local file. * The result can be accessed through KateBuffer::digest(). * * @return wheather the operation was attempted and succeeded. */ bool createDigest(); /** * create a string for the modonhd warnings, giving the reason. */ QString reasonedMOHString() const; /** * Removes all trailing whitespace in the document. */ void removeTrailingSpaces(); public: /** * Returns a git compatible sha1 checksum of this document on disk. * @return checksum for this document on disk */ QByteArray checksum() const Q_DECL_OVERRIDE; void updateFileType(const QString &newType, bool user = false); QString fileType() const { return m_fileType; } /** * Get access to buffer of this document. * Is needed to create cursors and ranges for example. * @return document buffer */ KateBuffer &buffer() { return *m_buffer; } /** * set indentation mode by user * this will remember that a user did set it and will avoid reset on save */ void rememberUserDidSetIndentationMode() { m_indenterSetByUser = true; } /** * User did set encoding for next reload => enforce it! */ void userSetEncodingForNextReload() { m_userSetEncodingForNextReload = true; } // // REALLY internal data ;) // private: // text buffer KateBuffer *const m_buffer; // indenter KateAutoIndent *const m_indenter; bool m_hlSetByUser; bool m_bomSetByUser; bool m_indenterSetByUser; bool m_userSetEncodingForNextReload; bool m_modOnHd; ModifiedOnDiskReason m_modOnHdReason; ModifiedOnDiskReason m_prevModOnHdReason; QString m_docName; int m_docNameNumber; // file type !!! QString m_fileType; bool m_fileTypeSetByUser; /** * document is still reloading a file */ bool m_reloading; public Q_SLOTS: void slotQueryClose_save(bool *handled, bool *abortClosing); public: bool queryClose() Q_DECL_OVERRIDE; void makeAttribs(bool needInvalidate = true); static bool checkOverwrite(QUrl u, QWidget *parent); /** * Configuration */ public: KateDocumentConfig *config() { return m_config; } KateDocumentConfig *config() const { return m_config; } void updateConfig(); private: KateDocumentConfig *const m_config; /** * Variable Reader * TODO add register functionality/ktexteditor interface */ private: /** * read dir config file */ void readDirConfig(); /** Reads all the variables in the document. Called when opening/saving a document */ void readVariables(bool onlyViewAndRenderer = false); /** Reads and applies the variables in a single line TODO registered variables gets saved in a [map] */ void readVariableLine(QString t, bool onlyViewAndRenderer = false); /** Sets a view variable in all the views. */ void setViewVariable(QString var, QString val); /** @return weather a string value could be converted to a bool value as supported. The value is put in *result. */ static bool checkBoolValue(QString value, bool *result); /** @return weather a string value could be converted to a integer value. The value is put in *result. */ static bool checkIntValue(QString value, int *result); /** Feeds value into @p col using QColor::setNamedColor() and returns wheather the color is valid */ static bool checkColorValue(QString value, QColor &col); bool m_fileChangedDialogsActivated; // // KTextEditor::ConfigInterface // public: QStringList configKeys() const Q_DECL_OVERRIDE; QVariant configValue(const QString &key) Q_DECL_OVERRIDE; void setConfigValue(const QString &key, const QVariant &value) Q_DECL_OVERRIDE; // // KTextEditor::RecoveryInterface // public: bool isDataRecoveryAvailable() const Q_DECL_OVERRIDE; void recoverData() Q_DECL_OVERRIDE; void discardDataRecovery() Q_DECL_OVERRIDE; // // Highlighting information // public: QStringList embeddedHighlightingModes() const Q_DECL_OVERRIDE; QString highlightingModeAt(const KTextEditor::Cursor &position) Q_DECL_OVERRIDE; // TODO KDE5: move to View virtual KTextEditor::Attribute::Ptr attributeAt(const KTextEditor::Cursor &position); // //BEGIN: KTextEditor::MessageInterface // public: bool postMessage(KTextEditor::Message *message) Q_DECL_OVERRIDE; public Q_SLOTS: void messageDestroyed(KTextEditor::Message *message); private: QHash > > m_messageHash; //END KTextEditor::MessageInterface public: QString defaultDictionary() const; QList > dictionaryRanges() const; bool isOnTheFlySpellCheckingEnabled() const; QString dictionaryForMisspelledRange(const KTextEditor::Range &range) const; void clearMisspellingForWord(const QString &word); public Q_SLOTS: void clearDictionaryRanges(); void setDictionary(const QString &dict, const KTextEditor::Range &range); void revertToDefaultDictionary(const KTextEditor::Range &range); void setDefaultDictionary(const QString &dict); void onTheFlySpellCheckingEnabled(bool enable); void refreshOnTheFlyCheck(const KTextEditor::Range &range = KTextEditor::Range::invalid()); Q_SIGNALS: void dictionaryRangesPresent(bool yesNo); void defaultDictionaryChanged(KTextEditor::DocumentPrivate *document); public: bool containsCharacterEncoding(const KTextEditor::Range &range); typedef QList > OffsetList; int computePositionWrtOffsets(const OffsetList &offsetList, int pos); /** * The first OffsetList is from decoded to encoded, and the second OffsetList from * encoded to decoded. **/ QString decodeCharacters(const KTextEditor::Range &range, KTextEditor::DocumentPrivate::OffsetList &decToEncOffsetList, KTextEditor::DocumentPrivate::OffsetList &encToDecOffsetList); void replaceCharactersByEncoding(const KTextEditor::Range &range); enum EncodedCharaterInsertionPolicy {EncodeAlways, EncodeWhenPresent, EncodeNever}; protected: KateOnTheFlyChecker *m_onTheFlyChecker; QString m_defaultDictionary; QList > m_dictionaryRanges; // from KTextEditor::MovingRangeFeedback void rangeInvalid(KTextEditor::MovingRange *movingRange) Q_DECL_OVERRIDE; void rangeEmpty(KTextEditor::MovingRange *movingRange) Q_DECL_OVERRIDE; void deleteDictionaryRange(KTextEditor::MovingRange *movingRange); private: Kate::SwapFile *m_swapfile; public: Kate::SwapFile *swapFile(); //helpers for scripting and codefolding int defStyleNum(int line, int column); bool isComment(int line, int column); public: /** * Find the next modified/saved line, starting at @p startLine. If @p down * is \e true, the search is performed downwards, otherwise upwards. * @return the touched line in the requested search direction, or -1 if not found */ int findTouchedLine(int startLine, bool down); private Q_SLOTS: /** * watch for all started io jobs to remember if file is perhaps loading atm * @param job started job */ void slotStarted(KIO::Job *job); void slotCompleted(); void slotCanceled(); /** * trigger display of loading message, after 1000 ms */ void slotTriggerLoadingMessage(); /** * Abort loading */ void slotAbortLoading(); void slotUrlChanged(const QUrl &url); private: /** * different possible states */ enum DocumentStates { /** * Idle */ DocumentIdle, /** * Loading */ DocumentLoading, /** * Saving */ DocumentSaving, /** * Pre Saving As, this is between ::saveAs is called and ::save */ DocumentPreSavingAs, /** * Saving As */ DocumentSavingAs }; /** * current state */ DocumentStates m_documentState; /** * read-write state before loading started */ bool m_readWriteStateBeforeLoading; /** * if the document is untitled */ bool m_isUntitled; /** * loading job, we want to cancel with cancel in the loading message */ QPointer m_loadingJob; /** * message to show during loading */ QPointer m_loadingMessage; /** * Was there any open error on last file loading? */ bool m_openingError; /** * Last open file error message */ QString m_openingErrorMessage; public: /** * reads the line length limit from config, if it is not overriden */ int lineLengthLimit() const; public Q_SLOTS: void openWithLineLengthLimitOverride(); private: /** * timer for delayed handling of mod on hd */ QTimer m_modOnHdTimer; private: /** * currently active template handler; there can be only one */ QPointer m_activeTemplateHandler; private: /** * current autobrace range */ QSharedPointer m_currentAutobraceRange; /** * current autobrace closing charater (e.g. ']') */ QChar m_currentAutobraceClosingChar; private Q_SLOTS: void checkCursorForAutobrace(KTextEditor::View* view, const KTextEditor::Cursor& newPos); public: void setActiveTemplateHandler(KateTemplateHandler* handler); Q_SIGNALS: void loaded(KTextEditor::DocumentPrivate *document); private Q_SLOTS: /** * trigger a close of this document in the application */ void closeDocumentInApplication(); }; #endif diff --git a/src/export/abstractexporter.h b/src/export/abstractexporter.h index c9d0f8ee..03ea9bc0 100644 --- a/src/export/abstractexporter.h +++ b/src/export/abstractexporter.h @@ -1,75 +1,75 @@ /** * This file is part of the KDE libraries * Copyright (C) 2009 Milian Wolff * * 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 ABSTRACTEXPORTER_H #define ABSTRACTEXPORTER_H #include #include #include #include #include #include class AbstractExporter { public: /// If \p m_encapsulate is set, you should add some kind of header in the ctor /// to \p m_output. AbstractExporter(KTextEditor::View *view, QTextStream &output, const bool encapsulate = false) : m_view(view), m_output(output), m_encapsulate(encapsulate), - m_defaultAttribute(0) + m_defaultAttribute(nullptr) { QColor defaultBackground; if (KTextEditor::ConfigInterface *ciface = qobject_cast< KTextEditor::ConfigInterface * >(m_view)) { QVariant variant = ciface->configValue(QStringLiteral("background-color")); if (variant.canConvert()) { defaultBackground = variant.value(); } } m_defaultAttribute = view->defaultStyleAttribute(KTextEditor::dsNormal); m_defaultAttribute->setBackground(QBrush(defaultBackground)); } /// Gets called after everything got exported. /// Hence, if \p m_encapsulate is set, you should probably add some kind of footer here. virtual ~AbstractExporter() {} /// Begin a new line. virtual void openLine() = 0; /// Finish the current line. virtual void closeLine(const bool lastLine) = 0; /// Export \p text with given text attribute \p attrib. virtual void exportText(const QString &text, const KTextEditor::Attribute::Ptr &attrib) = 0; protected: KTextEditor::View *m_view; QTextStream &m_output; bool m_encapsulate; KTextEditor::Attribute::Ptr m_defaultAttribute; }; #endif diff --git a/src/export/exporter.cpp b/src/export/exporter.cpp index eaedb4a6..42973017 100644 --- a/src/export/exporter.cpp +++ b/src/export/exporter.cpp @@ -1,130 +1,130 @@ /** * This file is part of the KDE libraries * Copyright (C) 2009 Milian Wolff * Copyright (C) 2002 John Firebaugh * Copyright (C) 2001 Christoph Cullmann * Copyright (C) 2001 Joseph Wenninger * Copyright (C) 1999 Jochen Wilhelmy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 "exporter.h" #include "abstractexporter.h" #include "htmlexporter.h" #include #include #include #include #include #include #include #include #include void KateExporter::exportToClipboard() { if (!m_view->selection()) { return; } QMimeData *data = new QMimeData(); QString s; QTextStream output(&s, QIODevice::WriteOnly); exportData(true, output); data->setHtml(s); data->setText(s); QApplication::clipboard()->setMimeData(data); } void KateExporter::exportToFile(const QString &file) { QFile savefile(file); if (!savefile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return; } QTextStream outputStream(&savefile); exportData(false, outputStream); } void KateExporter::exportData(const bool useSelection, QTextStream &output) { const KTextEditor::Range range = useSelection ? m_view->selectionRange() : m_view->document()->documentRange(); const bool blockwise = useSelection ? m_view->blockSelection() : false; if ((blockwise || range.onSingleLine()) && (range.start().column() > range.end().column())) { return; } output.setCodec(QTextCodec::codecForName("UTF-8")); ///TODO: add more exporters QScopedPointer exporter; exporter.reset(new HTMLExporter(m_view, output, !useSelection)); - const KTextEditor::Attribute::Ptr noAttrib(0); + const KTextEditor::Attribute::Ptr noAttrib(nullptr); for (int i = range.start().line(); (i <= range.end().line()) && (i < m_view->document()->lines()); ++i) { const QString &line = m_view->document()->line(i); QList attribs = m_view->lineAttributes(i); int lineStart = 0; int remainingChars = line.length(); if (blockwise || range.onSingleLine()) { lineStart = range.start().column(); remainingChars = range.columnWidth(); } else if (i == range.start().line()) { lineStart = range.start().column(); } else if (i == range.end().line()) { remainingChars = range.end().column(); } int handledUntil = lineStart; foreach (const KTextEditor::AttributeBlock & block, attribs) { // honor (block-) selections if (block.start + block.length <= lineStart) { continue; } else if (block.start >= lineStart + remainingChars) { break; } int start = qMax(block.start, lineStart); if (start > handledUntil) { exporter->exportText(line.mid(handledUntil, start - handledUntil), noAttrib); } int length = qMin(block.length, remainingChars); exporter->exportText(line.mid(start, length), block.attribute); handledUntil = start + length; } if (handledUntil < lineStart + remainingChars) { exporter->exportText(line.mid(handledUntil, remainingChars), noAttrib); } exporter->closeLine(i == range.end().line()); } output.flush(); } diff --git a/src/include/ktexteditor/codecompletionmodelcontrollerinterface.h b/src/include/ktexteditor/codecompletionmodelcontrollerinterface.h index 6f68eb0f..b5954d4e 100644 --- a/src/include/ktexteditor/codecompletionmodelcontrollerinterface.h +++ b/src/include/ktexteditor/codecompletionmodelcontrollerinterface.h @@ -1,190 +1,190 @@ /* This file is part of the KDE libraries Copyright (C) 2008 Niko Sams 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 KTEXTEDITOR_CODECOMPLETIONMODELCONTROLLERINTERFACE_H #define KTEXTEDITOR_CODECOMPLETIONMODELCONTROLLERINTERFACE_H #include #include #include "codecompletionmodel.h" class QModelIndex; namespace KTextEditor { class View; /** * \short Controller interface for a CodeCompletionModel * * \ingroup kte_group_ccmodel_extensions * * The CodeCompletionModelControllerInterface gives an CodeCompletionModel better * control over the completion. * * By implementing methods defined in this interface you can: * - control when automatic completion should start (shouldStartCompletion()) * - define a custom completion range (that will be replaced when the completion * is executed) (completionRange()) * - dynamically modify the completion range during completion (updateCompletionRange()) * - specify the string used for filtering the completion (filterString()) * - control when completion should stop (shouldAbortCompletion()) * * When the interface is not implemented, or no methods are overridden the * default behaviour is used, which will be correct in many situations. * * * \section markext_access Implemeting the Interface * To use this class implement it in your CodeCompletionModel. * * \code * class MyCodeCompletion : public KTextEditor::CodeCompletionTestModel, public KTextEditor::CodeCompletionModelControllerInterface * { * Q_OBJECT * Q_INTERFACES(KTextEditor::CodeCompletionModelControllerInterface) * public: * KTextEditor::Range completionRange(KTextEditor::View* view, const KTextEditor::Cursor &position); * }; * \endcode * * \see CodeCompletionModel * \author Niko Sams \ * \author Joseph Wenninger */ class KTEXTEDITOR_EXPORT CodeCompletionModelControllerInterface { public: CodeCompletionModelControllerInterface(); virtual ~CodeCompletionModelControllerInterface(); /** * This function decides if the automatic completion should be started when * the user entered some text. * * The default implementation will return true if the last character in * \p insertedText is a letter, a number, '.', '_' or '\>' * * \param view The view to generate completions for * \param insertedText The text that was inserted by the user * \param userInsertion Whether the text was inserted by the user using typing. * If false, it may have been inserted for example by code-completion. * \param position Current cursor position * \return \e true, if the completion should be started, otherwise \e false */ virtual bool shouldStartCompletion(View *view, const QString &insertedText, bool userInsertion, const Cursor &position); /** * This function returns the completion range that will be used for the * current completion. * * This range will be used for filtering the completion list and will get * replaced when executing the completion * * The default implementation will work for most languages that don't have * special chars in identifiers. * * \param view The view to generate completions for * \param position Current cursor position * \return the completion range */ virtual Range completionRange(View *view, const Cursor &position); /** * This function lets the CompletionModel dynamically modify the range. * Called after every change to the range (eg. when user entered text) * * The default implementation does nothing. * * \param view The view to generate completions for * \param range Reference to the current range * \returns the modified range */ virtual Range updateCompletionRange(View *view, const Range &range); /** * This function returns the filter-text used for the current completion. * Can return an empty string to disable filtering. * * The default implementation will return the text from \p range start to * the cursor \p position. * * \param view The view to generate completions for * \param range The completion range * \param position Current cursor position * \return the string used for filtering the completion list */ virtual QString filterString(View *view, const Range &range, const Cursor &position); /** * This function decides if the completion should be aborted. * Called after every change to the range (eg. when user entered text) * * The default implementation will return true when any special character was entered, or when the range is empty. * * \param view The view to generate completions for * \param range The completion range * \param currentCompletion The text typed so far * \return \e true, if the completion should be aborted, otherwise \e false */ virtual bool shouldAbortCompletion(View *view, const Range &range, const QString ¤tCompletion); /** * When an item within this model is currently selected in the completion-list, and the user inserted the given character, * should the completion-item be executed? This can be used to execute items from other inputs than the return-key. * For example a function item could be executed by typing '(', or variable items by typing '.'. * \param selected The currently selected index * \param inserted The character that was inserted by tue user */ virtual bool shouldExecute(const QModelIndex &selected, QChar inserted); /** * Notification that completion for this model has been aborted. * \param view The view in which the completion for this model was aborted */ virtual void aborted(View *view); enum MatchReaction { None = 0, HideListIfAutomaticInvocation = 1, /** If this is returned, the completion-list is hidden if it was invoked automatically */ ForExtension = 0xffff }; /** * Called whenever an item in the completion-list perfectly matches the current filter text. - * \param The index that is matched + * \param matched The index that is matched * \return Whether the completion-list should be hidden on this event. The default-implementation always returns HideListIfAutomaticInvocation */ virtual MatchReaction matchingItem(const QModelIndex &matched); /** * When multiple completion models are used at the same time, it may happen that multiple models add items with the same * name to the list. This option allows to hide items from this completion model when another model with higher priority * contains items with the same name. * \return Whether items of this completion model should be hidden if another completion model has items with the same name */ virtual bool shouldHideItemsWithEqualNames() const; }; } Q_DECLARE_INTERFACE(KTextEditor::CodeCompletionModelControllerInterface, "org.kde.KTextEditor.CodeCompletionModelControllerInterface") #endif // KTEXTEDITOR_CODECOMPLETIONMODELCONTROLLERINTERFACE_H diff --git a/src/include/ktexteditor/command.h b/src/include/ktexteditor/command.h index 3d57ef43..a0c3a8c0 100644 --- a/src/include/ktexteditor/command.h +++ b/src/include/ktexteditor/command.h @@ -1,212 +1,212 @@ /* This file is part of the KDE project Copyright (C) 2005 Christoph Cullmann (cullmann@kde.org) Copyright (C) 2005-2006 Dominik Haumann (dhdev@gmx.de) Copyright (C) 2008 Erlend Hamberg (ehamberg@gmail.com) 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 KTEXTEDITOR_COMMAND_H #define KTEXTEDITOR_COMMAND_H #include #include #include #include class QStringList; class KCompletion; namespace KTextEditor { class View; /** * \brief An Editor command line command. * * \section cmd_intro Introduction * * The Command class represents a command for the editor command line. A * command simply consists of a string, for example \e find. The command * auto-registers itself at the Editor. The Editor itself queries * the command for a list of accepted strings/commands by calling cmds(). * If the command gets invoked the function exec() is called, i.e. you have * to implement the \e reaction in exec(). Whenever the user needs help for * a command help() is called. * * \section cmd_information Command Information * To provide reasonable information about a specific command there are the * following accessor functions for a given command string: * - name() returns a label * - description() returns a descriptive text * - category() returns a category into which the command fits. * * These getters allow KTextEditor implementations to plug commands into menus * and toolbars, so that a user can assign shortcuts. * * \section cmd_completion Command Completion * * The Command optionally can show a completion popup to help the user select * a valid entry as first parameter to the Command. To this end, return a * valid completion item by reiplementing completionObject(). * * The returned completion object is deleted automatically once it is not needed * anymore. Therefore neither delete the completion object yourself nor return * the same completion object twice. * * \section cmd_interactive Interactive Commands * * In case the Command needs to interactively process the text of the parameters, * override wantsToProcessText() by returning @e true and reimplement processText(). * * A typical example of an interative command would be the incremental search. * * \section cmd_extension Command Extensions * * The class RangeCommand enables you to support ranges so that you can apply * commands on regions of text. * * \see KTextEditor::CommandInterface * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT Command : public QObject { Q_OBJECT public: /** * Constructor with \p parent. * Will register this command for the commands names given in \p cmds at the global editor instance. */ - Command(const QStringList &cmds, QObject *parent = Q_NULLPTR); + Command(const QStringList &cmds, QObject *parent = nullptr); /** * Virtual destructor. * Will unregister this command at the global editor instance. */ virtual ~Command(); public: /** * Return a list of strings a command may begin with. * This is the same list the command was constructed with. * A string is the start part of a pure text which can be handled by this * command, i.e. for the command s/sdl/sdf/g the corresponding string is * simply \e s, and for char:1212 simply \e char. * \return list of supported commands */ inline const QStringList &cmds() const { return m_cmds; } /** * Find out if a given command can act on a range. This is used for checking * if a command should be called when the user also gave a range or if an * error should be raised. * * \return \e true if command supports acting on a range of lines, false if * not, default implementation returns false */ virtual bool supportsRange(const QString &cmd); /** * Execute the command for the given \p view and \p cmd string. * Return the success value and a \p msg for status. As example we * consider a replace command. The replace command would return the number * of replaced strings as \p msg, like "16 replacements made." If an error * occurred in the usage it would return \e false and set the \p msg to * something like "missing argument." or such. * * If a non-invalid range is given, the command shall be executed on that range. * supportsRange() tells if the command supports that. * * \return \e true on success, otherwise \e false */ virtual bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) = 0; /** * Shows help for the given \p view and \p cmd string. * If your command has a help text for \p cmd you have to return \e true * and set the \p msg to a meaningful text. The help text is embedded by * the Editor in a Qt::RichText enabled widget, e.g. a QToolTip. * \return \e true if your command has a help text, otherwise \e false */ virtual bool help(KTextEditor::View *view, const QString &cmd, QString &msg) = 0; /** * Return a KCompletion object that will substitute the command line * default one while typing the first argument of the command \p cmdname. * The text will be added to the command separated by one space character. * * Override this method if your command can provide a completion object. * The returned completion object is deleted automatically once it is not needed * anymore. Therefore neither delete the completion object yourself nor return * the same completion object twice. * * The default implementation returns a null pointer (\e nullptr). * * \param view the view the command will work on * \param cmdname the command name associated with this request. * \return a valid completion object or \e nullptr, if a completion object is * not supported */ virtual KCompletion *completionObject(KTextEditor::View *view, const QString &cmdname); /** * Check, whether the command wants to process text interactively for the * given command with name \p cmdname. * If you return true, the command's processText() method is called * whenever the text in the command line changed. * * Reimplement this to return true, if your commands wants to process the * text while typing. * * \param cmdname the command name associated with this query. * \return \e true, if your command wants to process text interactively, * otherwise \e false * \see processText() */ virtual bool wantsToProcessText(const QString &cmdname); /** * This is called by the command line each time the argument text for the * command changed, if wantsToProcessText() returns \e true. * \param view the current view * \param text the current command text typed by the user * \see wantsToProcessText() */ virtual void processText(KTextEditor::View *view, const QString &text); private: /** * the command list this command got constructed with */ const QStringList m_cmds; /** * Private d-pointer */ class CommandPrivate * const d; }; } #endif diff --git a/src/include/ktexteditor/configinterface.h b/src/include/ktexteditor/configinterface.h index 10dbafa9..f175e237 100644 --- a/src/include/ktexteditor/configinterface.h +++ b/src/include/ktexteditor/configinterface.h @@ -1,146 +1,148 @@ /* This file is part of the KDE project Copyright (C) 2006 Matt Broadstone (mbroadst@gmail.com) 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 KTEXTEDITOR_CONFIGINTERFACE_H #define KTEXTEDITOR_CONFIGINTERFACE_H #include #include #include namespace KTextEditor { /** * \brief Config interface extension for the Document and View. * * \ingroup kte_group_view_extensions * \ingroup kte_group_doc_extensions * * \section config_intro Introduction * * The ConfigInterface provides methods to access and modify the low level * config information for a given Document or View. Examples of this config data can be * displaying the icon bar, showing line numbers, etc. This generally allows * access to settings that otherwise are only accessible during runtime. * * \section config_access Accessing the Interface * * The ConfigInterface is supposed to be an extension interface for a Document or View, * i.e. the Document or View inherits the interface \e provided that the * KTextEditor library in use implements the interface. Use qobject_cast to access * the interface: * \code * // ptr is of type KTextEditor::Document* or KTextEditor::View* * KTextEditor::ConfigInterface *iface = * qobject_cast( ptr ); * * if( iface ) { * * // the implementation supports the interface * // do stuff * } * \endcode * * \section config_data Accessing Data * * A list of available config variables (or keys) can be optained by calling * configKeys(). For all available keys configValue() returns the corresponding * value as QVariant. A value for a given key can be set by calling * setConfigValue(). Right now, when using KatePart as editor component, * KTextEditor::View has support for the following tuples: * - line-numbers [bool], show/hide line numbers * - icon-bar [bool], show/hide icon bar * - folding-bar [bool], show/hide the folding bar * - folding-preview [bool], enable/disable folding preview when mouse hovers * on folded region * - dynamic-word-wrap [bool], enable/disable dynamic word wrap * - background-color [QColor], read/set the default background color * - selection-color [QColor], read/set the default color for selections * - search-highlight-color [QColor], read/set the background color for search * - replace-highlight-color [QColor], read/set the background color for replaces * - default-mark-type [uint], read/set the default mark type * - allow-mark-menu [bool], enable/disable the menu shown when right clicking * on the left gutter. When disabled, click on the gutter will always set * or clear the mark of default type. * - icon-border-color [QColor] read/set the icon border color (on the left, * with the line numbers) * - folding-marker-color [QColor] read/set folding marker colors (in the icon border) * - line-number-color [QColor] read/set line number colors (in the icon border) * - current-line-number-color [QColor] read/set current line number color (in the icon border) * - modification-markers [bool] read/set whether the modification markers are shown * - word-count [bool] enable/disable the counting of words and characters in the statusbar * - scrollbar-minimap [bool] enable/disable scrollbar minimap * - scrollbar-preview [bool] enable/disable scrollbar text preview on hover + * - font [QFont] change the font * * KTextEditor::Document has support for the following: * - backup-on-save-local [bool], enable/disable backup when saving local files * - backup-on-save-remote [bool], enable/disable backup when saving remote files * - backup-on-save-suffix [string], set the suffix for file backups, e.g. "~" * - backup-on-save-prefix [string], set the prefix for file backups, e.g. "." * - replace-tabs [bool], whether to replace tabs * - indent-pasted-text [bool], whether to indent pasted text * - tab-width [int], read/set the width for tabs * - indent-width [int], read/set the indentation width + * - on-the-fly-spellcheck [bool], enable/disable on the fly spellcheck * * Either interface should emit the \p configChanged signal when appropriate. * TODO: Add to interface in KDE 5. * * For instance, if you want to enable dynamic word wrap of a KTextEditor::View * simply call * \code * iface->setConfigValue("dynamic-word-wrap", true); * \endcode * * \see KTextEditor::View, KTextEditor::Document * \author Matt Broadstone \ */ class KTEXTEDITOR_EXPORT ConfigInterface { public: ConfigInterface(); /** * Virtual destructor. */ virtual ~ConfigInterface(); public: /** * Get a list of all available keys. */ virtual QStringList configKeys() const = 0; /** * Get a value for the \p key. */ virtual QVariant configValue(const QString &key) = 0; /** * Set a the \p key's value to \p value. */ virtual void setConfigValue(const QString &key, const QVariant &value) = 0; private: class ConfigInterfacePrivate *const d; }; } Q_DECLARE_INTERFACE(KTextEditor::ConfigInterface, "org.kde.KTextEditor.ConfigInterface") #endif diff --git a/src/include/ktexteditor/document.h b/src/include/ktexteditor/document.h index e5a3f427..a8b4d097 100644 --- a/src/include/ktexteditor/document.h +++ b/src/include/ktexteditor/document.h @@ -1,1180 +1,1180 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2014 Christoph Cullmann Copyright (C) 2005-2014 Dominik Haumann (dhaumann@kde.org) 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 KTEXTEDITOR_DOCUMENT_H #define KTEXTEDITOR_DOCUMENT_H #include #include #include #include // our main baseclass of the KTextEditor::Document #include // the list of views #include #include class KConfigGroup; namespace KTextEditor { class DocumentPrivate; class EditingTransactionPrivate; class MainWindow; class Message; class View; /** * \brief Search flags for use with searchText. * * Modifies the behavior of searchText. * By default it is searched for a case-sensitive plaintext pattern, * without processing of escape sequences, with "whole words" off, * in forward direction, within a non-block-mode text range. * * \author Sebastian Pipping \ */ enum SearchOption { Default = 0, ///< Default settings // modes Regex = 1 << 1, ///< Treats the pattern as a regular expression // options for all modes CaseInsensitive = 1 << 4, ///< Ignores cases, e.g. "a" matches "A" Backwards = 1 << 5, ///< Searches in backward direction // options for plaintext EscapeSequences = 1 << 10, ///< Plaintext mode: Processes escape sequences WholeWords = 1 << 11, ///< Plaintext mode: Whole words only, e.g. @em not "amp" in "example" MaxSearchOption = 1 << 31 ///< Placeholder for binary compatability }; Q_DECLARE_FLAGS(SearchOptions, SearchOption) Q_DECLARE_OPERATORS_FOR_FLAGS(SearchOptions) /** * \brief A KParts derived class representing a text document. * * Topics: * - \ref doc_intro * - \ref doc_manipulation * - \ref doc_views * - \ref doc_readwrite * - \ref doc_notifications * - \ref doc_recovery * - \ref doc_extensions * * \section doc_intro Introduction * * The Document class represents a pure text document providing methods to * modify the content and create views. A document can have any number * of views, each view representing the same content, i.e. all views are * synchronized. Support for text selection is handled by a View and text * format attributes by the Attribute class. * * To load a document call KParts::ReadOnlyPart::openUrl(). * To reload a document from a file call documentReload(), to save the * document call documentSave() or documentSaveAs(). Whenever the modified * state of the document changes the signal modifiedChanged() is emitted. * Check the modified state with KParts::ReadWritePart::isModified(). * Further signals are documentUrlChanged(). The encoding can be specified * with setEncoding(), however this will only take effect on file reload and * file save. * * \section doc_manipulation Text Manipulation * * Get the whole content with text() and set new content with setText(). * Call insertText() or insertLine() to insert new text or removeText() * and removeLine() to remove content. Whenever the document's content * changed the signal textChanged() is emitted. Additional signals are * textInserted() and textRemoved(). Note, that the first line in the * document is line 0. * * A Document provides full undo/redo history. * Text manipulation actions can be grouped together to one undo/redo action by * using an the class EditingTransaction. You can stack multiple EditingTransaction%s. * Internally, the Document has a reference counter. If this reference counter * is increased the first time (by creating an instance of EditingTransaction), * the signal editingStarted() is emitted. Only when the internal reference counter * reaches zero again, the signal editingFinished() and optionally the signal * textChanged() are emitted. Whether an editing transaction is currently active * can be checked by calling isEditingTransactionRunning(). * * @note The signal editingFinished() is always emitted when the last istance * of EditingTransaction is destroyed. Contrary, the signal textChanged() * is emitted only if text changed. Hence, textChanged() is more accurate * with respect to changes in the Document. * * Every text editing transaction is also available through the signals * lineWrapped(), lineUnwrapped(), textInserted() and textRemoved(). * However, these signals should be used with care. Please be aware of the * following warning: * * @warning Never change the Document's contents when edit actions are active, * i.e. in between of (foreign) editing transactions. In case you * violate this, the currently active edit action may perform edits * that lead to undefined behavior. * * \section doc_views Document Views * * A View displays the document's content. As already mentioned, a document * can have any number of views, all synchronized. Get a list of all views * with views(). Create a new view with createView(). Every time a new view * is created the signal viewCreated() is emitted. * * \section doc_readwrite Read-Only Mode * * A Document may be in read-only mode, for instance due to missing file * permissions. The read-only mode can be checked with isReadWrite(). Further, * the signal readWriteChanged() is emitted whenever the state changes either * to read-only mode or to read/write mode. The read-only mode can be controlled * with setReadWrite(). * * \section doc_notifications Notifications in Documents and Views * * A Document has the ability to show a Message to the user in a View. * The Message then is shown either the specified View if Message::setView() * was called, or in all View%s of the Document. * * To post a message just create a new Message and send it with postMessage(). * Further information is available in the API documentation of Message. * * @see Message * * \section doc_recovery Crash Recovery for Documents * * When the system or the application using the editor component crashed * with unsaved changes in the Document, the View notifies the user about * the lost data and asks, whether the data should be recovered. * * This Document gives you control over the data recovery process. Use * isDataRecoveryAvailable() to check for lost data. If you do not want the * editor component to handle the data recovery process automatically, you can * either trigger the data recovery by calling recoverData() or discard it * through discardDataRecovery(). * * \section doc_extensions Document Extension Interfaces * * A simple document represents text and provides text manipulation methods. * However, a real text editor should support advanced concepts like session * support, textsearch support, bookmark/general mark support etc. That is why * the KTextEditor library provides several additional interfaces to extend * a document's capabilities via multiple inheritance. * * More information about interfaces for the document can be found in * \ref kte_group_doc_extensions. * * \see KParts::ReadWritePart, KTextEditor::Editor, KTextEditor::View, * KTextEditor::MarkInterface, KTextEditor::ModificationInterface, * KTextEditor::MovingInterface * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT Document : public KParts::ReadWritePart { Q_OBJECT protected: /** * Constructor. * * Create a new document with \p parent. * * Pass it the internal implementation to store a d-pointer. * * \param impl d-pointer to use * \param parent parent object * \see Editor::createDocument() */ Document(DocumentPrivate *impl, QObject *parent); public: /** * Virtual destructor. */ virtual ~Document(); /** * \name Manage View%s of this Document * * \{ */ public: /** * Create a new view attached to @p parent. * @param parent parent widget * @param mainWindow the main window responsible for this view, if any * @return the new view */ - virtual View *createView(QWidget *parent, KTextEditor::MainWindow *mainWindow = Q_NULLPTR) = 0; + virtual View *createView(QWidget *parent, KTextEditor::MainWindow *mainWindow = nullptr) = 0; /** * Returns the views pre-casted to KTextEditor::View%s */ virtual QList views() const = 0; Q_SIGNALS: /** * This signal is emitted whenever the \p document creates a new \p view. * It should be called for every view to help applications / plugins to * attach to the \p view. * \attention This signal should be emitted after the view constructor is * completed, e.g. in the createView() method. * \param document the document for which a new view is created * \param view the new view * \see createView() */ void viewCreated(KTextEditor::Document *document, KTextEditor::View *view); //!\} /** * \name General Information about this Document * * \{ */ public: /** * Get this document's name. * The editor part should provide some meaningful name, like some unique * "Untitled XYZ" for the document - \e without URL or basename for * documents with url. * \return readable document name */ virtual QString documentName() const = 0; /** * Get this document's mimetype. * \return mimetype */ virtual QString mimeType() = 0; /** * Get the git hash of the Document's contents on disk. * The returned hash equals the git hash of the file written to disk. * If the document is a remote document, the checksum may not be * available. In this case, QByteArray::isNull() returns \e true. * * git hash is defined as: * * sha1("blob " + filesize + "\0" + filecontent) * * \return the git hash of the document */ virtual QByteArray checksum() const = 0; /* * SIGNALS * following signals should be emitted by the editor document. */ Q_SIGNALS: /** * This signal is emitted whenever the \p document name changes. * \param document document which changed its name * \see documentName() */ void documentNameChanged(KTextEditor::Document *document); /** * This signal is emitted whenever the \p document URL changes. * \param document document which changed its URL * \see KParts::ReadOnlyPart::url() */ void documentUrlChanged(KTextEditor::Document *document); /** * This signal is emitted whenever the \p document's buffer changed from * either state \e unmodified to \e modified or vice versa. * * \param document document which changed its modified state * \see KParts::ReadWritePart::isModified(). * \see KParts::ReadWritePart::setModified() */ void modifiedChanged(KTextEditor::Document *document); /** * This signal is emitted whenever the readWrite state of a document * changes. * \param document the document whose read/write property changed * \see KParts::ReadWritePart::setReadWrite() */ void readWriteChanged(KTextEditor::Document *document); /* * VERY IMPORTANT: Methods to set and query the current encoding of the * document */ public: /** * Set the encoding for this document. This encoding will be used * while loading and saving files, it will \e not affect the already * existing content of the document, e.g. if the file has already been * opened without the correct encoding, this will \e not fix it, you * would for example need to trigger a reload for this. * \param encoding new encoding for the document, the name must be * accepted by QTextCodec, if an empty encoding name is given, the * part should fallback to its own default encoding, e.g. the * system encoding or the global user settings * \return \e true on success, or \e false, if the encoding could not be set. * \see encoding() */ virtual bool setEncoding(const QString &encoding) = 0; /** * Get the current chosen encoding. The return value is an empty string, * if the document uses the default encoding of the editor and no own * special encoding. * \return current encoding of the document * \see setEncoding() */ virtual QString encoding() const = 0; //!\} /** * \name File Loading and Saving * * All this actions cause user interaction in some cases. * \{ */ public: /** * Reload the current file. * The user will be prompted by the part on changes and more and can * cancel this action if it can harm. * \return \e true if the reload has been done, otherwise \e false. If * the document has no url set, it will just return \e false. */ virtual bool documentReload() = 0; /** * Save the current file. * The user will be asked for a filename if needed and more. * \return \e true on success, i.e. the save has been done, otherwise * \e false */ virtual bool documentSave() = 0; /** * Save the current file to another location. * The user will be asked for a filename and more. * \return \e true on success, i.e. the save has been done, otherwise * \e false */ virtual bool documentSaveAs() = 0; /** * True, eg if the file for opening could not be read * This doesn't have to handle the KPart job canceled cases. * @return was there some problem loading the file? */ bool openingError() const; /** * Error message if any problem occured on last load. * @return error message what went wrong on loading */ QString openingErrorMessage() const; /* * SIGNALS * Following signals should be emitted by the document if the text content * is changed. */ Q_SIGNALS: /** * This signal should be emitted after a document has been saved to disk or for remote files uploaded. * saveAs should be set to true, if the operation is a save as operation */ void documentSavedOrUploaded(KTextEditor::Document *document, bool saveAs); /** * Warn anyone listening that the current document is about to close. * At this point all of the information is still accessible, such as the text, * cursors and ranges. * * Any modifications made to the document at this point will be lost. * * \param document the document being closed */ void aboutToClose(KTextEditor::Document *document); /** * Warn anyone listening that the current document is about to reload. * At this point all of the information is still accessible, such as the text, * cursors and ranges. * * Any modifications made to the document at this point will be lost. * * \param document the document being reloaded */ void aboutToReload(KTextEditor::Document *document); /** * Emitted after the current document was reloaded. * At this point, some information might have been invalidated, like * for example the editing history. * * \param document the document that was reloaded. * * @since 4.6 */ void reloaded(KTextEditor::Document *document); //!\} /** * \name Text Manipulation * * \{ */ public: /** * Editing transaction support. * * Edit commands during this sequence will be bunched together so that * they represent a single undo command in the editor, and so that * repaint events do not occur in between. * * Your application should \e not return control to the event loop while * it has an unterminated (i.e. this object is not destructed) editing * sequence (result undefined) - so do all of your work in one go! * * Using this class typically looks as follows: * @code * void foo() { * KTextEditor::Document::EditingTransaction transaction(document); * // now call editing functions * document->removeText(...) * document->insertText(...) * } * @endcode * * Although usually not required, the EditingTransaction additionally * allows to manually call finish() and start() in between. * * @see editingStarted(), editingFinished() */ class KTEXTEDITOR_EXPORT EditingTransaction { public: /** * Constructs the object and starts an editing transaction by * calling start(). * * @param document document for the transaction * @see start() */ explicit EditingTransaction(Document *document); /** * Destructs the object and, if needed, finishs a running editing * transaction by calling finish(). * * @see finish() */ ~EditingTransaction(); /** * By calling start(), the editing transaction can be started again. * This function only is of use in combination with finish(). * * @see finish() */ void start(); /** * By calling finish(), the editing transaction can be finished * already before destruction of this instance. * * @see start() */ void finish(); private: /** * no copying allowed */ Q_DISABLE_COPY(EditingTransaction) /** * private d-pointer */ EditingTransactionPrivate *const d; }; /** * Check whether an editing transaction is currently running. * * @see EditingTransaction */ virtual bool isEditingTransactionRunning() const = 0; /* * General access to the document's text content. */ public: /** * Get the document content. * \return the complete document content * \see setText() */ virtual QString text() const = 0; /** * Get the document content within the given \p range. * \param range the range of text to retrieve * \param block Set this to \e true to receive text in a visual block, * rather than everything inside \p range. * \return the requested text part, or QString() for invalid ranges. * \see setText() */ virtual QString text(const Range &range, bool block = false) const = 0; /** * Get the character at text position \p cursor. * \param position the location of the character to retrieve * \return the requested character, or QChar() for invalid cursors. * \see setText() */ virtual QChar characterAt(const Cursor &position) const = 0; /** * Get the word at the text position \p cursor. * The returned word is defined by the word boundaries to the left and * right starting at \p cursor. The algorithm takes highlighting information * into account, e.g. a dash ('-') in C++ is interpreted as word boundary, * whereas e.g. CSS allows identifiers with dash ('-'). * * If \p cursor is not a valid text position or if there is no word * under the requested position \p cursor, an empty string is returned. * * \param cursor requested cursor position for the word * \return the word under the cursor or an empty string if there is no word. * * \see wordRangeAt(), characterAt() */ virtual QString wordAt(const KTextEditor::Cursor &cursor) const = 0; /** * Get the text range for the word located under the text position \p cursor. * The returned word is defined by the word boundaries to the left and * right starting at \p cursor. The algorithm takes highlighting information * into account, e.g. a dash ('-') in C++ is interpreted as word boundary, * whereas e.g. CSS allows identifiers with dash ('-'). * * If \p cursor is not a valid text position or if there is no word * under the requested position \p cursor, an invalid text range is returned. * If the text range is valid, it is \e always on a single line. * * \param cursor requested cursor position for the word * \return the Range spanning the word under the cursor or an invalid range if there is no word. * * \see wordAt(), characterAt(), KTextEditor::Range::isValid() */ virtual KTextEditor::Range wordRangeAt(const KTextEditor::Cursor &cursor) const = 0; /** * Get whether \p cursor is a valid text position. * A cursor position at (line, column) is valid, if * - line >= 0 and line < lines() holds, and * - column >= 0 and column <= lineLength(column). * * The text position \p cursor is also invalid if it is inside a Unicode surrogate. * Therefore, use this function when iterating over the characters of a line. * * \param cursor cursor position to check for validity * \return true, if \p cursor is a valid text position, otherwise \p false * * \since 5.0 */ virtual bool isValidTextPosition(const KTextEditor::Cursor& cursor) const = 0; /** * Get the document content within the given \p range. * \param range the range of text to retrieve * \param block Set this to \e true to receive text in a visual block, * rather than everything inside \p range. * \return the requested text lines, or QStringList() for invalid ranges. * no end of line termination is included. * \see setText() */ virtual QStringList textLines(const Range &range, bool block = false) const = 0; /** * Get a single text line. * \param line the wanted line * \return the requested line, or "" for invalid line numbers * \see text(), lineLength() */ virtual QString line(int line) const = 0; /** * Get the count of lines of the document. * \return the current number of lines in the document * \see length() */ virtual int lines() const = 0; /** * Check whether \p line currently contains unsaved data. * If \p line contains unsaved data, \e true is returned, otherwise \e false. * When the user saves the file, a modified line turns into a \e saved line. * In this case isLineModified() returns \e false and in its stead isLineSaved() * returns \e true. * \param line line to query * \see isLineSaved(), isLineTouched() * \since 5.0 */ virtual bool isLineModified(int line) const = 0; /** * Check whether \p line currently contains only saved text. * Saved text in this case implies that a line was touched at some point * by the user and then then changes were either undone or the user saved * the file. * * In case \p line was touched and currently contains only saved data, * \e true is returned, otherwise \e false. * \param line line to query * \see isLineModified(), isLineTouched() * \since 5.0 */ virtual bool isLineSaved(int line) const = 0; /** * Check whether \p line was touched since the file was opened. * This equals the statement isLineModified() || isLineSaved(). * \param line line to query * \see isLineModified(), isLineSaved() * \since 5.0 */ virtual bool isLineTouched(int line) const = 0; /** * End position of the document. * \return The last column on the last line of the document * \see all() */ virtual Cursor documentEnd() const = 0; /** * A Range which encompasses the whole document. * \return A range from the start to the end of the document */ inline Range documentRange() const { return Range(Cursor::start(), documentEnd()); } /** * Get the count of characters in the document. A TAB character counts as * only one character. * \return the number of characters in the document * \see lines() */ virtual int totalCharacters() const = 0; /** * Returns if the document is empty. */ virtual bool isEmpty() const; /** * Get the length of a given line in characters. * \param line line to get length from * \return the number of characters in the line or -1 if the line was * invalid * \see line() */ virtual int lineLength(int line) const = 0; /** * Get the end cursor position of line \p line. * \param line line * \see lineLength(), line() */ inline Cursor endOfLine(int line) const { return Cursor(line, lineLength(line)); } /** * Set the given text as new document content. * \param text new content for the document * \return \e true on success, otherwise \e false * \see text() */ virtual bool setText(const QString &text) = 0; /** * Set the given text as new document content. * \param text new content for the document * \return \e true on success, otherwise \e false * \see text() */ virtual bool setText(const QStringList &text) = 0; /** * Remove the whole content of the document. * \return \e true on success, otherwise \e false * \see removeText(), removeLine() */ virtual bool clear() = 0; /** * Insert \p text at \p position. * \param position position to insert the text * \param text text to insert * \param block insert this text as a visual block of text rather than a linear sequence * \return \e true on success, otherwise \e false * \see setText(), removeText() */ virtual bool insertText(const Cursor &position, const QString &text, bool block = false) = 0; /** * Insert \p text at \p position. * \param position position to insert the text * \param text text to insert * \param block insert this text as a visual block of text rather than a linear sequence * \return \e true on success, otherwise \e false * \see setText(), removeText() */ virtual bool insertText(const Cursor &position, const QStringList &text, bool block = false) = 0; /** * Replace text from \p range with specified \p text. * \param range range of text to replace * \param text text to replace with * \param block replace text as a visual block of text rather than a linear sequence * \return \e true on success, otherwise \e false * \see setText(), removeText(), insertText() */ virtual bool replaceText(const Range &range, const QString &text, bool block = false); /** * Replace text from \p range with specified \p text. * \param range range of text to replace * \param text text to replace with * \param block replace text as a visual block of text rather than a linear sequence * \return \e true on success, otherwise \e false * \see setText(), removeText(), insertText() */ virtual bool replaceText(const Range &range, const QStringList &text, bool block = false); /** * Remove the text specified in \p range. * \param range range of text to remove * \param block set this to true to remove a text block on the basis of columns, rather than everything inside \p range * \return \e true on success, otherwise \e false * \see setText(), insertText() */ virtual bool removeText(const Range &range, bool block = false) = 0; /** * Insert line(s) at the given line number. The newline character '\\n' * is treated as line delimiter, so it is possible to insert multiple * lines. To append lines at the end of the document, use * \code * insertLine( lines(), text ) * \endcode * \param line line where to insert the text * \param text text which should be inserted * \return \e true on success, otherwise \e false * \see insertText() */ virtual bool insertLine(int line, const QString &text) = 0; /** * Insert line(s) at the given line number. The newline character '\\n' * is treated as line delimiter, so it is possible to insert multiple * lines. To append lines at the end of the document, use * \code * insertLine( lines(), text ) * \endcode * \param line line where to insert the text * \param text text which should be inserted * \return \e true on success, otherwise \e false * \see insertText() */ virtual bool insertLines(int line, const QStringList &text) = 0; /** * Remove \p line from the document. * \param line line to remove * \return \e true on success, otherwise \e false * \see removeText(), clear() */ virtual bool removeLine(int line) = 0; /** * \brief Searches the given input range for a text pattern. * * Searches for a text pattern within the given input range. * The kind of search performed depends on the \p options * used. Use this function for plaintext searches as well as * regular expression searches. If no match is found the first * (and only) element in the vector return is the invalid range. * When searching for regular expressions, the first element holds * the range of the full match, the subsequent elements hold * the ranges of the capturing parentheses. * * \param range Input range to search in * \param pattern Text pattern to search for * \param options Combination of search flags * \return List of ranges (length >=1) * * \author Sebastian Pipping \ * * \since 5.11 */ QVector searchText(const KTextEditor::Range &range, const QString &pattern, const SearchOptions options = Default) const; /* * SIGNALS * Following signals should be emitted by the document if the text content * is changed. */ Q_SIGNALS: /** * Editing transaction has started. * \param document document which emitted this signal */ void editingStarted(KTextEditor::Document *document); /** * Editing transaction has finished. * * @note This signal is emitted also for editing actions that maybe do not * modify the @p document contents (think of having an empty * EditingTransaction). If you want to get notified only * after text really changed, connect to the signal textChanged(). * * \param document document which emitted this signal * @see textChanged() */ void editingFinished(KTextEditor::Document *document); /** * A line got wrapped. * \param document document which emitted this signal * @param position position where the wrap occurred */ void lineWrapped(KTextEditor::Document *document, const KTextEditor::Cursor &position); /** * A line got unwrapped. * \param document document which emitted this signal * @param line line where the unwrap occurred */ void lineUnwrapped(KTextEditor::Document *document, int line); /** * Text got inserted. * \param document document which emitted this signal * @param position position where the insertion occurred * @param text inserted text */ void textInserted(KTextEditor::Document *document, const KTextEditor::Cursor &position, const QString &text); /** * Text got removed. * \param document document which emitted this signal * @param range range where the removal occurred * @param text removed text */ void textRemoved(KTextEditor::Document *document, const KTextEditor::Range &range, const QString &text); /** * The \p document emits this signal whenever its text changes. * \param document document which emitted this signal * \see text(), textLine() */ void textChanged(KTextEditor::Document *document); //!\} /** * \name Highlighting and Related Information * * \{ */ public: /** * Get the default style of the character located at @p position. * If @p position is not a valid text position, the default style * DefaultStyle::dsNormal is returned. * * @note Further information about the colors of default styles depend on * the currently chosen schema. Since each View may have a different * color schema, the color information can be obtained through * View::defaultStyleAttribute() and View::lineAttributes(). * * @param position text position * @return default style, see enum KTextEditor::DefaultStyle * @see View::defaultStyleAttribute(), View::lineAttributes() */ virtual DefaultStyle defaultStyleAt(const KTextEditor::Cursor &position) const = 0; /** * Return the name of the currently used mode * \return name of the used mode * \see modes(), setMode() */ virtual QString mode() const = 0; /** * Return the name of the currently used mode * \return name of the used mode * \see highlightingModes(), setHighlightingMode() */ virtual QString highlightingMode() const = 0; /** * \brief Get all available highlighting modes for the current document. * * Each document can be highlighted using an arbitrary number of highlighting * contexts. This method will return the names for each of the used modes. * * Example: The "PHP (HTML)" mode includes the highlighting for PHP, HTML, CSS and JavaScript. * * \return Returns a list of embedded highlighting modes for the current Document. * * \see KTextEditor::Document::highlightingMode() */ virtual QStringList embeddedHighlightingModes() const = 0; /** * \brief Get the highlight mode used at a given position in the document. * * Retrieve the name of the applied highlight mode at a given \p position * in the current document. * * Calling this might trigger re-highlighting up to the given line. * Therefore this is not const. * * \see highlightingModes() */ virtual QString highlightingModeAt(const Cursor &position) = 0; /** * Return a list of the names of all possible modes * \return list of mode names * \see mode(), setMode() */ virtual QStringList modes() const = 0; /** * Return a list of the names of all possible modes * \return list of mode names * \see highlightingMode(), setHighlightingMode() */ virtual QStringList highlightingModes() const = 0; /** * Set the current mode of the document by giving its name * \param name name of the mode to use for this document * \return \e true on success, otherwise \e false * \see mode(), modes(), modeChanged() */ virtual bool setMode(const QString &name) = 0; /** * Set the current mode of the document by giving its name * \param name name of the mode to use for this document * \return \e true on success, otherwise \e false * \see highlightingMode(), highlightingModes(), highlightingModeChanged() */ virtual bool setHighlightingMode(const QString &name) = 0; /** * Returns the name of the section for a highlight given its index in the highlight * list (as returned by highlightModes()). * * You can use this function to build a tree of the highlight names, organized in sections. * * \param index the index of the highlight in the list returned by modes() */ virtual QString highlightingModeSection(int index) const = 0; /** * Returns the name of the section for a mode given its index in the highlight * list (as returned by modes()). * * You can use this function to build a tree of the mode names, organized in sections. * * \param index the index of the highlight in the list returned by modes() */ virtual QString modeSection(int index) const = 0; /* * SIGNALS * Following signals should be emitted by the document if the mode * of the document changes */ Q_SIGNALS: /** * Warn anyone listening that the current document's mode has * changed. * * \param document the document whose mode has changed * \see setMode() */ void modeChanged(KTextEditor::Document *document); /** * Warn anyone listening that the current document's highlighting mode has * changed. * * \param document the document which's mode has changed * \see setHighlightingMode() */ void highlightingModeChanged(KTextEditor::Document *document); //!\} /** * \name Printing * * \{ */ public: /** * Print the document. This should result in showing the print dialog. * * @returns true if document was printed */ virtual bool print() = 0; /** * Shows the print preview dialog/ */ virtual void printPreview() = 0; //!\} /** * \name Showing Interactive Notifications * * \{ */ public: /** * Post @p message to the Document and its View%s. * If multiple Message%s are posted, the one with the highest priority * is shown first. * * Usually, you can simply forget the pointer, as the Message is deleted * automatically, once it is processed or the document gets closed. * * If the Document does not have a View yet, the Message is queued and * shown, once a View for the Document is created. * * @param message the message to show * @return @e true, if @p message was posted. @e false, if message == 0. */ virtual bool postMessage(Message *message) = 0; //!\} /** * \name Session Configuration * * \{ */ public: /** * Read session settings from the given \p config. * * Known flags: * - \p SkipUrl => do not save/restore the file * - \p SkipMode => do not save/restore the mode * - \p SkipHighlighting => do not save/restore the highlighting * - \p SkipEncoding => do not save/restore the encoding * * \param config read the session settings from this KConfigGroup * \param flags additional flags * \see writeSessionConfig() */ virtual void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) = 0; /** * Write session settings to the \p config. * See readSessionConfig() for more details about available \p flags. * * \param config write the session settings to this KConfigGroup * \param flags additional flags * \see readSessionConfig() */ virtual void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) = 0; //!\} /** * \name Crash Recovery * * \{ */ public: /** * Returns whether a recovery is available for the current document. * * \see recoverData(), discardDataRecovery() */ virtual bool isDataRecoveryAvailable() const = 0; /** * If recover data is available, calling recoverData() will trigger the * recovery of the data. If isDataRecoveryAvailable() returns \e false, * calling this function does nothing. * * \see isDataRecoveryAvailable(), discardDataRecovery() */ virtual void recoverData() = 0; /** * If recover data is available, calling discardDataRecovery() will discard * the recover data and the recover data is lost. * If isDataRecoveryAvailable() returns \e false, calling this function * does nothing. * * \see isDataRecoveryAvailable(), recoverData() */ virtual void discardDataRecovery() = 0; //!\} private: /** * private d-pointer, pointing to the internal implementation */ DocumentPrivate *const d; }; } Q_DECLARE_METATYPE(KTextEditor::Document *) #endif diff --git a/src/include/ktexteditor/markinterface.h b/src/include/ktexteditor/markinterface.h index 4885e14b..0c1d16f4 100644 --- a/src/include/ktexteditor/markinterface.h +++ b/src/include/ktexteditor/markinterface.h @@ -1,405 +1,405 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann (cullmann@kde.org) Copyright (C) 2005 Dominik Haumann (dhdev@gmx.de) (documentation) 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 KTEXTEDITOR_MARKINTERFACE_H #define KTEXTEDITOR_MARKINTERFACE_H #include #include #include class QPixmap; class QPoint; class QMenu; namespace KTextEditor { class Document; /** * \brief Mark class containing line and mark types. * * \section mark_intro Introduction * * The class Mark represents a mark in a Document. It contains the \e line * and \e type. A line can have multiple marks, like a \e bookmark and a * \e breakpoint, i.e. the \e type contains all marks combined with a logical * \e OR (|). There are several predefined mark types, look into the * MarkInterface for further details. * * \see KTextEditor::MarkInterface, KTextEditor::Document */ class Mark { public: /** The line that contains the mark. */ int line; /** The mark types in the line, combined with logical OR. */ uint type; }; /** * \brief Mark extension interface for the Document. * * \ingroup kte_group_doc_extensions * * \section markext_intro Introduction * * The MarkInterface provides methods to enable and disable marks in a * Document, a marked line can be visualized for example with a shaded * background color and/or a pixmap in the iconborder of the Document's View. * There are a number of predefined mark types, specified in * reservedMarkersCount(). Additionally it is possible to add custom marks * and set custom pixmaps. * * \section markext_access Accessing the Interface * * The MarkInterface is supposed to be an extension interface for a Document, * i.e. the Document inherits the interface \e provided that the * KTextEditor library in use implements the interface. Use qobject_cast to access * the interface: * \code * // doc is of type KTextEditor::Document* * KTextEditor::MarkInterface *iface = * qobject_cast( doc ); * * if( iface ) { * // the implementation supports the interface * // do stuff * } * \endcode * * \section markext_handling Handling Marks * * Get all marks in the document by calling marks(). Use clearMarks() to * remove all marks in the entire document. A single mark can be retrieved * with mark(). To remove all marks from a line call clearMark(). To add * and remove marks from a given line use addMark() and removeMark(). It is * also possible to replace all marks with setMark(), i.e. setMark() is the * same as a call of clearMark() followed by addMark(). The signals * marksChanged() and markChanged() are emitted whenever a line's marks * changed. * * \attention A mark type is represented as an \e uint. An \e uint can have * several mark types combined (see above: logical OR). That means for * all functions/signals with an \e uint parameter, e.g. setMark(), * removeMark(), etc, the \e uint may contain \e multiple marks, i.e. * you can add and remove multiple marks \e simultaneously. * * \section markext_userdefined User Defined Marks * * All marks that should be editable by the user can be specified with a mark * mask via setEditableMarks(). To set a description and pixmap of a mark type * call setMarkDescription() and setMarkPixmap(). * * \see KTextEditor::Document, KTextEditor::Mark * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT MarkInterface { public: MarkInterface(); /** * Virtual destructor. */ virtual ~MarkInterface(); // // slots !!! // public: /** * Get all marks set on the \p line. * \param line requested line * \return a \e uint representing of the marks set in \p line concatenated * by logical OR * \see addMark(), removeMark() */ virtual uint mark(int line) = 0; /** * Set the \p line's mark types to \p markType. * If \p line already contains a mark of the given type it has no effect. * All other marks are deleted before the mark is set. You can achieve * the same by calling * \code * clearMark(line); * addMark(line, markType); * \endcode * \param line line to set the mark * \param markType mark type * \see clearMark(), addMark(), mark() */ virtual void setMark(int line, uint markType) = 0; /** * Clear all marks set in the \p line. * \param line line to clear marks * \see clearMarks(), removeMark(), addMark() */ virtual void clearMark(int line) = 0; /** * Add marks of type \p markType to \p line. Existing marks on this line * are preserved. If the mark \p markType already is set, nothing * happens. * \param line line to set the mark * \param markType mark type * \see removeMark(), setMark() */ virtual void addMark(int line, uint markType) = 0; /** * Remove the mark mask of type \p markType from \p line. * \param line line to remove the mark * \param markType mark type to be removed * \see clearMark() */ virtual void removeMark(int line, uint markType) = 0; /** * Get a hash holding all marks in the document. * The hash key for a mark is its line. * \return a hash holding all marks in the document */ virtual const QHash &marks() = 0; /** * Clear all marks in the entire document. * \see clearMark(), removeMark() */ /// TODO: dominik: add argument unit mask = 0 virtual void clearMarks() = 0; /** * Get the number of predefined mark types we have so far. * \note FIXME: If you change this you have to make sure katepart * supports the new size! * \return number of reserved marker types */ static int reservedMarkersCount() { return 7; } /** * Predefined mark types. * * To add a new standard mark type, edit this interface and document * the type. */ enum MarkTypes { /** Bookmark */ markType01 = 0x1, /** Breakpoint active */ markType02 = 0x2, /** Breakpoint reached */ markType03 = 0x4, /** Breakpoint disabled */ markType04 = 0x8, /** Execution mark */ markType05 = 0x10, /** Warning */ markType06 = 0x20, /** Error */ markType07 = 0x40, markType08 = 0x80, markType09 = 0x100, markType10 = 0x200, markType11 = 0x400, markType12 = 0x800, markType13 = 0x1000, markType14 = 0x2000, markType15 = 0x4000, markType16 = 0x8000, markType17 = 0x10000, markType18 = 0x20000, markType19 = 0x40000, markType20 = 0x80000, markType21 = 0x100000, markType22 = 0x200000, markType23 = 0x400000, markType24 = 0x800000, markType25 = 0x1000000, markType26 = 0x2000000, markType27 = 0x4000000, markType28 = 0x8000000, markType29 = 0x10000000, markType30 = 0x20000000, markType31 = 0x40000000, markType32 = 0x80000000, /* reserved marks */ Bookmark = markType01, BreakpointActive = markType02, BreakpointReached = markType03, BreakpointDisabled = markType04, Execution = markType05, Warning = markType06, - Error = markType07 + Error = markType07, + SearchMatch = markType32, }; // // signals !!! // public: /** * The \p document emits this signal whenever a mark mask changed. * \param document document which emitted this signal * \see markChanged() */ virtual void marksChanged(KTextEditor::Document *document) = 0; /* * Methods to modify mark properties. */ public: /** * Set the \p mark's pixmap to \p pixmap. * \param mark mark to which the pixmap will be attached * \param pixmap new pixmap * \see setMarkDescription() */ virtual void setMarkPixmap(MarkTypes mark, const QPixmap &pixmap) = 0; /** * Get the \p mark's pixmap. * \param mark mark type. If the pixmap does not exist the resulting is null * (check with QPixmap::isNull()). * \see setMarkDescription() */ virtual QPixmap markPixmap(MarkTypes mark) const = 0; /** * Set the \p mark's description to \p text. * \param mark mark to set the description * \param text new descriptive text * \see markDescription(), setMarkPixmap() */ virtual void setMarkDescription(MarkTypes mark, const QString &text) = 0; /** * Get the \p mark's description to text. * \param mark mark to set the description * \return text of the given \p mark or QString(), if the entry does not * exist * \see setMarkDescription(), setMarkPixmap() */ virtual QString markDescription(MarkTypes mark) const = 0; /** * Set the mark mask the user is allowed to toggle to \p markMask. * I.e. concatenate all editable marks with a logical OR. If the user should * be able to add a bookmark and set a breakpoint with the context menu in * the icon pane, you have to call * \code * // iface is of Type KTextEditor::MarkInterface* * // only make bookmark and breakpoint editable * iface->setEditableMarks( MarkInterface::Bookmark | * MarkInterface::BreakpointActive ); * * // or preserve last settings, and add bookmark and breakpoint * iface->setEditableMarks( iface->editableMarks() | * MarkInterface::Bookmark | * MarkInterface::BreakpointActive ); * \endcode * \param markMask bitmap pattern * \see editableMarks(), setMarkPixmap(), setMarkDescription() */ virtual void setEditableMarks(uint markMask) = 0; /** * Get, which marks can be toggled by the user. * The returned value is a mark mask containing all editable marks combined * with a logical OR. * \return mark mask containing all editable marks * \see setEditableMarks() */ virtual uint editableMarks() const = 0; /** * Possible actions on a mark. * \see markChanged() */ enum MarkChangeAction { MarkAdded = 0, /**< action: a mark was added. */ MarkRemoved = 1 /**< action: a mark was removed. */ }; // // signals !!! // public: /** * The \p document emits this signal whenever the \p mark changes. * \param document the document which emitted the signal * \param mark changed mark * \param action action, either removed or added * \see marksChanged() */ virtual void markChanged(KTextEditor::Document *document, KTextEditor::Mark mark, KTextEditor::MarkInterface::MarkChangeAction action) = 0; Q_SIGNALS: /** * The \p document emits this signal whenever the \p mark is hovered using the mouse, * and the receiver may show a tooltip. * \param document the document which emitted the signal * \param mark mark that was hovered * \param position mouse position during the hovering * \param handled set this to 'true' if this event was handled externally */ void markToolTipRequested(KTextEditor::Document *document, KTextEditor::Mark mark, QPoint position, bool &handled); /** * The \p document emits this signal whenever the \p mark is right-clicked to show a context menu. * The receiver may show an own context menu instead of the kate internal one. * \param document the document which emitted the signal * \param mark mark that was right-clicked * \param pos position where the menu should be started * \param handled set this to 'true' if this event was handled externally, and kate should not create an own context menu. */ void markContextMenuRequested(KTextEditor::Document *document, KTextEditor::Mark mark, QPoint pos, bool &handled); /** * The \p document emits this signal whenever the \p mark is left-clicked. * \param document the document which emitted the signal * \param mark mark that was right-clicked - * \param pos position where the menu should be started * \param handled set this to 'true' if this event was handled externally, and kate should not do own handling of the left click. */ void markClicked(KTextEditor::Document *document, KTextEditor::Mark mark, bool &handled); private: class MarkInterfacePrivate *const d; }; } Q_DECLARE_INTERFACE(KTextEditor::MarkInterface, "org.kde.KTextEditor.MarkInterface") #endif diff --git a/src/include/ktexteditor/movingrange.h b/src/include/ktexteditor/movingrange.h index c0aa7d96..1bd89e02 100644 --- a/src/include/ktexteditor/movingrange.h +++ b/src/include/ktexteditor/movingrange.h @@ -1,530 +1,530 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 KTEXTEDITOR_MOVINGRANGE_H #define KTEXTEDITOR_MOVINGRANGE_H #include #include #include #include #include namespace KTextEditor { class Document; class View; class MovingRangeFeedback; /** * \short A range that is bound to a specific Document, and maintains its * position. * * \ingroup kte_group_moving_classes * * \section movingrange_intro Introduction * * A MovingRange is an extension of the basic Range class. It maintains its * position in the document. As a result of this, MovingRange%s may not be * copied, as they need to maintain a connection to the associated Document. * * Create a new MovingRange like this: * \code * // Retrieve the MovingInterface * KTextEditor::MovingInterface* moving = * qobject_cast( yourDocument ); * * if ( moving ) { * KTextEditor::MovingRange* range = moving->newMovingRange(); * } * \endcode * * When finished with a MovingRange, simply delete it. * If the document the cursor belong to is deleted, it will get deleted * automatically. * * \section movingrange_behavior Editing Behavior * * The insert behavior controls how the range reacts to characters inserted * at the range boundaries, i.e. at the start of the range or the end of the * range. Either the range boundary moves with text insertion, or it stays. * Use setInsertBehaviors() and insertBehaviors() to set and query the current * insert behavior. * * When the start() and end() Cursor of a range equal, isEmpty() returns true. * Further, the empty-behavior can be changed such that the start() and end() * Cursor%s of MovingRange%s that get empty are automatically set to (-1, -1). * Use setEmptyBehavior() and emptyBehavior() to control the empty behavior. * * \warning MovingRanges may be set to (-1, -1, -1, -1) at any time, if the * user reloads a document (F5)! Use a MovingRangeFeedback to get notified * if you need to catch this case, and/or listen to the signal * MovingInterface::aboutToInvalidateMovingInterfaceContent(). * * \section movingrange_feedback MovingRange Feedback * * With setFeedback() a feedback instance can be associated with the moving * range. The MovingRangeFeedback notifies about the following events: * - the text cursor (caret) entered the range, * - the text cursor (caret) left the range, * - the mouse cursor entered the range, * - the mouse cursor left the range, * - the range got empty, i.e. start() == end(), * - the range got invalid, i.e. start() == end() == (-1, -1). * * If a feedback is not needed anymore, call setFeedback(0). * * \section movingrange_details Working with Ranges * * There are several convenience methods that make working with MovingRanges * very simple. For instance, use isEmpty() to check if the start() Cursor * equals the end() Cursor. Use contains(), containsLine() or containsColumn() * to check whether the MovingRange contains a Range, a Cursor, a line or * column. The same holds for overlaps(), overlapsLine() and overlapsColumn(). * Besides onSingleLine() returns whether a MovingRange spans only one line. * * For compatibility, a MovingRange can be explicitly converted to a simple * Range by calling toRange(), or implicitly by the Range operator. * * \section movingrange_highlighting Arbitrary Highlighting * * With setAttribute() highlighting Attribute%s can be assigned to a * MovingRange. By default, this highlighting is used in all views of a * document. Use setView(), if the highlighting should only appear in a * specific view. Further, if the additional highlighting should not be * printed call setAttributeOnlyForViews() with the parameter true. * * \section movingrange_example MovingRange Example * * In the following example, we assume the KTextEditor::Document has the * contents: * \code * void printText(const std::string & text); // this is line 3 * \endcode * In order to highlight the function name \e printText with a yellow background * color, the following code is needed: * \code * KTextEditor::View * view = ...; * KTextEditor::Document * doc = view->document(); * * auto iface = qobject_cast(doc); * if (!iface) { * return; * } * * // range is of type KTextEditor::MovingRange* * auto range = iface->newMovingRange(KTextEditor::Range(3, 5, 3, 14)); * * KTextEditor::Attribute::Ptr attrib = new KTextEditor::Attribute(); * attrib->setBackground(Qt::yellow); * * range->setAttribute(attrib); * \endcode * * MovingRange%s are deleted automatically when a document is cleared or closed. * Therefore, to avoid dangling pointers, make sure to read the API documentation * about MovingInterface::aboutToDeleteMovingInterfaceContent(). * * \sa Cursor, MovingCursor, Range, MovingInterface, MovingRangeFeedback * * \author Christoph Cullmann \ * * \since 4.5 */ class KTEXTEDITOR_EXPORT MovingRange { // // sub types // public: /// Determine how the range reacts to characters inserted immediately outside the range. enum InsertBehavior { /// Don't expand to encapsulate new characters in either direction. This is the default. DoNotExpand = 0x0, /// Expand to encapsulate new characters to the left of the range. ExpandLeft = 0x1, /// Expand to encapsulate new characters to the right of the range. ExpandRight = 0x2 }; Q_DECLARE_FLAGS(InsertBehaviors, InsertBehavior) /** * Behavior of range if it becomes empty. */ enum EmptyBehavior { AllowEmpty = 0x0, ///< allow range to be empty InvalidateIfEmpty = 0x1 ///< invalidate range, if it becomes empty }; typedef QSharedPointer Ptr; // // stuff that needs to be implemented by editor part cursors // public: /** * Set insert behaviors. * @param insertBehaviors new insert behaviors */ virtual void setInsertBehaviors(InsertBehaviors insertBehaviors) = 0; /** * Get current insert behaviors. * @return current insert behaviors */ virtual InsertBehaviors insertBehaviors() const = 0; /** * Set if this range will invalidate itself if it becomes empty. * @param emptyBehavior behavior on becoming empty */ virtual void setEmptyBehavior(EmptyBehavior emptyBehavior) = 0; /** * Will this range invalidate itself if it becomes empty? * @return behavior on becoming empty */ virtual EmptyBehavior emptyBehavior() const = 0; /** * Gets the document to which this range is bound. * \return a pointer to the document */ virtual Document *document() const = 0; /** * Set the range of this range. * A TextRange is not allowed to be empty, as soon as start == end position, it will become * automatically invalid! * @param range new range for this clever range */ virtual void setRange(const KTextEditor::Range &range) = 0; /** * Retrieve start cursor of this range, read-only. * @return start cursor */ virtual const MovingCursor &start() const = 0; /** * Retrieve end cursor of this range, read-only. * @return end cursor */ virtual const MovingCursor &end() const = 0; /** * Gets the active view for this range. Might be already invalid, internally only used for pointer comparisons. * * \return a pointer to the active view */ virtual View *view() const = 0; /** * Sets the currently active view for this range. * This will trigger update of the relevant view parts, if the view changed. * Set view before the attribute, that will avoid not needed redraws. * - * \param attribute View to assign to this range. If null, simply + * \param view View to assign to this range. If null, simply * removes the previous view. */ virtual void setView(View *view) = 0; /** * Gets the active Attribute for this range. * * \return a pointer to the active attribute */ virtual Attribute::Ptr attribute() const = 0; /** * Sets the currently active attribute for this range. * This will trigger update of the relevant view parts, if the attribute changed. * * \param attribute Attribute to assign to this range. If null, simply * removes the previous Attribute. */ virtual void setAttribute(Attribute::Ptr attribute) = 0; /** * Is this range's attribute only visible in views, not for example prints? * Default is false. * @return range visible only for views */ virtual bool attributeOnlyForViews() const = 0; /** * Set if this range's attribute is only visible in views, not for example prints. * @param onlyForViews attribute only valid for views */ virtual void setAttributeOnlyForViews(bool onlyForViews) = 0; /** * Gets the active MovingRangeFeedback for this range. * * \return a pointer to the active MovingRangeFeedback */ virtual MovingRangeFeedback *feedback() const = 0; /** * Sets the currently active MovingRangeFeedback for this range. * This will trigger evaluation if feedback must be send again (for example if mouse is already inside range). * - * \param attribute MovingRangeFeedback to assign to this range. If null, simply + * \param feedback MovingRangeFeedback to assign to this range. If null, simply * removes the previous MovingRangeFeedback. */ virtual void setFeedback(MovingRangeFeedback *feedback) = 0; /** * Gets the current Z-depth of this range. * Ranges with smaller Z-depth than others will win during rendering. * Default is 0.0. * * Defined depths for common kind of ranges use in editor components implenting this interface, * smaller depths are more more in the foreground and will win during rendering: * - Selection == -100000.0 * - Search == -10000.0 * - Bracket Highlighting == -1000.0 * - Folding Hover == -100.0 * * \return current Z-depth of this range */ virtual qreal zDepth() const = 0; /** * Set the current Z-depth of this range. * Ranges with smaller Z-depth than others will win during rendering. * This will trigger update of the relevant view parts, if the depth changed. * Set depth before the attribute, that will avoid not needed redraws. * Default is 0.0. * * \param zDepth new Z-depth of this range */ virtual void setZDepth(qreal zDepth) = 0; /** * Destruct the moving range. */ virtual ~MovingRange(); // // forbidden stuff // protected: /** * For inherited class only. */ MovingRange(); private: /** * no copy constructor, don't allow this to be copied. */ MovingRange(const MovingRange &); /** * no assignment operator, no copying around clever ranges. */ MovingRange &operator= (const MovingRange &); // // convenience API // public: /** * \overload * Set the range of this range * A TextRange is not allowed to be empty, as soon as start == end position, it will become * automatically invalid! * @param start new start for this clever range * @param end new end for this clever range */ void setRange(const Cursor &start, const Cursor &end); /** * Convert this clever range into a dumb one. * @return normal range */ const Range toRange() const { return Range(start().toCursor(), end().toCursor()); } /** * Convert this clever range into a dumb one. Equal to toRange, allowing to use implicit conversion. * @return normal range */ operator Range() const { return Range(start().toCursor(), end().toCursor()); } /** * qDebug() stream operator. Writes this range to the debug output in a nicely formatted way. * @param s debug stream - * @param cursor range to print + * @param range range to print * @return debug stream */ inline friend QDebug operator<< (QDebug s, const MovingRange *range) { if (range) { s << "[" << range->start() << " -> " << range->end() << "]"; } else { s << "(null range)"; } return s.space(); } /** * qDebug() stream operator. Writes this range to the debug output in a nicely formatted way. * @param s debug stream * @param range range to print * @return debug stream */ inline friend QDebug operator<< (QDebug s, const MovingRange &range) { return s << ⦥ } /** * Returns true if this range contains no characters, ie. the start() and * end() positions are the same. * * \returns \e true if the range contains no characters, otherwise \e false */ inline bool isEmpty() const { return start() == end(); } //BEGIN comparison functions /** * \name Comparison * * The following functions perform checks against this range in comparison * to other lines, columns, cursors, and ranges. */ /** * Check whether the this range wholly encompasses \e range. * * \param range range to check * * \return \e true, if this range contains \e range, otherwise \e false */ inline bool contains(const Range &range) const { return range.start() >= start() && range.end() <= end(); } /** * Check to see if \p cursor is contained within this range, ie >= start() and \< end(). * * \param cursor the position to test for containment * * \return \e true if the cursor is contained within this range, otherwise \e false. */ inline bool contains(const Cursor &cursor) const { return cursor >= start() && cursor < end(); } /** * Returns true if this range wholly encompasses \p line. * * \param line line to check * * \return \e true if the line is wholly encompassed by this range, otherwise \e false. */ inline bool containsLine(int line) const { return (line > start().line() || (line == start().line() && !start().column())) && line < end().line(); } /** * Check whether the range contains \e column. * * \param column column to check * * \return \e true if the range contains \e column, otherwise \e false */ inline bool containsColumn(int column) const { return column >= start().column() && column < end().column(); } /** * Check whether the this range overlaps with \e range. * * \param range range to check against * * \return \e true, if this range overlaps with \e range, otherwise \e false */ bool overlaps(const Range &range) const; /** * Check whether the range overlaps at least part of \e line. * * \param line line to check * * \return \e true, if the range overlaps at least part of \e line, otherwise \e false */ inline bool overlapsLine(int line) const { return line >= start().line() && line <= end().line(); } /** * Check to see if this range overlaps \p column; that is, if \p column is * between start().column() and end().column(). This function is most likely * to be useful in relation to block text editing. * * \param column the column to test * * \return \e true if the column is between the range's starting and ending * columns, otherwise \e false. */ inline bool overlapsColumn(int column) const { return start().column() <= column && end().column() > column; } /** * Check whether the start() and end() cursors of this range * are on the same line. * * \return \e true if both the start and end positions are on the same * line, otherwise \e false */ inline bool onSingleLine() const { return start().line() == end().line(); } /** * Returns the number of lines separating the start() and end() positions. * * \return the number of lines separating the start() and end() positions; * 0 if the start and end lines are the same. */ inline int numberOfLines() const Q_DECL_NOEXCEPT { return end().line() - start().line(); } //END comparison functions }; Q_DECLARE_OPERATORS_FOR_FLAGS(MovingRange::InsertBehaviors) } #endif diff --git a/src/include/ktexteditor/sessionconfiginterface.h b/src/include/ktexteditor/sessionconfiginterface.h index aa8ae8b0..f7de6047 100644 --- a/src/include/ktexteditor/sessionconfiginterface.h +++ b/src/include/ktexteditor/sessionconfiginterface.h @@ -1,124 +1,123 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2014 Christoph Cullmann Copyright (C) 2005-2014 Dominik Haumann (dhaumann@kde.org) Copyright (C) 2009 Michel Ludwig (michel.ludwig@kdemail.net) 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 KTEXTEDITOR_SESSIONCONFIGINTERFACE_H #define KTEXTEDITOR_SESSIONCONFIGINTERFACE_H #include class KConfigGroup; #include namespace KTextEditor { /** * \brief Session config interface extension for the Plugin and Plugin views. * * \ingroup kte_group_plugin_extensions * * \section sessionconfig_intro Introduction * * The SessionConfigInterface is an extension for Plugin%s and Plugin views * to add support for session-specific configuration settings. * readSessionConfig() is called whenever session-specific settings are to be * read from the given KConfigGroup and writeSessionConfig() whenever they are to * be written, for example when a session changed or was closed. * * \note A \e session does not have anything to do with an X-session under Unix. * What is meant is rather a context, think of sessions in Kate or * projects in KDevelop for example. * * \section sessionconfig_support Adding Session Support * * To add support for sessions, your Plugin has to inherit the SessionConfigInterface * and reimplement readSessionConfig() and writeSessionConfig(). * * \section sessionconfig_access Accessing the SessionConfigInterface * * This secion is for application developers such as Kate, KDevelop, etc that * what to support session configuration for plugins. * * The SessionConfigInterface is an extension interface for a Plugin or a * Plugin view, i.e. Plugin/Plugin view inherits the interface * \e provided that it implements the interface. Use qobject_cast to * access the interface: * \code * // object is of type Plugin* or, in case of a plugin view, QObject* * KTextEditor::SessionConfigInterface *iface = * qobject_cast( object ); * * if( iface ) { * // interface is supported * // do stuff * } * \endcode * * \see KTextEditor::Plugin * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT SessionConfigInterface { public: SessionConfigInterface(); /** * Virtual destructor. */ virtual ~SessionConfigInterface(); public: /** * Read session settings from the given \p config. * * That means for example * - a Document should reload the file, restore all marks etc... * - a View should scroll to the last position and restore the cursor * position etc... * - a Plugin should restore session specific settings * - If no file is being loaded, because an empty new document is going to be displayed, * this function should emit ReadOnlyPart::completed * * \param config read the session settings from this KConfigGroup - * \param flags additional flags set * \see writeSessionConfig() */ virtual void readSessionConfig(const KConfigGroup &config) = 0; /** * Write session settings to the \p config. * See readSessionConfig() for more details. * * \param config write the session settings to this KConfigGroup * \see readSessionConfig() */ virtual void writeSessionConfig(KConfigGroup &config) = 0; private: class SessionConfigInterfacePrivate *const d; }; } Q_DECLARE_INTERFACE(KTextEditor::SessionConfigInterface, "org.kde.KTextEditor.SessionConfigInterface") #endif diff --git a/src/include/ktexteditor/view.h b/src/include/ktexteditor/view.h index 37541ea0..f33d4f4b 100644 --- a/src/include/ktexteditor/view.h +++ b/src/include/ktexteditor/view.h @@ -1,774 +1,843 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Christoph Cullmann Copyright (C) 2005 Dominik Haumann (dhdev@gmx.de) (documentation) * * 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 KTEXTEDITOR_VIEW_H #define KTEXTEDITOR_VIEW_H #include #include #include #include // gui merging #include // widget #include class QMenu; class KConfigGroup; namespace KTextEditor { class Document; class MainWindow; class ViewPrivate; /** * \brief A text widget with KXMLGUIClient that represents a Document. * * Topics: * - \ref view_intro * - \ref view_hook_into_gui * - \ref view_selection * - \ref view_cursors * - \ref view_mouse_tracking * - \ref view_modes * - \ref view_extensions * * \section view_intro Introduction * * The View class represents a single view of a KTextEditor::Document, * get the document on which the view operates with document(). * A view provides both the graphical representation of the text and the * KXMLGUIClient for the actions. The view itself does not provide * text manipulation, use the methods from the Document instead. The only * method to insert text is insertText(), which inserts the given text * at the current cursor position and emits the signal textInserted(). * * Usually a view is created by using Document::createView(). * Furthermore a view can have a context menu. Set it with setContextMenu() * and get it with contextMenu(). * * \section view_hook_into_gui Merging the View's GUI * * A View is derived from the class KXMLGUIClient, so its GUI elements (like * menu entries and toolbar items) can be merged into the application's GUI * (or into a KXMLGUIFactory) by calling * \code * // view is of type KTextEditor::View* * mainWindow()->guiFactory()->addClient( view ); * \endcode * You can add only one view as client, so if you have several views, you first * have to remove the current view, and then add the new one, like this * \code * mainWindow()->guiFactory()->removeClient( currentView ); * mainWindow()->guiFactory()->addClient( newView ); * \endcode * * \section view_selection Text Selection * * As the view is a graphical text editor it provides \e normal and \e block * text selection. You can check with selection() whether a selection exists. * removeSelection() will remove the selection without removing the text, * whereas removeSelectionText() also removes both, the selection and the * selected text. Use selectionText() to get the selected text and * setSelection() to specify the selected text range. The signal * selectionChanged() is emitted whenever the selection changed. * * \section view_cursors Cursor Positions * * A view has one Cursor which represents a line/column tuple. Two different * kinds of cursor positions are supported: first is the \e real cursor * position where a \e tab character only counts one character. Second is the * \e virtual cursor position, where a \e tab character counts as many * spaces as defined. Get the real position with cursorPosition() and the * virtual position with cursorPositionVirtual(). Set the real cursor * position with setCursorPosition(). The signal cursorPositionChanged() is * emitted whenever the cursor position changed. * * Screen coordinates of the current text cursor position in pixels are obtained * through cursorPositionCoordinates(). Further conversion of screen pixel * coordinates and text cursor positions are provided by cursorToCoordinate() * and coordinatesToCursor(). * * \section view_mouse_tracking Mouse Tracking * * It is possible to get notified via the signal mousePositionChanged() for * mouse move events, if mouseTrackingEnabled() returns \e true. Mouse tracking * can be turned on/off by calling setMouseTrackingEnabled(). If an editor * implementation does not support mouse tracking, mouseTrackingEnabled() will * always return \e false. * * \section view_modes Input/View Modes * * A view supports several input modes. Common input modes are * \e NormalInputMode and \e ViInputMode. Which input modes the editor supports depends on the * implementation. The getter viewInputMode() returns enum \InputMode representing the current mode. * * Input modes can have their own view modes. In case of default \e NormalInputMode those are * \e NormalModeInsert and \e NormalModeOverwrite. You can use viewMode() getter to obtain those. * * For viewMode() and viewInputMode() there are also variants with \e Human suffix, which * returns the human readable representation (i18n) usable for displaying in user interface. * * Whenever the input/view mode changes the signals * viewInputModeChanged()/viewModeChanged() are emitted. * * \section view_extensions View Extension Interfaces * * A simple view represents the text of a Document and provides a text cursor, * text selection, edit modes etc. * Advanced concepts like code completion and text hints are defined in the * extension interfaces. An KTextEditor implementation does not need to * support all the extensions. To implement the interfaces multiple * inheritance is used. * * More information about interfaces for the view can be found in * \ref kte_group_view_extensions. * * \see KTextEditor::Document, KTextEditor::CodeCompletionInterface, * KXMLGUIClient * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT View : public QWidget, public KXMLGUIClient { Q_OBJECT protected: /** * Constructor. * * Create a view attached to the widget \p parent. * * Pass it the internal implementation to store a d-pointer. * * \param impl d-pointer to use * \param parent parent widget * \see Document::createView() */ View(ViewPrivate *impl, QWidget *parent); public: /** * Virtual destructor. */ virtual ~View(); /* * Accessor for the document */ public: /** * Get the view's \e document, that means the view is a view of the * returned document. * \return the view's document */ virtual Document *document() const = 0; /* * General information about this view */ public: /** * Possible input modes. * These correspond to various modes the text editor might be in. */ enum InputMode { NormalInputMode = 0, /**< Normal Mode. */ ViInputMode = 1 /**< Vi mode. The view will behave like the editor vi(m) */ }; /** * Possible view modes * These correspond to various modes the text editor might be in. */ enum ViewMode { NormalModeInsert = 0, /**< Insert mode. Characters will be added. */ NormalModeOverwrite = 1, /**< Overwrite mode. Characters will be replaced. */ ViModeNormal = 10, ViModeInsert = 11, ViModeVisual = 12, ViModeVisualLine = 13, ViModeVisualBlock = 14, ViModeReplace = 15 }; + /** + * Possible line types + */ + enum LineType { + RealLine = 0, /** < Real line. */ + VisibleLine = 1 /** < Visible line. Line that is not folded. */ + }; /** * Get the current view mode/state. * This can be used to detect the view's current mode. For * example \NormalInputMode, \ViInputMode or whatever other input modes are * supported. \see viewModeHuman() for translated version. - * \return + * \return current view mode/state * \see viewModeChanged() */ virtual ViewMode viewMode() const = 0; /** * Get the current view mode state. * This can be used to visually indicate the view's current mode, for * example \e INSERT mode, \e OVERWRITE mode or \e COMMAND mode - or * whatever other edit modes are supported. The string should be * translated (i18n), as this is a user aimed representation of the view * state, which should be shown in the GUI, for example in the status bar. * This string may be rich-text. - * \return + * \return Human-readable version of the view mode state * \see viewModeChanged() */ virtual QString viewModeHuman() const = 0; /** * Get the view's current input mode. * The current mode can be \NormalInputMode and \ViInputMode. * For human translated version \see viewInputModeHuman. * * \return the current input mode of this view * \see viewInputModeChanged() */ virtual InputMode viewInputMode() const = 0; /** * Get the view's current input mode in human readable form. * The string should be translated (i18n). For id like version \see viewInputMode * * \return the current input mode of this view in human readable form */ virtual QString viewInputModeHuman() const = 0; /** * Get the view's main window, if any * \return the view's main window, will always return at least some non-nullptr dummy interface */ virtual KTextEditor::MainWindow *mainWindow() const = 0; /* * SIGNALS * following signals should be emitted by the editor view */ Q_SIGNALS: /** * This signal is emitted whenever the \p view gets the focus. * \param view view which gets focus * \see focusOut() */ void focusIn(KTextEditor::View *view); /** * This signal is emitted whenever the \p view loses the focus. * \param view view which lost focus * \see focusIn() */ void focusOut(KTextEditor::View *view); /** * This signal is emitted whenever the view mode of \p view changes. * \param view the view which changed its mode * \param mode new view mode * \see viewMode() */ void viewModeChanged(KTextEditor::View *view, KTextEditor::View::ViewMode mode); /** * This signal is emitted whenever the \p view's input \p mode changes. * \param view view which changed its input mode * \param mode new input mode * \see viewInputMode() */ void viewInputModeChanged(KTextEditor::View *view, KTextEditor::View::InputMode mode); /** * This signal is emitted from \p view whenever the users inserts \p text * at \p position, that means the user typed/pasted text. * \param view view in which the text was inserted * \param position position where the text was inserted * \param text the text the user has typed into the editor * \see insertText() */ void textInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text); /* * Context menu handling */ public: /** * Set a context menu for this view to \p menu. * * \note any previously assigned menu is not deleted. If you are finished * with the previous menu, you may delete it. * * \warning Use this with care! Plugin xml gui clients are not merged * into this menu! * \warning !!!!!! DON'T USE THIS FUNCTION, UNLESS YOU ARE SURE YOU DON'T WANT PLUGINS TO WORK !!!!!! * * \param menu new context menu object for this view * \see contextMenu() */ virtual void setContextMenu(QMenu *menu) = 0; /** * Get the context menu for this view. The return value can be NULL * if no context menu object was set and kxmlgui is not initialized yet. * If there is no user set menu, the kxmlgui menu is returned. Do not delete this menu, if * if it is the xmlgui menu. * \return context menu object * \see setContextMenu() */ virtual QMenu *contextMenu() const = 0; /** * Populate \a menu with default text editor actions. If \a menu is * null, a menu will be created with the view as its parent. * * \note to use this menu, you will next need to call setContextMenu(), * as this does not assign the new context menu. * * \warning This contains only basic options from the editor component * (katepart). Plugins are \p not merged/integrated into it! * If you want to be a better citizen and take full advantage * of KTextEditor plugins do something like: * \code * KXMLGUIClient* client = view; * // search parent XmlGuiClient * while (client->parentClient()) { * client = client->parentClient(); * } * * if (client->factory()) { * QList conts = client->factory()->containers("menu"); * foreach (QWidget *w, conts) { * if (w->objectName() == "ktexteditor_popup") { * // do something with the menu (ie adding an onshow handler) * break; * } * } * } * \endcode * \warning or simply use the aboutToShow, aboutToHide signals !!!!! * * \param menu the menu to be populated, or null to create a new menu. * \return the menu, whether created or passed initially */ - virtual QMenu *defaultContextMenu(QMenu *menu = 0L) const = 0; + virtual QMenu *defaultContextMenu(QMenu *menu = nullptr) const = 0; Q_SIGNALS: /** * Signal which is emitted immediately prior to showing the current * context \a menu. */ void contextMenuAboutToShow(KTextEditor::View *view, QMenu *menu); /* * Cursor handling */ public: /** * Set the view's primary cursor to \p position. A \e TAB character * is handeled as only on character. All previous cursors are cleared. * \param position new cursor position * \return \e true on success, otherwise \e false * \see cursorPosition() */ virtual bool setCursorPosition(Cursor position) = 0; /** * Set the view's cursors to \p position. A \e TAB character * is handeled as only on character. All previous cursors are cleared. * \param position new cursor positions * \return \e true on success, otherwise \e false * \see cursorPosition(), setCursorPosition() */ virtual bool setCursorPositions(const QVector &positions) = 0; /** * Get the view's current cursor position. A \e TAB character is * handeled as only one character. * \return current cursor position * \see setCursorPosition() */ virtual Cursor cursorPosition() const = 0; /** * Get the view's current cursor positions. A \e TAB character is * handeled as only one character. * \return current cursor position * \see setCursorPosition() */ virtual QVector cursorPositions() const = 0; /** * Get the current \e virtual cursor position, \e virtual means the * tabulator character (TAB) counts \e multiple characters, as configured * by the user (e.g. one TAB is 8 spaces). The virtual cursor * position provides access to the user visible values of the current * cursor position. * * \return virtual cursor position * \see cursorPosition() */ virtual Cursor cursorPositionVirtual() const = 0; /** * Get the screen coordinates (x, y) of the supplied \a cursor relative * to the view widget in pixels. Thus, (0, 0) represents the top left hand * of the view widget. * * To map pixel coordinates to a Cursor position (the reverse transformation) * use coordinatesToCursor(). * * \param cursor cursor to determine coordinate for. * \return cursor screen coordinates relative to the view widget * * \see cursorPositionCoordinates(), coordinatesToCursor() */ virtual QPoint cursorToCoordinate(const KTextEditor::Cursor &cursor) const = 0; /** * Get the screen coordinates (x, y) of the cursor position in pixels. * The returned coordinates are relative to the View such that (0, 0) * represents tht top-left corner of the View. * * If global screen coordinates are required, e.g. for showing a QToolTip, * convert the view coordinates to global screen coordinates as follows: * \code * QPoint viewCoordinates = view->cursorPositionCoordinates(); * QPoint globalCoorinates = view->mapToGlobal(viewCoordinates); * \endcode * \return cursor screen coordinates * \see cursorToCoordinate(), coordinatesToCursor() */ virtual QPoint cursorPositionCoordinates() const = 0; /** * Get the text-cursor in the document from the screen coordinates, * relative to the view widget. * * To map a cursor to pixel coordinates (the reverse transformation) * use cursorToCoordinate(). * * \param coord coordinates relative to the view widget * \return cursor in the View, that points onto the character under * the given coordinate. May be KTextEditor::Cursor::invalid(). */ virtual KTextEditor::Cursor coordinatesToCursor(const QPoint &coord) const = 0; /* * SIGNALS * following signals should be emitted by the editor view * if the cursor position changes */ Q_SIGNALS: /** * This signal is emitted whenever the \p view's cursor position changed. * \param view view which emitted the signal * \param newPosition new position of the cursor (Kate will pass the real * cursor potition, not the virtual) * \see cursorPosition(), cursorPositionVirtual() */ void cursorPositionChanged(KTextEditor::View *view, const KTextEditor::Cursor &newPosition); /** * This signal should be emitted whenever the \p view is scrolled vertically. * \param view view which emitted the signal * \param newPos the new scroll position */ void verticalScrollPositionChanged(KTextEditor::View *view, const KTextEditor::Cursor &newPos); /** * This signal should be emitted whenever the \p view is scrolled horizontally. * \param view view which emitted the signal */ void horizontalScrollPositionChanged(KTextEditor::View *view); /* * Mouse position */ public: /** * Check, whether mouse tracking is enabled. * * Mouse tracking is required to have the signal mousePositionChanged() * emitted. * \return \e true, if mouse tracking is enabled, otherwise \e false * \see setMouseTrackingEnabled(), mousePositionChanged() */ virtual bool mouseTrackingEnabled() const = 0; /** * Try to enable or disable mouse tracking according to \p enable. * The return value contains the state of mouse tracking \e after the * request. Mouse tracking is required to have the mousePositionChanged() * signal emitted. * * \note Implementation Notes: An implementation is not forced to support * this, and should always return \e false if it does not have * support. * * \param enable if \e true, try to enable mouse tracking, otherwise disable * it. * \return the current state of mouse tracking * \see mouseTrackingEnabled(), mousePositionChanged() */ virtual bool setMouseTrackingEnabled(bool enable) = 0; Q_SIGNALS: /** * This signal is emitted whenever the position of the mouse changes over * this \a view. If the mouse moves off the view, an invalid cursor position * should be emitted, i.e. Cursor::invalid(). * \note If mouseTrackingEnabled() returns \e false, this signal is never * emitted. * \param view view which emitted the signal * \param newPosition new position of the mouse or Cursor::invalid(), if the * mouse moved out of the \p view. * \see mouseTrackingEnabled() */ void mousePositionChanged(KTextEditor::View *view, const KTextEditor::Cursor &newPosition); /* * Selection methodes. * This deals with text selection and copy&paste */ public: /** * Set the view's selection to the range \p selection. * The old selection will be discarded. * \param range the range of the new selection * \return \e true on success, otherwise \e false (e.g. when the cursor * range is invalid) * \see selectionRange(), selection() */ virtual bool setSelection(const Range &range) = 0; /** * Set the view's selections to the given \p ranges. * The old selection will be discarded. * \param ranges ranges of the new selections * \param cursors cursors for the ranges, must be start or end of the corresponding range * \return \e true on success, otherwise \e false (e.g. when the cursor * range is invalid) * \see selectionRange(), selection(), setSelection() */ virtual bool setSelections(const QVector &ranges, const QVector &cursors) = 0; /** * Query the view whether it has selected text, i.e. whether a selection * exists. * \return \e true if a text selection exists, otherwise \e false * \see setSelection(), selectionRange() */ virtual bool selection() const = 0; /** * Get the range occupied by the current primary selection. * \return selection range, valid only if a selection currently exists. * \see setSelection() */ virtual Range selectionRange() const = 0; /** * Get the ranges occupied by the current selections. * \return selection ranges, valid only if a selection currently exists. * \see setSelection() */ virtual QVector selectionRanges() const = 0; /** * Get the view's selected text. * \return the selected text * \see setSelection() */ virtual QString selectionText() const = 0; /** * Remove the view's current selection, \e without deleting the selected * text. * \return \e true on success, otherwise \e false * \see removeSelectionText() */ virtual bool removeSelection() = 0; /** * Remove the view's current selection \e including the selected text. * \return \e true on success, otherwise \e false * \see removeSelection() */ virtual bool removeSelectionText() = 0; /* * Blockselection stuff */ public: /** * Set block selection mode to state \p on. * \param on if \e true, block selection mode is turned on, otherwise off * \return \e true on success, otherwise \e false * \see blockSelection() */ virtual bool setBlockSelection(bool on) = 0; /** * Get the status of the selection mode. \e true indicates that block * selection mode is on. If this is \e true, selections applied via the * SelectionInterface are handled as block selections and the Copy&Paste * functions work on rectangular blocks of text rather than normal. * \return \e true, if block selection mode is enabled, otherwise \e false * \see setBlockSelection() */ virtual bool blockSelection() const = 0; /* * SIGNALS * following signals should be emitted by the editor view for selection * handling. */ Q_SIGNALS: /** * This signal is emitted whenever the \p view's selection changes. * \note If the mode switches from block selection to normal selection * or vice versa this signal should also be emitted. * \param view view in which the selection changed * \see selection(), selectionRange(), selectionText() */ void selectionChanged(KTextEditor::View *view); public: /** * This is a convenience function which inserts \p text at the view's * current cursor position. You do not necessarily need to reimplement * it, except you want to do some special things. * \param text Text to be inserted * \return \e true on success of insertion, otherwise \e false * \see textInserted() */ virtual bool insertText(const QString &text); /** * Insert a template into the document. The template can have editable fields * which can be filled by the user. You can create editable fields * with ${fieldname}; multiple fields with the same name will have their * contents synchronized automatically, and only the first one is editable * in this case. * Fields can have a default value specified by writing ${fieldname=default}. * Note that `default' is a JavaScript expression and strings need to be quoted. * You can also provide a piece of JavaScript for more complex logic. * To create a field which provides text based on a JS function call and the values * of the other, editable fields, use the ${func()} syntax. func() must be a callable * object defined in @p script. You can pass arguments to the function by just * writing any constant expression or a field name. * \param insertPosition where to insert the template - * \param templateScript template to insert using the above syntax + * \param templateString template to insert using the above syntax * \param script script with functions which can be used in @p templateScript * \return true on success, false if insertion failed (e.g. read-only mode) */ bool insertTemplate(const KTextEditor::Cursor& insertPosition, const QString& templateString, const QString& script = QString()); + + /** + * Scroll view to cursor. + * + * \param cursor the cursor position to scroll to. + */ + void setScrollPosition(KTextEditor::Cursor &cursor); + + /** + * Horizontally scroll view to position. + * + * \param x the pixel position to scroll to. + */ + void setHorizontalScrollPosition(int x); + + /** + * Get the cursor corresponding to the maximum position + * the view can vertically scroll to. + * + * \return cursor position of the maximum vertical scroll position. + */ + KTextEditor::Cursor maxScrollPosition() const; + + /** + * Get the first displayed line in the view. + * + * \param real if REAL (the default), it returns the real line number + * accounting for folded regions. In that case it walks over all folded + * regions + * O(n) for n == number of folded ranges + * + * \note if code is folded, many hundred lines can be + * between firstVisibleLine() and lastVisibleLine(). + * + * \return the first visible line. + * \see lastVisibleLine() + */ + int firstDisplayedLine(LineType lineType = RealLine) const; + + /** + * Get the last displayed line in the view. + * + * \param lineType if REAL (the default), it returns the real line number + * accounting for folded regions. In that case it walks over all folded + * regions + * O(n) for n == number of folded ranges + * + * \note if code is folded, many hundred lines can be + * between firstVisibleLine() and lastVisibleLine(). + * + * \return the last visible line. + * \see firstVisibleLine() + */ + int lastDisplayedLine(LineType lineType = RealLine) const; + + /** + * Get the view's text area rectangle excluding border, scrollbars, etc. + * + * \return the view's text area rectangle + */ + QRect textAreaRect() const; + public: /** * Print the document. This should result in showing the print dialog. * * @returns true if document was printed */ virtual bool print() = 0; /** * Shows the print preview dialog/ */ virtual void printPreview() = 0; /** * Is the status bar enabled? * * @return status bar enabled? */ bool isStatusBarEnabled() const; /** * Show/hide the status bar of the view. * Per default, the status bar is enabled. * * @param enable should the status bar be enabled? */ void setStatusBarEnabled(bool enable); Q_SIGNALS: /** * This signal is emitted whenever the status bar of \p view is toggled. * * @param enabled Whether the status bar is currently enabled or not */ void statusBarEnabledChanged(KTextEditor::View *view, bool enabled); public: /** * Read session settings from the given \p config. * * Known flags: * none atm * * \param config read the session settings from this KConfigGroup * \param flags additional flags * \see writeSessionConfig() */ virtual void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) = 0; /** * Write session settings to the \p config. * See readSessionConfig() for more details. * * \param config write the session settings to this KConfigGroup * \param flags additional flags * \see readSessionConfig() */ virtual void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) = 0; public: /** * Returns the attribute for the default style \p defaultStyle. * @param defaultStyle default style to get the attribute for * @see KTextEditor::Attribute */ virtual KTextEditor::Attribute::Ptr defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const = 0; /** * Get the list of AttributeBlocks for a given \p line in the document. * * \return list of AttributeBlocks for given \p line. */ virtual QList lineAttributes(int line) = 0; private: /** * private d-pointer, pointing to the internal implementation */ ViewPrivate *const d; }; } #endif diff --git a/src/inputmode/katenormalinputmode.cpp b/src/inputmode/katenormalinputmode.cpp index 3c2ed75f..9011bc2f 100644 --- a/src/inputmode/katenormalinputmode.cpp +++ b/src/inputmode/katenormalinputmode.cpp @@ -1,265 +1,265 @@ /* This file is part of the KDE libraries and the Kate part. * * 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 "katenormalinputmode.h" #include "kateviewinternal.h" #include "kateconfig.h" #include "katesearchbar.h" #include "katecompletionwidget.h" #include KateNormalInputMode::KateNormalInputMode(KateViewInternal *viewInternal) : KateAbstractInputMode(viewInternal) - , m_searchBar(0) - , m_cmdLine(0) + , m_searchBar(nullptr) + , m_cmdLine(nullptr) { } KateNormalInputMode::~KateNormalInputMode() { delete m_cmdLine; delete m_searchBar; } void KateNormalInputMode::activate() { view()->activateEditActions(); } void KateNormalInputMode::deactivate() { view()->deactivateEditActions(); } void KateNormalInputMode::reset() { // nothing todo } bool KateNormalInputMode::overwrite() const { return view()->doc()->config()->ovr(); } void KateNormalInputMode::overwrittenChar(const QChar &) { // nothing todo } void KateNormalInputMode::clearSelection() { view()->clearSelection(); } bool KateNormalInputMode::stealKey( QKeyEvent *) { return false; } KTextEditor::View::InputMode KateNormalInputMode::viewInputMode() const { return KTextEditor::View::NormalInputMode; } QString KateNormalInputMode::viewInputModeHuman() const { return i18n("Normal"); } KTextEditor::View::ViewMode KateNormalInputMode::viewMode() const { return view()->isOverwriteMode() ? KTextEditor::View::NormalModeOverwrite : KTextEditor::View::NormalModeInsert; } QString KateNormalInputMode::viewModeHuman() const { return view()->isOverwriteMode() ? i18n("OVERWRITE") : i18n("INSERT"); } void KateNormalInputMode::gotFocus() { view()->activateEditActions(); } void KateNormalInputMode::lostFocus() { view()->deactivateEditActions(); } void KateNormalInputMode::readSessionConfig(const KConfigGroup &) { // do nothing } void KateNormalInputMode::writeSessionConfig(KConfigGroup &) { // do nothing } void KateNormalInputMode::updateConfig() { // do nothing } void KateNormalInputMode::readWriteChanged(bool) { // inform search bar if (m_searchBar) { m_searchBar->slotReadWriteChanged(); } } void KateNormalInputMode::find() { KateSearchBar *const bar = searchBar(IncrementalSearchBar); view()->bottomViewBar()->addBarWidget(bar); view()->bottomViewBar()->showBarWidget(bar); bar->setFocus(); } void KateNormalInputMode::findSelectedForwards() { KateSearchBar::nextMatchForSelection(view(), KateSearchBar::SearchForward); } void KateNormalInputMode::findSelectedBackwards() { KateSearchBar::nextMatchForSelection(view(), KateSearchBar::SearchBackward); } void KateNormalInputMode::findReplace() { KateSearchBar *const bar = searchBar(PowerSearchBar); view()->bottomViewBar()->addBarWidget(bar); view()->bottomViewBar()->showBarWidget(bar); bar->setFocus(); } void KateNormalInputMode::findNext() { searchBar(IncrementalSearchBarOrKeepMode)->findNext(); } void KateNormalInputMode::findPrevious() { searchBar(IncrementalSearchBarOrKeepMode)->findPrevious(); } void KateNormalInputMode::activateCommandLine() { const KTextEditor::Range selection = view()->selectionRange(); // if the user has selected text, insert the selection's range (start line to end line) in the // command line when opened if (selection.start().line() != -1 && selection.end().line() != -1) { cmdLineBar()->setText(QString::number(selection.start().line() + 1) + QLatin1Char(',') + QString::number(selection.end().line() + 1)); } view()->bottomViewBar()->showBarWidget(cmdLineBar()); cmdLineBar()->setFocus(); } KateSearchBar *KateNormalInputMode::searchBar(const SearchBarMode mode) { /** * power mode wanted? */ const bool wantPowerMode = (mode == PowerSearchBar); /** * create search bar is not there? use right mode */ if (!m_searchBar) { m_searchBar = new KateSearchBar(wantPowerMode, view(), KateViewConfig::global()); } /** * else: switch mode if needed! */ else if (mode != IncrementalSearchBarOrKeepMode) { if (wantPowerMode) { m_searchBar->enterPowerMode(); } else { m_searchBar->enterIncrementalMode(); } } return m_searchBar; } KateCommandLineBar *KateNormalInputMode::cmdLineBar() { if (!m_cmdLine) { m_cmdLine = new KateCommandLineBar(view(), view()->bottomViewBar()); view()->bottomViewBar()->addBarWidget(m_cmdLine); } return m_cmdLine; } void KateNormalInputMode::updateRendererConfig() { if (m_searchBar) { m_searchBar->updateHighlightColors(); } } bool KateNormalInputMode::keyPress(QKeyEvent *e) { // Note: AND'ing with is a quick hack to fix Key_Enter const int key = e->key() | (e->modifiers() & Qt::ShiftModifier); if (view()->isCompletionActive()) { if (key == Qt::Key_Enter || key == Qt::Key_Return) { view()->completionWidget()->execute(); e->accept(); return true; } } return false; } bool KateNormalInputMode::blinkCaret() const { return true; } KateRenderer::caretStyles KateNormalInputMode::caretStyle() const { return view()->isOverwriteMode() ? KateRenderer::Block : KateRenderer::Line; } void KateNormalInputMode::toggleInsert() { view()->toggleInsert(); } void KateNormalInputMode::launchInteractiveCommand(const QString &command) { KateCommandLineBar *cmdLine = cmdLineBar(); view()->bottomViewBar()->showBarWidget(cmdLine); cmdLine->setText(command, false); } QString KateNormalInputMode::bookmarkLabel(int) const { return QString(); } diff --git a/src/inputmode/katenormalinputmodefactory.cpp b/src/inputmode/katenormalinputmodefactory.cpp index 505fecbb..b8b086bf 100644 --- a/src/inputmode/katenormalinputmodefactory.cpp +++ b/src/inputmode/katenormalinputmodefactory.cpp @@ -1,54 +1,54 @@ /* This file is part of the KDE libraries and the Kate part. * * 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 "katenormalinputmodefactory.h" #include "katenormalinputmode.h" #include KateNormalInputModeFactory::KateNormalInputModeFactory() : KateAbstractInputModeFactory() { } KateNormalInputModeFactory::~KateNormalInputModeFactory() { } KateAbstractInputMode *KateNormalInputModeFactory::createInputMode(KateViewInternal *viewInternal) { return new KateNormalInputMode(viewInternal); } KateConfigPage *KateNormalInputModeFactory::createConfigPage(QWidget *) { - return Q_NULLPTR; + return nullptr; } KTextEditor::View::InputMode KateNormalInputModeFactory::inputMode() { return KTextEditor::View::NormalInputMode; } QString KateNormalInputModeFactory::name() { return i18n("Normal Mode"); } diff --git a/src/inputmode/kateviinputmode.cpp b/src/inputmode/kateviinputmode.cpp index a7609cd8..32d9ff61 100644 --- a/src/inputmode/kateviinputmode.cpp +++ b/src/inputmode/kateviinputmode.cpp @@ -1,334 +1,334 @@ /* This file is part of the KDE libraries and the Kate part. * * 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 "kateviinputmode.h" #include "kateviewinternal.h" #include "kateconfig.h" #include #include #include #include #include #include #include #include #include namespace { QString viModeToString(KateVi::ViMode mode) { QString modeStr; switch (mode) { case KateVi::InsertMode: modeStr = i18n("VI: INSERT MODE"); break; case KateVi::NormalMode: modeStr = i18n("VI: NORMAL MODE"); break; case KateVi::VisualMode: modeStr = i18n("VI: VISUAL"); break; case KateVi::VisualBlockMode: modeStr = i18n("VI: VISUAL BLOCK"); break; case KateVi::VisualLineMode: modeStr = i18n("VI: VISUAL LINE"); break; case KateVi::ReplaceMode: modeStr = i18n("VI: REPLACE"); break; } return modeStr; } } KateViInputMode::KateViInputMode(KateViewInternal *viewInternal, KateVi::GlobalState *global) : KateAbstractInputMode(viewInternal) - , m_viModeEmulatedCommandBar(0) + , m_viModeEmulatedCommandBar(nullptr) , m_viGlobal(global) , m_caret(KateRenderer::Block) , m_nextKeypressIsOverriddenShortCut(false) , m_activated(false) { m_relLineNumbers = KateViewConfig::global()->viRelativeLineNumbers(); m_viModeManager = new KateVi::InputModeManager(this, view(), viewInternal); } KateViInputMode::~KateViInputMode() { delete m_viModeManager; } void KateViInputMode::activate() { m_activated = true; setCaretStyle(KateRenderer::Block); // TODO: can we end up in insert mode? reset(); // TODO: is this necessary? (well, not anymore I guess) if (view()->selection()) { m_viModeManager->changeViMode(KateVi::VisualMode); view()->setCursorPosition(KTextEditor::Cursor(view()->selectionRange().end().line(), view()->selectionRange().end().column() - 1)); m_viModeManager->m_viVisualMode->updateSelection(); } viewInternal()->iconBorder()->setRelLineNumbersOn(m_relLineNumbers); } void KateViInputMode::deactivate() { if (m_viModeEmulatedCommandBar) { m_viModeEmulatedCommandBar->hideMe(); } // make sure to turn off edits mergin when leaving vi input mode view()->doc()->setUndoMergeAllEdits(false); m_activated = false; viewInternal()->iconBorder()->setRelLineNumbersOn(false); } void KateViInputMode::reset() { if (m_viModeEmulatedCommandBar) { m_viModeEmulatedCommandBar->hideMe(); } delete m_viModeManager; m_viModeManager = new KateVi::InputModeManager(this, view(), viewInternal()); if (m_viModeEmulatedCommandBar) { m_viModeEmulatedCommandBar->setViInputModeManager(m_viModeManager); } } bool KateViInputMode::overwrite() const { return m_viModeManager->getCurrentViMode() == KateVi::ViMode::ReplaceMode; } void KateViInputMode::overwrittenChar(const QChar &c) { m_viModeManager->getViReplaceMode()->overwrittenChar(c); } void KateViInputMode::clearSelection() { // do nothing, handled elsewhere } bool KateViInputMode::stealKey(QKeyEvent *k) { if (!KateViewConfig::global()->viInputModeStealKeys()) { return false; } // Actually see if we can make use of this key - if so, we've stolen it; if not, // let Qt's shortcut handling system deal with it. const bool stolen = keyPress(k); if (stolen) { // Qt will replay this QKeyEvent, next time as an ordinary KeyPress. m_nextKeypressIsOverriddenShortCut = true; } return stolen; } KTextEditor::View::InputMode KateViInputMode::viewInputMode() const { return KTextEditor::View::ViInputMode; } QString KateViInputMode::viewInputModeHuman() const { return i18n("vi-mode"); } KTextEditor::View::ViewMode KateViInputMode::viewMode() const { return m_viModeManager->getCurrentViewMode(); } QString KateViInputMode::viewModeHuman() const { QString currentMode = viModeToString(m_viModeManager->getCurrentViMode()); if (m_viModeManager->macroRecorder()->isRecording()) { currentMode.prepend(QLatin1String("(") + i18n("recording") + QLatin1String(") ")); } QString cmd = m_viModeManager->getVerbatimKeys(); if (!cmd.isEmpty()) { currentMode.prepend(QStringLiteral("%1 ").arg(cmd)); } return QStringLiteral("%1").arg(currentMode); } void KateViInputMode::gotFocus() { // nothing to do } void KateViInputMode::lostFocus() { // nothing to do } void KateViInputMode::readSessionConfig(const KConfigGroup &config) { // restore vi registers and jump list m_viModeManager->readSessionConfig(config); } void KateViInputMode::writeSessionConfig(KConfigGroup &config) { // save vi registers and jump list m_viModeManager->writeSessionConfig(config); } void KateViInputMode::updateConfig() { KateViewConfig *cfg = view()->config(); // whether relative line numbers should be used or not. m_relLineNumbers = cfg->viRelativeLineNumbers(); if (m_activated) { viewInternal()->iconBorder()->setRelLineNumbersOn(m_relLineNumbers); } } void KateViInputMode::readWriteChanged(bool) { // nothing todo } void KateViInputMode::find() { showViModeEmulatedCommandBar(); viModeEmulatedCommandBar()->init(KateVi::EmulatedCommandBar::SearchForward); } void KateViInputMode::findSelectedForwards() { m_viModeManager->searcher()->findNext(); } void KateViInputMode::findSelectedBackwards() { m_viModeManager->searcher()->findPrevious(); } void KateViInputMode::findReplace() { showViModeEmulatedCommandBar(); viModeEmulatedCommandBar()->init(KateVi::EmulatedCommandBar::SearchForward); } void KateViInputMode::findNext() { m_viModeManager->searcher()->findNext(); } void KateViInputMode::findPrevious() { m_viModeManager->searcher()->findPrevious(); } void KateViInputMode::activateCommandLine() { showViModeEmulatedCommandBar(); viModeEmulatedCommandBar()->init(KateVi::EmulatedCommandBar::Command); } void KateViInputMode::showViModeEmulatedCommandBar() { view()->bottomViewBar()->addBarWidget(viModeEmulatedCommandBar()); view()->bottomViewBar()->showBarWidget(viModeEmulatedCommandBar()); } KateVi::EmulatedCommandBar *KateViInputMode::viModeEmulatedCommandBar() { if (!m_viModeEmulatedCommandBar) { m_viModeEmulatedCommandBar = new KateVi::EmulatedCommandBar(this, m_viModeManager, view()); m_viModeEmulatedCommandBar->hide(); } return m_viModeEmulatedCommandBar; } void KateViInputMode::updateRendererConfig() { // do nothing } bool KateViInputMode::keyPress(QKeyEvent *e) { if (m_nextKeypressIsOverriddenShortCut) { // This is just the replay of a shortcut that we stole, this time as a QKeyEvent. // Ignore it, as we'll have already handled it via stealKey()! m_nextKeypressIsOverriddenShortCut = false; return true; } if (m_viModeManager->handleKeypress(e)) { emit view()->viewModeChanged(view(), viewMode()); return true; } return false; } bool KateViInputMode::blinkCaret() const { return false; } KateRenderer::caretStyles KateViInputMode::caretStyle() const { return m_caret; } void KateViInputMode::toggleInsert() { // do nothing } void KateViInputMode::launchInteractiveCommand(const QString &) { // do nothing so far } QString KateViInputMode::bookmarkLabel(int line) const { return m_viModeManager->marks()->getMarksOnTheLine(line); } void KateViInputMode::setCaretStyle(const KateRenderer::caretStyles caret) { if (m_caret != caret) { m_caret = caret; view()->renderer()->setCaretStyle(m_caret); view()->renderer()->setDrawCaret(true); viewInternal()->paintCursor(); } } diff --git a/src/mode/katemodemanager.cpp b/src/mode/katemodemanager.cpp index 5412c6f7..1d0d0ecb 100644 --- a/src/mode/katemodemanager.cpp +++ b/src/mode/katemodemanager.cpp @@ -1,296 +1,296 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-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. */ //BEGIN Includes #include "katemodemanager.h" #include "katewildcardmatcher.h" #include "katedocument.h" #include "kateconfig.h" #include "kateview.h" #include "kateglobal.h" #include "katesyntaxmanager.h" #include "katesyntaxdocument.h" #include "katepartdebug.h" #include #include #include //END Includes static QStringList vectorToList(const QVector &v) { QStringList l; l.reserve(v.size()); std::copy(v.begin(), v.end(), std::back_inserter(l)); return l; } KateModeManager::KateModeManager() { update(); } KateModeManager::~KateModeManager() { qDeleteAll(m_types); } bool compareKateFileType(const KateFileType *const left, const KateFileType *const right) { int comparison = left->section.compare(right->section, Qt::CaseInsensitive); if (comparison == 0) { comparison = left->name.compare(right->name, Qt::CaseInsensitive); } return comparison < 0; } // // read the types from config file and update the internal list // void KateModeManager::update() { KConfig config(QStringLiteral("katemoderc"), KConfig::NoGlobals); QStringList g(config.groupList()); qDeleteAll(m_types); m_types.clear(); m_name2Type.clear(); for (int z = 0; z < g.count(); z++) { KConfigGroup cg(&config, g[z]); KateFileType *type = new KateFileType(); type->number = z; type->name = g[z]; type->section = cg.readEntry(QStringLiteral("Section")); type->wildcards = cg.readXdgListEntry(QStringLiteral("Wildcards")); type->mimetypes = cg.readXdgListEntry(QStringLiteral("Mimetypes")); type->priority = cg.readEntry(QStringLiteral("Priority"), 0); type->varLine = cg.readEntry(QStringLiteral("Variables")); type->indenter = cg.readEntry(QStringLiteral("Indenter")); type->hl = cg.readEntry(QStringLiteral("Highlighting")); // only for generated types... type->hlGenerated = cg.readEntry(QStringLiteral("Highlighting Generated"), false); type->version = cg.readEntry(QStringLiteral("Highlighting Version")); // insert into the list + hash... m_types.append(type); m_name2Type.insert(type->name, type); } // try if the hl stuff is up to date... const auto modes = KateHlManager::self()->modeList(); for (int i = 0; i < modes.size(); ++i) { - KateFileType *type = 0; + KateFileType *type = nullptr; bool newType = false; if (m_name2Type.contains(modes[i].name())) { type = m_name2Type[modes[i].name()]; } else { newType = true; type = new KateFileType(); type->name = modes[i].name(); type->priority = 0; m_types.append(type); m_name2Type.insert(type->name, type); } if (newType || type->version != QString::number(modes[i].version())) { type->name = modes[i].name(); type->section = modes[i].section(); type->wildcards = vectorToList(modes[i].extensions()); type->mimetypes = vectorToList(modes[i].mimeTypes()); type->priority = modes[i].priority(); type->version = QString::number(modes[i].version()); type->indenter = modes[i].indenter(); type->hl = modes[i].name(); type->hlGenerated = true; } type->translatedName = modes[i].translatedName(); type->translatedSection = modes[i].translatedSection(); } // sort the list... qSort(m_types.begin(), m_types.end(), compareKateFileType); // add the none type... KateFileType *t = new KateFileType(); // DO NOT TRANSLATE THIS, DONE LATER, marked by hlGenerated t->name = QStringLiteral("Normal"); t->hl = QStringLiteral("None"); t->hlGenerated = true; m_types.prepend(t); } // // save the given list to config file + update // void KateModeManager::save(const QList &v) { KConfig katerc(QStringLiteral("katemoderc"), KConfig::NoGlobals); QStringList newg; foreach (const KateFileType *type, v) { KConfigGroup config(&katerc, type->name); config.writeEntry("Section", type->section); config.writeXdgListEntry("Wildcards", type->wildcards); config.writeXdgListEntry("Mimetypes", type->mimetypes); config.writeEntry("Priority", type->priority); config.writeEntry("Indenter", type->indenter); QString varLine = type->varLine; if (QRegExp(QLatin1String("kate:(.*)")).indexIn(varLine) < 0) { varLine.prepend(QLatin1String("kate: ")); } config.writeEntry("Variables", varLine); config.writeEntry("Highlighting", type->hl); // only for generated types... config.writeEntry("Highlighting Generated", type->hlGenerated); config.writeEntry("Highlighting Version", type->version); newg << type->name; } foreach (const QString &groupName, katerc.groupList()) { if (newg.indexOf(groupName) == -1) { katerc.deleteGroup(groupName); } } katerc.sync(); update(); } QString KateModeManager::fileType(KTextEditor::DocumentPrivate *doc, const QString &fileToReadFrom) { if (!doc) { return QString(); } if (m_types.isEmpty()) { return QString(); } QString fileName = doc->url().toString(); int length = doc->url().toString().length(); QString result; // Try wildcards if (! fileName.isEmpty()) { static const QStringList commonSuffixes = QStringLiteral(".orig;.new;~;.bak;.BAK").split(QLatin1Char(';')); if (!(result = wildcardsFind(fileName)).isEmpty()) { return result; } QString backupSuffix = KateDocumentConfig::global()->backupSuffix(); if (fileName.endsWith(backupSuffix)) { if (!(result = wildcardsFind(fileName.left(length - backupSuffix.length()))).isEmpty()) { return result; } } for (QStringList::ConstIterator it = commonSuffixes.begin(); it != commonSuffixes.end(); ++it) { if (*it != backupSuffix && fileName.endsWith(*it)) { if (!(result = wildcardsFind(fileName.left(length - (*it).length()))).isEmpty()) { return result; } } } } // either read the file passed to this function (pre-load) or use the normal mimeType() KF KTextEditor API QString mtName; if (!fileToReadFrom.isEmpty()) { mtName = QMimeDatabase().mimeTypeForFile(fileToReadFrom).name(); } else { mtName = doc->mimeType(); } QList types; foreach (KateFileType *type, m_types) { if (type->mimetypes.indexOf(mtName) > -1) { types.append(type); } } if (!types.isEmpty()) { int pri = -1; QString name; foreach (KateFileType *type, types) { if (type->priority > pri) { pri = type->priority; name = type->name; } } return name; } return QString(); } QString KateModeManager::wildcardsFind(const QString &fileName) { - KateFileType *match = NULL; + KateFileType *match = nullptr; int minPrio = -1; foreach (KateFileType *type, m_types) { if (type->priority <= minPrio) { continue; } foreach (const QString &wildcard, type->wildcards) { if (KateWildcardMatcher::exactMatch(fileName, wildcard)) { match = type; minPrio = type->priority; break; } } } - return (match == NULL) ? QString() : match->name; + return (match == nullptr) ? QString() : match->name; } const KateFileType &KateModeManager::fileType(const QString &name) const { for (int i = 0; i < m_types.size(); ++i) if (m_types[i]->name == name) { return *m_types[i]; } static KateFileType notype; return notype; } diff --git a/src/mode/katemodemenu.cpp b/src/mode/katemodemenu.cpp index ef4594cd..a44d1b1d 100644 --- a/src/mode/katemodemenu.cpp +++ b/src/mode/katemodemenu.cpp @@ -1,145 +1,145 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-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. */ //BEGIN Includes #include "katemodemenu.h" #include "katedocument.h" #include "kateconfig.h" #include "kateview.h" #include "kateglobal.h" #include "katesyntaxmanager.h" #include "katesyntaxdocument.h" #include "katepartdebug.h" //END Includes void KateModeMenu::init() { - m_doc = 0; + m_doc = nullptr; connect(menu(), SIGNAL(triggered(QAction*)), this, SLOT(setType(QAction*))); connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); m_actionGroup = new QActionGroup(menu()); } KateModeMenu::~KateModeMenu() { qDeleteAll(subMenus); } void KateModeMenu::updateMenu(KTextEditor::Document *doc) { m_doc = static_cast(doc); } void KateModeMenu::slotAboutToShow() { KTextEditor::DocumentPrivate *doc = m_doc; int count = KTextEditor::EditorPrivate::self()->modeManager()->list().count(); for (int z = 0; z < count; z++) { QString nameRaw = KTextEditor::EditorPrivate::self()->modeManager()->list().at(z)->name; QString hlName = KTextEditor::EditorPrivate::self()->modeManager()->list().at(z)->nameTranslated(); QString hlSection = KTextEditor::EditorPrivate::self()->modeManager()->list().at(z)->sectionTranslated(); if (!hlSection.isEmpty() && !names.contains(hlName)) { if (!subMenusName.contains(hlSection)) { subMenusName << hlSection; QMenu *qmenu = new QMenu(hlSection); connect(qmenu, SIGNAL(triggered(QAction*)), this, SLOT(setType(QAction*))); subMenus.append(qmenu); menu()->addMenu(qmenu); } int m = subMenusName.indexOf(hlSection); names << hlName; QAction *action = subMenus.at(m)->addAction(hlName); m_actionGroup->addAction(action); action->setCheckable(true); action->setData(nameRaw); } else if (!names.contains(hlName)) { names << hlName; disconnect(menu(), SIGNAL(triggered(QAction*)), this, SLOT(setType(QAction*))); connect(menu(), SIGNAL(triggered(QAction*)), this, SLOT(setType(QAction*))); QAction *action = menu()->addAction(hlName); m_actionGroup->addAction(action); action->setCheckable(true); action->setData(nameRaw); } } if (!doc) { return; } for (int i = 0; i < subMenus.count(); i++) { QList actions = subMenus.at(i)->actions(); for (int j = 0; j < actions.count(); ++j) { actions[ j ]->setChecked(false); } } QList actions = menu()->actions(); for (int i = 0; i < actions.count(); ++i) { actions[ i ]->setChecked(false); } if (doc->fileType().isEmpty() || doc->fileType() == QLatin1String("Normal")) { for (int i = 0; i < actions.count(); ++i) { if (actions[ i ]->data().toString() == QLatin1String("Normal")) { actions[ i ]->setChecked(true); } } } else { if (!doc->fileType().isEmpty()) { const KateFileType &t = KTextEditor::EditorPrivate::self()->modeManager()->fileType(doc->fileType()); int i = subMenusName.indexOf(t.section); if (i >= 0 && subMenus.at(i)) { QList actions = subMenus.at(i)->actions(); for (int j = 0; j < actions.count(); ++j) { if (actions[ j ]->data().toString() == doc->fileType()) { actions[ j ]->setChecked(true); } } } else { QList actions = menu()->actions(); for (int j = 0; j < actions.count(); ++j) { if (actions[ j ]->data().toString().isEmpty()) { actions[ j ]->setChecked(true); } } } } } } void KateModeMenu::setType(QAction *action) { KTextEditor::DocumentPrivate *doc = m_doc; if (doc) { doc->updateFileType(action->data().toString(), true); } } diff --git a/src/part/katepart.desktop b/src/part/katepart.desktop index ac9f2299..5bcdc974 100644 --- a/src/part/katepart.desktop +++ b/src/part/katepart.desktop @@ -1,64 +1,64 @@ [Desktop Entry] Name=Embedded Advanced Text Editor Name[ar]=محرّر نصوص مضمّن متقدّم -Name[ast]=Editor de testu empotráu avanzáu +Name[ast]=Editor empotráu y avanzáu de testu Name[bg]=Вграден усъвършенстван текстов редактор Name[bs]=Ugrađeni napredni uređivač teksta Name[ca]=Editor de text avançat incrustat Name[ca@valencia]=Editor de text avançat incrustat Name[cs]=Zabudovaný rozšířený editor Name[da]=Indlejret avanceret teksteditor Name[de]=Einbettungsfähige erweiterte Editorkomponente Name[el]=Ενσωματωμένος προηγμένος επεξεργαστής κειμένου Name[en_GB]=Embedded Advanced Text Editor Name[es]=Editor de texto avanzado empotrado Name[et]=Põimitud võimas tekstiredaktor Name[eu]=Testu-editore aurreratu kapsulagarria Name[fi]=Kehittynyt upotettava tekstimuokkain Name[fr]=Éditeur de texte intégré avancé Name[ga]=Ardeagarthóir Leabaithe Téacs Name[gd]=Deasaiche teacsa adhartach leabaichte Name[gl]=Editor avanzado de textos integrado Name[hu]=Beágyazott szövegszerkesztő Name[ia]=Avantiate Editor Interne de Texto Name[is]=Ívefjanlegur þróaður textaritill Name[it]=Editor di testi avanzato integrato Name[ja]=埋め込みテキストエディタ Name[kk]=Ендірілетін үздік мәтін редакторы Name[km]=កម្មវិធី​និពន្ធ​អត្ថបទ​កម្រិត​ខ្ពស់​ដែល​បង្កប់​ Name[ko]=끼워넣은 고급 텍스트 편집기 Name[lt]=Vidinis sudėtingesnis teksto redaktorius Name[lv]=Iegultais paplašinātais teksta redaktors Name[mr]=अंतर्भूतीत प्रगत पाठ्य संपादक Name[nb]=Innebygget, avansert skriveprogram Name[nds]=Inbett verwiedert Texteditor Name[nl]=Ingebed geavanceerd tekstinvoercomponent Name[nn]=Innebyggbart avansert skriveprogram Name[pa]=ਇੰਬੈੱਡ ਮਾਹਰ ਟੈਕਸਟ ਐਡੀਟਰ Name[pl]=Zaawansowany osadzony edytor tekstu Name[pt]=Editor de Texto Avançado Incorporado Name[pt_BR]=Editor de texto avançado integrado Name[ro]=Redactor de text avansat înglobat Name[ru]=Встроенный расширенный текстовый редактор Name[si]=තිළැලි උසස් පෙළ සකසනය Name[sk]=Zabudovaný pokročilý textový editor Name[sl]=Vgrajen napreden urejevalnik besedil Name[sr]=Угнежђени напредни уређивач текста Name[sr@ijekavian]=Угнијежђени напредни уређивач текста Name[sr@ijekavianlatin]=Ugniježđeni napredni uređivač teksta Name[sr@latin]=Ugnežđeni napredni uređivač teksta Name[sv]=Inbäddningsbar avancerad texteditor Name[tg]=Таҳриргари матнии беҳтаршудаи дарунсохт Name[tr]=Gelişmiş Gömülü Metin Düzenleyici Name[ug]=سىڭدۈرمە KDE ئالىي تېكىست تەھرىرلىگۈچ Name[uk]=Вмонтований потужний текстовий редактор Name[wa]=Ravalé aspougneu di tecse avancî Name[x-test]=xxEmbedded Advanced Text Editorxx Name[zh_CN]=嵌入式高级文本编辑器 Name[zh_TW]=嵌入式進階文字編輯器 X-KDE-Library=kf5/parts/katepart Icon=accessories-text-editor X-KDE-ServiceTypes=KParts/ReadOnlyPart,KParts/ReadWritePart,KTextEditor/Document Type=Service InitialPreference=8 MimeType=text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-patch;text/x-adasrc;text/x-chdr;text/x-csrc;text/css;application/x-desktop;text/x-patch;text/x-fortran;text/html;text/x-java;text/x-tex;text/x-makefile;text/x-objcsrc;text/x-pascal;application/x-perl;application/x-perl;application/x-php;text/vnd.wap.wml;text/x-python;application/x-ruby;text/sgml;application/xml;model/vrml; diff --git a/src/printing/kateprinter.cpp b/src/printing/kateprinter.cpp index ff913cfd..e78f29f9 100644 --- a/src/printing/kateprinter.cpp +++ b/src/printing/kateprinter.cpp @@ -1,184 +1,184 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2002-2010 Anders Lund * * Rewritten based on code of Copyright (c) 2002 Michael Goffioul * * 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 "kateprinter.h" #include "kateconfig.h" #include "katedocument.h" #include "kateview.h" #include #include #include #include #include #include #include "printconfigwidgets.h" #include "printpainter.h" using namespace KatePrinter; //BEGIN KatePrinterPrivate class KatePrinterPrivate : public QObject { Q_OBJECT public: - KatePrinterPrivate(KTextEditor::DocumentPrivate *doc, KTextEditor::ViewPrivate *view = 0); + KatePrinterPrivate(KTextEditor::DocumentPrivate *doc, KTextEditor::ViewPrivate *view = nullptr); ~KatePrinterPrivate(); bool print(QPrinter *printer); public Q_SLOTS: void paint(QPrinter *printer); private: KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; PrintPainter *m_painter; }; KatePrinterPrivate::KatePrinterPrivate(KTextEditor::DocumentPrivate *doc, KTextEditor::ViewPrivate *view) : QObject() , m_view(view) , m_doc(doc) , m_painter(new PrintPainter(m_doc, m_view)) { } KatePrinterPrivate::~KatePrinterPrivate() { delete m_painter; } bool KatePrinterPrivate::print(QPrinter *printer) { // docname is now always there, including the right Untitled name printer->setDocName(m_doc->documentName()); KatePrintTextSettings *kpts = new KatePrintTextSettings; KatePrintHeaderFooter *kphf = new KatePrintHeaderFooter; KatePrintLayout *kpl = new KatePrintLayout; QList tabs; tabs << kpts; tabs << kphf; tabs << kpl; QWidget *parentWidget = m_doc->widget(); if (!parentWidget) { parentWidget = QApplication::activeWindow(); } QPointer printDialog(new QPrintDialog(printer, parentWidget)); printDialog->setOptionTabs(tabs); if (m_view && m_view->selection()) { printer->setPrintRange(QPrinter::Selection); printDialog->setOption(QAbstractPrintDialog::PrintSelection, true); } printDialog->setOption(QAbstractPrintDialog::PrintPageRange, true); const int dlgCode = printDialog->exec(); if (dlgCode != QDialog::Accepted || !printDialog) { delete printDialog; return false; } // configure the painter m_painter->setPrintGuide(kpts->printGuide()); m_painter->setPrintLineNumbers(kpts->printLineNumbers()); m_painter->setColorScheme(kpl->colorScheme()); m_painter->setUseBackground(kpl->useBackground()); m_painter->setUseBox(kpl->useBox()); m_painter->setBoxMargin(kpl->boxMargin()); m_painter->setBoxWidth(kpl->boxWidth()); m_painter->setBoxColor(kpl->boxColor()); m_painter->setHeadersFont(kphf->font()); m_painter->setUseHeader(kphf->useHeader()); m_painter->setHeaderBackground(kphf->headerBackground()); m_painter->setHeaderForeground(kphf->headerForeground()); m_painter->setUseHeaderBackground(kphf->useHeaderBackground()); m_painter->setHeaderFormat(kphf->headerFormat()); m_painter->setUseFooter(kphf->useFooter()); m_painter->setFooterBackground(kphf->footerBackground()); m_painter->setFooterForeground(kphf->footerForeground()); m_painter->setUseFooterBackground(kphf->useFooterBackground()); m_painter->setFooterFormat(kphf->footerFormat()); m_painter->paint(printer); delete printDialog; return true; } void KatePrinterPrivate::paint(QPrinter *printer) { m_painter->paint(printer); } //END KatePrinterPrivate //BEGIN KatePrinter bool KatePrinter::print(KTextEditor::ViewPrivate *view) { QPrinter printer; KatePrinterPrivate p(view->doc(), view); return p.print(&printer); } bool KatePrinter::printPreview(KTextEditor::ViewPrivate *view) { QPrinter printer; KatePrinterPrivate p(view->doc(), view); QPrintPreviewDialog preview(&printer); QObject::connect(&preview, SIGNAL(paintRequested(QPrinter*)), &p, SLOT(paint(QPrinter*))); return preview.exec(); } bool KatePrinter::print(KTextEditor::DocumentPrivate *doc) { QPrinter printer; KatePrinterPrivate p(doc); return p.print(&printer); } bool KatePrinter::printPreview(KTextEditor::DocumentPrivate *doc) { QPrinter printer; KatePrinterPrivate p(doc); QPrintPreviewDialog preview(&printer); QObject::connect(&preview, SIGNAL(paintRequested(QPrinter*)), &p, SLOT(paint(QPrinter*))); return preview.exec(); } //END KatePrinter #include "kateprinter.moc" diff --git a/src/printing/printconfigwidgets.cpp b/src/printing/printconfigwidgets.cpp index aed984e8..a6686b5a 100644 --- a/src/printing/printconfigwidgets.cpp +++ b/src/printing/printconfigwidgets.cpp @@ -1,662 +1,662 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2002-2010 Anders Lund * * Rewritten based on code of Copyright (c) 2002 Michael Goffioul * * 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 "printconfigwidgets.h" #include "kateglobal.h" #include "kateschema.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KatePrinter; //BEGIN KatePrintTextSettings KatePrintTextSettings::KatePrintTextSettings(QWidget *parent) : QWidget(parent) { setWindowTitle(i18n("Te&xt Settings")); QVBoxLayout *lo = new QVBoxLayout(this); cbLineNumbers = new QCheckBox(i18n("Print line &numbers"), this); lo->addWidget(cbLineNumbers); cbGuide = new QCheckBox(i18n("Print &legend"), this); lo->addWidget(cbGuide); lo->addStretch(1); // set defaults - nothing to do :-) // whatsthis cbLineNumbers->setWhatsThis(i18n( "

If enabled, line numbers will be printed on the left side of the page(s).

")); cbGuide->setWhatsThis(i18n( "

Print a box displaying typographical conventions for the document type, as " "defined by the syntax highlighting being used.

")); readSettings(); } KatePrintTextSettings::~KatePrintTextSettings() { writeSettings(); } bool KatePrintTextSettings::printLineNumbers() { return cbLineNumbers->isChecked(); } bool KatePrintTextSettings::printGuide() { return cbGuide->isChecked(); } void KatePrintTextSettings::readSettings() { KSharedConfigPtr config = KTextEditor::EditorPrivate::config(); KConfigGroup printGroup(config, "Printing"); KConfigGroup textGroup(&printGroup, "Text"); bool isLineNumbersChecked = textGroup.readEntry("LineNumbers", false); cbLineNumbers->setChecked(isLineNumbersChecked); bool isLegendChecked = textGroup.readEntry("Legend", false); cbGuide->setChecked(isLegendChecked); } void KatePrintTextSettings::writeSettings() { KSharedConfigPtr config = KTextEditor::EditorPrivate::config(); KConfigGroup printGroup(config, "Printing"); KConfigGroup textGroup(&printGroup, "Text"); textGroup.writeEntry("LineNumbers", printLineNumbers()); textGroup.writeEntry("Legend", printGuide()); config->sync(); } //END KatePrintTextSettings //BEGIN KatePrintHeaderFooter KatePrintHeaderFooter::KatePrintHeaderFooter(QWidget *parent) : QWidget(parent) { setWindowTitle(i18n("Hea&der && Footer")); QVBoxLayout *lo = new QVBoxLayout(this); // enable QHBoxLayout *lo1 = new QHBoxLayout(); lo->addLayout(lo1); cbEnableHeader = new QCheckBox(i18n("Pr&int header"), this); lo1->addWidget(cbEnableHeader); cbEnableFooter = new QCheckBox(i18n("Pri&nt footer"), this); lo1->addWidget(cbEnableFooter); // font QHBoxLayout *lo2 = new QHBoxLayout(); lo->addLayout(lo2); lo2->addWidget(new QLabel(i18n("Header/footer font:"), this)); lFontPreview = new QLabel(this); lFontPreview->setFrameStyle(QFrame::Panel | QFrame::Sunken); lo2->addWidget(lFontPreview); lo2->setStretchFactor(lFontPreview, 1); QPushButton *btnChooseFont = new QPushButton(i18n("Choo&se Font..."), this); lo2->addWidget(btnChooseFont); connect(btnChooseFont, SIGNAL(clicked()), this, SLOT(setHFFont())); // header gbHeader = new QGroupBox(this); gbHeader->setTitle(i18n("Header Properties")); QGridLayout *grid = new QGridLayout(gbHeader); lo->addWidget(gbHeader); QLabel *lHeaderFormat = new QLabel(i18n("&Format:"), gbHeader); grid->addWidget(lHeaderFormat, 0, 0); QFrame *hbHeaderFormat = new QFrame(gbHeader); QHBoxLayout *layoutFormat = new QHBoxLayout(hbHeaderFormat); grid->addWidget(hbHeaderFormat, 0, 1); leHeaderLeft = new KLineEdit(hbHeaderFormat); layoutFormat->addWidget(leHeaderLeft); leHeaderCenter = new KLineEdit(hbHeaderFormat); layoutFormat->addWidget(leHeaderCenter); leHeaderRight = new KLineEdit(hbHeaderFormat); lHeaderFormat->setBuddy(leHeaderLeft); layoutFormat->addWidget(leHeaderRight); leHeaderLeft->setContextMenuPolicy(Qt::CustomContextMenu); leHeaderCenter->setContextMenuPolicy(Qt::CustomContextMenu); leHeaderRight->setContextMenuPolicy(Qt::CustomContextMenu); connect(leHeaderLeft, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); connect(leHeaderCenter, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); connect(leHeaderRight, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); grid->addWidget(new QLabel(i18n("Colors:"), gbHeader), 1, 0); QFrame *hbHeaderColors = new QFrame(gbHeader); QHBoxLayout *layoutColors = new QHBoxLayout(hbHeaderColors); layoutColors->setSpacing(-1); grid->addWidget(hbHeaderColors, 1, 1); QLabel *lHeaderFgCol = new QLabel(i18n("Foreground:"), hbHeaderColors); layoutColors->addWidget(lHeaderFgCol); kcbtnHeaderFg = new KColorButton(hbHeaderColors); layoutColors->addWidget(kcbtnHeaderFg); lHeaderFgCol->setBuddy(kcbtnHeaderFg); cbHeaderEnableBgColor = new QCheckBox(i18n("Bac&kground"), hbHeaderColors); layoutColors->addWidget(cbHeaderEnableBgColor); kcbtnHeaderBg = new KColorButton(hbHeaderColors); layoutColors->addWidget(kcbtnHeaderBg); gbFooter = new QGroupBox(this); gbFooter->setTitle(i18n("Footer Properties")); grid = new QGridLayout(gbFooter); lo->addWidget(gbFooter); // footer QLabel *lFooterFormat = new QLabel(i18n("For&mat:"), gbFooter); grid->addWidget(lFooterFormat, 0, 0); QFrame *hbFooterFormat = new QFrame(gbFooter); layoutFormat = new QHBoxLayout(hbFooterFormat); layoutFormat->setSpacing(-1); grid->addWidget(hbFooterFormat, 0, 1); leFooterLeft = new KLineEdit(hbFooterFormat); layoutFormat->addWidget(leFooterLeft); leFooterCenter = new KLineEdit(hbFooterFormat); layoutFormat->addWidget(leFooterCenter); leFooterRight = new KLineEdit(hbFooterFormat); layoutFormat->addWidget(leFooterRight); lFooterFormat->setBuddy(leFooterLeft); leFooterLeft->setContextMenuPolicy(Qt::CustomContextMenu); leFooterCenter->setContextMenuPolicy(Qt::CustomContextMenu); leFooterRight->setContextMenuPolicy(Qt::CustomContextMenu); connect(leFooterLeft, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); connect(leFooterCenter, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); connect(leFooterRight, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint))); grid->addWidget(new QLabel(i18n("Colors:"), gbFooter), 1, 0); QFrame *hbFooterColors = new QFrame(gbFooter); layoutColors = new QHBoxLayout(hbFooterColors); layoutColors->setSpacing(-1); grid->addWidget(hbFooterColors, 1, 1); QLabel *lFooterBgCol = new QLabel(i18n("Foreground:"), hbFooterColors); layoutColors->addWidget(lFooterBgCol); kcbtnFooterFg = new KColorButton(hbFooterColors); layoutColors->addWidget(kcbtnFooterFg); lFooterBgCol->setBuddy(kcbtnFooterFg); cbFooterEnableBgColor = new QCheckBox(i18n("&Background"), hbFooterColors); layoutColors->addWidget(cbFooterEnableBgColor); kcbtnFooterBg = new KColorButton(hbFooterColors); layoutColors->addWidget(kcbtnFooterBg); lo->addStretch(1); // user friendly connect(cbEnableHeader, SIGNAL(toggled(bool)), gbHeader, SLOT(setEnabled(bool))); connect(cbEnableFooter, SIGNAL(toggled(bool)), gbFooter, SLOT(setEnabled(bool))); connect(cbHeaderEnableBgColor, SIGNAL(toggled(bool)), kcbtnHeaderBg, SLOT(setEnabled(bool))); connect(cbFooterEnableBgColor, SIGNAL(toggled(bool)), kcbtnFooterBg, SLOT(setEnabled(bool))); // set defaults cbEnableHeader->setChecked(true); leHeaderLeft->setText(QStringLiteral("%y")); leHeaderCenter->setText(QStringLiteral("%f")); leHeaderRight->setText(QStringLiteral("%p")); kcbtnHeaderFg->setColor(Qt::black); cbHeaderEnableBgColor->setChecked(false); kcbtnHeaderBg->setColor(Qt::lightGray); cbEnableFooter->setChecked(true); leFooterRight->setText(QStringLiteral("%U")); kcbtnFooterFg->setColor(Qt::black); cbFooterEnableBgColor->setChecked(false); kcbtnFooterBg->setColor(Qt::lightGray); // whatsthis QString s = i18n("

Format of the page header. The following tags are supported:

"); QString s1 = i18n( "
  • %u: current user name
  • " "
  • %d: complete date/time in short format
  • " "
  • %D: complete date/time in long format
  • " "
  • %h: current time
  • " "
  • %y: current date in short format
  • " "
  • %Y: current date in long format
  • " "
  • %f: file name
  • " "
  • %U: full URL of the document
  • " "
  • %p: page number
  • " "
  • %P: total amount of pages
  • " "

"); leHeaderRight->setWhatsThis(s + s1); leHeaderCenter->setWhatsThis(s + s1); leHeaderLeft->setWhatsThis(s + s1); s = i18n("

Format of the page footer. The following tags are supported:

"); leFooterRight->setWhatsThis(s + s1); leFooterCenter->setWhatsThis(s + s1); leFooterLeft->setWhatsThis(s + s1); readSettings(); } KatePrintHeaderFooter::~KatePrintHeaderFooter() { writeSettings(); } QFont KatePrintHeaderFooter::font() { return lFontPreview->font(); } bool KatePrintHeaderFooter::useHeader() { return cbEnableHeader->isChecked(); } QStringList KatePrintHeaderFooter::headerFormat() { QStringList l; l << leHeaderLeft->text() << leHeaderCenter->text() << leHeaderRight->text(); return l; } QColor KatePrintHeaderFooter::headerForeground() { return kcbtnHeaderFg->color(); } QColor KatePrintHeaderFooter::headerBackground() { return kcbtnHeaderBg->color(); } bool KatePrintHeaderFooter::useHeaderBackground() { return cbHeaderEnableBgColor->isChecked(); } bool KatePrintHeaderFooter::useFooter() { return cbEnableFooter->isChecked(); } QStringList KatePrintHeaderFooter::footerFormat() { QStringList l; l << leFooterLeft->text() << leFooterCenter->text() << leFooterRight->text(); return l; } QColor KatePrintHeaderFooter::footerForeground() { return kcbtnFooterFg->color(); } QColor KatePrintHeaderFooter::footerBackground() { return kcbtnFooterBg->color(); } bool KatePrintHeaderFooter::useFooterBackground() { return cbFooterEnableBgColor->isChecked(); } void KatePrintHeaderFooter::setHFFont() { bool ok; QFont selectedFont = QFontDialog::getFont(&ok, lFontPreview->font(), this); if (ok) { lFontPreview->setFont(selectedFont); QString text = i18n("%1, %2pt"); lFontPreview->setText(text.arg(selectedFont.family()).arg(selectedFont.pointSize())); } } void KatePrintHeaderFooter::showContextMenu(const QPoint &pos) { QLineEdit *lineEdit = qobject_cast(sender()); if (!lineEdit) { return; } QMenu *const contextMenu = lineEdit->createStandardContextMenu(); - if (contextMenu == NULL) { + if (contextMenu == nullptr) { return; } contextMenu->addSeparator(); // create original context menu QMenu *menu = contextMenu->addMenu(i18n("Add Placeholder...")); menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); QAction *a = menu->addAction(i18n("Current User Name") + QLatin1String("\t%u")); a->setData(QLatin1String("%u")); a = menu->addAction(i18n("Complete Date/Time (short format)") + QLatin1String("\t%d")); a->setData(QLatin1String("%d")); a = menu->addAction(i18n("Complete Date/Time (long format)") + QLatin1String("\t%D")); a->setData(QLatin1String("%D")); a = menu->addAction(i18n("Current Time") + QLatin1String("\t%h")); a->setData(QLatin1String("%h")); a = menu->addAction(i18n("Current Date (short format)") + QLatin1String("\t%y")); a->setData(QLatin1String("%y")); a = menu->addAction(i18n("Current Date (long format)") + QLatin1String("\t%Y")); a->setData(QLatin1String("%Y")); a = menu->addAction(i18n("File Name") + QLatin1String("\t%f")); a->setData(QLatin1String("%f")); a = menu->addAction(i18n("Full document URL") + QLatin1String("\t%U")); a->setData(QLatin1String("%U")); a = menu->addAction(i18n("Page Number") + QLatin1String("\t%p")); a->setData(QLatin1String("%p")); a = menu->addAction(i18n("Total Amount of Pages") + QLatin1String("\t%P")); a->setData(QLatin1String("%P")); QAction *const result = contextMenu->exec(lineEdit->mapToGlobal(pos)); if (result) { QString placeHolder = result->data().toString(); if (!placeHolder.isEmpty()) { lineEdit->insert(placeHolder); } } } void KatePrintHeaderFooter::readSettings() { KSharedConfigPtr config = KTextEditor::EditorPrivate::config(); KConfigGroup printGroup(config, "Printing"); // Header KConfigGroup headerFooterGroup(&printGroup, "HeaderFooter"); bool isHeaderEnabledChecked = headerFooterGroup.readEntry("HeaderEnabled", true); cbEnableHeader->setChecked(isHeaderEnabledChecked); QString headerFormatLeft = headerFooterGroup.readEntry("HeaderFormatLeft", "%y"); leHeaderLeft->setText(headerFormatLeft); QString headerFormatCenter = headerFooterGroup.readEntry("HeaderFormatCenter", "%f"); leHeaderCenter->setText(headerFormatCenter); QString headerFormatRight = headerFooterGroup.readEntry("HeaderFormatRight", "%p"); leHeaderRight->setText(headerFormatRight); QColor headerForeground = headerFooterGroup.readEntry("HeaderForeground", QColor("black")); kcbtnHeaderFg->setColor(headerForeground); bool isHeaderBackgroundChecked = headerFooterGroup.readEntry("HeaderBackgroundEnabled", false); cbHeaderEnableBgColor->setChecked(isHeaderBackgroundChecked); QColor headerBackground = headerFooterGroup.readEntry("HeaderBackground", QColor("lightgrey")); kcbtnHeaderBg->setColor(headerBackground); // Footer bool isFooterEnabledChecked = headerFooterGroup.readEntry("FooterEnabled", true); cbEnableFooter->setChecked(isFooterEnabledChecked); QString footerFormatLeft = headerFooterGroup.readEntry("FooterFormatLeft", QString()); leFooterLeft->setText(footerFormatLeft); QString footerFormatCenter = headerFooterGroup.readEntry("FooterFormatCenter", QString()); leFooterCenter->setText(footerFormatCenter); QString footerFormatRight = headerFooterGroup.readEntry("FooterFormatRight", "%U"); leFooterRight->setText(footerFormatRight); QColor footerForeground = headerFooterGroup.readEntry("FooterForeground", QColor("black")); kcbtnFooterFg->setColor(footerForeground); bool isFooterBackgroundChecked = headerFooterGroup.readEntry("FooterBackgroundEnabled", false); cbFooterEnableBgColor->setChecked(isFooterBackgroundChecked); QColor footerBackground = headerFooterGroup.readEntry("FooterBackground", QColor("lightgrey")); kcbtnFooterBg->setColor(footerBackground); // Font QFont headerFooterFont = headerFooterGroup.readEntry("HeaderFooterFont", QFont()); lFontPreview->setFont(headerFooterFont); lFontPreview->setText(QString(headerFooterFont.family() + QLatin1String(", %1pt")).arg(headerFooterFont.pointSize())); } void KatePrintHeaderFooter::writeSettings() { KSharedConfigPtr config = KTextEditor::EditorPrivate::config(); KConfigGroup printGroup(config, "Printing"); // Header KConfigGroup headerFooterGroup(&printGroup, "HeaderFooter"); headerFooterGroup.writeEntry("HeaderEnabled", useHeader()); QStringList format = headerFormat(); headerFooterGroup.writeEntry("HeaderFormatLeft", format[0]); headerFooterGroup.writeEntry("HeaderFormatCenter", format[1]); headerFooterGroup.writeEntry("HeaderFormatRight", format[2]); headerFooterGroup.writeEntry("HeaderForeground", headerForeground()); headerFooterGroup.writeEntry("HeaderBackgroundEnabled", useHeaderBackground()); headerFooterGroup.writeEntry("HeaderBackground", headerBackground()); // Footer headerFooterGroup.writeEntry("FooterEnabled", useFooter()); format = footerFormat(); headerFooterGroup.writeEntry("FooterFormatLeft", format[0]); headerFooterGroup.writeEntry("FooterFormatCenter", format[1]); headerFooterGroup.writeEntry("FooterFormatRight", format[2]); headerFooterGroup.writeEntry("FooterForeground", footerForeground()); headerFooterGroup.writeEntry("FooterBackgroundEnabled", useFooterBackground()); headerFooterGroup.writeEntry("FooterBackground", footerBackground()); // Font headerFooterGroup.writeEntry("HeaderFooterFont", font()); config->sync(); } //END KatePrintHeaderFooter //BEGIN KatePrintLayout KatePrintLayout::KatePrintLayout(QWidget *parent) : QWidget(parent) { setWindowTitle(i18n("L&ayout")); QVBoxLayout *lo = new QVBoxLayout(this); QHBoxLayout *hb = new QHBoxLayout(); lo->addLayout(hb); QLabel *lSchema = new QLabel(i18n("&Schema:"), this); hb->addWidget(lSchema); cmbSchema = new KComboBox(this); hb->addWidget(cmbSchema); cmbSchema->setEditable(false); lSchema->setBuddy(cmbSchema); cbDrawBackground = new QCheckBox(i18n("Draw bac&kground color"), this); lo->addWidget(cbDrawBackground); cbEnableBox = new QCheckBox(i18n("Draw &boxes"), this); lo->addWidget(cbEnableBox); gbBoxProps = new QGroupBox(this); gbBoxProps->setTitle(i18n("Box Properties")); QGridLayout *grid = new QGridLayout(gbBoxProps); lo->addWidget(gbBoxProps); QLabel *lBoxWidth = new QLabel(i18n("W&idth:"), gbBoxProps); grid->addWidget(lBoxWidth, 0, 0); sbBoxWidth = new QSpinBox(gbBoxProps); sbBoxWidth->setRange(1, 100); sbBoxWidth->setSingleStep(1); grid->addWidget(sbBoxWidth, 0, 1); lBoxWidth->setBuddy(sbBoxWidth); QLabel *lBoxMargin = new QLabel(i18n("&Margin:"), gbBoxProps); grid->addWidget(lBoxMargin, 1, 0); sbBoxMargin = new QSpinBox(gbBoxProps); sbBoxMargin->setRange(0, 100); sbBoxMargin->setSingleStep(1); grid->addWidget(sbBoxMargin, 1, 1); lBoxMargin->setBuddy(sbBoxMargin); QLabel *lBoxColor = new QLabel(i18n("Co&lor:"), gbBoxProps); grid->addWidget(lBoxColor, 2, 0); kcbtnBoxColor = new KColorButton(gbBoxProps); grid->addWidget(kcbtnBoxColor, 2, 1); lBoxColor->setBuddy(kcbtnBoxColor); connect(cbEnableBox, SIGNAL(toggled(bool)), gbBoxProps, SLOT(setEnabled(bool))); lo->addStretch(1); // set defaults: sbBoxMargin->setValue(6); gbBoxProps->setEnabled(false); Q_FOREACH (const KateSchema &schema, KTextEditor::EditorPrivate::self()->schemaManager()->list()) { cmbSchema->addItem(schema.translatedName(), QVariant(schema.rawName)); } // default is printing, MUST BE THERE cmbSchema->setCurrentIndex(cmbSchema->findData(QVariant(QStringLiteral("Printing")))); // whatsthis cmbSchema->setWhatsThis(i18n( "Select the color scheme to use for the print.")); cbDrawBackground->setWhatsThis(i18n( "

If enabled, the background color of the editor will be used.

" "

This may be useful if your color scheme is designed for a dark background.

")); cbEnableBox->setWhatsThis(i18n( "

If enabled, a box as defined in the properties below will be drawn " "around the contents of each page. The Header and Footer will be separated " "from the contents with a line as well.

")); sbBoxWidth->setWhatsThis(i18n( "The width of the box outline")); sbBoxMargin->setWhatsThis(i18n( "The margin inside boxes, in pixels")); kcbtnBoxColor->setWhatsThis(i18n( "The line color to use for boxes")); readSettings(); } KatePrintLayout::~KatePrintLayout() { writeSettings(); } QString KatePrintLayout::colorScheme() { return cmbSchema->itemData(cmbSchema->currentIndex()).toString(); } bool KatePrintLayout::useBackground() { return cbDrawBackground->isChecked(); } bool KatePrintLayout::useBox() { return cbEnableBox->isChecked(); } int KatePrintLayout::boxWidth() { return sbBoxWidth->value(); } int KatePrintLayout::boxMargin() { return sbBoxMargin->value(); } QColor KatePrintLayout::boxColor() { return kcbtnBoxColor->color(); } void KatePrintLayout::readSettings() { KSharedConfigPtr config = KTextEditor::EditorPrivate::config(); KConfigGroup printGroup(config, "Printing"); KConfigGroup layoutGroup(&printGroup, "Layout"); // get color schema back QString colorScheme = layoutGroup.readEntry("ColorScheme", "Printing"); int index = cmbSchema->findData(QVariant(colorScheme)); if (index != -1) { cmbSchema->setCurrentIndex(index); } bool isBackgroundChecked = layoutGroup.readEntry("BackgroundColorEnabled", false); cbDrawBackground->setChecked(isBackgroundChecked); bool isBoxChecked = layoutGroup.readEntry("BoxEnabled", false); cbEnableBox->setChecked(isBoxChecked); int boxWidth = layoutGroup.readEntry("BoxWidth", 1); sbBoxWidth->setValue(boxWidth); int boxMargin = layoutGroup.readEntry("BoxMargin", 6); sbBoxMargin->setValue(boxMargin); QColor boxColor = layoutGroup.readEntry("BoxColor", QColor()); kcbtnBoxColor->setColor(boxColor); } void KatePrintLayout::writeSettings() { KSharedConfigPtr config = KTextEditor::EditorPrivate::config(); KConfigGroup printGroup(config, "Printing"); KConfigGroup layoutGroup(&printGroup, "Layout"); layoutGroup.writeEntry("ColorScheme", colorScheme()); layoutGroup.writeEntry("BackgroundColorEnabled", useBackground()); layoutGroup.writeEntry("BoxEnabled", useBox()); layoutGroup.writeEntry("BoxWidth", boxWidth()); layoutGroup.writeEntry("BoxMargin", boxMargin()); layoutGroup.writeEntry("BoxColor", boxColor()); config->sync(); } //END KatePrintLayout diff --git a/src/printing/printconfigwidgets.h b/src/printing/printconfigwidgets.h index 1eb7edaa..775b9418 100644 --- a/src/printing/printconfigwidgets.h +++ b/src/printing/printconfigwidgets.h @@ -1,155 +1,155 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2002-2010 Anders Lund * * Rewritten based on code of Copyright (c) 2002 Michael Goffioul * * 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_PRINTER_CONFIG_WIDGETS_H__ #define __KATE_PRINTER_CONFIG_WIDGETS_H__ #include class QCheckBox; class QLabel; class KLineEdit; class KColorButton; class KComboBox; class QSpinBox; class QGroupBox; namespace KatePrinter { //BEGIN Text settings /* * Text settings page: * - Print Line Numbers * () Smart () Yes () No */ class KatePrintTextSettings : public QWidget { Q_OBJECT public: - explicit KatePrintTextSettings(QWidget *parent = 0); + explicit KatePrintTextSettings(QWidget *parent = nullptr); ~KatePrintTextSettings(); bool printLineNumbers(); bool printGuide(); private: void readSettings(); void writeSettings(); QCheckBox *cbLineNumbers; QCheckBox *cbGuide; }; //END Text Settings //BEGIN Header/Footer /* * Header & Footer page: * - enable header/footer * - header/footer props * o formats * o colors */ class KatePrintHeaderFooter : public QWidget { Q_OBJECT public: - explicit KatePrintHeaderFooter(QWidget *parent = 0); + explicit KatePrintHeaderFooter(QWidget *parent = nullptr); ~KatePrintHeaderFooter(); QFont font(); bool useHeader(); QStringList headerFormat(); QColor headerForeground(); QColor headerBackground(); bool useHeaderBackground(); bool useFooter(); QStringList footerFormat(); QColor footerForeground(); QColor footerBackground(); bool useFooterBackground(); public Q_SLOTS: void setHFFont(); void showContextMenu(const QPoint &pos); private: void readSettings(); void writeSettings(); QCheckBox *cbEnableHeader, *cbEnableFooter; QLabel *lFontPreview; QGroupBox *gbHeader, *gbFooter; KLineEdit *leHeaderLeft, *leHeaderCenter, *leHeaderRight; KColorButton *kcbtnHeaderFg, *kcbtnHeaderBg; QCheckBox *cbHeaderEnableBgColor; KLineEdit *leFooterLeft, *leFooterCenter, *leFooterRight; KColorButton *kcbtnFooterFg, *kcbtnFooterBg; QCheckBox *cbFooterEnableBgColor; }; //END Header/Footer //BEGIN Layout /* * Layout page: * - Color scheme * - Use Box * - Box properties * o Width * o Margin * o Color */ class KatePrintLayout : public QWidget { Q_OBJECT public: - explicit KatePrintLayout(QWidget *parent = 0); + explicit KatePrintLayout(QWidget *parent = nullptr); ~KatePrintLayout(); QString colorScheme(); bool useBackground(); bool useBox(); int boxWidth(); int boxMargin(); QColor boxColor(); private: void readSettings(); void writeSettings(); KComboBox *cmbSchema; QCheckBox *cbEnableBox; QCheckBox *cbDrawBackground; QGroupBox *gbBoxProps; QSpinBox *sbBoxWidth; QSpinBox *sbBoxMargin; KColorButton *kcbtnBoxColor; }; //END Layout } #endif diff --git a/src/render/katelayoutcache.cpp b/src/render/katelayoutcache.cpp index 3eb268fd..60af61d7 100644 --- a/src/render/katelayoutcache.cpp +++ b/src/render/katelayoutcache.cpp @@ -1,593 +1,593 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005 Hamish Rodda * Copyright (C) 2008 Dominik Haumann * * 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 "katelayoutcache.h" #include #include "katerenderer.h" #include "kateview.h" #include "katedocument.h" #include "katebuffer.h" #include "katepartdebug.h" namespace { bool enableLayoutCache = false; bool lessThan(const KateLineLayoutMap::LineLayoutPair &lhs, const KateLineLayoutMap::LineLayoutPair &rhs) { return lhs.first < rhs.first; } } //BEGIN KateLineLayoutMap KateLineLayoutMap::KateLineLayoutMap() { } KateLineLayoutMap::~KateLineLayoutMap() { } void KateLineLayoutMap::clear() { m_lineLayouts.clear(); } bool KateLineLayoutMap::contains(int i) const { LineLayoutMap::const_iterator it = qBinaryFind(m_lineLayouts.constBegin(), m_lineLayouts.constEnd(), LineLayoutPair(i, KateLineLayoutPtr()), lessThan); return (it != m_lineLayouts.constEnd()); } void KateLineLayoutMap::insert(int realLine, const KateLineLayoutPtr &lineLayoutPtr) { LineLayoutMap::iterator it = qBinaryFind(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(realLine, KateLineLayoutPtr()), lessThan); if (it != m_lineLayouts.end()) { (*it).second = lineLayoutPtr; } else { it = qUpperBound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(realLine, KateLineLayoutPtr()), lessThan); m_lineLayouts.insert(it, LineLayoutPair(realLine, lineLayoutPtr)); } } void KateLineLayoutMap::viewWidthIncreased() { LineLayoutMap::iterator it = m_lineLayouts.begin(); for (; it != m_lineLayouts.end(); ++it) { if ((*it).second->isValid() && (*it).second->viewLineCount() > 1) { (*it).second->invalidateLayout(); } } } void KateLineLayoutMap::viewWidthDecreased(int newWidth) { LineLayoutMap::iterator it = m_lineLayouts.begin(); for (; it != m_lineLayouts.end(); ++it) { if ((*it).second->isValid() && ((*it).second->viewLineCount() > 1 || (*it).second->width() > newWidth)) { (*it).second->invalidateLayout(); } } } void KateLineLayoutMap::relayoutLines(int startRealLine, int endRealLine) { LineLayoutMap::iterator start = qLowerBound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(startRealLine, KateLineLayoutPtr()), lessThan); LineLayoutMap::iterator end = qUpperBound(start, m_lineLayouts.end(), LineLayoutPair(endRealLine, KateLineLayoutPtr()), lessThan); while (start != end) { (*start).second->setLayoutDirty(); ++start; } } void KateLineLayoutMap::slotEditDone(int fromLine, int toLine, int shiftAmount) { LineLayoutMap::iterator start = qLowerBound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(fromLine, KateLineLayoutPtr()), lessThan); LineLayoutMap::iterator end = qUpperBound(start, m_lineLayouts.end(), LineLayoutPair(toLine, KateLineLayoutPtr()), lessThan); LineLayoutMap::iterator it; if (shiftAmount != 0) { for (it = end; it != m_lineLayouts.end(); ++it) { (*it).first += shiftAmount; (*it).second->setLine((*it).second->line() + shiftAmount); } for (it = start; it != end; ++it) { (*it).second->clear(); } m_lineLayouts.erase(start, end); } else { for (it = start; it != end; ++it) { (*it).second->setLayoutDirty(); } } } KateLineLayoutPtr &KateLineLayoutMap::operator[](int i) { LineLayoutMap::iterator it = qBinaryFind(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(i, KateLineLayoutPtr()), lessThan); return (*it).second; } //END KateLineLayoutMap KateLayoutCache::KateLayoutCache(KateRenderer *renderer, QObject *parent) : QObject(parent) , m_renderer(renderer) , m_startPos(-1, -1) , m_viewWidth(0) , m_wrap(false) , m_acceptDirtyLayouts(false) { Q_ASSERT(m_renderer); /** * connect to all possible editing primitives */ connect(&m_renderer->doc()->buffer(), SIGNAL(lineWrapped(KTextEditor::Cursor)), this, SLOT(wrapLine(KTextEditor::Cursor))); connect(&m_renderer->doc()->buffer(), SIGNAL(lineUnwrapped(int)), this, SLOT(unwrapLine(int))); connect(&m_renderer->doc()->buffer(), SIGNAL(textInserted(KTextEditor::Cursor,QString)), this, SLOT(insertText(KTextEditor::Cursor,QString))); connect(&m_renderer->doc()->buffer(), SIGNAL(textRemoved(KTextEditor::Range,QString)), this, SLOT(removeText(KTextEditor::Range))); } void KateLayoutCache::updateViewCache(const KTextEditor::Cursor &startPos, int newViewLineCount, int viewLinesScrolled) { //qCDebug(LOG_KTE) << startPos << " nvlc " << newViewLineCount << " vls " << viewLinesScrolled; int oldViewLineCount = m_textLayouts.count(); if (newViewLineCount == -1) { newViewLineCount = oldViewLineCount; } enableLayoutCache = true; int realLine; if (newViewLineCount == -1) { realLine = m_renderer->folding().visibleLineToLine(m_renderer->folding().lineToVisibleLine(startPos.line())); } else { realLine = m_renderer->folding().visibleLineToLine(startPos.line()); } int _viewLine = 0; if (wrap()) { // TODO check these assumptions are ok... probably they don't give much speedup anyway? if (startPos == m_startPos && !m_textLayouts.isEmpty()) { _viewLine = m_textLayouts.first().viewLine(); } else if (viewLinesScrolled > 0 && viewLinesScrolled < m_textLayouts.count()) { _viewLine = m_textLayouts[viewLinesScrolled].viewLine(); } else { KateLineLayoutPtr l = line(realLine); if (l) { Q_ASSERT(l->isValid()); Q_ASSERT(l->length() >= startPos.column() || m_renderer->view()->wrapCursor()); bool found = false; for (; _viewLine < l->viewLineCount(); ++_viewLine) { const KateTextLayout &t = l->viewLine(_viewLine); if (t.startCol() >= startPos.column() || _viewLine == l->viewLineCount() - 1) { found = true; break; } } // FIXME FIXME need to calculate past-end-of-line position here... Q_ASSERT(found); Q_UNUSED(found); } } } m_startPos = startPos; // Move the text layouts if we've just scrolled... if (viewLinesScrolled != 0) { // loop backwards if we've just scrolled up... bool forwards = viewLinesScrolled >= 0 ? true : false; for (int z = forwards ? 0 : m_textLayouts.count() - 1; forwards ? (z < m_textLayouts.count()) : (z >= 0); forwards ? z++ : z--) { int oldZ = z + viewLinesScrolled; if (oldZ >= 0 && oldZ < m_textLayouts.count()) { m_textLayouts[z] = m_textLayouts[oldZ]; } } } // Resize functionality if (newViewLineCount > oldViewLineCount) { m_textLayouts.reserve(newViewLineCount); } else if (newViewLineCount < oldViewLineCount) { /* FIXME reintroduce... check we're not missing any int lastLine = m_textLayouts[newSize - 1].line(); for (int i = oldSize; i < newSize; i++) { const KateTextLayout& layout = m_textLayouts[i]; if (layout.line() > lastLine && !layout.viewLine()) layout.kateLineLayout()->layout()->setCacheEnabled(false); }*/ m_textLayouts.resize(newViewLineCount); } KateLineLayoutPtr l = line(realLine); for (int i = 0; i < newViewLineCount; ++i) { if (!l) { if (i < m_textLayouts.count()) { if (m_textLayouts[i].isValid()) { m_textLayouts[i] = KateTextLayout::invalid(); } } else { m_textLayouts.append(KateTextLayout::invalid()); } continue; } Q_ASSERT(l->isValid()); Q_ASSERT(_viewLine < l->viewLineCount()); if (i < m_textLayouts.count()) { bool dirty = false; if (m_textLayouts[i].line() != realLine || m_textLayouts[i].viewLine() != _viewLine || (!m_textLayouts[i].isValid() && l->viewLine(_viewLine).isValid())) { dirty = true; } m_textLayouts[i] = l->viewLine(_viewLine); if (dirty) { m_textLayouts[i].setDirty(true); } } else { m_textLayouts.append(l->viewLine(_viewLine)); } //qCDebug(LOG_KTE) << "Laid out line " << realLine << " (" << l << "), viewLine " << _viewLine << " (" << m_textLayouts[i].kateLineLayout().data() << ")"; //m_textLayouts[i].debugOutput(); _viewLine++; if (_viewLine > l->viewLineCount() - 1) { int virtualLine = l->virtualLine() + 1; realLine = m_renderer->folding().visibleLineToLine(virtualLine); _viewLine = 0; if (realLine < m_renderer->doc()->lines()) { l = line(realLine, virtualLine); } else { - l = 0; + l = nullptr; } } } enableLayoutCache = false; } KateLineLayoutPtr KateLayoutCache::line(int realLine, int virtualLine) { if (m_lineLayouts.contains(realLine)) { KateLineLayoutPtr l = m_lineLayouts[realLine]; // ensure line is OK Q_ASSERT(l->line() == realLine); Q_ASSERT(realLine < m_renderer->doc()->buffer().lines()); if (virtualLine != -1) { l->setVirtualLine(virtualLine); } if (!l->isValid()) { l->setUsePlainTextLine(acceptDirtyLayouts()); l->textLine(!acceptDirtyLayouts()); m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache); } else if (l->isLayoutDirty() && !acceptDirtyLayouts()) { // reset textline l->setUsePlainTextLine(false); l->textLine(true); m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache); } Q_ASSERT(l->isValid() && (!l->isLayoutDirty() || acceptDirtyLayouts())); return l; } if (realLine < 0 || realLine >= m_renderer->doc()->lines()) { return KateLineLayoutPtr(); } KateLineLayoutPtr l(new KateLineLayout(*m_renderer)); l->setLine(realLine, virtualLine); // Mark it dirty, because it may not have the syntax highlighting applied // mark this here, to allow layoutLine to use plainLines... if (acceptDirtyLayouts()) { l->setUsePlainTextLine(true); } m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache); Q_ASSERT(l->isValid()); if (acceptDirtyLayouts()) { l->setLayoutDirty(true); } m_lineLayouts.insert(realLine, l); return l; } KateLineLayoutPtr KateLayoutCache::line(const KTextEditor::Cursor &realCursor) { return line(realCursor.line()); } KateTextLayout KateLayoutCache::textLayout(const KTextEditor::Cursor &realCursor) { /*if (realCursor >= viewCacheStart() && (realCursor < viewCacheEnd() || realCursor == viewCacheEnd() && !m_textLayouts.last().wrap())) foreach (const KateTextLayout& l, m_textLayouts) if (l.line() == realCursor.line() && (l.endCol() < realCursor.column() || !l.wrap())) return l;*/ return line(realCursor.line())->viewLine(viewLine(realCursor)); } KateTextLayout KateLayoutCache::textLayout(uint realLine, int _viewLine) { /*if (m_textLayouts.count() && (realLine >= m_textLayouts.first().line() && _viewLine >= m_textLayouts.first().viewLine()) && (realLine <= m_textLayouts.last().line() && _viewLine <= m_textLayouts.first().viewLine())) foreach (const KateTextLayout& l, m_textLayouts) if (l.line() == realLine && l.viewLine() == _viewLine) return const_cast(l);*/ return line(realLine)->viewLine(_viewLine); } KateTextLayout &KateLayoutCache::viewLine(int _viewLine) { Q_ASSERT(_viewLine >= 0 && _viewLine < m_textLayouts.count()); return m_textLayouts[_viewLine]; } int KateLayoutCache::viewCacheLineCount() const { return m_textLayouts.count(); } KTextEditor::Cursor KateLayoutCache::viewCacheStart() const { return !m_textLayouts.isEmpty() ? m_textLayouts.first().start() : KTextEditor::Cursor(); } KTextEditor::Cursor KateLayoutCache::viewCacheEnd() const { return !m_textLayouts.isEmpty() ? m_textLayouts.last().end() : KTextEditor::Cursor(); } int KateLayoutCache::viewWidth() const { return m_viewWidth; } /** * This returns the view line upon which realCursor is situated. * The view line is the number of lines in the view from the first line * The supplied cursor should be in real lines. */ int KateLayoutCache::viewLine(const KTextEditor::Cursor &realCursor) { if (realCursor.column() <= 0 || realCursor.line() < 0) { return 0; } Q_ASSERT(realCursor.line() < m_renderer->doc()->lines()); KateLineLayoutPtr thisLine = line(realCursor.line()); for (int i = 0; i < thisLine->viewLineCount(); ++i) { const KateTextLayout &l = thisLine->viewLine(i); if (realCursor.column() >= l.startCol() && realCursor.column() < l.endCol()) { return i; } } return thisLine->viewLineCount() - 1; } int KateLayoutCache::displayViewLine(const KTextEditor::Cursor &virtualCursor, bool limitToVisible) { if (!virtualCursor.isValid()) { return -1; } KTextEditor::Cursor work = viewCacheStart(); // only try this with valid lines! if (work.isValid()) { work.setLine(m_renderer->folding().lineToVisibleLine(work.line())); } if (!work.isValid()) { return virtualCursor.line(); } int limit = m_textLayouts.count(); // Efficient non-word-wrapped path if (!m_renderer->view()->dynWordWrap()) { int ret = virtualCursor.line() - work.line(); if (limitToVisible && (ret < 0 || ret > limit)) { return -1; } else { return ret; } } if (work == virtualCursor) { return 0; } int ret = -(int)viewLine(viewCacheStart()); bool forwards = (work < virtualCursor); // FIXME switch to using ranges? faster? if (forwards) { while (work.line() != virtualCursor.line()) { ret += viewLineCount(m_renderer->folding().visibleLineToLine(work.line())); work.setLine(work.line() + 1); if (limitToVisible && ret > limit) { return -1; } } } else { while (work.line() != virtualCursor.line()) { work.setLine(work.line() - 1); ret -= viewLineCount(m_renderer->folding().visibleLineToLine(work.line())); if (limitToVisible && ret < 0) { return -1; } } } // final difference KTextEditor::Cursor realCursor = virtualCursor; realCursor.setLine(m_renderer->folding().visibleLineToLine(realCursor.line())); if (realCursor.column() == -1) { realCursor.setColumn(m_renderer->doc()->lineLength(realCursor.line())); } ret += viewLine(realCursor); if (limitToVisible && (ret < 0 || ret > limit)) { return -1; } return ret; } int KateLayoutCache::lastViewLine(int realLine) { if (!m_renderer->view()->dynWordWrap()) { return 0; } KateLineLayoutPtr l = line(realLine); Q_ASSERT(l); return l->viewLineCount() - 1; } int KateLayoutCache::viewLineCount(int realLine) { return lastViewLine(realLine) + 1; } void KateLayoutCache::viewCacheDebugOutput() const { qCDebug(LOG_KTE) << "Printing values for " << m_textLayouts.count() << " lines:"; if (!m_textLayouts.isEmpty()) { foreach (const KateTextLayout &t, m_textLayouts) if (t.isValid()) { t.debugOutput(); } else { qCDebug(LOG_KTE) << "Line Invalid."; } } } void KateLayoutCache::wrapLine(const KTextEditor::Cursor &position) { m_lineLayouts.slotEditDone(position.line(), position.line() + 1, 1); } void KateLayoutCache::unwrapLine(int line) { m_lineLayouts.slotEditDone(line - 1, line, -1); } void KateLayoutCache::insertText(const KTextEditor::Cursor &position, const QString &) { m_lineLayouts.slotEditDone(position.line(), position.line(), 0); } void KateLayoutCache::removeText(const KTextEditor::Range &range) { m_lineLayouts.slotEditDone(range.start().line(), range.start().line(), 0); } void KateLayoutCache::clear() { m_textLayouts.clear(); m_lineLayouts.clear(); m_startPos = KTextEditor::Cursor(-1, -1); } void KateLayoutCache::setViewWidth(int width) { bool wider = width > m_viewWidth; m_viewWidth = width; m_lineLayouts.clear(); m_startPos = KTextEditor::Cursor(-1, -1); // Only get rid of layouts that we have to if (wider) { m_lineLayouts.viewWidthIncreased(); } else { m_lineLayouts.viewWidthDecreased(width); } } bool KateLayoutCache::wrap() const { return m_wrap; } void KateLayoutCache::setWrap(bool wrap) { m_wrap = wrap; clear(); } void KateLayoutCache::relayoutLines(int startRealLine, int endRealLine) { if (startRealLine > endRealLine) { qCWarning(LOG_KTE) << "start" << startRealLine << "before end" << endRealLine; } m_lineLayouts.relayoutLines(startRealLine, endRealLine); } bool KateLayoutCache::acceptDirtyLayouts() { return m_acceptDirtyLayouts; } void KateLayoutCache::setAcceptDirtyLayouts(bool accept) { m_acceptDirtyLayouts = accept; } diff --git a/src/render/katelinelayout.cpp b/src/render/katelinelayout.cpp index 3d733f64..b04d532d 100644 --- a/src/render/katelinelayout.cpp +++ b/src/render/katelinelayout.cpp @@ -1,259 +1,259 @@ /* This file is part of the KDE libraries Copyright (C) 2002-2005 Hamish Rodda Copyright (C) 2003 Anakim Border * * 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 "katelinelayout.h" #include "katetextlayout.h" #include "katetextfolding.h" #include #include "katepartdebug.h" #include "katedocument.h" #include "katerenderer.h" KateLineLayout::KateLineLayout(KateRenderer &renderer) : m_renderer(renderer) - , m_textLine(0L) + , m_textLine() , m_line(-1) , m_virtualLine(-1) , m_shiftX(0) - , m_layout(0L) + , m_layout(nullptr) , m_layoutDirty(true) , m_usePlainTextLine(false) { } KateLineLayout::~KateLineLayout() { delete m_layout; } void KateLineLayout::clear() { m_textLine = Kate::TextLine(); m_line = -1; m_virtualLine = -1; m_shiftX = 0; // not touching dirty delete m_layout; - m_layout = 0L; + m_layout = nullptr; // not touching layout dirty } bool KateLineLayout::includesCursor(const KTextEditor::Cursor &realCursor) const { return realCursor.line() == line(); } const Kate::TextLine &KateLineLayout::textLine(bool reloadForce) const { if (reloadForce || !m_textLine) { m_textLine = usePlainTextLine() ? m_renderer.doc()->plainKateTextLine(line()) : m_renderer.doc()->kateTextLine(line()); } Q_ASSERT(m_textLine); return m_textLine; } int KateLineLayout::line() const { return m_line; } void KateLineLayout::setLine(int line, int virtualLine) { m_line = line; m_virtualLine = (virtualLine == -1) ? m_renderer.folding().lineToVisibleLine(line) : virtualLine; m_textLine = Kate::TextLine(); } int KateLineLayout::virtualLine() const { return m_virtualLine; } void KateLineLayout::setVirtualLine(int virtualLine) { m_virtualLine = virtualLine; } bool KateLineLayout::startsInvisibleBlock() const { if (!isValid()) { return false; } return (virtualLine() + 1) != m_renderer.folding().lineToVisibleLine(line() + 1); } int KateLineLayout::shiftX() const { return m_shiftX; } void KateLineLayout::setShiftX(int shiftX) { m_shiftX = shiftX; } KTextEditor::DocumentPrivate *KateLineLayout::doc() const { return m_renderer.doc(); } bool KateLineLayout::isValid() const { return line() != -1 && layout() && textLine(); } QTextLayout *KateLineLayout::layout() const { return m_layout; } void KateLineLayout::setLayout(QTextLayout *layout) { if (m_layout != layout) { delete m_layout; m_layout = layout; } m_layoutDirty = !m_layout; m_dirtyList.clear(); if (m_layout) for (int i = 0; i < qMax(1, m_layout->lineCount()); ++i) { m_dirtyList.append(true); } } void KateLineLayout::invalidateLayout() { - setLayout(0L); + setLayout(nullptr); } bool KateLineLayout::isDirty(int viewLine) const { Q_ASSERT(isValid() && viewLine >= 0 && viewLine < viewLineCount()); return m_dirtyList[viewLine]; } bool KateLineLayout::setDirty(int viewLine, bool dirty) { Q_ASSERT(isValid() && viewLine >= 0 && viewLine < viewLineCount()); m_dirtyList[viewLine] = dirty; return dirty; } KTextEditor::Cursor KateLineLayout::start() const { return KTextEditor::Cursor(line(), 0); } int KateLineLayout::length() const { return textLine()->length(); } int KateLineLayout::viewLineCount() const { return m_layout->lineCount(); } KateTextLayout KateLineLayout::viewLine(int viewLine) const { if (viewLine < 0) { viewLine += viewLineCount(); } Q_ASSERT(isValid()); Q_ASSERT(viewLine >= 0 && viewLine < viewLineCount()); return KateTextLayout(KateLineLayoutPtr(const_cast(this)), viewLine); } int KateLineLayout::width() const { int width = 0; for (int i = 0; i < m_layout->lineCount(); ++i) { width = qMax((int)m_layout->lineAt(i).naturalTextWidth(), width); } return width; } int KateLineLayout::widthOfLastLine() const { const KateTextLayout &lastLine = viewLine(viewLineCount() - 1); return lastLine.width() + lastLine.xOffset(); } bool KateLineLayout::isOutsideDocument() const { return line() < 0 || line() >= m_renderer.doc()->lines(); } void KateLineLayout::debugOutput() const { qCDebug(LOG_KTE) << "KateLineLayout: " << this << " valid " << isValid() << " line " << line() << " length " << length() << " width " << width() << " viewLineCount " << viewLineCount(); } int KateLineLayout::viewLineForColumn(int column) const { int len = 0; int i = 0; for (; i < m_layout->lineCount() - 1; ++i) { len += m_layout->lineAt(i).textLength(); if (column < len) { return i; } } return i; } bool KateLineLayout::isLayoutDirty() const { return m_layoutDirty; } void KateLineLayout::setLayoutDirty(bool dirty) { m_layoutDirty = dirty; } bool KateLineLayout::usePlainTextLine() const { return m_usePlainTextLine; } void KateLineLayout::setUsePlainTextLine(bool plain) { m_usePlainTextLine = plain; } bool KateLineLayout::isRightToLeft() const { if (!m_layout) { return false; } return m_layout->textOption().textDirection() == Qt::RightToLeft; } diff --git a/src/render/katerenderer.cpp b/src/render/katerenderer.cpp index 3d23b49a..8ca56650 100644 --- a/src/render/katerenderer.cpp +++ b/src/render/katerenderer.cpp @@ -1,1116 +1,1116 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Mirko Stocker Copyright (C) 2003-2005 Hamish Rodda Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy 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 02110-1301, USA. */ #include "katerenderer.h" #include "katedocument.h" #include "kateconfig.h" #include "katehighlight.h" #include "kateview.h" #include "katerenderrange.h" #include "katetextlayout.h" #include "katebuffer.h" #include "katepartdebug.h" #include #include #include #include #include #include // qCeil static const QChar tabChar(QLatin1Char('\t')); static const QChar spaceChar(QLatin1Char(' ')); static const QChar nbSpaceChar(0xa0); // non-breaking space KateRenderer::KateRenderer(KTextEditor::DocumentPrivate *doc, Kate::TextFolding &folding, KTextEditor::ViewPrivate *view) : m_doc(doc) , m_folding(folding) , m_view(view) , m_tabWidth(m_doc->config()->tabWidth()) , m_indentWidth(m_doc->config()->indentationWidth()) , m_caretStyle(KateRenderer::Line) , m_drawCaret(true) , m_showSelections(true) , m_showTabs(true) , m_showSpaces(true) , m_showNonPrintableSpaces(false) , m_printerFriendly(false) , m_config(new KateRendererConfig(this)) { updateAttributes(); // initialize with a sane font height updateFontHeight(); } KateRenderer::~KateRenderer() { delete m_config; } void KateRenderer::updateAttributes() { m_attributes = m_doc->highlight()->attributes(config()->schema()); } KTextEditor::Attribute::Ptr KateRenderer::attribute(uint pos) const { if (pos < (uint)m_attributes.count()) { return m_attributes[pos]; } return m_attributes[0]; } KTextEditor::Attribute::Ptr KateRenderer::specificAttribute(int context) const { if (context >= 0 && context < m_attributes.count()) { return m_attributes[context]; } return m_attributes[0]; } void KateRenderer::setDrawCaret(bool drawCaret) { m_drawCaret = drawCaret; } void KateRenderer::setCaretStyle(KateRenderer::caretStyles style) { m_caretStyle = style; } void KateRenderer::setShowTabs(bool showTabs) { m_showTabs = showTabs; } void KateRenderer::setShowTrailingSpaces(bool showSpaces) { m_showSpaces = showSpaces; } void KateRenderer::setShowNonPrintableSpaces(const bool on) { m_showNonPrintableSpaces = on; } void KateRenderer::setTabWidth(int tabWidth) { m_tabWidth = tabWidth; } bool KateRenderer::showIndentLines() const { return m_config->showIndentationLines(); } void KateRenderer::setShowIndentLines(bool showIndentLines) { m_config->setShowIndentationLines(showIndentLines); } void KateRenderer::setIndentWidth(int indentWidth) { m_indentWidth = indentWidth; } void KateRenderer::setShowSelections(bool showSelections) { m_showSelections = showSelections; } void KateRenderer::increaseFontSizes() { QFont f(config()->font()); f.setPointSize(f.pointSize() + 1); config()->setFont(f); } void KateRenderer::decreaseFontSizes() { QFont f(config()->font()); if ((f.pointSize() - 1) > 0) { f.setPointSize(f.pointSize() - 1); } config()->setFont(f); } bool KateRenderer::isPrinterFriendly() const { return m_printerFriendly; } void KateRenderer::setPrinterFriendly(bool printerFriendly) { m_printerFriendly = printerFriendly; setShowTabs(false); setShowTrailingSpaces(false); setShowSelections(false); setDrawCaret(false); } void KateRenderer::paintTextLineBackground(QPainter &paint, KateLineLayoutPtr layout, int currentViewLine, int xStart, int xEnd) { if (isPrinterFriendly()) { return; } // Normal background color QColor backgroundColor(config()->backgroundColor()); // paint the current line background if we're on the current line QColor currentLineColor = config()->highlightedLineColor(); // Check for mark background int markRed = 0, markGreen = 0, markBlue = 0, markCount = 0; // Retrieve marks for this line uint mrk = m_doc->mark(layout->line()); if (mrk) { for (uint bit = 0; bit < 32; bit++) { KTextEditor::MarkInterface::MarkTypes markType = (KTextEditor::MarkInterface::MarkTypes)(1 << bit); if (mrk & markType) { QColor markColor = config()->lineMarkerColor(markType); if (markColor.isValid()) { markCount++; markRed += markColor.red(); markGreen += markColor.green(); markBlue += markColor.blue(); } } } // for } // Marks if (markCount) { markRed /= markCount; markGreen /= markCount; markBlue /= markCount; backgroundColor.setRgb( int((backgroundColor.red() * 0.9) + (markRed * 0.1)), int((backgroundColor.green() * 0.9) + (markGreen * 0.1)), int((backgroundColor.blue() * 0.9) + (markBlue * 0.1)) ); } // Draw line background paint.fillRect(0, 0, xEnd - xStart, lineHeight() * layout->viewLineCount(), backgroundColor); // paint the current line background if we're on the current line if (currentViewLine != -1) { if (markCount) { markRed /= markCount; markGreen /= markCount; markBlue /= markCount; currentLineColor.setRgb( int((currentLineColor.red() * 0.9) + (markRed * 0.1)), int((currentLineColor.green() * 0.9) + (markGreen * 0.1)), int((currentLineColor.blue() * 0.9) + (markBlue * 0.1)) ); } paint.fillRect(0, lineHeight() * currentViewLine, xEnd - xStart, lineHeight(), currentLineColor); } } void KateRenderer::paintTabstop(QPainter &paint, qreal x, qreal y) { QPen penBackup(paint.pen()); QPen pen(config()->tabMarkerColor()); pen.setWidthF(qMax(1.0, spaceWidth() / 10.0)); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, false); int dist = spaceWidth() * 0.3; QPoint points[8]; points[0] = QPoint(x - dist, y - dist); points[1] = QPoint(x, y); points[2] = QPoint(x, y); points[3] = QPoint(x - dist, y + dist); x += spaceWidth() / 3.0; points[4] = QPoint(x - dist, y - dist); points[5] = QPoint(x, y); points[6] = QPoint(x, y); points[7] = QPoint(x - dist, y + dist); paint.drawLines(points, 4); paint.setPen(penBackup); } void KateRenderer::paintTrailingSpace(QPainter &paint, qreal x, qreal y) { QPen penBackup(paint.pen()); QPen pen(config()->tabMarkerColor()); pen.setWidthF(spaceWidth() / 3.5); pen.setCapStyle(Qt::RoundCap); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, true); paint.drawPoint(QPointF(x, y)); paint.setPen(penBackup); } void KateRenderer::paintNonBreakSpace(QPainter &paint, qreal x, qreal y) { QPen penBackup(paint.pen()); QPen pen(config()->tabMarkerColor()); pen.setWidthF(qMax(1.0, spaceWidth() / 10.0)); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, false); const int height = fontHeight(); const int width = spaceWidth(); QPoint points[6]; points[0] = QPoint(x + width / 10, y + height / 4); points[1] = QPoint(x + width / 10, y + height / 3); points[2] = QPoint(x + width / 10, y + height / 3); points[3] = QPoint(x + width - width / 10, y + height / 3); points[4] = QPoint(x + width - width / 10, y + height / 3); points[5] = QPoint(x + width - width / 10, y + height / 4); paint.drawLines(points, 3); paint.setPen(penBackup); } void KateRenderer::paintNonPrintableSpaces(QPainter &paint, qreal x, qreal y, const QChar &chr) { paint.save(); QPen pen(config()->spellingMistakeLineColor()); pen.setWidthF(qMax(1.0, spaceWidth() * 0.1)); paint.setPen(pen); paint.setRenderHint(QPainter::Antialiasing, false); const int height = fontHeight(); const int width = config()->fontMetrics().width(chr); const int offset = spaceWidth() * 0.1; QPoint points[8]; points[0] = QPoint(x - offset, y + offset); points[1] = QPoint(x + width + offset, y + offset); points[2] = QPoint(x + width + offset, y + offset); points[3] = QPoint(x + width + offset, y - height - offset); points[4] = QPoint(x + width + offset, y - height - offset); points[5] = QPoint(x - offset, y - height - offset); points[6] = QPoint(x - offset, y - height - offset); points[7] = QPoint(x - offset, y + offset); paint.drawLines(points, 4); paint.restore(); } void KateRenderer::paintIndentMarker(QPainter &paint, uint x, uint y /*row*/) { QPen penBackup(paint.pen()); QPen myPen(config()->indentationLineColor()); static const QVector dashPattern = QVector() << 1 << 1; myPen.setDashPattern(dashPattern); if (y % 2) { myPen.setDashOffset(1); } paint.setPen(myPen); const int height = fontHeight(); const int top = 0; const int bottom = height - 1; QPainter::RenderHints renderHints = paint.renderHints(); paint.setRenderHints(renderHints, false); paint.drawLine(x + 2, top, x + 2, bottom); paint.setRenderHints(renderHints, true); paint.setPen(penBackup); } static bool rangeLessThanForRenderer(const Kate::TextRange *a, const Kate::TextRange *b) { // compare Z-Depth first // smaller Z-Depths should win! if (a->zDepth() > b->zDepth()) { return true; } else if (a->zDepth() < b->zDepth()) { return false; } // end of a > end of b? if (a->end().toCursor() > b->end().toCursor()) { return true; } // if ends are equal, start of a < start of b? if (a->end().toCursor() == b->end().toCursor()) { return a->start().toCursor() < b->start().toCursor(); } return false; } QList KateRenderer::decorationsForLine(const Kate::TextLine &textLine, int line, bool selectionsOnly, KateRenderRange *completionHighlight, bool completionSelected) const { QList newHighlight; // qDebug() << "paint line:" << line << "selection ranges:" << m_view->selections()->selections(); // Don't compute the highlighting if there isn't going to be any highlighting - QList rangesWithAttributes = m_doc->buffer().rangesForLine(line, m_printerFriendly ? 0 : m_view, true); + QList rangesWithAttributes = m_doc->buffer().rangesForLine(line, m_printerFriendly ? nullptr : m_view, true); if (selectionsOnly || !textLine->attributesList().isEmpty() || !rangesWithAttributes.isEmpty()) { RenderRangeList renderRanges; // Add the inbuilt highlighting to the list NormalRenderRange *inbuiltHighlight = new NormalRenderRange(); const QVector &al = textLine->attributesList(); for (int i = 0; i < al.count(); ++i) if (al[i].length > 0 && al[i].attributeValue > 0) { inbuiltHighlight->addRange(new KTextEditor::Range(KTextEditor::Cursor(line, al[i].offset), al[i].length), specificAttribute(al[i].attributeValue)); } renderRanges.append(inbuiltHighlight); if (!completionHighlight) { // check for dynamic hl stuff - const QSet *rangesMouseIn = m_view ? m_view->rangesMouseIn() : 0; - const QSet *rangesCaretIn = m_view ? m_view->rangesCaretIn() : 0; + const QSet *rangesMouseIn = m_view ? m_view->rangesMouseIn() : nullptr; + const QSet *rangesCaretIn = m_view ? m_view->rangesCaretIn() : nullptr; bool anyDynamicHlsActive = m_view && (!rangesMouseIn->empty() || !rangesCaretIn->empty()); // sort all ranges, we want that the most specific ranges win during rendering, multiple equal ranges are kind of random, still better than old smart rangs behavior ;) qSort(rangesWithAttributes.begin(), rangesWithAttributes.end(), rangeLessThanForRenderer); // loop over all ranges for (int i = 0; i < rangesWithAttributes.size(); ++i) { // real range Kate::TextRange *kateRange = rangesWithAttributes[i]; // calculate attribute, default: normal attribute KTextEditor::Attribute::Ptr attribute = kateRange->attribute(); if (anyDynamicHlsActive) { // check mouse in if (KTextEditor::Attribute::Ptr attributeMouseIn = attribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)) { if (rangesMouseIn->contains(kateRange)) { attribute = attributeMouseIn; } } // check caret in if (KTextEditor::Attribute::Ptr attributeCaretIn = attribute->dynamicAttribute(KTextEditor::Attribute::ActivateCaretIn)) { if (rangesCaretIn->contains(kateRange)) { attribute = attributeCaretIn; } } } // span range NormalRenderRange *additionaHl = new NormalRenderRange(); additionaHl->addRange(new KTextEditor::Range(*kateRange), attribute); renderRanges.append(additionaHl); } } else { // Add the code completion arbitrary highlight to the list renderRanges.append(completionHighlight); } // Add selection highlighting if we're creating the selection decorations if ((m_view && selectionsOnly && showSelections() && m_view->selection()) || (completionHighlight && completionSelected) || (m_view && m_view->blockSelection())) { // Set up the selection background attribute TODO: move this elsewhere, eg. into the config? static KTextEditor::Attribute::Ptr backgroundAttribute; if (!backgroundAttribute) { backgroundAttribute = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); } backgroundAttribute->setBackground(config()->selectionColor()); backgroundAttribute->setForeground(attribute(KTextEditor::dsNormal)->selectedForeground().color()); // Create ranges for the current selection if (completionHighlight && completionSelected) { auto selectionHighlight = new NormalRenderRange(); selectionHighlight->addRange(new KTextEditor::Range(line, 0, line + 1, 0), backgroundAttribute); renderRanges.append(selectionHighlight); } else if (m_view->blockSelection() && m_view->selections()->overlapsLine(line)) { auto selectionHighlight = new NormalRenderRange(); selectionHighlight->addRange(new KTextEditor::Range(m_doc->rangeOnLine(m_view->selectionRange(), line)), backgroundAttribute); renderRanges.append(selectionHighlight); } else { auto s = m_view->selections()->selections(); Q_FOREACH ( const auto& range, s ) { if ( !range.overlapsLine(line) ) { continue; } auto start = qMax(range.start(), KTextEditor::Cursor(line, 0)); auto end = qMin(range.end(), KTextEditor::Cursor(line, textLine->length())); // qDebug() << "adding highlight range:" << start << end << "for line" << line; auto selectionHighlight = new NormalRenderRange(); selectionHighlight->addRange(new KTextEditor::Range(start, end), backgroundAttribute); renderRanges.append(selectionHighlight); } } } KTextEditor::Cursor currentPosition, endPosition; // Calculate the range which we need to iterate in order to get the highlighting for just this line if (m_view && selectionsOnly) { if (m_view->blockSelection()) { KTextEditor::Range subRange = m_doc->rangeOnLine(m_view->selectionRange(), line); currentPosition = subRange.start(); endPosition = subRange.end(); } else { KTextEditor::Range rangeNeeded = KTextEditor::Range(line, 0, line + 1, 0); currentPosition = rangeNeeded.start(); endPosition = rangeNeeded.end(); } } else { currentPosition = KTextEditor::Cursor(line, 0); endPosition = KTextEditor::Cursor(line + 1, 0); } // Main iterative loop. This walks through each set of highlighting ranges, and stops each // time the highlighting changes. It then creates the corresponding QTextLayout::FormatRanges. while (currentPosition < endPosition) { renderRanges.advanceTo(currentPosition); // qDebug() << "advanced to:" << currentPosition << renderRanges.hasAttribute(); if (!renderRanges.hasAttribute()) { // No attribute, don't need to create a FormatRange for this text range currentPosition = renderRanges.nextBoundary(); continue; } KTextEditor::Cursor nextPosition = renderRanges.nextBoundary(); // Create the format range and populate with the correct start, length and format info QTextLayout::FormatRange fr; fr.start = currentPosition.column(); if (nextPosition < endPosition || endPosition.line() <= line) { fr.length = nextPosition.column() - currentPosition.column(); } else { // +1 to force background drawing at the end of the line when it's warranted fr.length = textLine->length() - currentPosition.column() + 1; } KTextEditor::Attribute::Ptr a = renderRanges.generateAttribute(); if (a) { fr.format = *a; #warning fixme, no idea how this should work. halp // if (selectionsOnly) { // assignSelectionBrushesFromAttribute(fr, *a); // } } newHighlight.append(fr); currentPosition = nextPosition; } if (completionHighlight) // Don't delete external completion render range { renderRanges.removeAll(completionHighlight); } qDeleteAll(renderRanges); } return newHighlight; } void KateRenderer::assignSelectionBrushesFromAttribute(QTextLayout::FormatRange &target, const KTextEditor::Attribute &attribute) const { if (attribute.hasProperty(SelectedForeground)) { target.format.setForeground(attribute.selectedForeground()); } if (attribute.hasProperty(SelectedBackground)) { target.format.setBackground(attribute.selectedBackground()); } } /* The ultimate line painting function. Currently missing features: - draw indent lines */ void KateRenderer::paintTextLine(QPainter &paint, KateLineLayoutPtr range, int xStart, int xEnd, const KTextEditor::Cursor *cursor, PaintTextLineFlags flags) { Q_ASSERT(range->isValid()); // qCDebug(LOG_KTE)<<"KateRenderer::paintTextLine"; // font data const QFontMetricsF &fm = config()->fontMetrics(); int currentViewLine = -1; if (cursor && cursor->line() == range->line()) { currentViewLine = range->viewLineForColumn(cursor->column()); } paintTextLineBackground(paint, range, currentViewLine, xStart, xEnd); if (range->layout()) { bool drawSelection = m_view && m_view->selection() && showSelections() && m_view->selections()->overlapsLine(range->line()); // qDebug() << "draw selection:" << drawSelection << range->line(); // Draw selection in block selecton mode. We need past-end-of-line selection which QTextLayout::draw can't render if (drawSelection && m_view->blockSelection()) { int selectionStartColumn = m_doc->fromVirtualColumn(range->line(), m_doc->toVirtualColumn(m_view->selectionRange().start())); int selectionEndColumn = m_doc->fromVirtualColumn(range->line(), m_doc->toVirtualColumn(m_view->selectionRange().end())); QBrush selectionBrush = config()->selectionColor(); if (selectionStartColumn != selectionEndColumn) { KateTextLayout lastLine = range->viewLine(range->viewLineCount() - 1); if (selectionEndColumn > lastLine.startCol()) { int selectionStartX = (selectionStartColumn > lastLine.startCol()) ? cursorToX(lastLine, selectionStartColumn, true) : 0; int selectionEndX = cursorToX(lastLine, selectionEndColumn, true); paint.fillRect(QRect(selectionStartX - xStart, (int)lastLine.lineLayout().y(), selectionEndX - selectionStartX, lineHeight()), selectionBrush); } } } QVector additionalFormats; if (range->length() > 0) { // We may have changed the pen, be absolutely sure it gets set back to // normal foreground color before drawing text for text that does not // set the pen color paint.setPen(attribute(KTextEditor::dsNormal)->foreground().color()); // Draw the text :) if (drawSelection) { // FIXME toVector() may be a performance issue additionalFormats = decorationsForLine(range->textLine(), range->line(), true).toVector(); // qDebug() << "selection formats:" << additionalFormats.size(); // Q_FOREACH ( const auto& fmt, additionalFormats ) { // qDebug() << fmt.start << fmt.length << fmt.format; // } range->layout()->draw(&paint, QPoint(-xStart, 0), additionalFormats); } else { range->layout()->draw(&paint, QPoint(-xStart, 0)); } } QBrush backgroundBrush; bool backgroundBrushSet = false; // Loop each individual line for additional text decoration etc. QListIterator it = range->layout()->additionalFormats(); QVectorIterator it2 = additionalFormats; for (int i = 0; i < range->viewLineCount(); ++i) { KateTextLayout line = range->viewLine(i); bool haveBackground = false; // Determine the background to use, if any, for the end of this view line backgroundBrushSet = false; while (it2.hasNext()) { const QTextLayout::FormatRange &fr = it2.peekNext(); if (fr.start > line.endCol()) { break; } if (fr.start + fr.length > line.endCol()) { if (fr.format.hasProperty(QTextFormat::BackgroundBrush)) { backgroundBrushSet = true; backgroundBrush = fr.format.background(); } haveBackground = true; break; } it2.next(); } while (!haveBackground && it.hasNext()) { const QTextLayout::FormatRange &fr = it.peekNext(); if (fr.start > line.endCol()) { break; } if (fr.start + fr.length > line.endCol()) { if (fr.format.hasProperty(QTextFormat::BackgroundBrush)) { backgroundBrushSet = true; backgroundBrush = fr.format.background(); } break; } it.next(); } // Draw selection or background color outside of areas where text is rendered if (!m_printerFriendly) { bool draw = false; QBrush drawBrush; if (m_view && m_view->selection() && !m_view->blockSelection() && m_view->lineEndSelected(line.end(true))) { draw = true; drawBrush = config()->selectionColor(); } else if (backgroundBrushSet && !m_view->blockSelection()) { draw = true; drawBrush = backgroundBrush; } if (draw) { int fillStartX = line.endX() - line.startX() + line.xOffset() - xStart; int fillStartY = lineHeight() * i; int width = xEnd - xStart - fillStartX; int height = lineHeight(); // reverse X for right-aligned lines if (range->layout()->textOption().alignment() == Qt::AlignRight) { fillStartX = 0; } if (width > 0) { QRect area(fillStartX, fillStartY, width, height); paint.fillRect(area, drawBrush); } } } // Draw indent lines if (showIndentLines() && i == 0) { const qreal w = spaceWidth(); const int lastIndentColumn = range->textLine()->indentDepth(m_tabWidth); for (int x = m_indentWidth; x < lastIndentColumn; x += m_indentWidth) { paintIndentMarker(paint, x * w + 1 - xStart, range->line()); } } // draw an open box to mark non-breaking spaces const QString &text = range->textLine()->string(); int y = lineHeight() * i + fm.ascent() - fm.strikeOutPos(); int nbSpaceIndex = text.indexOf(nbSpaceChar, line.lineLayout().xToCursor(xStart)); while (nbSpaceIndex != -1 && nbSpaceIndex < line.endCol()) { int x = line.lineLayout().cursorToX(nbSpaceIndex); if (x > xEnd) { break; } paintNonBreakSpace(paint, x - xStart, y); nbSpaceIndex = text.indexOf(nbSpaceChar, nbSpaceIndex + 1); } // draw tab stop indicators if (showTabs()) { int tabIndex = text.indexOf(tabChar, line.lineLayout().xToCursor(xStart)); while (tabIndex != -1 && tabIndex < line.endCol()) { int x = line.lineLayout().cursorToX(tabIndex); if (x > xEnd) { break; } paintTabstop(paint, x - xStart + spaceWidth() / 2.0, y); tabIndex = text.indexOf(tabChar, tabIndex + 1); } } // draw trailing spaces if (showTrailingSpaces()) { int spaceIndex = line.endCol() - 1; int trailingPos = range->textLine()->lastChar(); if (trailingPos < 0) { trailingPos = 0; } if (spaceIndex >= trailingPos) { while (spaceIndex >= line.startCol() && text.at(spaceIndex).isSpace()) { if (text.at(spaceIndex) != QLatin1Char('\t') || !showTabs()) { paintTrailingSpace(paint, line.lineLayout().cursorToX(spaceIndex) - xStart + spaceWidth() / 2.0, y); } --spaceIndex; } } } if (showNonPrintableSpaces()) { const int y = lineHeight() * i + fm.ascent(); static const QRegularExpression nonPrintableSpacesRegExp(QStringLiteral("[\\x{2000}-\\x{200F}\\x{2028}-\\x{202F}\\x{205F}-\\x{2064}\\x{206A}-\\x{206F}]")); QRegularExpressionMatchIterator i = nonPrintableSpacesRegExp.globalMatch(text, line.lineLayout().xToCursor(xStart)); while (i.hasNext()) { const int charIndex = i.next().capturedStart(); const int x = line.lineLayout().cursorToX(charIndex); if (x > xEnd) { break; } paintNonPrintableSpaces(paint, x - xStart, y, text[charIndex]); } } } // draw word-wrap-honor-indent filling if ((range->viewLineCount() > 1) && range->shiftX() && (range->shiftX() > xStart)) { if (backgroundBrushSet) paint.fillRect(0, lineHeight(), range->shiftX() - xStart, lineHeight() * (range->viewLineCount() - 1), backgroundBrush); paint.fillRect(0, lineHeight(), range->shiftX() - xStart, lineHeight() * (range->viewLineCount() - 1), QBrush(config()->wordWrapMarkerColor(), Qt::Dense4Pattern)); } // Draw caret auto drawCaretAt = [this, &range, &paint, &xEnd, &xStart](const KTextEditor::Cursor* cursor, int alpha=255) { if (drawCaret() && cursor && range->includesCursor(*cursor)) { int caretWidth, lineWidth = 2; QColor color; QTextLine line = range->layout()->lineForTextPosition(qMin(cursor->column(), range->length())); // Determine the caret's style caretStyles style = caretStyle(); // Make the caret the desired width if (style == Line) { caretWidth = lineWidth; } else if (line.isValid() && cursor->column() < range->length()) { caretWidth = int(line.cursorToX(cursor->column() + 1) - line.cursorToX(cursor->column())); if (caretWidth < 0) { caretWidth = -caretWidth; } } else { caretWidth = spaceWidth(); } // Determine the color if (m_caretOverrideColor.isValid()) { // Could actually use the real highlighting system for this... // would be slower, but more accurate for corner cases color = m_caretOverrideColor; } else { // search for the FormatRange that includes the cursor foreach (const QTextLayout::FormatRange &r, range->layout()->additionalFormats()) { if ((r.start <= cursor->column()) && ((r.start + r.length) > cursor->column())) { // check for Qt::NoBrush, as the returned color is black() and no invalid QColor QBrush foregroundBrush = r.format.foreground(); if (foregroundBrush != Qt::NoBrush) { color = r.format.foreground().color(); } break; } } // still no color found, fall back to default style if (!color.isValid()) { color = attribute(KTextEditor::dsNormal)->foreground().color(); } } paint.save(); switch (style) { case Line : paint.setPen(QPen(color, caretWidth)); break; case Block : // use a gray caret so it's possible to see the character color.setAlpha(128); paint.setPen(QPen(color, caretWidth)); break; case Underline : break; case Half : color.setAlpha(128); paint.setPen(QPen(color, caretWidth)); break; } if (cursor->column() <= range->length()) { range->layout()->drawCursor(&paint, QPoint(-xStart, 0), cursor->column(), caretWidth); } else { // Off the end of the line... must be block mode. Draw the caret ourselves. const KateTextLayout &lastLine = range->viewLine(range->viewLineCount() - 1); int x = cursorToX(lastLine, KTextEditor::Cursor(range->line(), cursor->column()), true); if ((x >= xStart) && (x <= xEnd)) { paint.fillRect(x - xStart, (int)lastLine.lineLayout().y(), caretWidth, lineHeight(), color); } } paint.restore(); } }; drawCaretAt(cursor); foreach ( const auto& secondary, view()->cursors()->secondaryCursors() ) { drawCaretAt(&secondary, 128); } } // Draws the dashed underline at the start of a folded block of text. if (!(flags & SkipDrawFirstInvisibleLineUnderlined) && range->startsInvisibleBlock()) { const QPainter::RenderHints backupRenderHints = paint.renderHints(); paint.setRenderHint(QPainter::Antialiasing, false); QPen pen(config()->wordWrapMarkerColor()); pen.setCosmetic(true); pen.setStyle(Qt::DashLine); pen.setDashOffset(xStart); paint.setPen(pen); paint.drawLine(0, (lineHeight() * range->viewLineCount()) - 1, xEnd - xStart, (lineHeight() * range->viewLineCount()) - 1); paint.setRenderHints(backupRenderHints); } // show word wrap marker if desirable if ((!isPrinterFriendly()) && config()->wordWrapMarker() && QFontInfo(config()->font()).fixedPitch()) { const QPainter::RenderHints backupRenderHints = paint.renderHints(); paint.setRenderHint(QPainter::Antialiasing, false); paint.setPen(config()->wordWrapMarkerColor()); int _x = qreal(m_doc->config()->wordWrapAt()) * fm.width(QLatin1Char('x')) - xStart; paint.drawLine(_x, 0, _x, lineHeight()); paint.setRenderHints(backupRenderHints); } } const QFont &KateRenderer::currentFont() const { return config()->font(); } const QFontMetricsF &KateRenderer::currentFontMetrics() const { return config()->fontMetrics(); } uint KateRenderer::fontHeight() const { return m_fontHeight; } uint KateRenderer::documentHeight() const { return m_doc->lines() * lineHeight(); } int KateRenderer::lineHeight() const { return fontHeight(); } void KateRenderer::updateConfig() { // update the attibute list pointer updateAttributes(); // update font height, do this before we update the view! updateFontHeight(); // trigger view update, if any! if (m_view) { m_view->updateRendererConfig(); } } void KateRenderer::updateFontHeight() { // use height of font + round down, ensure we have at least one pixel // we round down to avoid artifacts: line height too large vs. qt background rendering of text attributes const qreal height = config()->fontMetrics().height(); m_fontHeight = qMax(1, qFloor(height)); } qreal KateRenderer::spaceWidth() const { return config()->fontMetrics().width(spaceChar); } void KateRenderer::layoutLine(KateLineLayoutPtr lineLayout, int maxwidth, bool cacheLayout) const { // if maxwidth == -1 we have no wrap Kate::TextLine textLine = lineLayout->textLine(); Q_ASSERT(textLine); QTextLayout *l = lineLayout->layout(); if (!l) { l = new QTextLayout(textLine->string(), config()->font()); } else { l->setText(textLine->string()); l->setFont(config()->font()); } l->setCacheEnabled(cacheLayout); // Initial setup of the QTextLayout. // Tab width QTextOption opt; opt.setFlags(QTextOption::IncludeTrailingSpaces); opt.setTabStop(m_tabWidth * config()->fontMetrics().width(spaceChar)); opt.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); // Find the first strong character in the string. // If it is an RTL character, set the base layout direction of the string to RTL. // // See http://www.unicode.org/reports/tr9/#The_Paragraph_Level (Sections P2 & P3). // Qt's text renderer ("scribe") version 4.2 assumes a "higher-level protocol" // (such as KatePart) will specify the paragraph level, so it does not apply P2 & P3 // by itself. If this ever change in Qt, the next code block could be removed. if (isLineRightToLeft(lineLayout)) { opt.setAlignment(Qt::AlignRight); opt.setTextDirection(Qt::RightToLeft); } else { opt.setAlignment(Qt::AlignLeft); opt.setTextDirection(Qt::LeftToRight); } l->setTextOption(opt); // Syntax highlighting, inbuilt and arbitrary l->setAdditionalFormats(decorationsForLine(textLine, lineLayout->line())); // Begin layouting l->beginLayout(); int height = 0; int shiftX = 0; bool needShiftX = (maxwidth != -1) && m_view && (m_view->config()->dynWordWrapAlignIndent() > 0); forever { QTextLine line = l->createLine(); if (!line.isValid()) { break; } if (maxwidth > 0) { line.setLineWidth(maxwidth); } line.setPosition(QPoint(line.lineNumber() ? shiftX : 0, height)); if (needShiftX && line.width() > 0) { needShiftX = false; // Determine x offset for subsequent-lines-of-paragraph indenting int pos = textLine->nextNonSpaceChar(0); if (pos > 0) { shiftX = (int)line.cursorToX(pos); } // check for too deep shift value and limit if necessary if (shiftX > ((double)maxwidth / 100 * m_view->config()->dynWordWrapAlignIndent())) { shiftX = 0; } // if shiftX > 0, the maxwidth has to adapted maxwidth -= shiftX; lineLayout->setShiftX(shiftX); } height += lineHeight(); } l->endLayout(); lineLayout->setLayout(l); } // 1) QString::isRightToLeft() sux // 2) QString::isRightToLeft() is marked as internal (WTF?) // 3) QString::isRightToLeft() does not seem to work on my setup // 4) isStringRightToLeft() should behave much better than QString::isRightToLeft() therefore: // 5) isStringRightToLeft() kicks ass bool KateRenderer::isLineRightToLeft(KateLineLayoutPtr lineLayout) const { QString s = lineLayout->textLine()->string(); int i = 0; // borrowed from QString::updateProperties() while (i != s.length()) { QChar c = s.at(i); switch (c.direction()) { case QChar::DirL: case QChar::DirLRO: case QChar::DirLRE: return false; case QChar::DirR: case QChar::DirAL: case QChar::DirRLO: case QChar::DirRLE: return true; default: break; } i ++; } return false; #if 0 // or should we use the direction of the widget? QWidget *display = qobject_cast(view()->parent()); if (!display) { return false; } return display->layoutDirection() == Qt::RightToLeft; #endif } int KateRenderer::cursorToX(const KateTextLayout &range, int col, bool returnPastLine) const { return cursorToX(range, KTextEditor::Cursor(range.line(), col), returnPastLine); } int KateRenderer::cursorToX(const KateTextLayout &range, const KTextEditor::Cursor &pos, bool returnPastLine) const { Q_ASSERT(range.isValid()); int x; if (range.lineLayout().width() > 0) { x = (int)range.lineLayout().cursorToX(pos.column()); } else { x = 0; } int over = pos.column() - range.endCol(); if (returnPastLine && over > 0) { x += over * spaceWidth(); } return x; } KTextEditor::Cursor KateRenderer::xToCursor(const KateTextLayout &range, int x, bool returnPastLine) const { Q_ASSERT(range.isValid()); KTextEditor::Cursor ret(range.line(), range.lineLayout().xToCursor(x)); // TODO wrong for RTL lines? if (returnPastLine && range.endCol(true) == -1 && x > range.width() + range.xOffset()) { ret.setColumn(ret.column() + ((x - (range.width() + range.xOffset())) / spaceWidth())); } return ret; } void KateRenderer::setCaretOverrideColor(const QColor &color) { m_caretOverrideColor = color; } diff --git a/src/render/katerenderer.h b/src/render/katerenderer.h index 883b3bf6..78c9ebcb 100644 --- a/src/render/katerenderer.h +++ b/src/render/katerenderer.h @@ -1,425 +1,424 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Mirko Stocker Copyright (C) 2003-2005 Hamish Rodda Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KATE_RENDERER_H__ #define __KATE_RENDERER_H__ #include #include "katetextline.h" #include "katelinelayout.h" #include #include #include #include #include namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } class KateRendererConfig; class KateRenderRange; namespace Kate { class TextFolding; } /** * Handles all of the work of rendering the text * (used for the views and printing) * **/ class KateRenderer { public: /** * Style of Caret * * The caret is displayed as a vertical bar (Line), a filled box * (Block), a horizontal bar (Underline), or a half-height filled * box (Half). The default is Line. * * Line Block Underline Half * * ## _ ######### _ _ * ## __| | #####| |# __| | __| | * ## / _' | ##/ _' |# / _' | / _' | * ##| (_| | #| (#| |# | (_| | #| (#| |# * ## \__,_| ##\__,_|# \__,_| ##\__,_|# * ## ######### ######### ######### */ enum caretStyles { Line, Block, Underline, Half }; /** * Constructor * @param doc document to render * @param folding folding information * @param view view which is output (0 for example for rendering to print) */ - explicit KateRenderer(KTextEditor::DocumentPrivate *doc, Kate::TextFolding &folding, KTextEditor::ViewPrivate *view = 0); + explicit KateRenderer(KTextEditor::DocumentPrivate *doc, Kate::TextFolding &folding, KTextEditor::ViewPrivate *view = nullptr); /** * Destructor */ ~KateRenderer(); /** * Returns the document to which this renderer is bound */ KTextEditor::DocumentPrivate *doc() const { return m_doc; } /** * Returns the folding info to which this renderer is bound * @return folding info */ Kate::TextFolding &folding() const { return m_folding; } /** * Returns the view to which this renderer is bound */ KTextEditor::ViewPrivate *view() const { return m_view; } /** * update the highlighting attributes * (for example after an hl change or after hl config changed) */ void updateAttributes(); /** * Determine whether the caret (text cursor) will be drawn. * @return should it be drawn? */ inline bool drawCaret() const { return m_drawCaret; } /** * Set whether the caret (text cursor) will be drawn. * @param drawCaret should caret be drawn? */ void setDrawCaret(bool drawCaret); /** * The style of the caret (text cursor) to be painted. * @return caretStyle */ inline KateRenderer::caretStyles caretStyle() const { return m_caretStyle; } /** * Set the style of caret to be painted. * @param style style to set */ void setCaretStyle(KateRenderer::caretStyles style); /** * Set a \a brush with which to override drawing of the caret. Set to QColor() to clear. */ void setCaretOverrideColor(const QColor &color); /** * @returns whether tabs should be shown (ie. a small mark * drawn to identify a tab) * @return tabs should be shown */ inline bool showTabs() const { return m_showTabs; } /** * Set whether a mark should be painted to help identifying tabs. * @param showTabs show the tabs? */ void setShowTabs(bool showTabs); /** * @returns whether trailing spaces should be shown. */ inline bool showTrailingSpaces() const { return m_showSpaces; } /** * Set whether a mark should be painted for trailing spaces. */ void setShowTrailingSpaces(bool showSpaces); /** * @returns whether non-printable spaces should be shown */ inline bool showNonPrintableSpaces() const { return m_showNonPrintableSpaces; } /** * Set whether box should be drawn around non-printable spaces */ void setShowNonPrintableSpaces(const bool showNonPrintableSpaces); /** * Sets the width of the tab. Helps performance. * @param tabWidth new tab width */ void setTabWidth(int tabWidth); /** * @returns whether indent lines should be shown * @return indent lines should be shown */ bool showIndentLines() const; /** * Set whether a guide should be painted to help identifying indent lines. * @param showLines show the indent lines? */ void setShowIndentLines(bool showLines); /** * Sets the width of the tab. Helps performance. * @param indentWidth new indent width */ void setIndentWidth(int indentWidth); /** * Show the view's selection? * @return show sels? */ inline bool showSelections() const { return m_showSelections; } /** * Set whether the view's selections should be shown. * The default is true. * @param showSelections show the selections? */ void setShowSelections(bool showSelections); /** * Change to a different font (soon to be font set?) */ void increaseFontSizes(); void decreaseFontSizes(); const QFont ¤tFont() const; const QFontMetricsF ¤tFontMetrics() const; /** * @return whether the renderer is configured to paint in a * printer-friendly fashion. */ bool isPrinterFriendly() const; /** * Configure this renderer to paint in a printer-friendly fashion. * * Sets the other options appropriately if true. */ void setPrinterFriendly(bool printerFriendly); /** * Text width & height calculation functions... */ void layoutLine(KateLineLayoutPtr line, int maxwidth = -1, bool cacheLayout = false) const; /** * This is a smaller QString::isRightToLeft(). It's also marked as internal to kate * instead of internal to Qt, so we can modify. This method searches for the first * strong character in the paragraph and then returns its direction. In case of a * line without any strong characters, the direction is forced to be LTR. * * Back in KDE 4.1 this method counted chars, which lead to unwanted side effects. * (see https://bugs.kde.org/show_bug.cgi?id=178594). As this function is internal * the way it work will probably change between releases. Be warned! */ bool isLineRightToLeft(KateLineLayoutPtr lineLayout) const; /** * The ultimate decoration creation function. * - * \param range line to return decoration for * \param selectionsOnly return decorations for selections and/or dynamic highlighting. */ - QList decorationsForLine(const Kate::TextLine &textLine, int line, bool selectionsOnly = false, KateRenderRange *completionHighlight = 0L, bool completionSelected = false) const; + QList decorationsForLine(const Kate::TextLine &textLine, int line, bool selectionsOnly = false, KateRenderRange *completionHighlight = nullptr, bool completionSelected = false) const; // Width calculators qreal spaceWidth() const; /** * Returns the x position of cursor \p col on the line \p range. */ int cursorToX(const KateTextLayout &range, int col, bool returnPastLine = false) const; /// \overload int cursorToX(const KateTextLayout &range, const KTextEditor::Cursor &pos, bool returnPastLine = false) const; /** * Returns the real cursor which is occupied by the specified x value, or that closest to it. * If \p returnPastLine is true, the column will be extrapolated out with the assumption * that the extra characters are spaces. */ KTextEditor::Cursor xToCursor(const KateTextLayout &range, int x, bool returnPastLine = false) const; // Font height uint fontHeight() const; // Line height int lineHeight() const; // Document height uint documentHeight() const; /** * Flags to customize the paintTextLine function behavior */ enum PaintTextLineFlag { /** * Skip drawing the dashed underline at the start of a folded block of text? */ SkipDrawFirstInvisibleLineUnderlined = 0x1, }; Q_DECLARE_FLAGS(PaintTextLineFlags, PaintTextLineFlag) /** * This is the ultimate function to perform painting of a text line. * * The text line is painted from the upper limit of (0,0). To move that, * apply a transform to your painter. * * @param paint painter to use * @param range layout to use in painting this line * @param xStart starting width in pixels. * @param xEnd ending width in pixels. * @param cursor position of the caret, if placed on the current line. * @param flags flags for customizing the drawing of the line */ - void paintTextLine(QPainter &paint, KateLineLayoutPtr range, int xStart, int xEnd, const KTextEditor::Cursor *cursor = 0L, PaintTextLineFlags flags = PaintTextLineFlags()); + void paintTextLine(QPainter &paint, KateLineLayoutPtr range, int xStart, int xEnd, const KTextEditor::Cursor *cursor = nullptr, PaintTextLineFlags flags = PaintTextLineFlags()); /** * Paint the background of a line * * Split off from the main @ref paintTextLine method to make it smaller. As it's being * called only once per line it shouldn't noticably affect performance and it * helps readability a LOT. * * @param paint painter to use * @param layout layout to use in painting this line * @param currentViewLine if one of the view lines is the current line, set * this to the index; otherwise -1. * @param xStart starting width in pixels. * @param xEnd ending width in pixels. */ void paintTextLineBackground(QPainter &paint, KateLineLayoutPtr layout, int currentViewLine, int xStart, int xEnd); /** * This takes an in index, and returns all the attributes for it. * For example, if you have a ktextline, and want the KTextEditor::Attribute * for a given position, do: * * attribute(myktextline->attribute(position)); */ KTextEditor::Attribute::Ptr attribute(uint pos) const; KTextEditor::Attribute::Ptr specificAttribute(int context) const; private: /** * Paint a trailing space on position (x, y). */ void paintTrailingSpace(QPainter &paint, qreal x, qreal y); /** * Paint a tab stop marker on position (x, y). */ void paintTabstop(QPainter &paint, qreal x, qreal y); /** * Paint a non-breaking space marker on position (x, y). */ void paintNonBreakSpace(QPainter &paint, qreal x, qreal y); /** * Paint non printable spaces bounding box */ void paintNonPrintableSpaces(QPainter &paint, qreal x, qreal y, const QChar &chr); /** Paint a SciTE-like indent marker. */ void paintIndentMarker(QPainter &paint, uint x, uint y); void assignSelectionBrushesFromAttribute(QTextLayout::FormatRange &target, const KTextEditor::Attribute &attribute) const; // update font height void updateFontHeight(); KTextEditor::DocumentPrivate *const m_doc; Kate::TextFolding &m_folding; KTextEditor::ViewPrivate *const m_view; // cache of config values int m_tabWidth; int m_indentWidth; int m_fontHeight; // some internal flags KateRenderer::caretStyles m_caretStyle; bool m_drawCaret; bool m_showSelections; bool m_showTabs; bool m_showSpaces; bool m_showNonPrintableSpaces; bool m_printerFriendly; QColor m_caretOverrideColor; QList m_attributes; /** * Configuration */ public: inline KateRendererConfig *config() const { return m_config; } void updateConfig(); private: KateRendererConfig *const m_config; }; #endif diff --git a/src/schema/katecategorydrawer.cpp b/src/schema/katecategorydrawer.cpp index b425a7f6..a4cf0ea8 100644 --- a/src/schema/katecategorydrawer.cpp +++ b/src/schema/katecategorydrawer.cpp @@ -1,278 +1,278 @@ /* * Copyright (C) 2009 by Rafael Fernández López * Copyright (C) 2013 by Dominik Haumann * * 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 "katecategorydrawer.h" #include #include #include -KateCategoryDrawer::KateCategoryDrawer() : KCategoryDrawer(0) +KateCategoryDrawer::KateCategoryDrawer() : KCategoryDrawer(nullptr) { } void KateCategoryDrawer::drawCategory(const QModelIndex &index, int sortRole, const QStyleOption &option, QPainter *painter) const { Q_UNUSED(sortRole) painter->setRenderHint(QPainter::Antialiasing); const QRect optRect = option.rect; QFont font(QApplication::font()); font.setBold(true); const int height = categoryHeight(index, option); const bool leftToRight = painter->layoutDirection() == Qt::LeftToRight; //BEGIN: decoration gradient { QPainterPath path(optRect.bottomLeft()); path.lineTo(QPoint(optRect.topLeft().x(), optRect.topLeft().y() - 3)); const QPointF topLeft(optRect.topLeft()); QRectF arc(topLeft, QSizeF(4, 4)); path.arcTo(arc, 180, -90); path.lineTo(optRect.topRight()); path.lineTo(optRect.bottomRight()); path.lineTo(optRect.bottomLeft()); QColor window(option.palette.window().color()); const QColor base(option.palette.base().color()); window.setAlphaF(0.4); QLinearGradient decoGradient1; if (leftToRight) { decoGradient1.setStart(optRect.topLeft()); decoGradient1.setFinalStop(optRect.bottomLeft()); } else { decoGradient1.setStart(optRect.topRight()); decoGradient1.setFinalStop(optRect.bottomRight()); } decoGradient1.setColorAt(0, window); decoGradient1.setColorAt(1, Qt::transparent); QLinearGradient decoGradient2; if (leftToRight) { decoGradient2.setStart(optRect.topLeft()); decoGradient2.setFinalStop(optRect.topRight()); } else { decoGradient2.setStart(optRect.topRight()); decoGradient2.setFinalStop(optRect.topLeft()); } decoGradient2.setColorAt(0, Qt::transparent); decoGradient2.setColorAt(1, base); painter->fillPath(path, decoGradient1); painter->fillPath(path, decoGradient2); } //END: decoration gradient { QRect newOptRect(optRect); if (leftToRight) { newOptRect.translate(1, 1); } else { newOptRect.translate(-1, 1); } //BEGIN: inner top left corner { painter->save(); painter->setPen(option.palette.base().color()); QRectF arc; if (leftToRight) { const QPointF topLeft(newOptRect.topLeft()); arc = QRectF(topLeft, QSizeF(4, 4)); arc.translate(0.5, 0.5); painter->drawArc(arc, 1440, 1440); } else { QPointF topRight(newOptRect.topRight()); topRight.rx() -= 4; arc = QRectF(topRight, QSizeF(4, 4)); arc.translate(-0.5, 0.5); painter->drawArc(arc, 0, 1440); } painter->restore(); } //END: inner top left corner //BEGIN: inner left vertical line { QPoint start; QPoint verticalGradBottom; if (leftToRight) { start = newOptRect.topLeft(); verticalGradBottom = newOptRect.topLeft(); } else { start = newOptRect.topRight(); verticalGradBottom = newOptRect.topRight(); } start.ry() += 3; verticalGradBottom.ry() += newOptRect.height() - 3; QLinearGradient gradient(start, verticalGradBottom); gradient.setColorAt(0, option.palette.base().color()); gradient.setColorAt(1, Qt::transparent); painter->fillRect(QRect(start, QSize(1, newOptRect.height() - 3)), gradient); } //END: inner left vertical line //BEGIN: inner horizontal line { QPoint start; QPoint horizontalGradTop; if (leftToRight) { start = newOptRect.topLeft(); horizontalGradTop = newOptRect.topLeft(); start.rx() += 3; horizontalGradTop.rx() += newOptRect.width() - 3; } else { start = newOptRect.topRight(); horizontalGradTop = newOptRect.topRight(); start.rx() -= 3; horizontalGradTop.rx() -= newOptRect.width() - 3; } QLinearGradient gradient(start, horizontalGradTop); gradient.setColorAt(0, option.palette.base().color()); gradient.setColorAt(1, Qt::transparent); QSize rectSize; if (leftToRight) { rectSize = QSize(newOptRect.width() - 3, 1); } else { rectSize = QSize(-newOptRect.width() + 3, 1); } painter->fillRect(QRect(start, rectSize), gradient); } //END: inner horizontal line } QColor outlineColor = option.palette.text().color(); outlineColor.setAlphaF(0.35); //BEGIN: top left corner { painter->save(); painter->setPen(outlineColor); QRectF arc; if (leftToRight) { const QPointF topLeft(optRect.topLeft()); arc = QRectF(topLeft, QSizeF(4, 4)); arc.translate(0.5, 0.5); painter->drawArc(arc, 1440, 1440); } else { QPointF topRight(optRect.topRight()); topRight.rx() -= 4; arc = QRectF(topRight, QSizeF(4, 4)); arc.translate(-0.5, 0.5); painter->drawArc(arc, 0, 1440); } painter->restore(); } //END: top left corner //BEGIN: left vertical line { QPoint start; QPoint verticalGradBottom; if (leftToRight) { start = optRect.topLeft(); verticalGradBottom = optRect.topLeft(); } else { start = optRect.topRight(); verticalGradBottom = optRect.topRight(); } start.ry() += 3; verticalGradBottom.ry() += optRect.height() - 3; QLinearGradient gradient(start, verticalGradBottom); gradient.setColorAt(0, outlineColor); gradient.setColorAt(1, option.palette.base().color()); painter->fillRect(QRect(start, QSize(1, optRect.height() - 3)), gradient); } //END: left vertical line //BEGIN: horizontal line { QPoint start; QPoint horizontalGradTop; if (leftToRight) { start = optRect.topLeft(); horizontalGradTop = optRect.topLeft(); start.rx() += 3; horizontalGradTop.rx() += optRect.width() - 3; } else { start = optRect.topRight(); horizontalGradTop = optRect.topRight(); start.rx() -= 3; horizontalGradTop.rx() -= optRect.width() - 3; } QLinearGradient gradient(start, horizontalGradTop); gradient.setColorAt(0, outlineColor); gradient.setColorAt(1, option.palette.base().color()); QSize rectSize; if (leftToRight) { rectSize = QSize(optRect.width() - 3, 1); } else { rectSize = QSize(-optRect.width() + 3, 1); } painter->fillRect(QRect(start, rectSize), gradient); } //END: horizontal line //BEGIN: draw text { const QString category = index.model()->data(index, Qt::DisplayRole).toString(); // KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); QRect textRect = QRect(option.rect.topLeft(), QSize(option.rect.width() - 2 - 3 - 3, height)); textRect.setTop(textRect.top() + 2 + 3 /* corner */); textRect.setLeft(textRect.left() + 2 + 3 /* corner */ + 3 /* a bit of margin */); painter->save(); painter->setFont(font); QColor penColor(option.palette.text().color()); penColor.setAlphaF(0.6); painter->setPen(penColor); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignTop, category); painter->restore(); } //END: draw text } int KateCategoryDrawer::categoryHeight(const QModelIndex &index, const QStyleOption &option) const { Q_UNUSED(index); Q_UNUSED(option); QFont font(QApplication::font()); font.setBold(true); const QFontMetrics fontMetrics = QFontMetrics(font); return fontMetrics.height() + 2 + 12 /* vertical spacing */; } int KateCategoryDrawer::leftMargin() const { return 7; } int KateCategoryDrawer::rightMargin() const { return 7; } diff --git a/src/schema/katecolortreewidget.cpp b/src/schema/katecolortreewidget.cpp index c41d594c..b0fa8baf 100644 --- a/src/schema/katecolortreewidget.cpp +++ b/src/schema/katecolortreewidget.cpp @@ -1,386 +1,386 @@ /* This file is part of the KDE libraries Copyright (C) 2012 Dominik Haumann * * 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 "katecolortreewidget.h" #include "katecategorydrawer.h" #include #include #include #include #include #include "katepartdebug.h" #include #include #include #include #include //BEGIN KateColorTreeItem class KateColorTreeItem : public QTreeWidgetItem { public: - KateColorTreeItem(const KateColorItem &colorItem, QTreeWidgetItem *parent = 0) + KateColorTreeItem(const KateColorItem &colorItem, QTreeWidgetItem *parent = nullptr) : QTreeWidgetItem(parent) , m_colorItem(colorItem) { setText(0, m_colorItem.name); if (!colorItem.whatsThis.isEmpty()) { setData(1, Qt::WhatsThisRole, colorItem.whatsThis); } if (!colorItem.useDefault) { setData(2, Qt::ToolTipRole, i18n("Use default color from the KDE color scheme")); } } QColor color() const { return m_colorItem.color; } void setColor(const QColor &c) { m_colorItem.color = c; } QColor defaultColor() const { return m_colorItem.defaultColor; } bool useDefaultColor() const { return m_colorItem.useDefault; } void setUseDefaultColor(bool useDefault) { m_colorItem.useDefault = useDefault; QString tooltip = useDefault ? QString() : i18n("Use default color from the KDE color scheme"); setData(2, Qt::ToolTipRole, tooltip); } QString key() { return m_colorItem.key; } KateColorItem colorItem() const { return m_colorItem; } private: KateColorItem m_colorItem; }; //END KateColorTreeItem //BEGIN KateColorTreeDelegate class KateColorTreeDelegate : public QStyledItemDelegate { public: KateColorTreeDelegate(KateColorTreeWidget *widget) : QStyledItemDelegate(widget) , m_tree(widget) { } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE { QSize sh = QStyledItemDelegate::sizeHint(option, index); if (!index.parent().isValid()) { sh.rheight() += 2 * m_categoryDrawer.leftMargin(); } else { sh.rheight() += m_categoryDrawer.leftMargin(); } if (index.column() == 0) { sh.rwidth() += m_categoryDrawer.leftMargin(); } else if (index.column() == 1) { sh.rwidth() = 150; } else { sh.rwidth() += m_categoryDrawer.leftMargin(); } return sh; } QRect fullCategoryRect(const QStyleOptionViewItem &option, const QModelIndex &index) const { QModelIndex i = index; if (i.parent().isValid()) { i = i.parent(); } QTreeWidgetItem *item = m_tree->itemFromIndex(i); QRect r = m_tree->visualItemRect(item); // adapt width r.setLeft(m_categoryDrawer.leftMargin()); r.setWidth(m_tree->viewport()->width() - m_categoryDrawer.leftMargin() - m_categoryDrawer.rightMargin()); // adapt height if (item->isExpanded() && item->childCount() > 0) { const int childCount = item->childCount(); const int h = sizeHint(option, index.child(0, 0)).height(); r.setHeight(r.height() + childCount * h); } r.setTop(r.top() + m_categoryDrawer.leftMargin()); return r; } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE { Q_ASSERT(index.isValid()); Q_ASSERT(index.column() >= 0 && index.column() <= 2); //BEGIN: draw toplevel items if (!index.parent().isValid()) { QStyleOptionViewItem opt(option); const QRegion cl = painter->clipRegion(); painter->setClipRect(opt.rect); opt.rect = fullCategoryRect(option, index); m_categoryDrawer.drawCategory(index, 0, opt, painter); painter->setClipRegion(cl); return; } //END: draw toplevel items //BEGIN: draw background of category for all other items { QStyleOptionViewItem opt(option); opt.rect = fullCategoryRect(option, index); const QRegion cl = painter->clipRegion(); QRect cr = option.rect; if (index.column() == 0) { if (m_tree->layoutDirection() == Qt::LeftToRight) { cr.setLeft(5); } else { cr.setRight(opt.rect.right()); } } painter->setClipRect(cr); m_categoryDrawer.drawCategory(index, 0, opt, painter); painter->setClipRegion(cl); painter->setRenderHint(QPainter::Antialiasing, false); } //END: draw background of category for all other items // paint the text QStyledItemDelegate::paint(painter, option, index); if (index.column() == 0) { return; } painter->setClipRect(option.rect); KateColorTreeItem *item = dynamic_cast(m_tree->itemFromIndex(index)); //BEGIN: draw color button if (index.column() == 1) { QColor color = item->useDefaultColor() ? item->defaultColor() : item->color(); QStyleOptionButton opt; opt.rect = option.rect; opt.palette = m_tree->palette(); m_tree->style()->drawControl(QStyle::CE_PushButton, &opt, painter, m_tree); opt.rect = m_tree->style()->subElementRect(QStyle::SE_PushButtonContents, &opt, m_tree); opt.rect.adjust(1, 1, -1, -1); painter->fillRect(opt.rect, color); - qDrawShadePanel(painter, opt.rect, opt.palette, true, 1, NULL); + qDrawShadePanel(painter, opt.rect, opt.palette, true, 1, nullptr); } //END: draw color button //BEGIN: draw reset icon if (index.column() == 2 && !item->useDefaultColor()) { // get right pixmap const bool enabled = (option.state & QStyle::State_MouseOver || option.state & QStyle::State_HasFocus); const QPixmap p = QIcon::fromTheme(QStringLiteral("edit-undo")).pixmap(16, 16, enabled ? QIcon::Normal : QIcon::Disabled); // compute rect with scaled sizes const QRect rect(option.rect.left() + 10, option.rect.top() + (option.rect.height() - p.height() / p.devicePixelRatio() + 1) / 2, p.width() / p.devicePixelRatio(), p.height() / p.devicePixelRatio()); painter->drawPixmap(rect, p); } //END: draw reset icon } private: KateColorTreeWidget *m_tree; KateCategoryDrawer m_categoryDrawer; }; //END KateColorTreeDelegate KateColorTreeWidget::KateColorTreeWidget(QWidget *parent) : QTreeWidget(parent) { setItemDelegate(new KateColorTreeDelegate(this)); QStringList headers; headers << QString() // i18nc("@title:column the color name", "Color Role") << QString() // i18nc("@title:column a color button", "Color") << QString();// i18nc("@title:column use default color", "Reset") setHeaderLabels(headers); setHeaderHidden(true); setRootIsDecorated(false); setIndentation(25); } bool KateColorTreeWidget::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) { // accept edit only for color buttons in column 1 and reset in column 2 if (!index.parent().isValid() || index.column() < 1) { return QTreeWidget::edit(index, trigger, event); } bool accept = false; if (event && event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); accept = (ke->key() == Qt::Key_Space); // allow Space to edit } switch (trigger) { case QAbstractItemView::DoubleClicked: case QAbstractItemView::SelectedClicked: case QAbstractItemView::EditKeyPressed: // = F2 accept = true; break; default: break; } if (accept) { KateColorTreeItem *item = dynamic_cast(itemFromIndex(index)); const QColor color = item->useDefaultColor() ? item->defaultColor() : item->color(); if (index.column() == 1) { const QColor selectedColor = QColorDialog::getColor(color, this); if (selectedColor.isValid()) { item->setUseDefaultColor(false); item->setColor(selectedColor); viewport()->update(); emit changed(); } } else if (index.column() == 2 && !item->useDefaultColor()) { item->setUseDefaultColor(true); viewport()->update(); emit changed(); } return false; } return QTreeWidget::edit(index, trigger, event); } void KateColorTreeWidget::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const { Q_UNUSED(painter) Q_UNUSED(rect) Q_UNUSED(index) } void KateColorTreeWidget::selectDefaults() { bool somethingChanged = false; // use default colors for all selected items for (int a = 0; a < topLevelItemCount(); ++a) { QTreeWidgetItem *top = topLevelItem(a); for (int b = 0; b < top->childCount(); ++b) { KateColorTreeItem *it = dynamic_cast(top->child(b)); Q_ASSERT(it); if (!it->useDefaultColor()) { it->setUseDefaultColor(true); somethingChanged = true; } } } if (somethingChanged) { viewport()->update(); emit changed(); } } void KateColorTreeWidget::addColorItem(const KateColorItem &colorItem) { - QTreeWidgetItem *categoryItem = 0; + QTreeWidgetItem *categoryItem = nullptr; for (int i = 0; i < topLevelItemCount(); ++i) { if (topLevelItem(i)->text(0) == colorItem.category) { categoryItem = topLevelItem(i); break; } } if (!categoryItem) { categoryItem = new QTreeWidgetItem(); categoryItem->setText(0, colorItem.category); addTopLevelItem(categoryItem); expandItem(categoryItem); } new KateColorTreeItem(colorItem, categoryItem); resizeColumnToContents(0); } void KateColorTreeWidget::addColorItems(const QVector &colorItems) { foreach (const KateColorItem &item, colorItems) { addColorItem(item); } } QVector KateColorTreeWidget::colorItems() const { QVector items; for (int a = 0; a < topLevelItemCount(); ++a) { QTreeWidgetItem *top = topLevelItem(a); for (int b = 0; b < top->childCount(); ++b) { KateColorTreeItem *item = dynamic_cast(top->child(b)); Q_ASSERT(item); items.append(item->colorItem()); } } return items; } QColor KateColorTreeWidget::findColor(const QString &key) const { for (int a = 0; a < topLevelItemCount(); ++a) { QTreeWidgetItem *top = topLevelItem(a); for (int b = 0; b < top->childCount(); ++b) { KateColorTreeItem *item = dynamic_cast(top->child(b)); if (item->key() == key) { if (item->useDefaultColor()) { return item->defaultColor(); } else { return item->color(); } } } } return QColor(); } diff --git a/src/schema/katecolortreewidget.h b/src/schema/katecolortreewidget.h index 636ad62b..c1e32791 100644 --- a/src/schema/katecolortreewidget.h +++ b/src/schema/katecolortreewidget.h @@ -1,71 +1,71 @@ /* This file is part of the KDE libraries Copyright (C) 2012 Dominik Haumann * * 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_COLOR_TREE_WIDGET_H #define KATE_COLOR_TREE_WIDGET_H #include class KateColorItem { public: KateColorItem() : useDefault(true) { } QString name; // translated name QString category; // translated category for tree view hierarchy QString whatsThis; // what's this info QString key; // untranslated id, used as key to save/load from KConfig QColor color; // user visible color QColor defaultColor; // used when "Default" is clicked bool useDefault; // flag whether to use the default color }; class KateColorTreeWidget : public QTreeWidget { Q_OBJECT friend class KateColorTreeItem; friend class KateColorTreeDelegate; public: - explicit KateColorTreeWidget(QWidget *parent = 0); + explicit KateColorTreeWidget(QWidget *parent = nullptr); public: void addColorItem(const KateColorItem &colorItem); void addColorItems(const QVector &colorItems); QVector colorItems() const; QColor findColor(const QString &key) const; public Q_SLOTS: void selectDefaults(); Q_SIGNALS: void changed(); protected: bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) Q_DECL_OVERRIDE; void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const Q_DECL_OVERRIDE; }; #endif diff --git a/src/schema/kateschema.cpp b/src/schema/kateschema.cpp index 326ab010..3909fb11 100644 --- a/src/schema/kateschema.cpp +++ b/src/schema/kateschema.cpp @@ -1,144 +1,144 @@ /* This file is part of the KDE libraries Copyright (C) 2007, 2008 Matthew Woehlke Copyright (C) 2001-2003 Christoph Cullmann Copyright (C) 2002, 2003 Anders Lund * * 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. */ //BEGIN Includes #include "kateschema.h" #include "kateconfig.h" #include "kateglobal.h" #include "kateview.h" #include "katerenderer.h" #include "katepartdebug.h" #include //END //BEGIN KateSchemaManager KateSchemaManager::KateSchemaManager() : m_config(KTextEditor::EditorPrivate::unitTestMode() ? QString() : QStringLiteral("kateschemarc") , KTextEditor::EditorPrivate::unitTestMode() ? KConfig::SimpleConfig : KConfig::NoGlobals) // skip config for unit tests! { } KConfigGroup KateSchemaManager::schema(const QString &name) { return m_config.group(name); } KateSchema KateSchemaManager::schemaData(const QString &name) { KConfigGroup cg(schema(name)); KateSchema schema; schema.rawName = name; schema.shippedDefaultSchema = cg.readEntry("ShippedDefaultSchema", 0); return schema; } static bool schemasCompare(const KateSchema &s1, const KateSchema &s2) { if (s1.shippedDefaultSchema > s2.shippedDefaultSchema) { return true; } return s1.translatedName().localeAwareCompare(s1.translatedName()) < 0; } QList KateSchemaManager::list() { QList schemas; Q_FOREACH (QString s, m_config.groupList()) { schemas.append(schemaData(s)); } // sort: prio given by default schema and name qSort(schemas.begin(), schemas.end(), schemasCompare); return schemas; } //END //BEGIN SCHEMA ACTION -- the 'View->Schema' menu action void KateViewSchemaAction::init() { - m_group = 0; - m_view = 0; + m_group = nullptr; + m_view = nullptr; last = 0; connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); } void KateViewSchemaAction::updateMenu(KTextEditor::ViewPrivate *view) { m_view = view; } void KateViewSchemaAction::slotAboutToShow() { KTextEditor::ViewPrivate *view = m_view; QList schemas = KTextEditor::EditorPrivate::self()->schemaManager()->list(); if (!m_group) { m_group = new QActionGroup(menu()); m_group->setExclusive(true); } for (int z = 0; z < schemas.count(); z++) { QString hlName = schemas[z].translatedName(); if (!names.contains(hlName)) { names << hlName; QAction *a = menu()->addAction(hlName, this, SLOT(setSchema())); a->setData(schemas[z].rawName); a->setCheckable(true); a->setActionGroup(m_group); } } if (!view) { return; } QString id = view->renderer()->config()->schema(); foreach (QAction *a, menu()->actions()) { a->setChecked(a->data().toString() == id); } } void KateViewSchemaAction::setSchema() { QAction *action = qobject_cast(sender()); if (!action) { return; } QString mode = action->data().toString(); KTextEditor::ViewPrivate *view = m_view; if (view) { view->renderer()->config()->setSchema(mode); } } //END SCHEMA ACTION diff --git a/src/schema/kateschemaconfig.cpp b/src/schema/kateschemaconfig.cpp index 3da5bf31..0b245441 100644 --- a/src/schema/kateschemaconfig.cpp +++ b/src/schema/kateschemaconfig.cpp @@ -1,1380 +1,1380 @@ /* This file is part of the KDE libraries Copyright (C) 2007, 2008 Matthew Woehlke Copyright (C) 2001-2003 Christoph Cullmann Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2012 Dominik Haumann * * 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. */ //BEGIN Includes #include "kateschemaconfig.h" #include "katedocument.h" #include "kateschema.h" #include "kateconfig.h" #include "kateglobal.h" #include "kateview.h" #include "katerenderer.h" #include "katestyletreewidget.h" #include "katecolortreewidget.h" #include "katepartdebug.h" #include "katedefaultcolors.h" #include "ui_howtoimportschema.h" #include #include #include #include #include #include #include #include #include //END //BEGIN KateSchemaConfigColorTab -- 'Colors' tab KateSchemaConfigColorTab::KateSchemaConfigColorTab() { QGridLayout *l = new QGridLayout(this); setLayout(l); ui = new KateColorTreeWidget(this); QPushButton *btnUseColorScheme = new QPushButton(i18n("Use KDE Color Scheme"), this); l->addWidget(ui, 0, 0, 1, 2); l->addWidget(btnUseColorScheme, 1, 1); l->setColumnStretch(0, 1); l->setColumnStretch(1, 0); connect(btnUseColorScheme, SIGNAL(clicked()), ui, SLOT(selectDefaults())); connect(ui, SIGNAL(changed()), SIGNAL(changed())); } KateSchemaConfigColorTab::~KateSchemaConfigColorTab() { } QVector KateSchemaConfigColorTab::colorItemList() const { QVector items; // use global color instance, creation is expensive! const KateDefaultColors &colors(KTextEditor::EditorPrivate::self()->defaultColors()); // // editor background colors // KateColorItem ci; ci.category = i18n("Editor Background Colors"); ci.name = i18n("Text Area"); ci.key = QStringLiteral("Color Background"); ci.whatsThis = i18n("

Sets the background color of the editing area.

"); ci.defaultColor = colors.color(Kate::Background); items.append(ci); ci.name = i18n("Selected Text"); ci.key = QStringLiteral("Color Selection"); ci.whatsThis = i18n("

Sets the background color of the selection.

To set the text color for selected text, use the "Configure Highlighting" dialog.

"); ci.defaultColor = colors.color(Kate::SelectionBackground); items.append(ci); ci.name = i18n("Current Line"); ci.key = QStringLiteral("Color Highlighted Line"); ci.whatsThis = i18n("

Sets the background color of the currently active line, which means the line where your cursor is positioned.

"); ci.defaultColor = colors.color(Kate::HighlightedLineBackground); items.append(ci); ci.name = i18n("Search Highlight"); ci.key = QStringLiteral("Color Search Highlight"); ci.whatsThis = i18n("

Sets the background color of search results.

"); ci.defaultColor = colors.color(Kate::SearchHighlight); items.append(ci); ci.name = i18n("Replace Highlight"); ci.key = QStringLiteral("Color Replace Highlight"); ci.whatsThis = i18n("

Sets the background color of replaced text.

"); ci.defaultColor = colors.color(Kate::ReplaceHighlight); items.append(ci); // // icon border // ci.category = i18n("Icon Border"); ci.name = i18n("Background Area"); ci.key = QStringLiteral("Color Icon Bar"); ci.whatsThis = i18n("

Sets the background color of the icon border.

"); ci.defaultColor = colors.color(Kate::IconBar); items.append(ci); ci.name = i18n("Line Numbers"); ci.key = QStringLiteral("Color Line Number"); ci.whatsThis = i18n("

This color will be used to draw the line numbers (if enabled).

"); ci.defaultColor = colors.color(Kate::LineNumber); items.append(ci); ci.name = i18n("Current Line Number"); ci.key = QStringLiteral("Color Current Line Number"); ci.whatsThis = i18n("

This color will be used to draw the number of the current line (if enabled).

"); ci.defaultColor = colors.color(Kate::CurrentLineNumber); items.append(ci); ci.name = i18n("Separator"); ci.key = QStringLiteral("Color Separator"); ci.whatsThis = i18n("

This color will be used to draw the line between line numbers and the icon borders, if both are enabled.

"); ci.defaultColor = colors.color(Kate::Separator); items.append(ci); ci.name = i18n("Word Wrap Marker"); ci.key = QStringLiteral("Color Word Wrap Marker"); ci.whatsThis = i18n("

Sets the color of Word Wrap-related markers:

Static Word Wrap
A vertical line which shows the column where text is going to be wrapped
Dynamic Word Wrap
An arrow shown to the left of visually-wrapped lines
"); ci.defaultColor = colors.color(Kate::WordWrapMarker); items.append(ci); ci.name = i18n("Code Folding"); ci.key = QStringLiteral("Color Code Folding"); ci.whatsThis = i18n("

Sets the color of the code folding bar.

"); ci.defaultColor = colors.color(Kate::CodeFolding); items.append(ci); ci.name = i18n("Modified Lines"); ci.key = QStringLiteral("Color Modified Lines"); ci.whatsThis = i18n("

Sets the color of the line modification marker for modified lines.

"); ci.defaultColor = colors.color(Kate::ModifiedLine); items.append(ci); ci.name = i18n("Saved Lines"); ci.key = QStringLiteral("Color Saved Lines"); ci.whatsThis = i18n("

Sets the color of the line modification marker for saved lines.

"); ci.defaultColor = colors.color(Kate::SavedLine); items.append(ci); // // text decorations // ci.category = i18n("Text Decorations"); ci.name = i18n("Spelling Mistake Line"); ci.key = QStringLiteral("Color Spelling Mistake Line"); ci.whatsThis = i18n("

Sets the color of the line that is used to indicate spelling mistakes.

"); ci.defaultColor = colors.color(Kate::SpellingMistakeLine); items.append(ci); ci.name = i18n("Tab and Space Markers"); ci.key = QStringLiteral("Color Tab Marker"); ci.whatsThis = i18n("

Sets the color of the tabulator marks.

"); ci.defaultColor = colors.color(Kate::TabMarker); items.append(ci); ci.name = i18n("Indentation Line"); ci.key = QStringLiteral("Color Indentation Line"); ci.whatsThis = i18n("

Sets the color of the vertical indentation lines.

"); ci.defaultColor = colors.color(Kate::IndentationLine); items.append(ci); ci.name = i18n("Bracket Highlight"); ci.key = QStringLiteral("Color Highlighted Bracket"); ci.whatsThis = i18n("

Sets the bracket matching color. This means, if you place the cursor e.g. at a (, the matching ) will be highlighted with this color.

"); ci.defaultColor = colors.color(Kate::HighlightedBracket); items.append(ci); // // marker colors // ci.category = i18n("Marker Colors"); const QString markerNames[Kate::LAST_MARK + 1] = { i18n("Bookmark"), i18n("Active Breakpoint"), i18n("Reached Breakpoint"), i18n("Disabled Breakpoint"), i18n("Execution"), i18n("Warning"), i18n("Error") }; ci.whatsThis = i18n("

Sets the background color of mark type.

Note: The marker color is displayed lightly because of transparency.

"); for (int i = Kate::FIRST_MARK; i <= Kate::LAST_MARK; ++i) { ci.defaultColor = colors.mark(i); ci.name = markerNames[i]; ci.key = QLatin1String("Color MarkType ") + QString::number(i + 1); items.append(ci); } // // text templates // ci.category = i18n("Text Templates & Snippets"); ci.whatsThis = QString(); // TODO: add whatsThis for text templates ci.name = i18n("Background"); ci.key = QStringLiteral("Color Template Background"); ci.defaultColor = colors.color(Kate::TemplateBackground); items.append(ci); ci.name = i18n("Editable Placeholder"); ci.key = QStringLiteral("Color Template Editable Placeholder"); ci.defaultColor = colors.color(Kate::TemplateEditablePlaceholder); items.append(ci); ci.name = i18n("Focused Editable Placeholder"); ci.key = QStringLiteral("Color Template Focused Editable Placeholder"); ci.defaultColor = colors.color(Kate::TemplateFocusedEditablePlaceholder); items.append(ci); ci.name = i18n("Not Editable Placeholder"); ci.key = QStringLiteral("Color Template Not Editable Placeholder"); ci.defaultColor = colors.color(Kate::TemplateNotEditablePlaceholder); items.append(ci); // // finally, add all elements // return items; } void KateSchemaConfigColorTab::schemaChanged(const QString &newSchema) { // save curent schema if (!m_currentSchema.isEmpty()) { if (m_schemas.contains(m_currentSchema)) { m_schemas.remove(m_currentSchema); // clear this color schema } // now add it again m_schemas[m_currentSchema] = ui->colorItems(); } if (newSchema == m_currentSchema) { return; } // switch m_currentSchema = newSchema; // If we havent this schema, read in from config file if (! m_schemas.contains(newSchema)) { KConfigGroup config = KTextEditor::EditorPrivate::self()->schemaManager()->schema(newSchema); QVector items = readConfig(config); m_schemas[ newSchema ] = items; } // first block signals otherwise setColor emits changed const bool blocked = blockSignals(true); ui->clear(); ui->addColorItems(m_schemas[m_currentSchema]); blockSignals(blocked); } QVector KateSchemaConfigColorTab::readConfig(KConfigGroup &config) { QVector items = colorItemList(); for (int i = 0; i < items.count(); ++i) { KateColorItem &item(items[i]); item.useDefault = !config.hasKey(item.key); if (item.useDefault) { item.color = item.defaultColor; } else { item.color = config.readEntry(item.key, item.defaultColor); if (!item.color.isValid()) { config.deleteEntry(item.key); item.useDefault = true; item.color = item.defaultColor; } } } return items; } void KateSchemaConfigColorTab::importSchema(KConfigGroup &config) { m_schemas[m_currentSchema] = readConfig(config); // first block signals otherwise setColor emits changed const bool blocked = blockSignals(true); ui->clear(); ui->addColorItems(m_schemas[m_currentSchema]); blockSignals(blocked); } void KateSchemaConfigColorTab::exportSchema(KConfigGroup &config) { QVector items = ui->colorItems(); foreach (const KateColorItem &item, items) { QColor c = item.useDefault ? item.defaultColor : item.color; config.writeEntry(item.key, c); } } void KateSchemaConfigColorTab::apply() { schemaChanged(m_currentSchema); QMap >::Iterator it; for (it = m_schemas.begin(); it != m_schemas.end(); ++it) { KConfigGroup config = KTextEditor::EditorPrivate::self()->schemaManager()->schema(it.key()); foreach (const KateColorItem &item, m_schemas[it.key()]) { if (item.useDefault) { config.deleteEntry(item.key); } else { config.writeEntry(item.key, item.color); } } // add dummy entry to prevent the config group from being empty. // As if the group is empty, KateSchemaManager will not find it anymore. config.writeEntry("dummy", "prevent-empty-group"); } // all colors are written, so throw away all cached schemas m_schemas.clear(); } void KateSchemaConfigColorTab::reload() { // drop all cached data m_schemas.clear(); // load from config KConfigGroup config = KTextEditor::EditorPrivate::self()->schemaManager()->schema(m_currentSchema); QVector items = readConfig(config); // first block signals otherwise setColor emits changed const bool blocked = blockSignals(true); ui->clear(); ui->addColorItems(items); blockSignals(blocked); } QColor KateSchemaConfigColorTab::backgroundColor() const { return ui->findColor(QStringLiteral("Color Background")); } QColor KateSchemaConfigColorTab::selectionColor() const { return ui->findColor(QStringLiteral("Color Selection")); } //END KateSchemaConfigColorTab //BEGIN FontConfig -- 'Fonts' tab KateSchemaConfigFontTab::KateSchemaConfigFontTab() { QGridLayout *grid = new QGridLayout(this); m_fontchooser = new KFontChooser(this, KFontChooser::NoDisplayFlags); grid->addWidget(m_fontchooser, 0, 0); } KateSchemaConfigFontTab::~KateSchemaConfigFontTab() { } void KateSchemaConfigFontTab::slotFontSelected(const QFont &font) { if (!m_currentSchema.isEmpty()) { m_fonts[m_currentSchema] = font; emit changed(); } } void KateSchemaConfigFontTab::apply() { QMap::Iterator it; for (it = m_fonts.begin(); it != m_fonts.end(); ++it) { KTextEditor::EditorPrivate::self()->schemaManager()->schema(it.key()).writeEntry("Font", it.value()); } // all fonts are written, so throw away all cached schemas m_fonts.clear(); } void KateSchemaConfigFontTab::reload() { // drop all cached data m_fonts.clear(); // now set current schema font in the font chooser schemaChanged(m_currentSchema); } void KateSchemaConfigFontTab::schemaChanged(const QString &newSchema) { m_currentSchema = newSchema; // reuse font, if cached QFont newFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); if (m_fonts.contains(m_currentSchema)) { newFont = m_fonts[m_currentSchema]; } else { newFont = KTextEditor::EditorPrivate::self()->schemaManager()->schema(m_currentSchema).readEntry("Font", newFont); } m_fontchooser->disconnect(this); m_fontchooser->setFont(newFont); connect(m_fontchooser, SIGNAL(fontSelected(QFont)), this, SLOT(slotFontSelected(QFont))); } void KateSchemaConfigFontTab::importSchema(KConfigGroup &config) { QFont f(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_fontchooser->setFont(config.readEntry("Font", f)); m_fonts[m_currentSchema] = m_fontchooser->font(); } void KateSchemaConfigFontTab::exportSchema(KConfigGroup &config) { config.writeEntry("Font", m_fontchooser->font()); } //END FontConfig //BEGIN FontColorConfig -- 'Normal Text Styles' tab KateSchemaConfigDefaultStylesTab::KateSchemaConfigDefaultStylesTab(KateSchemaConfigColorTab *colorTab) { m_colorTab = colorTab; // size management QGridLayout *grid = new QGridLayout(this); m_defaultStyles = new KateStyleTreeWidget(this); connect(m_defaultStyles, SIGNAL(changed()), this, SIGNAL(changed())); grid->addWidget(m_defaultStyles, 0, 0); m_defaultStyles->setWhatsThis(i18n( "

This list displays the default styles for the current schema and " "offers the means to edit them. The style name reflects the current " "style settings.

" "

To edit the colors, click the colored squares, or select the color " "to edit from the popup menu.

You can unset the Background and Selected " "Background colors from the popup menu when appropriate.

")); } KateSchemaConfigDefaultStylesTab::~KateSchemaConfigDefaultStylesTab() { qDeleteAll(m_defaultStyleLists); } KateAttributeList *KateSchemaConfigDefaultStylesTab::attributeList(const QString &schema) { if (!m_defaultStyleLists.contains(schema)) { KateAttributeList *list = new KateAttributeList(); KateHlManager::self()->getDefaults(schema, *list); m_defaultStyleLists.insert(schema, list); } return m_defaultStyleLists[schema]; } void KateSchemaConfigDefaultStylesTab::schemaChanged(const QString &schema) { m_currentSchema = schema; m_defaultStyles->clear(); KateAttributeList *l = attributeList(schema); updateColorPalette(l->at(0)->foreground().color()); // normal text and source code QTreeWidgetItem *parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable", "Normal Text & Source Code")); parent->setFirstColumnSpanned(true); for (int i = (int)KTextEditor::dsNormal; i <= (int)KTextEditor::dsAttribute; ++i) { m_defaultStyles->addItem(parent, KateHlManager::self()->defaultStyleName(i, true), l->at(i)); } // Number, Types & Constants parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable", "Numbers, Types & Constants")); parent->setFirstColumnSpanned(true); for (int i = (int)KTextEditor::dsDataType; i <= (int)KTextEditor::dsConstant; ++i) { m_defaultStyles->addItem(parent, KateHlManager::self()->defaultStyleName(i, true), l->at(i)); } // strings & characters parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable", "Strings & Characters")); parent->setFirstColumnSpanned(true); for (int i = (int)KTextEditor::dsChar; i <= (int)KTextEditor::dsImport; ++i) { m_defaultStyles->addItem(parent, KateHlManager::self()->defaultStyleName(i, true), l->at(i)); } // comments & documentation parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable", "Comments & Documentation")); parent->setFirstColumnSpanned(true); for (int i = (int)KTextEditor::dsComment; i <= (int)KTextEditor::dsAlert; ++i) { m_defaultStyles->addItem(parent, KateHlManager::self()->defaultStyleName(i, true), l->at(i)); } // Misc parent = new QTreeWidgetItem(m_defaultStyles, QStringList() << i18nc("@item:intable", "Miscellaneous")); parent->setFirstColumnSpanned(true); for (int i = (int)KTextEditor::dsOthers; i <= (int)KTextEditor::dsError; ++i) { m_defaultStyles->addItem(parent, KateHlManager::self()->defaultStyleName(i, true), l->at(i)); } m_defaultStyles->expandAll(); } void KateSchemaConfigDefaultStylesTab::updateColorPalette(const QColor &textColor) { QPalette p(m_defaultStyles->palette()); p.setColor(QPalette::Base, m_colorTab->backgroundColor()); p.setColor(QPalette::Highlight, m_colorTab->selectionColor()); p.setColor(QPalette::Text, textColor); m_defaultStyles->setPalette(p); } void KateSchemaConfigDefaultStylesTab::reload() { m_defaultStyles->clear(); qDeleteAll(m_defaultStyleLists); m_defaultStyleLists.clear(); schemaChanged(m_currentSchema); } void KateSchemaConfigDefaultStylesTab::apply() { QHashIterator it = m_defaultStyleLists; while (it.hasNext()) { it.next(); KateHlManager::self()->setDefaults(it.key(), *it.value()); } } void KateSchemaConfigDefaultStylesTab::exportSchema(const QString &schema, KConfig *cfg) { KateHlManager::self()->setDefaults(schema, *(m_defaultStyleLists[schema]), cfg); } void KateSchemaConfigDefaultStylesTab::importSchema(const QString &schemaName, const QString &schema, KConfig *cfg) { KateHlManager::self()->getDefaults(schemaName, *(m_defaultStyleLists[schema]), cfg); } void KateSchemaConfigDefaultStylesTab::showEvent(QShowEvent *event) { if (!event->spontaneous() && !m_currentSchema.isEmpty()) { KateAttributeList *l = attributeList(m_currentSchema); - Q_ASSERT(l != 0); + Q_ASSERT(l != nullptr); updateColorPalette(l->at(0)->foreground().color()); } QWidget::showEvent(event); } //END FontColorConfig //BEGIN KateSchemaConfigHighlightTab -- 'Highlighting Text Styles' tab KateSchemaConfigHighlightTab::KateSchemaConfigHighlightTab(KateSchemaConfigDefaultStylesTab *page, KateSchemaConfigColorTab *colorTab) { m_defaults = page; m_colorTab = colorTab; m_hl = 0; QVBoxLayout *layout = new QVBoxLayout(this); QHBoxLayout *headerLayout = new QHBoxLayout; layout->addLayout(headerLayout); QLabel *lHl = new QLabel(i18n("H&ighlight:"), this); headerLayout->addWidget(lHl); hlCombo = new KComboBox(this); hlCombo->setEditable(false); headerLayout->addWidget(hlCombo); lHl->setBuddy(hlCombo); connect(hlCombo, SIGNAL(activated(int)), this, SLOT(hlChanged(int))); QPushButton *btnexport = new QPushButton(i18n("Export..."), this); headerLayout->addWidget(btnexport); connect(btnexport, SIGNAL(clicked()), this, SLOT(exportHl())); QPushButton *btnimport = new QPushButton(i18n("Import..."), this); headerLayout->addWidget(btnimport); connect(btnimport, SIGNAL(clicked()), this, SLOT(importHl())); headerLayout->addStretch(); for (int i = 0; i < KateHlManager::self()->highlights(); i++) { if (KateHlManager::self()->hlSection(i).length() > 0) { hlCombo->addItem(KateHlManager::self()->hlSection(i) + QLatin1String("/") + KateHlManager::self()->hlNameTranslated(i)); } else { hlCombo->addItem(KateHlManager::self()->hlNameTranslated(i)); } } hlCombo->setCurrentIndex(0); // styles listview m_styles = new KateStyleTreeWidget(this, true); connect(m_styles, SIGNAL(changed()), this, SIGNAL(changed())); layout->addWidget(m_styles, 999); // get current highlighting from the host application int hl = 0; KTextEditor::ViewPrivate *kv = qobject_cast(KTextEditor::EditorPrivate::self()->application()->activeMainWindow()->activeView()); if (kv) { const QString hlName = kv->doc()->highlight()->name(); hl = KateHlManager::self()->nameFind(hlName); Q_ASSERT(hl >= 0); } hlCombo->setCurrentIndex(hl); hlChanged(hl); m_styles->setWhatsThis(i18n( "

This list displays the contexts of the current syntax highlight mode and " "offers the means to edit them. The context name reflects the current " "style settings.

To edit using the keyboard, press " "<SPACE> and choose a property from the popup menu.

" "

To edit the colors, click the colored squares, or select the color " "to edit from the popup menu.

You can unset the Background and Selected " "Background colors from the context menu when appropriate.

")); } KateSchemaConfigHighlightTab::~KateSchemaConfigHighlightTab() { } void KateSchemaConfigHighlightTab::hlChanged(int z) { m_hl = z; schemaChanged(m_schema); } bool KateSchemaConfigHighlightTab::loadAllHlsForSchema(const QString &schema) { QProgressDialog progress(i18n("Loading all highlightings for schema"), QString(), 0, KateHlManager::self()->highlights(), this); progress.setWindowModality(Qt::WindowModal); for (int i = 0; i < KateHlManager::self()->highlights(); ++i) { if (!m_hlDict[schema].contains(i)) { QList list; KateHlManager::self()->getHl(i)->getKateExtendedAttributeListCopy(schema, list); m_hlDict[schema].insert(i, list); } progress.setValue(progress.value() + 1); } progress.setValue(KateHlManager::self()->highlights()); return true; } void KateSchemaConfigHighlightTab::schemaChanged(const QString &schema) { m_schema = schema; m_styles->clear(); if (!m_hlDict.contains(m_schema)) { m_hlDict.insert(schema, QHash >()); } if (!m_hlDict[m_schema].contains(m_hl)) { QList list; KateHlManager::self()->getHl(m_hl)->getKateExtendedAttributeListCopy(m_schema, list); m_hlDict[m_schema].insert(m_hl, list); } KateAttributeList *l = m_defaults->attributeList(schema); // Set listview colors updateColorPalette(l->at(0)->foreground().color()); QHash prefixes; QList::ConstIterator it = m_hlDict[m_schema][m_hl].constBegin(); while (it != m_hlDict[m_schema][m_hl].constEnd()) { const KTextEditor::Attribute::Ptr itemData = *it; Q_ASSERT(itemData); // All stylenames have their language mode prefixed, e.g. HTML:Comment // split them and put them into nice substructures. int c = itemData->name().indexOf(QLatin1Char(':')); if (c > 0) { QString prefix = itemData->name().left(c); QString name = itemData->name().mid(c + 1); QTreeWidgetItem *parent = prefixes[prefix]; if (! parent) { parent = new QTreeWidgetItem(m_styles, QStringList() << prefix); m_styles->expandItem(parent); prefixes.insert(prefix, parent); } m_styles->addItem(parent, name, l->at(itemData->defaultStyle()), itemData); } else { m_styles->addItem(itemData->name(), l->at(itemData->defaultStyle()), itemData); } ++it; } m_styles->resizeColumns(); } void KateSchemaConfigHighlightTab::updateColorPalette(const QColor &textColor) { QPalette p(m_styles->palette()); p.setColor(QPalette::Base, m_colorTab->backgroundColor()); p.setColor(QPalette::Highlight, m_colorTab->selectionColor()); p.setColor(QPalette::Text, textColor); m_styles->setPalette(p); } void KateSchemaConfigHighlightTab::reload() { m_styles->clear(); m_hlDict.clear(); hlChanged(hlCombo->currentIndex()); } void KateSchemaConfigHighlightTab::apply() { QMutableHashIterator > > it = m_hlDict; while (it.hasNext()) { it.next(); QMutableHashIterator > it2 = it.value(); while (it2.hasNext()) { it2.next(); KateHlManager::self()->getHl(it2.key())->setKateExtendedAttributeList(it.key(), it2.value()); } } } QList KateSchemaConfigHighlightTab::hlsForSchema(const QString &schema) { return m_hlDict[schema].keys(); } void KateSchemaConfigHighlightTab::importHl(const QString &fromSchemaName, QString schema, int hl, KConfig *cfg) { QString schemaNameForLoading(fromSchemaName); QString hlName; - bool doManage = (cfg == 0); + bool doManage = (cfg == nullptr); if (schema.isEmpty()) { schema = m_schema; } if (doManage) { QString srcName = QFileDialog::getOpenFileName(this, i18n("Importing colors for single highlighting"), KateHlManager::self()->getHl(hl)->name() + QLatin1String(".katehlcolor"), QStringLiteral("%1 (*.katehlcolor)").arg(i18n("Kate color schema"))); if (srcName.isEmpty()) { return; } cfg = new KConfig(srcName, KConfig::SimpleConfig); KConfigGroup grp(cfg, "KateHLColors"); hlName = grp.readEntry("highlight", QString()); schemaNameForLoading = grp.readEntry("schema", QString()); if ((grp.readEntry("full schema", "true").toUpper() != QLatin1String("FALSE")) || hlName.isEmpty() || schemaNameForLoading.isEmpty()) { //ERROR - file format KMessageBox::information( this, i18n("File is not a single highlighting color file"), i18n("Fileformat error")); hl = -1; schemaNameForLoading = QString(); } else { hl = KateHlManager::self()->nameFind(hlName); if (hl == -1) { //hl not found KMessageBox::information( this, i18n("The selected file contains colors for a non existing highlighting:%1", hlName), i18n("Import failure")); hl = -1; schemaNameForLoading = QString(); } } } if ((hl != -1) && (!schemaNameForLoading.isEmpty())) { QList list; KateHlManager::self()->getHl(hl)->getKateExtendedAttributeListCopy(schemaNameForLoading, list, cfg); KateHlManager::self()->getHl(hl)->setKateExtendedAttributeList(schema, list); m_hlDict[schema].insert(hl, list); } if (cfg && doManage) { apply(); delete cfg; - cfg = 0; + cfg = nullptr; if ((hl != -1) && (!schemaNameForLoading.isEmpty())) { hlChanged(m_hl); KMessageBox::information( this, i18n("Colors have been imported for highlighting: %1", hlName), i18n("Import has finished")); } } } void KateSchemaConfigHighlightTab::exportHl(QString schema, int hl, KConfig *cfg) { - bool doManage = (cfg == 0); + bool doManage = (cfg == nullptr); if (schema.isEmpty()) { schema = m_schema; } if (hl == -1) { hl = m_hl; } QList items = m_hlDict[schema][hl]; if (doManage) { QString destName = QFileDialog::getSaveFileName(this, i18n("Exporting colors for single highlighting: %1", KateHlManager::self()->getHl(hl)->name()), KateHlManager::self()->getHl(hl)->name() + QLatin1String(".katehlcolor"), QStringLiteral("%1 (*.katehlcolor)").arg(i18n("Kate color schema"))); if (destName.isEmpty()) { return; } cfg = new KConfig(destName, KConfig::SimpleConfig); KConfigGroup grp(cfg, "KateHLColors"); grp.writeEntry("highlight", KateHlManager::self()->getHl(hl)->name()); grp.writeEntry("schema", schema); grp.writeEntry("full schema", "false"); } KateHlManager::self()->getHl(hl)->setKateExtendedAttributeList(schema, items, cfg, doManage); if (doManage) { cfg->sync(); delete cfg; } } void KateSchemaConfigHighlightTab::showEvent(QShowEvent *event) { if (!event->spontaneous()) { KateAttributeList *l = m_defaults->attributeList(m_schema); - Q_ASSERT(l != 0); + Q_ASSERT(l != nullptr); updateColorPalette(l->at(0)->foreground().color()); } QWidget::showEvent(event); } //END KateSchemaConfigHighlightTab //BEGIN KateSchemaConfigPage -- Main dialog page KateSchemaConfigPage::KateSchemaConfigPage(QWidget *parent) : KateConfigPage(parent), m_currentSchema(-1) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setMargin(0); // header QHBoxLayout *headerLayout = new QHBoxLayout; layout->addLayout(headerLayout); QLabel *lHl = new QLabel(i18n("&Schema:"), this); headerLayout->addWidget(lHl); schemaCombo = new KComboBox(this); schemaCombo->setEditable(false); lHl->setBuddy(schemaCombo); headerLayout->addWidget(schemaCombo); connect(schemaCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(comboBoxIndexChanged(int))); QPushButton *btnnew = new QPushButton(i18n("&New..."), this); headerLayout->addWidget(btnnew); connect(btnnew, SIGNAL(clicked()), this, SLOT(newSchema())); btndel = new QPushButton(i18n("&Delete"), this); headerLayout->addWidget(btndel); connect(btndel, SIGNAL(clicked()), this, SLOT(deleteSchema())); QPushButton *btnexport = new QPushButton(i18n("Export..."), this); headerLayout->addWidget(btnexport); connect(btnexport, SIGNAL(clicked()), this, SLOT(exportFullSchema())); QPushButton *btnimport = new QPushButton(i18n("Import..."), this); headerLayout->addWidget(btnimport); connect(btnimport, SIGNAL(clicked()), this, SLOT(importFullSchema())); headerLayout->addStretch(); // tabs QTabWidget *tabWidget = new QTabWidget(this); layout->addWidget(tabWidget); m_colorTab = new KateSchemaConfigColorTab(); tabWidget->addTab(m_colorTab, i18n("Colors")); connect(m_colorTab, SIGNAL(changed()), SLOT(slotChanged())); m_fontTab = new KateSchemaConfigFontTab(); tabWidget->addTab(m_fontTab, i18n("Font")); connect(m_fontTab, SIGNAL(changed()), SLOT(slotChanged())); m_defaultStylesTab = new KateSchemaConfigDefaultStylesTab(m_colorTab); tabWidget->addTab(m_defaultStylesTab, i18n("Default Text Styles")); connect(m_defaultStylesTab, SIGNAL(changed()), SLOT(slotChanged())); m_highlightTab = new KateSchemaConfigHighlightTab(m_defaultStylesTab, m_colorTab); tabWidget->addTab(m_highlightTab, i18n("Highlighting Text Styles")); connect(m_highlightTab, SIGNAL(changed()), SLOT(slotChanged())); QHBoxLayout *footLayout = new QHBoxLayout; layout->addLayout(footLayout); lHl = new QLabel(i18n("&Default schema for %1:", QCoreApplication::applicationName()), this); footLayout->addWidget(lHl); defaultSchemaCombo = new KComboBox(this); footLayout->addWidget(defaultSchemaCombo); defaultSchemaCombo->setEditable(false); lHl->setBuddy(defaultSchemaCombo); reload(); connect(defaultSchemaCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); } KateSchemaConfigPage::~KateSchemaConfigPage() { } void KateSchemaConfigPage::exportFullSchema() { // get save destination const QString currentSchemaName = m_currentSchema; QString destName = QFileDialog::getSaveFileName(this, i18n("Exporting color schema: %1", currentSchemaName), currentSchemaName + QLatin1String(".kateschema"), QStringLiteral("%1 (*.kateschema)").arg(i18n("Kate color schema"))); if (destName.isEmpty()) { return; } // open config file KConfig cfg(destName, KConfig::SimpleConfig); // // export editor Colors (background, ...) // KConfigGroup colorConfigGroup(&cfg, "Editor Colors"); m_colorTab->exportSchema(colorConfigGroup); // // export Default Styles // m_defaultStylesTab->exportSchema(m_currentSchema, &cfg); // // export Highlighting Text Styles // // force a load of all Highlighting Text Styles QStringList hlList; m_highlightTab->loadAllHlsForSchema(m_currentSchema); QList hls = m_highlightTab->hlsForSchema(m_currentSchema); int cnt = 0; QProgressDialog progress(i18n("Exporting schema"), QString(), 0, hls.count(), this); progress.setWindowModality(Qt::WindowModal); foreach (int hl, hls) { hlList << KateHlManager::self()->getHl(hl)->name(); m_highlightTab->exportHl(m_currentSchema, hl, &cfg); progress.setValue(++cnt); if (progress.wasCanceled()) { break; } } progress.setValue(hls.count()); KConfigGroup grp(&cfg, "KateSchema"); grp.writeEntry("full schema", "true"); grp.writeEntry("highlightings", hlList); grp.writeEntry("schema", currentSchemaName); m_fontTab->exportSchema(grp); cfg.sync(); } QString KateSchemaConfigPage::requestSchemaName(const QString &suggestedName) { QString schemaName = suggestedName; bool reask = true; do { QDialog howToImportDialog(this); Ui_KateHowToImportSchema howToImport; QVBoxLayout *mainLayout = new QVBoxLayout; howToImportDialog.setLayout(mainLayout); QWidget *w = new QWidget(&howToImportDialog); mainLayout->addWidget(w); howToImport.setupUi(w); QDialogButtonBox *buttons = new QDialogButtonBox(&howToImportDialog); mainLayout->addWidget(buttons); QPushButton *okButton = new QPushButton; okButton->setDefault(true); KGuiItem::assign(okButton, KStandardGuiItem::ok()); buttons->addButton(okButton, QDialogButtonBox::AcceptRole); connect(okButton, SIGNAL(clicked()), &howToImportDialog, SLOT(accept())); QPushButton *cancelButton = new QPushButton; KGuiItem::assign(cancelButton, KStandardGuiItem::cancel()); buttons->addButton(cancelButton, QDialogButtonBox::RejectRole); connect(cancelButton, SIGNAL(clicked()), &howToImportDialog, SLOT(reject())); // // if schema exists, prepare option to replace if (KTextEditor::EditorPrivate::self()->schemaManager()->schema(schemaName).exists()) { howToImport.radioReplaceExisting->show(); howToImport.radioReplaceExisting->setText(i18n("Replace existing schema %1", schemaName)); howToImport.radioReplaceExisting->setChecked(true); } else { howToImport.radioReplaceExisting->hide(); howToImport.newName->setText(schemaName); } // cancel pressed? if (howToImportDialog.exec() == QDialog::Rejected) { schemaName.clear(); reask = false; } // check what the user wants else { // replace existing if (howToImport.radioReplaceExisting->isChecked()) { reask = false; } // replace current else if (howToImport.radioReplaceCurrent->isChecked()) { schemaName = m_currentSchema; reask = false; } // new one, check again, whether the schema already exists else if (howToImport.radioAsNew->isChecked()) { schemaName = howToImport.newName->text(); if (KTextEditor::EditorPrivate::self()->schemaManager()->schema(schemaName).exists()) { reask = true; } else { reask = false; } } // should never happen else { reask = true; } } } while (reask); return schemaName; } void KateSchemaConfigPage::importFullSchema() { const QString srcName = QFileDialog::getOpenFileName(this, i18n("Importing Color Schema"), QString(), QStringLiteral("%1 (*.kateschema)").arg(i18n("Kate color schema"))); if (srcName.isEmpty()) { return; } // carete config + sanity check for full color schema KConfig cfg(srcName, KConfig::SimpleConfig); KConfigGroup schemaGroup(&cfg, "KateSchema"); if (schemaGroup.readEntry("full schema", "false").toUpper() != QLatin1String("TRUE")) { KMessageBox::sorry(this, i18n("The file does not contain a full color schema."), i18n("Fileformat error")); return; } // read color schema name const QStringList highlightings = schemaGroup.readEntry("highlightings", QStringList()); const QString fromSchemaName = schemaGroup.readEntry("schema", i18n("Name unspecified")); // request valid schema name const QString schemaName = requestSchemaName(fromSchemaName); if (schemaName.isEmpty()) { return; } // if the schema already exists, select it in the combo box if (schemaCombo->findData(schemaName) != -1) { schemaCombo->setCurrentIndex(schemaCombo->findData(schemaName)); } else { // it is really a new schema, easy meat :-) newSchema(schemaName); } // make sure the correct schema is activated schemaChanged(schemaName); // Finally, the correct schema is activated. // Next, start importing. // // import editor Colors (background, ...) // KConfigGroup colorConfigGroup(&cfg, "Editor Colors"); m_colorTab->importSchema(colorConfigGroup); // // import font // m_fontTab->importSchema(schemaGroup); // // import Default Styles // m_defaultStylesTab->importSchema(fromSchemaName, schemaName, &cfg); // // import all Highlighting Text Styles // // create mapping from highlighting name to internal id const int hlCount = KateHlManager::self()->highlights(); QHash nameToId; for (int i = 0; i < hlCount; ++i) { nameToId.insert(KateHlManager::self()->hlName(i), i); } // may take some time, as we have > 200 highlightings int cnt = 0; QProgressDialog progress(i18n("Importing schema"), QString(), 0, highlightings.count(), this); progress.setWindowModality(Qt::WindowModal); foreach (const QString &hl, highlightings) { if (nameToId.contains(hl)) { const int i = nameToId[hl]; m_highlightTab->importHl(fromSchemaName, schemaName, i, &cfg); } progress.setValue(++cnt); } progress.setValue(highlightings.count()); } void KateSchemaConfigPage::apply() { // remember name + index const QString schemaName = schemaCombo->itemData(schemaCombo->currentIndex()).toString(); // first apply all tabs m_colorTab->apply(); m_fontTab->apply(); m_defaultStylesTab->apply(); m_highlightTab->apply(); // just sync the config and reload KTextEditor::EditorPrivate::self()->schemaManager()->config().sync(); KTextEditor::EditorPrivate::self()->schemaManager()->config().reparseConfiguration(); // clear all attributes for (int i = 0; i < KateHlManager::self()->highlights(); ++i) { KateHlManager::self()->getHl(i)->clearAttributeArrays(); } // than reload the whole stuff KateRendererConfig::global()->setSchema(defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString()); KateRendererConfig::global()->reloadSchema(); // sync the hl config for real KateHlManager::self()->getKConfig()->sync(); // KateSchemaManager::update() sorts the schema alphabetically, hence the // schema indexes change. Thus, repopulate the schema list... refillCombos(schemaCombo->itemData(schemaCombo->currentIndex()).toString(), defaultSchemaCombo->itemData(defaultSchemaCombo->currentIndex()).toString()); schemaChanged(schemaName); } void KateSchemaConfigPage::reload() { // now reload the config from disc KTextEditor::EditorPrivate::self()->schemaManager()->config().reparseConfiguration(); // reinitialize combo boxes refillCombos(KateRendererConfig::global()->schema(), KateRendererConfig::global()->schema()); // finally, activate the current schema again schemaChanged(schemaCombo->itemData(schemaCombo->currentIndex()).toString()); // all tabs need to reload to discard all the cached data, as the index // mapping may have changed m_colorTab->reload(); m_fontTab->reload(); m_defaultStylesTab->reload(); m_highlightTab->reload(); } void KateSchemaConfigPage::refillCombos(const QString &schemaName, const QString &defaultSchemaName) { schemaCombo->blockSignals(true); defaultSchemaCombo->blockSignals(true); // reinitialize combo boxes schemaCombo->clear(); defaultSchemaCombo->clear(); QList schemaList = KTextEditor::EditorPrivate::self()->schemaManager()->list(); foreach (const KateSchema &s, schemaList) { schemaCombo->addItem(s.translatedName(), s.rawName); defaultSchemaCombo->addItem(s.translatedName(), s.rawName); } // set the correct indexes again, fallback to always existing "Normal" int schemaIndex = schemaCombo->findData(schemaName); if (schemaIndex == -1) { schemaIndex = schemaCombo->findData(QLatin1String("Normal")); } int defaultSchemaIndex = defaultSchemaCombo->findData(defaultSchemaName); if (defaultSchemaIndex == -1) { defaultSchemaIndex = defaultSchemaCombo->findData(QLatin1String("Normal")); } Q_ASSERT(schemaIndex != -1); Q_ASSERT(defaultSchemaIndex != -1); defaultSchemaCombo->setCurrentIndex(defaultSchemaIndex); schemaCombo->setCurrentIndex(schemaIndex); schemaCombo->blockSignals(false); defaultSchemaCombo->blockSignals(false); } void KateSchemaConfigPage::reset() { reload(); } void KateSchemaConfigPage::defaults() { reload(); } void KateSchemaConfigPage::deleteSchema() { const int comboIndex = schemaCombo->currentIndex(); const QString schemaNameToDelete = schemaCombo->itemData(comboIndex).toString(); if (KTextEditor::EditorPrivate::self()->schemaManager()->schemaData(schemaNameToDelete).shippedDefaultSchema) { // Default and Printing schema cannot be deleted. return; } // kill group KTextEditor::EditorPrivate::self()->schemaManager()->config().deleteGroup(schemaNameToDelete); // fallback to Default schema schemaCombo->setCurrentIndex(schemaCombo->findData(QVariant(QStringLiteral("Normal")))); if (defaultSchemaCombo->currentIndex() == defaultSchemaCombo->findData(schemaNameToDelete)) { defaultSchemaCombo->setCurrentIndex(defaultSchemaCombo->findData(QVariant(QStringLiteral("Normal")))); } // remove schema from combo box schemaCombo->removeItem(comboIndex); defaultSchemaCombo->removeItem(comboIndex); // Reload the color tab, since it uses cached schemas m_colorTab->reload(); } bool KateSchemaConfigPage::newSchema(const QString &newName) { // get sane name QString schemaName(newName); if (newName.isEmpty()) { bool ok = false; schemaName = QInputDialog::getText(this, i18n("Name for New Schema"), i18n("Name:"), QLineEdit::Normal, i18n("New Schema"), &ok); if (!ok) { return false; } } // try if schema already around if (KTextEditor::EditorPrivate::self()->schemaManager()->schema(schemaName).exists()) { KMessageBox::information(this, i18n("

The schema %1 already exists.

Please choose a different schema name.

", schemaName), i18n("New Schema")); return false; } // append items to combo boxes schemaCombo->addItem(schemaName, QVariant(schemaName)); defaultSchemaCombo->addItem(schemaName, QVariant(schemaName)); // finally, activate new schema (last item in the list) schemaCombo->setCurrentIndex(schemaCombo->count() - 1); return true; } void KateSchemaConfigPage::schemaChanged(const QString &schema) { btndel->setEnabled(!KTextEditor::EditorPrivate::self()->schemaManager()->schemaData(schema).shippedDefaultSchema); // propagate changed schema to all tabs m_colorTab->schemaChanged(schema); m_fontTab->schemaChanged(schema); m_defaultStylesTab->schemaChanged(schema); m_highlightTab->schemaChanged(schema); // save current schema index m_currentSchema = schema; } void KateSchemaConfigPage::comboBoxIndexChanged(int currentIndex) { schemaChanged(schemaCombo->itemData(currentIndex).toString()); } QString KateSchemaConfigPage::name() const { return i18n("Fonts & Colors"); } QString KateSchemaConfigPage::fullName() const { return i18n("Font & Color Schemas"); } QIcon KateSchemaConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("preferences-desktop-color")); } //END KateSchemaConfigPage diff --git a/src/schema/kateschemaconfig.h b/src/schema/kateschemaconfig.h index 0f903fe2..25e58429 100644 --- a/src/schema/kateschemaconfig.h +++ b/src/schema/kateschemaconfig.h @@ -1,216 +1,216 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2003 Christoph Cullmann Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2012 Dominik Haumann * * 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_SCHEMA_CONFIG_H__ #define __KATE_SCHEMA_CONFIG_H__ #include "katedialogs.h" #include "katecolortreewidget.h" #include "kateextendedattribute.h" #include #include class KateStyleTreeWidget; class KComboBox; class KateSchemaConfigColorTab : public QWidget { Q_OBJECT public: KateSchemaConfigColorTab(); ~KateSchemaConfigColorTab(); QColor backgroundColor() const; QColor selectionColor() const; public Q_SLOTS: void apply(); void reload(); void schemaChanged(const QString &newSchema); void importSchema(KConfigGroup &config); void exportSchema(KConfigGroup &config); Q_SIGNALS: void changed(); private: QVector colorItemList() const; QVector readConfig(KConfigGroup &config); private: // multiple shemas may be edited. Hence, we need one ColorList for each schema QMap > m_schemas; QString m_currentSchema; KateColorTreeWidget *ui; }; class KateSchemaConfigFontTab : public QWidget { Q_OBJECT public: KateSchemaConfigFontTab(); ~KateSchemaConfigFontTab(); public: void readConfig(KConfig *config); void importSchema(KConfigGroup &config); void exportSchema(KConfigGroup &config); public Q_SLOTS: void apply(); void reload(); void schemaChanged(const QString &newSchema); Q_SIGNALS: void changed(); private: class KFontChooser *m_fontchooser; QMap m_fonts; QString m_currentSchema; private Q_SLOTS: void slotFontSelected(const QFont &font); }; class KateSchemaConfigDefaultStylesTab : public QWidget { Q_OBJECT public: KateSchemaConfigDefaultStylesTab(KateSchemaConfigColorTab *colorTab); ~KateSchemaConfigDefaultStylesTab(); Q_SIGNALS: void changed(); public: void schemaChanged(const QString &schema); void reload(); void apply(); KateAttributeList *attributeList(const QString &schema); void exportSchema(const QString &schema, KConfig *cfg); void importSchema(const QString &schemaName, const QString &schema, KConfig *cfg); protected: void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; void updateColorPalette(const QColor &textColor); private: KateStyleTreeWidget *m_defaultStyles; QHash m_defaultStyleLists; KateSchemaConfigColorTab *m_colorTab; QString m_currentSchema; }; class KateSchemaConfigHighlightTab : public QWidget { Q_OBJECT public: explicit KateSchemaConfigHighlightTab(KateSchemaConfigDefaultStylesTab *page, KateSchemaConfigColorTab *colorTab); ~KateSchemaConfigHighlightTab(); void schemaChanged(const QString &schema); void reload(); void apply(); Q_SIGNALS: void changed(); protected Q_SLOTS: void hlChanged(int z); public Q_SLOTS: - void exportHl(QString schema = QString(), int hl = -1, KConfig *cfg = 0); - void importHl(const QString &fromSchemaName = QString(), QString schema = QString(), int hl = -1, KConfig *cfg = 0); + void exportHl(QString schema = QString(), int hl = -1, KConfig *cfg = nullptr); + void importHl(const QString &fromSchemaName = QString(), QString schema = QString(), int hl = -1, KConfig *cfg = nullptr); protected: void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; void updateColorPalette(const QColor &textColor); private: KateSchemaConfigDefaultStylesTab *m_defaults; KateSchemaConfigColorTab *m_colorTab; KComboBox *hlCombo; KateStyleTreeWidget *m_styles; QString m_schema; int m_hl; QHash > > m_hlDict; public: QList hlsForSchema(const QString &schema); bool loadAllHlsForSchema(const QString &schema); }; class KateSchemaConfigPage : public KateConfigPage { Q_OBJECT public: explicit KateSchemaConfigPage(QWidget *parent); virtual ~KateSchemaConfigPage(); QString name() const Q_DECL_OVERRIDE; QString fullName() const Q_DECL_OVERRIDE; QIcon icon() const Q_DECL_OVERRIDE; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; void exportFullSchema(); void importFullSchema(); private Q_SLOTS: void deleteSchema(); bool newSchema(const QString &newName = QString()); void schemaChanged(const QString &schema); void comboBoxIndexChanged(int currentIndex); private: void refillCombos(const QString &schemaName, const QString &defaultSchemaName); QString requestSchemaName(const QString &suggestedName); private: QString m_currentSchema; class QPushButton *btndel; class KComboBox *defaultSchemaCombo; class KComboBox *schemaCombo; KateSchemaConfigColorTab *m_colorTab; KateSchemaConfigFontTab *m_fontTab; KateSchemaConfigDefaultStylesTab *m_defaultStylesTab; KateSchemaConfigHighlightTab *m_highlightTab; }; #endif diff --git a/src/schema/katestyletreewidget.cpp b/src/schema/katestyletreewidget.cpp index f87b0c4f..699e6dda 100644 --- a/src/schema/katestyletreewidget.cpp +++ b/src/schema/katestyletreewidget.cpp @@ -1,739 +1,739 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2003 Christoph Cullmann Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2005-2006 Hamish Rodda Copyright (C) 2007 Mirko Stocker * * 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 "katestyletreewidget.h" #include "kateconfig.h" #include "kateextendedattribute.h" #include "katedefaultcolors.h" #include "kateglobal.h" #include #include #include #include #include #include #include #include #include //BEGIN KateStyleTreeDelegate class KateStyleTreeDelegate : public QStyledItemDelegate { public: KateStyleTreeDelegate(KateStyleTreeWidget *widget); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; private: QBrush getBrushForColorColumn(const QModelIndex &index, int column) const; KateStyleTreeWidget *m_widget; }; //END //BEGIN KateStyleTreeWidgetItem decl /* QListViewItem subclass to display/edit a style, bold/italic is check boxes, normal and selected colors are boxes, which will display a color chooser when activated. The context name for the style will be drawn using the editor default font and the chosen colors. This widget id designed to handle the default as well as the individual hl style lists. This widget is designed to work with the KateStyleTreeWidget class exclusively. Added by anders, jan 23 2002. */ class KateStyleTreeWidgetItem : public QTreeWidgetItem { public: KateStyleTreeWidgetItem(QTreeWidgetItem *parent, const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data = KTextEditor::Attribute::Ptr()); KateStyleTreeWidgetItem(QTreeWidget *parent, const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data = KTextEditor::Attribute::Ptr()); ~KateStyleTreeWidgetItem() {} enum columns { Context = 0, Bold, Italic, Underline, StrikeOut, Foreground, SelectedForeground, Background, SelectedBackground, UseDefaultStyle, NumColumns }; /* initializes the style from the default and the hldata */ void initStyle(); /* updates the hldata's style */ void updateStyle(); /* For bool fields, toggles them, for color fields, display a color chooser */ void changeProperty(int p); /** unset a color. * c is 100 (BGColor) or 101 (SelectedBGColor) for now. */ void unsetColor(int c); /* style context name */ QString contextName() const { return text(0); } /* only true for a hl mode item using its default style */ bool defStyle() const; /* true for default styles */ bool isDefault() const; /* whichever style is active (currentStyle for hl mode styles not using the default style, defaultStyle otherwise) */ KTextEditor::Attribute::Ptr style() const { return currentStyle; } QVariant data(int column, int role) const Q_DECL_OVERRIDE; KateStyleTreeWidget *treeWidget() const; private: /* private methods to change properties */ void toggleDefStyle(); void setColor(int); /* helper function to copy the default style into the KateExtendedAttribute, when a property is changed and we are using default style. */ KTextEditor::Attribute::Ptr currentStyle, // the style currently in use (was "is") defaultStyle; // default style for hl mode contexts and default styles (was "ds") KTextEditor::Attribute::Ptr actualStyle; // itemdata for hl mode contexts (was "st") }; //END //BEGIN KateStyleTreeWidget KateStyleTreeWidget::KateStyleTreeWidget(QWidget *parent, bool showUseDefaults) : QTreeWidget(parent) { setItemDelegate(new KateStyleTreeDelegate(this)); setRootIsDecorated(false); QStringList headers; headers << i18nc("@title:column Meaning of text in editor", "Context") << QString() << QString() << QString() << QString() << i18nc("@title:column Text style", "Normal") << i18nc("@title:column Text style", "Selected") << i18nc("@title:column Text style", "Background") << i18nc("@title:column Text style", "Background Selected"); if (showUseDefaults) { headers << i18n("Use Default Style"); } setHeaderLabels(headers); headerItem()->setIcon(1, QIcon::fromTheme(QStringLiteral("format-text-bold"))); headerItem()->setIcon(2, QIcon::fromTheme(QStringLiteral("format-text-italic"))); headerItem()->setIcon(3, QIcon::fromTheme(QStringLiteral("format-text-underline"))); headerItem()->setIcon(4, QIcon::fromTheme(QStringLiteral("format-text-strikethrough"))); // grap the bg color, selected color and default font const KColorScheme &colors(KTextEditor::EditorPrivate::self()->defaultColors().view()); normalcol = colors.foreground().color(); bgcol = KateRendererConfig::global()->backgroundColor(); selcol = KateRendererConfig::global()->selectionColor(); docfont = KateRendererConfig::global()->font(); QPalette pal = viewport()->palette(); pal.setColor(QPalette::Background, bgcol); viewport()->setPalette(pal); } QIcon brushIcon(const QColor &color) { QPixmap pm(16, 16); QRect all(0, 0, 15, 15); { QPainter p(&pm); p.fillRect(all, color); p.setPen(Qt::black); p.drawRect(all); } return QIcon(pm); } bool KateStyleTreeWidget::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) { if (index.column() == KateStyleTreeWidgetItem::Context) { return false; } KateStyleTreeWidgetItem *i = dynamic_cast(itemFromIndex(index)); if (!i) { return QTreeWidget::edit(index, trigger, event); } switch (trigger) { case QAbstractItemView::DoubleClicked: case QAbstractItemView::SelectedClicked: case QAbstractItemView::EditKeyPressed: i->changeProperty(index.column()); update(index); update(index.sibling(index.row(), KateStyleTreeWidgetItem::Context)); return false; default: return QTreeWidget::edit(index, trigger, event); } } void KateStyleTreeWidget::resizeColumns() { for (int i = 0; i < columnCount(); ++i) { resizeColumnToContents(i); } } void KateStyleTreeWidget::showEvent(QShowEvent *event) { QTreeWidget::showEvent(event); resizeColumns(); } void KateStyleTreeWidget::contextMenuEvent(QContextMenuEvent *event) { KateStyleTreeWidgetItem *i = dynamic_cast(itemAt(event->pos())); if (!i) { return; } QMenu m(this); KTextEditor::Attribute::Ptr currentStyle = i->style(); // the title is used, because the menu obscures the context name when // displayed on behalf of spacePressed(). QPainter p; p.setPen(Qt::black); const QIcon emptyColorIcon = brushIcon(viewport()->palette().base().color()); QIcon cl = brushIcon(i->style()->foreground().color()); QIcon scl = brushIcon(i->style()->selectedForeground().color()); QIcon bgcl = i->style()->hasProperty(QTextFormat::BackgroundBrush) ? brushIcon(i->style()->background().color()) : emptyColorIcon; QIcon sbgcl = i->style()->hasProperty(CustomProperties::SelectedBackground) ? brushIcon(i->style()->selectedBackground().color()) : emptyColorIcon; m.addSection(i->contextName()); QAction *a = m.addAction(i18n("&Bold"), this, SLOT(changeProperty())); a->setCheckable(true); a->setChecked(currentStyle->fontBold()); a->setData(KateStyleTreeWidgetItem::Bold); a = m.addAction(i18n("&Italic"), this, SLOT(changeProperty())); a->setCheckable(true); a->setChecked(currentStyle->fontItalic()); a->setData(KateStyleTreeWidgetItem::Italic); a = m.addAction(i18n("&Underline"), this, SLOT(changeProperty())); a->setCheckable(true); a->setChecked(currentStyle->fontUnderline()); a->setData(KateStyleTreeWidgetItem::Underline); a = m.addAction(i18n("S&trikeout"), this, SLOT(changeProperty())); a->setCheckable(true); a->setChecked(currentStyle->fontStrikeOut()); a->setData(KateStyleTreeWidgetItem::StrikeOut); m.addSeparator(); a = m.addAction(cl, i18n("Normal &Color..."), this, SLOT(changeProperty())); a->setData(KateStyleTreeWidgetItem::Foreground); a = m.addAction(scl, i18n("&Selected Color..."), this, SLOT(changeProperty())); a->setData(KateStyleTreeWidgetItem::SelectedForeground); a = m.addAction(bgcl, i18n("&Background Color..."), this, SLOT(changeProperty())); a->setData(KateStyleTreeWidgetItem::Background); a = m.addAction(sbgcl, i18n("S&elected Background Color..."), this, SLOT(changeProperty())); a->setData(KateStyleTreeWidgetItem::SelectedBackground); // defaulters m.addSeparator(); a = m.addAction(emptyColorIcon, i18n("Unset Normal Color"), this, SLOT(unsetColor())); a->setData(1); a = m.addAction(emptyColorIcon, i18n("Unset Selected Color"), this, SLOT(unsetColor())); a->setData(2); // unsetters KTextEditor::Attribute::Ptr style = i->style(); if (style->hasProperty(QTextFormat::BackgroundBrush)) { a = m.addAction(emptyColorIcon, i18n("Unset Background Color"), this, SLOT(unsetColor())); a->setData(3); } if (style->hasProperty(CustomProperties::SelectedBackground)) { a = m.addAction(emptyColorIcon, i18n("Unset Selected Background Color"), this, SLOT(unsetColor())); a->setData(4); } if (! i->isDefault() && ! i->defStyle()) { m.addSeparator(); a = m.addAction(i18n("Use &Default Style"), this, SLOT(changeProperty())); a->setCheckable(true); a->setChecked(i->defStyle()); a->setData(KateStyleTreeWidgetItem::UseDefaultStyle); } m.exec(event->globalPos()); } void KateStyleTreeWidget::changeProperty() { static_cast(currentItem())->changeProperty(static_cast(sender())->data().toInt()); } void KateStyleTreeWidget::unsetColor() { static_cast(currentItem())->unsetColor(static_cast(sender())->data().toInt()); } void KateStyleTreeWidget::updateGroupHeadings() { for (int i = 0; i < topLevelItemCount(); i++) { QTreeWidgetItem *currentTopLevelItem = topLevelItem(i); QTreeWidgetItem *firstChild = currentTopLevelItem->child(0); if (firstChild) { QColor foregroundColor = firstChild->data(KateStyleTreeWidgetItem::Foreground, Qt::DisplayRole).value(); QColor backgroundColor = firstChild->data(KateStyleTreeWidgetItem::Background, Qt::DisplayRole).value(); currentTopLevelItem->setForeground(KateStyleTreeWidgetItem::Context, foregroundColor); if (backgroundColor.isValid()) { currentTopLevelItem->setBackground(KateStyleTreeWidgetItem::Context, backgroundColor); } } } } void KateStyleTreeWidget::emitChanged() { updateGroupHeadings(); emit changed(); } void KateStyleTreeWidget::addItem(const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data) { new KateStyleTreeWidgetItem(this, styleName, defaultstyle, data); } void KateStyleTreeWidget::addItem(QTreeWidgetItem *parent, const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data) { new KateStyleTreeWidgetItem(parent, styleName, defaultstyle, data); updateGroupHeadings(); } //END //BEGIN KateStyleTreeWidgetItem KateStyleTreeDelegate::KateStyleTreeDelegate(KateStyleTreeWidget *widget) : m_widget(widget) { } QBrush KateStyleTreeDelegate::getBrushForColorColumn(const QModelIndex &index, int column) const { QModelIndex colorIndex = index.sibling(index.row(), column); QVariant displayData = colorIndex.model()->data(colorIndex); return displayData.value(); } void KateStyleTreeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { static QSet columns; if (columns.isEmpty()) { columns << KateStyleTreeWidgetItem::Foreground << KateStyleTreeWidgetItem::SelectedForeground << KateStyleTreeWidgetItem::Background << KateStyleTreeWidgetItem::SelectedBackground; } if (index.column() == KateStyleTreeWidgetItem::Context) { QStyleOptionViewItem styleContextItem(option); QBrush brush = getBrushForColorColumn(index, KateStyleTreeWidgetItem::SelectedBackground); if (brush != QBrush()) { styleContextItem.palette.setBrush(QPalette::Highlight, brush); } brush = getBrushForColorColumn(index, KateStyleTreeWidgetItem::SelectedForeground); if (brush != QBrush()) { styleContextItem.palette.setBrush(QPalette::HighlightedText, brush); } return QStyledItemDelegate::paint(painter, styleContextItem, index); } QStyledItemDelegate::paint(painter, option, index); if (!columns.contains(index.column())) { return; } QVariant displayData = index.model()->data(index); if (displayData.type() != QVariant::Brush) { return; } QBrush brush = displayData.value(); QStyleOptionButton opt; opt.rect = option.rect; opt.palette = m_widget->palette(); bool set = brush != QBrush(); if (!set) { opt.text = i18nc("No text or background color set", "None set"); brush = Qt::white; } m_widget->style()->drawControl(QStyle::CE_PushButton, &opt, painter, m_widget); if (set) { painter->fillRect(m_widget->style()->subElementRect(QStyle::SE_PushButtonContents, &opt, m_widget), brush); } } KateStyleTreeWidgetItem::KateStyleTreeWidgetItem(QTreeWidgetItem *parent, const QString &stylename, KTextEditor::Attribute::Ptr defaultAttribute, KTextEditor::Attribute::Ptr actualAttribute) : QTreeWidgetItem(parent), - currentStyle(0L), + currentStyle(nullptr), defaultStyle(defaultAttribute), actualStyle(actualAttribute) { initStyle(); setText(0, stylename); } KateStyleTreeWidgetItem::KateStyleTreeWidgetItem(QTreeWidget *parent, const QString &stylename, KTextEditor::Attribute::Ptr defaultAttribute, KTextEditor::Attribute::Ptr actualAttribute) : QTreeWidgetItem(parent), - currentStyle(0L), + currentStyle(nullptr), defaultStyle(defaultAttribute), actualStyle(actualAttribute) { initStyle(); setText(0, stylename); } void KateStyleTreeWidgetItem::initStyle() { if (!actualStyle) { currentStyle = defaultStyle; } else { currentStyle = new KTextEditor::Attribute(*defaultStyle); if (actualStyle->hasAnyProperty()) { *currentStyle += *actualStyle; } } setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); } static Qt::CheckState toCheckState(bool b) { return b ? Qt::Checked : Qt::Unchecked; } QVariant KateStyleTreeWidgetItem::data(int column, int role) const { if (column == Context) { switch (role) { case Qt::ForegroundRole: if (style()->hasProperty(QTextFormat::ForegroundBrush)) { return style()->foreground().color(); } break; case Qt::BackgroundRole: if (style()->hasProperty(QTextFormat::BackgroundBrush)) { return style()->background().color(); } break; case Qt::FontRole: return style()->font(); break; } } if (role == Qt::CheckStateRole) { switch (column) { case Bold: return toCheckState(style()->fontBold()); case Italic: return toCheckState(style()->fontItalic()); case Underline: return toCheckState(style()->fontUnderline()); case StrikeOut: return toCheckState(style()->fontStrikeOut()); case UseDefaultStyle: /* can't compare all attributes, currentStyle has always more than defaultStyle (e.g. the item's name), * so we just compare the important ones:*/ return toCheckState( currentStyle->foreground() == defaultStyle->foreground() && currentStyle->background() == defaultStyle->background() && currentStyle->selectedForeground() == defaultStyle->selectedForeground() && currentStyle->selectedBackground() == defaultStyle->selectedBackground() && currentStyle->fontBold() == defaultStyle->fontBold() && currentStyle->fontItalic() == defaultStyle->fontItalic() && currentStyle->fontUnderline() == defaultStyle->fontUnderline() && currentStyle->fontStrikeOut() == defaultStyle->fontStrikeOut()); } } if (role == Qt::DisplayRole) { switch (column) { case Foreground: return style()->foreground(); case SelectedForeground: return style()->selectedForeground(); case Background: return style()->background(); case SelectedBackground: return style()->selectedBackground(); } } return QTreeWidgetItem::data(column, role); } void KateStyleTreeWidgetItem::updateStyle() { // nothing there, not update it, will crash if (!actualStyle) { return; } if (currentStyle->hasProperty(QTextFormat::FontWeight)) { if (currentStyle->fontWeight() != actualStyle->fontWeight()) { actualStyle->setFontWeight(currentStyle->fontWeight()); } } else { actualStyle->clearProperty(QTextFormat::FontWeight); } if (currentStyle->hasProperty(QTextFormat::FontItalic)) { if (currentStyle->fontItalic() != actualStyle->fontItalic()) { actualStyle->setFontItalic(currentStyle->fontItalic()); } } else { actualStyle->clearProperty(QTextFormat::FontItalic); } if (currentStyle->hasProperty(QTextFormat::FontStrikeOut)) { if (currentStyle->fontStrikeOut() != actualStyle->fontStrikeOut()) { actualStyle->setFontStrikeOut(currentStyle->fontStrikeOut()); } } else { actualStyle->clearProperty(QTextFormat::FontStrikeOut); } if (currentStyle->hasProperty(QTextFormat::FontUnderline)) { if (currentStyle->fontUnderline() != actualStyle->fontUnderline()) { actualStyle->setFontUnderline(currentStyle->fontUnderline()); } } else { actualStyle->clearProperty(QTextFormat::FontUnderline); } if (currentStyle->hasProperty(CustomProperties::Outline)) { if (currentStyle->outline() != actualStyle->outline()) { actualStyle->setOutline(currentStyle->outline()); } } else { actualStyle->clearProperty(CustomProperties::Outline); } if (currentStyle->hasProperty(QTextFormat::ForegroundBrush)) { if (currentStyle->foreground() != actualStyle->foreground()) { actualStyle->setForeground(currentStyle->foreground()); } } else { actualStyle->clearProperty(QTextFormat::ForegroundBrush); } if (currentStyle->hasProperty(CustomProperties::SelectedForeground)) { if (currentStyle->selectedForeground() != actualStyle->selectedForeground()) { actualStyle->setSelectedForeground(currentStyle->selectedForeground()); } } else { actualStyle->clearProperty(CustomProperties::SelectedForeground); } if (currentStyle->hasProperty(QTextFormat::BackgroundBrush)) { if (currentStyle->background() != actualStyle->background()) { actualStyle->setBackground(currentStyle->background()); } } else { actualStyle->clearProperty(QTextFormat::BackgroundBrush); } if (currentStyle->hasProperty(CustomProperties::SelectedBackground)) { if (currentStyle->selectedBackground() != actualStyle->selectedBackground()) { actualStyle->setSelectedBackground(currentStyle->selectedBackground()); } } else { actualStyle->clearProperty(CustomProperties::SelectedBackground); } } /* only true for a hl mode item using its default style */ bool KateStyleTreeWidgetItem::defStyle() const { return actualStyle && actualStyle->properties() != defaultStyle->properties(); } /* true for default styles */ bool KateStyleTreeWidgetItem::isDefault() const { return actualStyle ? false : true; } void KateStyleTreeWidgetItem::changeProperty(int p) { if (p == Bold) { currentStyle->setFontBold(! currentStyle->fontBold()); } else if (p == Italic) { currentStyle->setFontItalic(! currentStyle->fontItalic()); } else if (p == Underline) { currentStyle->setFontUnderline(! currentStyle->fontUnderline()); } else if (p == StrikeOut) { currentStyle->setFontStrikeOut(! currentStyle->fontStrikeOut()); } else if (p == UseDefaultStyle) { toggleDefStyle(); } else { setColor(p); } updateStyle(); treeWidget()->emitChanged(); } void KateStyleTreeWidgetItem::toggleDefStyle() { if (*currentStyle == *defaultStyle) { KMessageBox::information(treeWidget(), i18n("\"Use Default Style\" will be automatically unset when you change any style properties."), i18n("Kate Styles"), QStringLiteral("Kate hl config use defaults")); } else { currentStyle = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute(*defaultStyle)); updateStyle(); QModelIndex currentIndex = treeWidget()->currentIndex(); while (currentIndex.isValid()) { treeWidget()->update(currentIndex); currentIndex = currentIndex.sibling(currentIndex.row(), currentIndex.column() - 1); } } } void KateStyleTreeWidgetItem::setColor(int column) { QColor c; // use this QColor d; // default color if (column == Foreground) { c = currentStyle->foreground().color(); d = defaultStyle->foreground().color(); } else if (column == SelectedForeground) { c = currentStyle->selectedForeground().color(); d = defaultStyle->selectedForeground().color(); } else if (column == Background) { c = currentStyle->background().color(); d = defaultStyle->background().color(); } else if (column == SelectedBackground) { c = currentStyle->selectedBackground().color(); d = defaultStyle->selectedBackground().color(); } if (!c.isValid()) { c = d; } const QColor selectedColor = QColorDialog::getColor(c, treeWidget()); if (!selectedColor.isValid()) { return; } // if set default, and the attrib is set in the default style use it // else if set default, unset it // else set the selected color switch (column) { case Foreground: currentStyle->setForeground(selectedColor); break; case SelectedForeground: currentStyle->setSelectedForeground(selectedColor); break; case Background: currentStyle->setBackground(selectedColor); break; case SelectedBackground: currentStyle->setSelectedBackground(selectedColor); break; } //FIXME //repaint(); } void KateStyleTreeWidgetItem::unsetColor(int colorId) { switch (colorId) { case 1: if (defaultStyle->hasProperty(QTextFormat::ForegroundBrush)) { currentStyle->setForeground(defaultStyle->foreground()); } else { currentStyle->clearProperty(QTextFormat::ForegroundBrush); } break; case 2: if (defaultStyle->hasProperty(CustomProperties::SelectedForeground)) { currentStyle->setSelectedForeground(defaultStyle->selectedForeground()); } else { currentStyle->clearProperty(CustomProperties::SelectedForeground); } break; case 3: if (currentStyle->hasProperty(QTextFormat::BackgroundBrush)) { currentStyle->clearProperty(QTextFormat::BackgroundBrush); } break; case 4: if (currentStyle->hasProperty(CustomProperties::SelectedBackground)) { currentStyle->clearProperty(CustomProperties::SelectedBackground); } break; } updateStyle(); treeWidget()->emitChanged(); } KateStyleTreeWidget *KateStyleTreeWidgetItem::treeWidget() const { return static_cast(QTreeWidgetItem::treeWidget()); } //END diff --git a/src/schema/katestyletreewidget.h b/src/schema/katestyletreewidget.h index 53ef4219..a067993f 100644 --- a/src/schema/katestyletreewidget.h +++ b/src/schema/katestyletreewidget.h @@ -1,82 +1,82 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2003 Christoph Cullmann Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2005-2006 Hamish Rodda Copyright (C) 2007 Mirko Stocker * * 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 KATESTYLETREEWIDGET_H #define KATESTYLETREEWIDGET_H #include #include "kateextendedattribute.h" /** * QTreeWidget that automatically adds columns for KateStyleListItems and provides a * popup menu and a slot to edit a style using the keyboard. * Added by anders, jan 23 2002. */ class KateStyleTreeWidget : public QTreeWidget { Q_OBJECT friend class KateStyleListItem; public: - explicit KateStyleTreeWidget(QWidget *parent = 0, bool showUseDefaults = false); + explicit KateStyleTreeWidget(QWidget *parent = nullptr, bool showUseDefaults = false); void emitChanged(); void setBgCol(const QColor &c) { bgcol = c; } void setSelCol(const QColor &c) { selcol = c; } void setNormalCol(const QColor &c) { normalcol = c; } void addItem(QTreeWidgetItem *parent, const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data = KTextEditor::Attribute::Ptr()); void addItem(const QString &styleName, KTextEditor::Attribute::Ptr defaultstyle, KTextEditor::Attribute::Ptr data = KTextEditor::Attribute::Ptr()); void resizeColumns(); Q_SIGNALS: void changed(); protected: void contextMenuEvent(QContextMenuEvent *event) Q_DECL_OVERRIDE; void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) Q_DECL_OVERRIDE; private Q_SLOTS: void changeProperty(); void unsetColor(); void updateGroupHeadings(); private: QColor bgcol, selcol, normalcol; QFont docfont; }; #endif diff --git a/src/script/data/commands/emmet.js b/src/script/data/commands/emmet.js index fb48de1d..46532620 100644 --- a/src/script/data/commands/emmet.js +++ b/src/script/data/commands/emmet.js @@ -1,199 +1,199 @@ var katescript = { "author": "Gregor Petrin", "license": "BSD", - "revision": 2, + "revision": 3, "kate-version": "5.1", - "functions": ["emmetExpand", "emmetWrap", "emmetSelectTagPairInwards", "emmetSelectTagPairOutwards", "emmetMatchingPair", "emmetToggleComment ,emmetNext", "emmetPrev", "emmetSelectNext", "emmetSelectPrev", "emmetDelete", "emmetSplitJoinTab", "emmetEvaluateMathExpression", "emmetDecrementNumberBy1", "emmetDecrementNumberBy10", "emmetDecrementNumberBy01", "emmetIncrementNumberBy1", "emmetIncrementNumberBy10", "emmetIncrementNumberBy01"], + "functions": ["emmetExpand", "emmetWrap", "emmetSelectTagPairInwards", "emmetSelectTagPairOutwards", "emmetMatchingPair", "emmetToggleComment", "emmetNext", "emmetPrev", "emmetSelectNext", "emmetSelectPrev", "emmetDelete", "emmetSplitJoinTab", "emmetEvaluateMathExpression", "emmetDecrementNumberBy1", "emmetDecrementNumberBy10", "emmetDecrementNumberBy01", "emmetIncrementNumberBy1", "emmetIncrementNumberBy10", "emmetIncrementNumberBy01"], "actions": [ { "function": "emmetExpand", "name": "Expand abbreviation", "category": "Emmet" }, { "function": "emmetWrap", "name": "Wrap with tag", "category": "Emmet", "interactive": 1 }, { "function": "emmetMatchingPair", "name": "Move cursor to matching tag", "category": "Emmet" }, { "function": "emmetSelectTagPairInwards", "name": "Select HTML/XML tag contents inwards", "category": "Emmet" }, { "function": "emmetSelectTagPairOutwards", "name": "Select HTML/XML tag contents outwards", "category": "Emmet" }, { "function": "emmetToggleComment", "name": "Toggle comment", "category": "Emmet" }, { "function": "emmetNext", "name": "Go to next edit point", "category": "Emmet" }, { "function": "emmetPrev", "name": "Go to previous edit point", "category": "Emmet" }, { "function": "emmetSelectNext", "name": "Select next edit point", "category": "Emmet" }, { "function": "emmetSelectPrev", "name": "Select previous edit point", "category": "Emmet" }, { "function": "emmetDelete", "name": "Delete tag under cursor", "category": "Emmet" }, { "function": "emmetSplitJoinTab", "name": "Split or join a tag", "category": "Emmet" }, { "function": "emmetEvaluateMathExpression", "name": "Evaluate a simple math expression", "category": "Emmet" }, { "function": "emmetDecrementNumberBy1", "name": "Decrement number by 1", "category": "Emmet" }, { "function": "emmetDecrementNumberBy10", "name": "Decrement number by 10", "category": "Emmet" }, { "function": "emmetDecrementNumberBy01", "name": "Decrement number by 0.1", "category": "Emmet" }, { "function": "emmetIncrementNumberBy1", "name": "Increment number by 1", "category": "Emmet" }, { "function": "emmetIncrementNumberBy10", "name": "Increment number by 10", "category": "Emmet" }, { "function": "emmetIncrementNumberBy01", "name": "Increment number by 0.1", "category": "Emmet" } ] }; // kate-script-header, must be at the start of the file without comments, pure json // required katepart js libraries require ("range.js"); require ("emmet/lib.js"); require ("emmet/editor_interface.js"); function help(cmd) { cmds = { "emmetExpand" : i18n("Expands the abbreviation using Emmet expressions; see http://code.google.com/p/zen-coding/wiki/ZenHTMLSelectorsEn"), "emmetWrap" : i18n("Wraps the selected text in XML tags constructed from the provided Emmet expression (defaults to div)."), "emmetMatchingPair" : i18n("Moves the caret to the current tag's pair"), "emmetSelectTagPairInwards" : i18n("Select contents of HTML/XML tag, moving inward on continuous invocations"), "emmetSelectTagPairOutwards" : i18n("Select contents of HTML/XML tag, moving outwards on continuous invocations"), "emmetNext" : i18n("Move to the next edit point (tag or empty attribute)."), "emmetPrev" : i18n("Move to the previous edit point (tag or empty attribute)."), "emmetSelectNext" : i18n("Select next edit point (tag or empty attribute)."), "emmetSelectPrev" : i18n("Select previous edit point (tag or empty attribute)."), "emmetToggleComment" : i18n("Toggle comment of current tag or CSS selector"), "emmetDelete" : i18n("Deletes tag under cursor"), "emmetSplitJoinTab" : i18n("Splits or joins a tag"), "emmetEvaluateMathExpression" : i18n("Evaluates a simple math expression"), "emmetDecrementNumberBy1" : i18n("Decrement number under cursor by 1"), "emmetDecrementNumberBy10" : i18n("Decrement number under cursor by 10"), "emmetDecrementNumberBy01" : i18n("Decrement number under cursor by 0.1"), "emmetIncrementNumberBy1" : i18n("Increment number under cursor by 1"), "emmetIncrementNumberBy10" : i18n("Increment number under cursor by 10"), "emmetIncrementNumberBy01" : i18n("Increment number under cursor by 0.1") } return cmds[cmd]; } //Returns the kate editor interface function getInterface() { var kateInterface = new zen_editor(document, view); return kateInterface; } function emmetExpand() { emmet.require('actions').run('expand_abbreviation', getInterface()); } function emmetWrap(par) { emmet.require('actions').run('wrap_with_abbreviation', getInterface(), par || 'div'); } function emmetMatchingPair() { emmet.require('actions').run('matching_pair', getInterface()); } function emmetSelectTagPairInwards() { emmet.require('actions').run('match_pair', getInterface()); } function emmetSelectTagPairOutwards() { emmet.require('actions').run('match_pair', getInterface()); } function emmetNext() { emmet.require('actions').run('next_edit_point', getInterface()); } function emmetPrev() { emmet.require('actions').run('prev_edit_point', getInterface()); } function emmetSelectNext() { emmet.require('actions').run('select_next_item', getInterface()); } function emmetSelectPrev() { emmet.require('actions').run('select_previous_item', getInterface()); } function emmetToggleComment() { emmet.require('actions').run('toggle_comment', getInterface()); } function emmetDelete() { emmet.require('actions').run('remove_tag', getInterface()); } function emmetSplitJoinTab() { emmet.require('actions').run('split_join_tag', getInterface()); } function emmetEvaluateMathExpression() { emmet.require('actions').run('evaluate_math_expression', getInterface()); } function emmetIncrementNumberBy1() { emmet.require('actions').run('increment_number_by_1', getInterface()); } function emmetIncrementNumberBy10() { emmet.require('actions').run('increment_number_by_10', getInterface()); } function emmetIncrementNumberBy01() { emmet.require('actions').run('increment_number_by_01', getInterface()); } function emmetDecrementNumberBy1() { emmet.require('actions').run('decrement_number_by_1', getInterface()); } function emmetDecrementNumberBy10() { emmet.require('actions').run('decrement_number_by_10', getInterface()); } function emmetDecrementNumberBy01() { emmet.require('actions').run('decrement_number_by_01', getInterface()); } diff --git a/src/script/data/commands/utils.js b/src/script/data/commands/utils.js index a47e2b41..0995424d 100644 --- a/src/script/data/commands/utils.js +++ b/src/script/data/commands/utils.js @@ -1,627 +1,627 @@ var katescript = { "author": "Dominik Haumann , Milian Wolff , Gerald Senarclens de Grancy , Alex Turbov ", "license": "LGPL-2.1+", "revision": 9, "kate-version": "5.1", "functions": ["sort", "moveLinesDown", "moveLinesUp", "natsort", "uniq", "rtrim", "ltrim", "trim", "join", "rmblank", "unwrap", "each", "filter", "map", "duplicateLinesUp", "duplicateLinesDown", "rewrap", "encodeURISelection", "decodeURISelection"], "actions": [ { "function": "sort", "name": "Sort Selected Text", "category": "Editing" }, { "function": "moveLinesDown", "name": "Move Lines Down", "shortcut": "Ctrl+Shift+Down", "category": "Editing" }, { "function": "moveLinesUp", "name": "Move Lines Up", "shortcut": "Ctrl+Shift+Up", "category": "Editing" }, { "function": "duplicateLinesDown", "name": "Duplicate Selected Lines Down", "shortcut": "Ctrl+Alt+Down", "category": "Editing" }, { "function": "duplicateLinesUp", "name": "Duplicate Selected Lines Up", "shortcut": "Ctrl+Alt+Up", "category": "Editing" }, { "function": "encodeURISelection", "name": "URI-encode Selected Text", "category": "Editing" }, { "function": "decodeURISelection", "name": "URI-decode Selected Text", "category": "Editing" } ] }; // kate-script-header, must be at the start of the file without comments, pure json // required katepart js libraries require ("range.js"); function sort() { each(function(lines){return lines.sort()}); } function uniq() { each(function(lines) { for ( var i = 1; i < lines.length; ++i ) { for ( var j = i - 1; j >= 0; --j ) { if ( lines[i] == lines[j] ) { lines.splice(i, 1); // gets increased in the for --i; break; } } } return lines; }); } function natsort() { each(function(lines){return lines.sort(natcompare);}); } function rtrim() { map(function(l){ return l.replace(/\s+$/, ''); }); } function ltrim() { map(function(l){ return l.replace(/^\s+/, ''); }); } function trim() { map(function(l){ return l.replace(/^\s+|\s+$/, ''); }); } function rmblank() { filter(function(l) { return l.length > 0; }); } function join(separator) { if (typeof(separator) != "string") { separator = ""; } each(function(lines){ return [lines.join(separator)]; }); } // unwrap does the opposite of the script word wrap function unwrap () { var selectionRange = view.selection(); if (selectionRange.isValid()) { // unwrap all paragraphs in the selection range var currentLine = selectionRange.start.line; var count = selectionRange.end.line - selectionRange.start.line; document.editBegin(); while (count >= 0) { // skip empty lines while (count >= 0 && document.firstColumn(currentLine) == -1) { --count; ++currentLine; } // find block of text lines to join var anchorLine = currentLine; while (count >= 0 && document.firstColumn(currentLine) != -1) { --count; ++currentLine; } if (currentLine != anchorLine) { document.joinLines(anchorLine, currentLine - 1); currentLine -= currentLine - anchorLine - 1; } } document.editEnd(); } else { // unwrap paragraph under the cursor var cursorPosition = view.cursorPosition(); if (document.firstColumn(cursorPosition.line) != -1) { var startLine = cursorPosition.line; while (startLine > 0) { if (document.firstColumn(startLine - 1) == -1) { break; } --startLine; } var endLine = cursorPosition.line; var lineCount = document.lines(); while (endLine < lineCount) { if (document.firstColumn(endLine + 1) == -1) { break; } ++endLine; } if (startLine != endLine) { document.editBegin(); document.joinLines(startLine, endLine); document.editEnd(); } } } } /// \note Range contains a block selected by lines (\b ONLY)! /// And it is an open range! I.e. [start, end) (like in STL). function _getBlockForAction() { // Check if selection present in a view... var blockRange = Range(view.selection()); var cursorPosition = view.cursorPosition(); if (blockRange.isValid()) { blockRange.start.column = 0; if (blockRange.end.column != 0) blockRange.end.line++; blockRange.end.column = 0; } else { // No, it doesn't! Ok, lets select the current line only // from current position to the end blockRange = new Range(cursorPosition.line, 0, cursorPosition.line + 1, 0); } return blockRange; } // Adjusts ("moves") the current selection by offset. // Positive offsets move down, negatives up. function _adjustSelection(selection, offset) { if (selection.end.line + offset < document.lines()) { selection = new Range(selection.start.line + offset, selection.start.column, selection.end.line + offset, selection.end.column); } else { selection = new Range(selection.start.line + offset, selection.start.column, selection.end.line + offset - 1, document.lineLength(selection.end.line + offset - 1)); } view.setSelection(selection); } function moveLinesDown() { var selection = view.selection(); var blockRange = _getBlockForAction(); // Check is there a space to move? if (blockRange.end.line < document.lines()) { document.editBegin(); // Move a block to one line down: // 0) take one line after the block var text = document.line(blockRange.end.line); // 1) remove the line after the block document.removeLine(blockRange.end.line); // 2) insert a line before the block document.insertLine(blockRange.start.line, text); document.editEnd(); if (view.hasSelection()) _adjustSelection(selection, 1); } } function moveLinesUp() { var cursor = view.cursorPosition(); var selection = view.selection(); var blockRange = _getBlockForAction(); // Check is there a space to move? if (0 < blockRange.start.line) { document.editBegin(); // Move a block to one line up: // 0) take one line before the block, var text = document.line(blockRange.start.line - 1); // 1) and insert it after the block document.insertLine(blockRange.end.line, text); // 2) remove the original line document.removeLine(blockRange.start.line - 1); view.setCursorPosition(cursor.line - 1, cursor.column); if (view.hasSelection()) _adjustSelection(selection, -1); document.editEnd(); } } function duplicateLinesDown() { var selection = view.selection(); var blockRange = _getBlockForAction(); document.editBegin(); document.insertText(blockRange.start, document.text(blockRange)); _adjustSelection(selection, blockRange.end.line - blockRange.start.line); document.editEnd(); } function duplicateLinesUp() { var cursor = view.cursorPosition(); var selection = view.selection(); var blockRange = _getBlockForAction(); document.editBegin(); if (blockRange.end.line == document.lines()) { var lastLine = document.lines() - 1; var lastCol = document.lineLength(document.lines() - 1); blockRange.end.line = lastLine; blockRange.end.column = lastCol; document.insertText(lastLine, lastCol, document.text(blockRange)); document.wrapLine(lastLine, lastCol); } else { document.insertText(blockRange.end, document.text(blockRange)); } view.setCursorPosition(cursor.line, cursor.column); _adjustSelection(selection, 0); document.editEnd(); } function rewrap() { // initialize line span var fromLine = view.cursorPosition().line; var toLine = fromLine; var hasSelection = view.hasSelection(); // if a text selection is present, use it to reformat the paragraph if (hasSelection) { var range = view.selection(); fromLine = range.start.line; toLine = range.end.line; } else { // abort, if the cursor is in an empty line if (document.firstColumn(fromLine) == -1) return; // no text selection present: search for start & end of paragraph while (fromLine > 0 && document.prevNonEmptyLine(fromLine-1) == fromLine - 1) --fromLine; while (toLine < document.lines() - 1 && document.nextNonEmptyLine(toLine+1) == toLine + 1) ++toLine; } // initialize wrap columns var wrapColumn = 80; var softWrapColumn = 82; var exceptionColumn = 70; document.editBegin(); if (fromLine < toLine) { document.joinLines(fromLine, toLine); } var line = fromLine; // as long as the line is too long... while (document.lastColumn(line) > wrapColumn) { // ...search for current word boundaries... var range = document.wordRangeAt(line, wrapColumn); if (!range.isValid()) { break; } // ...and wrap at a 'smart' position var wrapCursor = range.start; if (range.start.column < exceptionColumn && range.end.column <= softWrapColumn) { wrapCursor = range.end; } if (!document.wrapLine(wrapCursor)) break; ++line; } document.editEnd(); } function _uri_transform_selection(transformer) { var selection = view.selection(); var cursor = view.cursorPosition(); // TODO Multiline conversions are meaningless!? if (selection.isValid() && selection.onSingleLine()) { var text = document.text(selection); var coded_text = transformer(text); document.editBegin(); document.removeText(selection); document.insertText(selection.start, coded_text); document.editEnd(); var size_diff = coded_text.length - text.length; selection.end.column += size_diff; view.setSelection(selection); if (selection.start.column < cursor.column) { cursor.column += size_diff; } view.setCursorPosition(cursor); } } function encodeURISelection() { _uri_transform_selection(encodeURIComponent); } function decodeURISelection() { _uri_transform_selection(decodeURIComponent); } function help(cmd) { if (cmd == "sort") { return i18n("Sort the selected text or whole document."); } else if (cmd == "moveLinesDown") { return i18n("Move selected lines down."); } else if (cmd == "moveLinesUp") { return i18n("Move selected lines up."); } else if (cmd == "uniq") { return i18n("Remove duplicate lines from the selected text or whole document."); } else if (cmd == "natsort") { return i18n("Sort the selected text or whole document in natural order.
Here is an example to show the difference to the normal sort method:
sort(a10, a1, a2) => a1, a10, a2
natsort(a10, a1, a2) => a1, a2, a10"); } else if (cmd == "rtrim") { return i18n("Trims trailing whitespace from selection or whole document."); } else if (cmd == "ltrim") { return i18n("Trims leading whitespace from selection or whole document."); } else if (cmd == "trim") { return i18n("Trims leading and trailing whitespace from selection or whole document."); } else if (cmd == "join") { return i18n("Joins selected lines or whole document. Optionally pass a separator to put between each line:
join ', ' will e.g. join lines and separate them by a comma."); } else if (cmd == "rmblank") { return i18n("Removes empty lines from selection or whole document."); } else if (cmd == "unwrap") { return "Unwraps all paragraphs in the text selection, or the paragraph under the text cursor if there is no selected text."; } else if (cmd == "each") { return i18n("Given a JavaScript function as argument, call that for the list of (selected) lines and replace them with the return value of that callback.
Example (join selected lines):
each 'function(lines){return lines.join(\", \");}'
To save you some typing, you can also do this to achieve the same:
each 'lines.join(\", \")'"); } else if (cmd == "filter") { return i18n("Given a JavaScript function as argument, call that for the list of (selected) lines and remove those where the callback returns false.
Example (see also rmblank):
filter 'function(l){return l.length > 0;}'
To save you some typing, you can also do this to achieve the same:
filter 'line.length > 0'"); } else if (cmd == "map") { - return i18n("Given a JavaScript function as argument, call that for the list of (selected) lines and replace the line with the return value of the callback.
Example (see also ltrim):
map 'function(line){return line.replace(/^\s+/, \"\");}'
To save you some typing, you can also do this to achieve the same:
map 'line.replace(/^\s+/, \"\")'"); + return i18n("Given a JavaScript function as argument, call that for the list of (selected) lines and replace the line with the return value of the callback.
Example (see also ltrim):
map 'function(line){return line.replace(/^\\s+/, \"\");}'
To save you some typing, you can also do this to achieve the same:
map 'line.replace(/^\\s+/, \"\")'"); } else if (cmd == "duplicateLinesUp") { return i18n("Duplicates the selected lines up."); } else if (cmd == "duplicateLinesDown") { return i18n("Duplicates the selected lines down."); } else if (cmd == "encodeURISelection") { return i18n("Encode special chars in a single line selection, so the result text can be used as URI."); } else if (cmd == "decodeURISelection") { return i18n("Reverse action of URI encode."); } } /// helper code below: function __toFunc(func, defaultArgName) { if ( typeof(func) != "function" ) { try { func = eval("(" + func + ")"); } catch(e) {} debug(func, typeof(func)) if ( typeof(func) != "function" ) { try { // one more try to support e.g.: // map 'l+l' // or: // each 'lines.join("\n")' func = eval("(function(" + defaultArgName + "){ return " + func + ";})"); debug(func, typeof(func)) } catch(e) {} if ( typeof(func) != "function" ) { throw "parameter is not a valid JavaScript callback function: " + typeof(func); } } } return func; } function each(func) { func = __toFunc(func, 'lines'); var selection = view.selection(); if (!selection.isValid()) { // use whole range selection = document.documentRange(); } else { selection.start.column = 0; selection.end.column = document.lineLength(selection.end.line); } var text = document.text(selection); var lines = text.split("\n"); lines = func(lines); if ( typeof(lines) == "object" ) { text = lines.join("\n"); } else if ( typeof(lines) == "string" ) { text = lines } else { throw "callback function for each has to return object or array of lines"; } view.clearSelection(); document.editBegin(); document.removeText(selection); document.insertText(selection.start, text); document.editEnd(); } function filter(func) { each(function(lines) { return lines.filter(__toFunc(func, 'line')); }); } function map(func) { each(function(lines) { return lines.map(__toFunc(func, 'line')); }); } /* natcompare.js -- Perform 'natural order' comparisons of strings in JavaScript. Copyright (C) 2005 by SCK-CEN (Belgian Nucleair Research Centre) Written by Kristof Coomans Based on the Java version by Pierre-Luc Paour, of which this is more or less a straight conversion. Copyright (C) 2003 by Pierre-Luc Paour The Java version was based on the C version by Martin Pool. Copyright (C) 2000 by Martin Pool This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ function isWhitespaceChar(a) { var charCode; charCode = a.charCodeAt(0); if ( charCode <= 32 ) { return true; } else { return false; } } function isDigitChar(a) { var charCode; charCode = a.charCodeAt(0); if ( charCode >= 48 && charCode <= 57 ) { return true; } else { return false; } } function compareRight(a,b) { var bias = 0; var ia = 0; var ib = 0; var ca; var cb; // The longest run of digits wins. That aside, the greatest // value wins, but we can't know that it will until we've scanned // both numbers to know that they have the same magnitude, so we // remember it in BIAS. for (;; ia++, ib++) { ca = a.charAt(ia); cb = b.charAt(ib); if (!isDigitChar(ca) && !isDigitChar(cb)) { return bias; } else if (!isDigitChar(ca)) { return -1; } else if (!isDigitChar(cb)) { return +1; } else if (ca < cb) { if (bias == 0) { bias = -1; } } else if (ca > cb) { if (bias == 0) bias = +1; } else if (ca == 0 && cb == 0) { return bias; } } } function natcompare(a,b) { var ia = 0, ib = 0; var nza = 0, nzb = 0; var ca, cb; var result; while (true) { // only count the number of zeroes leading the last number compared nza = nzb = 0; ca = a.charAt(ia); cb = b.charAt(ib); // skip over leading spaces or zeros while ( isWhitespaceChar( ca ) || ca =='0' ) { if (ca == '0') { nza++; } else { // only count consecutive zeroes nza = 0; } ca = a.charAt(++ia); } while ( isWhitespaceChar( cb ) || cb == '0') { if (cb == '0') { nzb++; } else { // only count consecutive zeroes nzb = 0; } cb = b.charAt(++ib); } // process run of digits if (isDigitChar(ca) && isDigitChar(cb)) { if ((result = compareRight(a.substring(ia), b.substring(ib))) != 0) { return result; } } if (ca == 0 && cb == 0) { // The strings compare the same. Perhaps the caller // will want to call strcmp to break the tie. return nza - nzb; } if (ca < cb) { return -1; } else if (ca > cb) { return +1; } ++ia; ++ib; } } // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/src/script/katescript.cpp b/src/script/katescript.cpp index eac7dfb0..61edad06 100644 --- a/src/script/katescript.cpp +++ b/src/script/katescript.cpp @@ -1,287 +1,287 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2009, 2010 Dominik Haumann // Copyright (C) 2010 Joseph Wenninger // // 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) version 3. // // 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 "katescript.h" #include "katescriptdocument.h" #include "katescriptview.h" #include "katescripthelpers.h" #include "kateview.h" #include "katedocument.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include //BEGIN conversion functions for Cursors and Ranges /** Converstion function from KTextEditor::Cursor to QtScript cursor */ static QScriptValue cursorToScriptValue(QScriptEngine *engine, const KTextEditor::Cursor &cursor) { QString code = QStringLiteral("new Cursor(%1, %2);").arg(cursor.line()) .arg(cursor.column()); return engine->evaluate(code); } /** Converstion function from QtScript cursor to KTextEditor::Cursor */ static void cursorFromScriptValue(const QScriptValue &obj, KTextEditor::Cursor &cursor) { cursor.setPosition(obj.property(QStringLiteral("line")).toInt32(), obj.property(QStringLiteral("column")).toInt32()); } /** Converstion function from QtScript range to KTextEditor::Range */ static QScriptValue rangeToScriptValue(QScriptEngine *engine, const KTextEditor::Range &range) { QString code = QStringLiteral("new Range(%1, %2, %3, %4);").arg(range.start().line()) .arg(range.start().column()) .arg(range.end().line()) .arg(range.end().column()); return engine->evaluate(code); } /** Converstion function from QtScript range to KTextEditor::Range */ static void rangeFromScriptValue(const QScriptValue &obj, KTextEditor::Range &range) { range.setRange(KTextEditor::Cursor(obj.property(QStringLiteral("start")).property(QStringLiteral("line")).toInt32(), obj.property(QStringLiteral("start")).property(QStringLiteral("column")).toInt32()), KTextEditor::Cursor(obj.property(QStringLiteral("end")).property(QStringLiteral("line")).toInt32(), obj.property(QStringLiteral("end")).property(QStringLiteral("column")).toInt32())); } //END KateScript::KateScript(const QString &urlOrScript, enum InputType inputType) : m_loaded(false) , m_loadSuccessful(false) , m_url(inputType == InputURL ? urlOrScript : QString()) - , m_engine(0) - , m_document(0) - , m_view(0) + , m_engine(nullptr) + , m_document(nullptr) + , m_view(nullptr) , m_inputType(inputType) , m_script(inputType == InputSCRIPT ? urlOrScript : QString()) { } KateScript::~KateScript() { if (m_loadSuccessful) { // remove data... delete m_engine; delete m_document; delete m_view; } } QString KateScript::backtrace(const QScriptValue &error, const QString &header) { QString bt; if (!header.isNull()) { bt += header + QLatin1String(":\n"); } if (error.isError()) { bt += error.toString() + QLatin1Char('\n'); } bt += m_engine->uncaughtExceptionBacktrace().join(QStringLiteral("\n")) + QLatin1Char('\n'); return bt; } void KateScript::displayBacktrace(const QScriptValue &error, const QString &header) { if (!m_engine) { std::cerr << "KateScript::displayBacktrace: no engine, cannot display error\n"; return; } std::cerr << "\033[31m" << qPrintable(backtrace(error, header)) << "\033[0m" << '\n'; } void KateScript::clearExceptions() { if (!load()) { return; } m_engine->clearExceptions(); } QScriptValue KateScript::global(const QString &name) { // load the script if necessary if (!load()) { return QScriptValue(); } return m_engine->globalObject().property(name); } QScriptValue KateScript::function(const QString &name) { QScriptValue value = global(name); if (!value.isFunction()) { return QScriptValue(); } return value; } bool KateScript::load() { if (m_loaded) { return m_loadSuccessful; } m_loaded = true; m_loadSuccessful = false; // here set to false, and at end of function to true // read the script file into memory QString source; if (m_inputType == InputURL) { if (!Kate::Script::readFile(m_url, source)) { return false; } } else { source = m_script; } // create script engine, register meta types m_engine = new QScriptEngine(); qScriptRegisterMetaType(m_engine, cursorToScriptValue, cursorFromScriptValue); qScriptRegisterMetaType(m_engine, rangeToScriptValue, rangeFromScriptValue); // export read & require function and add the require guard object m_engine->globalObject().setProperty(QStringLiteral("read"), m_engine->newFunction(Kate::Script::read)); m_engine->globalObject().setProperty(QStringLiteral("require"), m_engine->newFunction(Kate::Script::require)); m_engine->globalObject().setProperty(QStringLiteral("require_guard"), m_engine->newObject()); // export debug function m_engine->globalObject().setProperty(QStringLiteral("debug"), m_engine->newFunction(Kate::Script::debug)); // export translation functions m_engine->globalObject().setProperty(QStringLiteral("i18n"), m_engine->newFunction(Kate::Script::i18n)); m_engine->globalObject().setProperty(QStringLiteral("i18nc"), m_engine->newFunction(Kate::Script::i18nc)); m_engine->globalObject().setProperty(QStringLiteral("i18ncp"), m_engine->newFunction(Kate::Script::i18ncp)); m_engine->globalObject().setProperty(QStringLiteral("i18np"), m_engine->newFunction(Kate::Script::i18np)); // register default styles as ds* global properties m_engine->globalObject().setProperty(QStringLiteral("dsNormal"), KTextEditor::dsNormal); m_engine->globalObject().setProperty(QStringLiteral("dsKeyword"), KTextEditor::dsKeyword); m_engine->globalObject().setProperty(QStringLiteral("dsFunction"), KTextEditor::dsFunction); m_engine->globalObject().setProperty(QStringLiteral("dsVariable"), KTextEditor::dsVariable); m_engine->globalObject().setProperty(QStringLiteral("dsControlFlow"), KTextEditor::dsControlFlow); m_engine->globalObject().setProperty(QStringLiteral("dsOperator"), KTextEditor::dsOperator); m_engine->globalObject().setProperty(QStringLiteral("dsBuiltIn"), KTextEditor::dsBuiltIn); m_engine->globalObject().setProperty(QStringLiteral("dsExtension"), KTextEditor::dsExtension); m_engine->globalObject().setProperty(QStringLiteral("dsPreprocessor"), KTextEditor::dsPreprocessor); m_engine->globalObject().setProperty(QStringLiteral("dsAttribute"), KTextEditor::dsAttribute); m_engine->globalObject().setProperty(QStringLiteral("dsChar"), KTextEditor::dsChar); m_engine->globalObject().setProperty(QStringLiteral("dsSpecialChar"), KTextEditor::dsSpecialChar); m_engine->globalObject().setProperty(QStringLiteral("dsString"), KTextEditor::dsString); m_engine->globalObject().setProperty(QStringLiteral("dsVerbatimString"), KTextEditor::dsVerbatimString); m_engine->globalObject().setProperty(QStringLiteral("dsSpecialString"), KTextEditor::dsSpecialString); m_engine->globalObject().setProperty(QStringLiteral("dsImport"), KTextEditor::dsImport); m_engine->globalObject().setProperty(QStringLiteral("dsDataType"), KTextEditor::dsDataType); m_engine->globalObject().setProperty(QStringLiteral("dsDecVal"), KTextEditor::dsDecVal); m_engine->globalObject().setProperty(QStringLiteral("dsBaseN"), KTextEditor::dsBaseN); m_engine->globalObject().setProperty(QStringLiteral("dsFloat"), KTextEditor::dsFloat); m_engine->globalObject().setProperty(QStringLiteral("dsConstant"), KTextEditor::dsConstant); m_engine->globalObject().setProperty(QStringLiteral("dsComment"), KTextEditor::dsComment); m_engine->globalObject().setProperty(QStringLiteral("dsDocumentation"), KTextEditor::dsDocumentation); m_engine->globalObject().setProperty(QStringLiteral("dsAnnotation"), KTextEditor::dsAnnotation); m_engine->globalObject().setProperty(QStringLiteral("dsCommentVar"), KTextEditor::dsCommentVar); m_engine->globalObject().setProperty(QStringLiteral("dsRegionMarker"), KTextEditor::dsRegionMarker); m_engine->globalObject().setProperty(QStringLiteral("dsInformation"), KTextEditor::dsInformation); m_engine->globalObject().setProperty(QStringLiteral("dsWarning"), KTextEditor::dsWarning); m_engine->globalObject().setProperty(QStringLiteral("dsAlert"), KTextEditor::dsAlert); m_engine->globalObject().setProperty(QStringLiteral("dsOthers"), KTextEditor::dsOthers); m_engine->globalObject().setProperty(QStringLiteral("dsError"), KTextEditor::dsError); // register scripts itself QScriptValue result = m_engine->evaluate(source, m_url); if (hasException(result, m_url)) { return false; } // AFTER SCRIPT: set the view/document objects as necessary m_engine->globalObject().setProperty(QStringLiteral("document"), m_engine->newQObject(m_document = new KateScriptDocument())); m_engine->globalObject().setProperty(QStringLiteral("view"), m_engine->newQObject(m_view = new KateScriptView())); // yip yip! m_loadSuccessful = true; return true; } QScriptValue KateScript::evaluate(const QString& program, const FieldMap& env) { if ( !load() ) { qWarning() << "load of script failed:" << program; return QScriptValue(); } // set up stuff in a new context, to not pollute the global stuff auto context = m_engine->pushContext(); auto obj = context->activationObject(); for ( auto it = env.begin(); it != env.end(); it++ ) { obj.setProperty(it.key(), *it); } auto result = m_engine->evaluate(program); m_engine->popContext(); return result; } bool KateScript::hasException(const QScriptValue &object, const QString &file) { if (m_engine->hasUncaughtException()) { displayBacktrace(object, i18n("Error loading script %1\n", file)); m_errorMessage = i18n("Error loading script %1", file); delete m_engine; - m_engine = 0; + m_engine = nullptr; m_loadSuccessful = false; return true; } return false; } bool KateScript::setView(KTextEditor::ViewPrivate *view) { if (!load()) { return false; } // setup the stuff m_document->setDocument(view->doc()); m_view->setView(view); return true; } void KateScript::setGeneralHeader(const KateScriptHeader &generalHeader) { m_generalHeader = generalHeader; } KateScriptHeader &KateScript::generalHeader() { return m_generalHeader; } diff --git a/src/script/katescriptdocument.cpp b/src/script/katescriptdocument.cpp index 32c64a0c..d206da35 100644 --- a/src/script/katescriptdocument.cpp +++ b/src/script/katescriptdocument.cpp @@ -1,773 +1,773 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2009 Dominik Haumann // // 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) version 3. // // 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 "katescriptdocument.h" #include "katedocument.h" #include "kateview.h" #include "katerenderer.h" #include "kateconfig.h" #include "katehighlight.h" #include "katescript.h" #include "katepartdebug.h" #include #include KateScriptDocument::KateScriptDocument(QObject *parent) - : QObject(parent), m_document(0) + : QObject(parent), m_document(nullptr) { } void KateScriptDocument::setDocument(KTextEditor::DocumentPrivate *document) { m_document = document; } KTextEditor::DocumentPrivate *KateScriptDocument::document() { return m_document; } int KateScriptDocument::defStyleNum(int line, int column) { return m_document->defStyleNum(line, column); } int KateScriptDocument::defStyleNum(const KTextEditor::Cursor &cursor) { return defStyleNum(cursor.line(), cursor.column()); } bool KateScriptDocument::isCode(int line, int column) { const int defaultStyle = defStyleNum(line, column); return _isCode(defaultStyle); } bool KateScriptDocument::isCode(const KTextEditor::Cursor &cursor) { return isCode(cursor.line(), cursor.column()); } bool KateScriptDocument::isComment(int line, int column) { return m_document->isComment(line, column); } bool KateScriptDocument::isComment(const KTextEditor::Cursor &cursor) { return isComment(cursor.line(), cursor.column()); } bool KateScriptDocument::isString(int line, int column) { const int defaultStyle = defStyleNum(line, column); return defaultStyle == KTextEditor::dsString; } bool KateScriptDocument::isString(const KTextEditor::Cursor &cursor) { return isString(cursor.line(), cursor.column()); } bool KateScriptDocument::isRegionMarker(int line, int column) { const int defaultStyle = defStyleNum(line, column); return defaultStyle == KTextEditor::dsRegionMarker; } bool KateScriptDocument::isRegionMarker(const KTextEditor::Cursor &cursor) { return isRegionMarker(cursor.line(), cursor.column()); } bool KateScriptDocument::isChar(int line, int column) { const int defaultStyle = defStyleNum(line, column); return defaultStyle == KTextEditor::dsChar; } bool KateScriptDocument::isChar(const KTextEditor::Cursor &cursor) { return isChar(cursor.line(), cursor.column()); } bool KateScriptDocument::isOthers(int line, int column) { const int defaultStyle = defStyleNum(line, column); return defaultStyle == KTextEditor::dsOthers; } bool KateScriptDocument::isOthers(const KTextEditor::Cursor &cursor) { return isOthers(cursor.line(), cursor.column()); } int KateScriptDocument::firstVirtualColumn(int line) { const int tabWidth = m_document->config()->tabWidth(); Kate::TextLine textLine = m_document->plainKateTextLine(line); const int firstPos = textLine ? textLine->firstChar() : -1; if (!textLine || firstPos == -1) { return -1; } return textLine->indentDepth(tabWidth); } int KateScriptDocument::lastVirtualColumn(int line) { const int tabWidth = m_document->config()->tabWidth(); Kate::TextLine textLine = m_document->plainKateTextLine(line); const int lastPos = textLine ? textLine->lastChar() : -1; if (!textLine || lastPos == -1) { return -1; } return textLine->toVirtualColumn(lastPos, tabWidth); } int KateScriptDocument::toVirtualColumn(int line, int column) { const int tabWidth = m_document->config()->tabWidth(); Kate::TextLine textLine = m_document->plainKateTextLine(line); if (!textLine || column < 0 || column > textLine->length()) { return -1; } return textLine->toVirtualColumn(column, tabWidth); } int KateScriptDocument::toVirtualColumn(const KTextEditor::Cursor &cursor) { return toVirtualColumn(cursor.line(), cursor.column()); } KTextEditor::Cursor KateScriptDocument::toVirtualCursor(const KTextEditor::Cursor &cursor) { return KTextEditor::Cursor(cursor.line(), toVirtualColumn(cursor.line(), cursor.column())); } int KateScriptDocument::fromVirtualColumn(int line, int virtualColumn) { const int tabWidth = m_document->config()->tabWidth(); Kate::TextLine textLine = m_document->plainKateTextLine(line); if (!textLine || virtualColumn < 0 || virtualColumn > textLine->virtualLength(tabWidth)) { return -1; } return textLine->fromVirtualColumn(virtualColumn, tabWidth); } int KateScriptDocument::fromVirtualColumn(const KTextEditor::Cursor &virtualCursor) { return fromVirtualColumn(virtualCursor.line(), virtualCursor.column()); } KTextEditor::Cursor KateScriptDocument::fromVirtualCursor(const KTextEditor::Cursor &virtualCursor) { return KTextEditor::Cursor(virtualCursor.line(), fromVirtualColumn(virtualCursor.line(), virtualCursor.column())); } KTextEditor::Cursor KateScriptDocument::rfind(int line, int column, const QString &text, int attribute) { KTextEditor::DocumentCursor cursor(document(), line, column); const int start = cursor.line(); do { Kate::TextLine textLine = m_document->plainKateTextLine(cursor.line()); if (!textLine) { break; } if (cursor.line() != start) { cursor.setColumn(textLine->length()); } else if (column >= textLine->length()) { cursor.setColumn(qMax(textLine->length(), 0)); } int foundAt; while ((foundAt = textLine->string().leftRef(cursor.column()).lastIndexOf(text, -1, Qt::CaseSensitive)) >= 0) { bool hasStyle = true; if (attribute != -1) { const KTextEditor::DefaultStyle ds = m_document->highlight()->defaultStyleForAttribute(textLine->attribute(foundAt)); hasStyle = (ds == attribute); } if (hasStyle) { return KTextEditor::Cursor(cursor.line(), foundAt); } else { cursor.setColumn(foundAt); } } } while (cursor.gotoPreviousLine()); return KTextEditor::Cursor::invalid(); } KTextEditor::Cursor KateScriptDocument::rfind(const KTextEditor::Cursor &cursor, const QString &text, int attribute) { return rfind(cursor.line(), cursor.column(), text, attribute); } KTextEditor::Cursor KateScriptDocument::anchor(int line, int column, QChar character) { QChar lc; QChar rc; if (character == QLatin1Char('(') || character == QLatin1Char(')')) { lc = QLatin1Char('('); rc = QLatin1Char(')'); } else if (character == QLatin1Char('{') || character == QLatin1Char('}')) { lc = QLatin1Char('{'); rc = QLatin1Char('}'); } else if (character == QLatin1Char('[') || character == QLatin1Char(']')) { lc = QLatin1Char('['); rc = QLatin1Char(']'); } else { qCDebug(LOG_KTE) << "invalid anchor character:" << character << " allowed are: (){}[]"; return KTextEditor::Cursor::invalid(); } // cache line Kate::TextLine currentLine = document()->plainKateTextLine(line); if (!currentLine) return KTextEditor::Cursor::invalid(); // Move backwards char by char and find the opening character int count = 1; KTextEditor::DocumentCursor cursor(document(), KTextEditor::Cursor(line, column)); while (cursor.move(-1, KTextEditor::DocumentCursor::Wrap)) { // need to fetch new line? if (line != cursor.line()) { line = cursor.line(); currentLine = document()->plainKateTextLine(line); if (!currentLine) return KTextEditor::Cursor::invalid(); } // get current char const QChar ch = currentLine->at(cursor.column()); if (ch == lc) { const KTextEditor::DefaultStyle ds = m_document->highlight()->defaultStyleForAttribute(currentLine->attribute(cursor.column())); if (_isCode(ds)) { --count; } } else if (ch == rc) { const KTextEditor::DefaultStyle ds = m_document->highlight()->defaultStyleForAttribute(currentLine->attribute(cursor.column())); if (_isCode(ds)) { ++count; } } if (count == 0) { return cursor.toCursor(); } } return KTextEditor::Cursor::invalid(); } KTextEditor::Cursor KateScriptDocument::anchor(const KTextEditor::Cursor &cursor, QChar character) { return anchor(cursor.line(), cursor.column(), character); } bool KateScriptDocument::startsWith(int line, const QString &pattern, bool skipWhiteSpaces) { Kate::TextLine textLine = m_document->plainKateTextLine(line); if (!textLine) { return false; } if (skipWhiteSpaces) { return textLine->matchesAt(textLine->firstChar(), pattern); } return textLine->startsWith(pattern); } bool KateScriptDocument::endsWith(int line, const QString &pattern, bool skipWhiteSpaces) { Kate::TextLine textLine = m_document->plainKateTextLine(line); if (!textLine) { return false; } if (skipWhiteSpaces) { return textLine->matchesAt(textLine->lastChar() - pattern.length() + 1, pattern); } return textLine->endsWith(pattern); } //BEGIN Automatically generated QString KateScriptDocument::fileName() { return m_document->documentName(); } QString KateScriptDocument::url() { return m_document->url().toString(); } QString KateScriptDocument::mimeType() { return m_document->mimeType(); } QString KateScriptDocument::encoding() { return m_document->encoding(); } QString KateScriptDocument::highlightingMode() { return m_document->highlightingMode(); } QStringList KateScriptDocument::embeddedHighlightingModes() { return m_document->embeddedHighlightingModes(); } QString KateScriptDocument::highlightingModeAt(const KTextEditor::Cursor &pos) { return m_document->highlightingModeAt(pos); } bool KateScriptDocument::isModified() { return m_document->isModified(); } QString KateScriptDocument::text() { return m_document->text(); } QString KateScriptDocument::text(int fromLine, int fromColumn, int toLine, int toColumn) { return text(KTextEditor::Range(fromLine, fromColumn, toLine, toColumn)); } QString KateScriptDocument::text(const KTextEditor::Cursor &from, const KTextEditor::Cursor &to) { return text(KTextEditor::Range(from, to)); } QString KateScriptDocument::text(const KTextEditor::Range &range) { return m_document->text(range); } QString KateScriptDocument::line(int line) { return m_document->line(line); } QString KateScriptDocument::wordAt(int line, int column) { return m_document->wordAt(KTextEditor::Cursor(line, column)); } QString KateScriptDocument::wordAt(const KTextEditor::Cursor &cursor) { return m_document->wordAt(cursor); } KTextEditor::Range KateScriptDocument::wordRangeAt(int line, int column) { return wordRangeAt(KTextEditor::Cursor(line, column)); } KTextEditor::Range KateScriptDocument::wordRangeAt(const KTextEditor::Cursor &cursor) { return m_document->wordRangeAt(cursor); } QString KateScriptDocument::charAt(int line, int column) { return charAt(KTextEditor::Cursor(line, column)); } QString KateScriptDocument::charAt(const KTextEditor::Cursor &cursor) { const QChar c = m_document->characterAt(cursor); return c.isNull() ? QString() : QString(c); } QString KateScriptDocument::firstChar(int line) { Kate::TextLine textLine = m_document->plainKateTextLine(line); if (!textLine) { return QString(); } // check for isNull(), as the returned character then would be "\0" const QChar c = textLine->at(textLine->firstChar()); return c.isNull() ? QString() : QString(c); } QString KateScriptDocument::lastChar(int line) { Kate::TextLine textLine = m_document->plainKateTextLine(line); if (!textLine) { return QString(); } // check for isNull(), as the returned character then would be "\0" const QChar c = textLine->at(textLine->lastChar()); return c.isNull() ? QString() : QString(c); } bool KateScriptDocument::isSpace(int line, int column) { return isSpace(KTextEditor::Cursor(line, column)); } bool KateScriptDocument::isSpace(const KTextEditor::Cursor &cursor) { return m_document->characterAt(cursor).isSpace(); } bool KateScriptDocument::matchesAt(int line, int column, const QString &s) { Kate::TextLine textLine = m_document->plainKateTextLine(line); return textLine ? textLine->matchesAt(column, s) : false; } bool KateScriptDocument::matchesAt(const KTextEditor::Cursor &cursor, const QString &s) { return matchesAt(cursor.line(), cursor.column(), s); } bool KateScriptDocument::setText(const QString &s) { return m_document->setText(s); } bool KateScriptDocument::clear() { return m_document->clear(); } bool KateScriptDocument::truncate(int line, int column) { Kate::TextLine textLine = m_document->plainKateTextLine(line); if (!textLine || textLine->text().size() < column) { return false; } KTextEditor::Cursor from(line, column), to(line, textLine->text().size() - column); return removeText(KTextEditor::Range(from, to)); } bool KateScriptDocument::truncate(const KTextEditor::Cursor &cursor) { return truncate(cursor.line(), cursor.column()); } bool KateScriptDocument::insertText(int line, int column, const QString &s) { return insertText(KTextEditor::Cursor(line, column), s); } bool KateScriptDocument::insertText(const KTextEditor::Cursor &cursor, const QString &s) { return m_document->insertText(cursor, s); } bool KateScriptDocument::removeText(int fromLine, int fromColumn, int toLine, int toColumn) { return removeText(KTextEditor::Range(fromLine, fromColumn, toLine, toColumn)); } bool KateScriptDocument::removeText(const KTextEditor::Cursor &from, const KTextEditor::Cursor &to) { return removeText(KTextEditor::Range(from, to)); } bool KateScriptDocument::removeText(const KTextEditor::Range &range) { return m_document->removeText(range); } bool KateScriptDocument::insertLine(int line, const QString &s) { return m_document->insertLine(line, s); } bool KateScriptDocument::removeLine(int line) { return m_document->removeLine(line); } bool KateScriptDocument::wrapLine(int line, int column) { return m_document->editWrapLine(line, column); } bool KateScriptDocument::wrapLine(const KTextEditor::Cursor &cursor) { return wrapLine(cursor.line(), cursor.column()); } void KateScriptDocument::joinLines(int startLine, int endLine) { m_document->joinLines(startLine, endLine); } int KateScriptDocument::lines() { return m_document->lines(); } bool KateScriptDocument::isLineModified(int line) { return m_document->isLineModified(line); } bool KateScriptDocument::isLineSaved(int line) { return m_document->isLineSaved(line); } bool KateScriptDocument::isLineTouched(int line) { return m_document->isLineTouched(line); } int KateScriptDocument::findTouchedLine(int startLine, bool down) { return m_document->findTouchedLine(startLine, down); } int KateScriptDocument::length() { return m_document->totalCharacters(); } int KateScriptDocument::lineLength(int line) { return m_document->lineLength(line); } void KateScriptDocument::editBegin() { m_document->editBegin(); } void KateScriptDocument::editEnd() { m_document->editEnd(); } bool KateScriptDocument::isValidTextPosition(int line, int column) { return m_document->isValidTextPosition(KTextEditor::Cursor(line, column)); } bool KateScriptDocument::isValidTextPosition(const KTextEditor::Cursor& cursor) { return m_document->isValidTextPosition(cursor); } int KateScriptDocument::firstColumn(int line) { Kate::TextLine textLine = m_document->plainKateTextLine(line); return textLine ? textLine->firstChar() : -1; } int KateScriptDocument::lastColumn(int line) { Kate::TextLine textLine = m_document->plainKateTextLine(line); return textLine ? textLine->lastChar() : -1; } int KateScriptDocument::prevNonSpaceColumn(int line, int column) { Kate::TextLine textLine = m_document->plainKateTextLine(line); if (!textLine) { return -1; } return textLine->previousNonSpaceChar(column); } int KateScriptDocument::prevNonSpaceColumn(const KTextEditor::Cursor &cursor) { return prevNonSpaceColumn(cursor.line(), cursor.column()); } int KateScriptDocument::nextNonSpaceColumn(int line, int column) { Kate::TextLine textLine = m_document->plainKateTextLine(line); if (!textLine) { return -1; } return textLine->nextNonSpaceChar(column); } int KateScriptDocument::nextNonSpaceColumn(const KTextEditor::Cursor &cursor) { return nextNonSpaceColumn(cursor.line(), cursor.column()); } int KateScriptDocument::prevNonEmptyLine(int line) { const int startLine = line; for (int currentLine = startLine; currentLine >= 0; --currentLine) { Kate::TextLine textLine = m_document->plainKateTextLine(currentLine); if (!textLine) { return -1; } if (textLine->firstChar() != -1) { return currentLine; } } return -1; } int KateScriptDocument::nextNonEmptyLine(int line) { const int startLine = line; for (int currentLine = startLine; currentLine < m_document->lines(); ++currentLine) { Kate::TextLine textLine = m_document->plainKateTextLine(currentLine); if (!textLine) { return -1; } if (textLine->firstChar() != -1) { return currentLine; } } return -1; } bool KateScriptDocument::isInWord(const QString &character, int attribute) { return m_document->highlight()->isInWord(character.at(0), attribute); } bool KateScriptDocument::canBreakAt(const QString &character, int attribute) { return m_document->highlight()->canBreakAt(character.at(0), attribute); } bool KateScriptDocument::canComment(int startAttribute, int endAttribute) { return m_document->highlight()->canComment(startAttribute, endAttribute); } QString KateScriptDocument::commentMarker(int attribute) { return m_document->highlight()->getCommentSingleLineStart(attribute); } QString KateScriptDocument::commentStart(int attribute) { return m_document->highlight()->getCommentStart(attribute); } QString KateScriptDocument::commentEnd(int attribute) { return m_document->highlight()->getCommentEnd(attribute); } KTextEditor::Range KateScriptDocument::documentRange() { return m_document->documentRange(); } KTextEditor::Cursor KateScriptDocument::documentEnd() { return m_document->documentEnd(); } int KateScriptDocument::attribute(int line, int column) { Kate::TextLine textLine = m_document->kateTextLine(line); if (!textLine) { return 0; } return textLine->attribute(column); } int KateScriptDocument::attribute(const KTextEditor::Cursor &cursor) { return attribute(cursor.line(), cursor.column()); } bool KateScriptDocument::isAttribute(int line, int column, int attr) { return attr == attribute(line, column); } bool KateScriptDocument::isAttribute(const KTextEditor::Cursor &cursor, int attr) { return isAttribute(cursor.line(), cursor.column(), attr); } QString KateScriptDocument::attributeName(int line, int column) { // just use the global default schema, we anyway only want the style number! QList attributes = m_document->highlight()->attributes(KateRendererConfig::global()->schema()); KTextEditor::Attribute::Ptr a = attributes[document()->plainKateTextLine(line)->attribute(column)]; return a->name(); } QString KateScriptDocument::attributeName(const KTextEditor::Cursor &cursor) { return attributeName(cursor.line(), cursor.column()); } bool KateScriptDocument::isAttributeName(int line, int column, const QString &name) { return name == attributeName(line, column); } bool KateScriptDocument::isAttributeName(const KTextEditor::Cursor &cursor, const QString &name) { return isAttributeName(cursor.line(), cursor.column(), name); } QString KateScriptDocument::variable(const QString &s) { return m_document->variable(s); } void KateScriptDocument::setVariable(const QString &s, const QString &v) { m_document->setVariable(s, v); } //END bool KateScriptDocument::_isCode(int defaultStyle) { return (defaultStyle != KTextEditor::dsComment && defaultStyle != KTextEditor::dsString && defaultStyle != KTextEditor::dsRegionMarker && defaultStyle != KTextEditor::dsChar && defaultStyle != KTextEditor::dsOthers); } void KateScriptDocument::indent(KTextEditor::Range range, int change) { m_document->indent(range, change); } diff --git a/src/script/katescriptdocument.h b/src/script/katescriptdocument.h index 773dd3fb..e66c201c 100644 --- a/src/script/katescriptdocument.h +++ b/src/script/katescriptdocument.h @@ -1,194 +1,194 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2009 Dominik Haumann // // 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) version 3. // // 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_SCRIPT_DOCUMENT_H #define KATE_SCRIPT_DOCUMENT_H #include #include #include #include #include #include #include namespace KTextEditor { class DocumentPrivate; } /** * Thinish wrapping around KTextEditor::DocumentPrivate, exposing the methods we want exposed * and adding some helper methods. * * We inherit from QScriptable to have more thight access to the scripting * engine. * * setDocument _must_ be called before using any other method. This is not checked * for the sake of speed. */ class KTEXTEDITOR_EXPORT KateScriptDocument : public QObject, protected QScriptable { Q_OBJECT // Note: we have no Q_PROPERTIES due to consistency: everything is a function. public: - KateScriptDocument(QObject *parent = 0); + KateScriptDocument(QObject *parent = nullptr); void setDocument(KTextEditor::DocumentPrivate *document); KTextEditor::DocumentPrivate *document(); //BEGIN Q_INVOKABLE QString fileName(); Q_INVOKABLE QString url(); Q_INVOKABLE QString mimeType(); Q_INVOKABLE QString encoding(); Q_INVOKABLE QString highlightingMode(); Q_INVOKABLE QStringList embeddedHighlightingModes(); Q_INVOKABLE QString highlightingModeAt(const KTextEditor::Cursor &pos); Q_INVOKABLE bool isModified(); Q_INVOKABLE QString text(); Q_INVOKABLE QString text(int fromLine, int fromColumn, int toLine, int toColumn); Q_INVOKABLE QString text(const KTextEditor::Cursor &from, const KTextEditor::Cursor &to); Q_INVOKABLE QString text(const KTextEditor::Range &range); Q_INVOKABLE QString line(int line); Q_INVOKABLE QString wordAt(int line, int column); Q_INVOKABLE QString wordAt(const KTextEditor::Cursor &cursor); Q_INVOKABLE KTextEditor::Range wordRangeAt(int line, int column); Q_INVOKABLE KTextEditor::Range wordRangeAt(const KTextEditor::Cursor &cursor); Q_INVOKABLE QString charAt(int line, int column); Q_INVOKABLE QString charAt(const KTextEditor::Cursor &cursor); Q_INVOKABLE QString firstChar(int line); Q_INVOKABLE QString lastChar(int line); Q_INVOKABLE bool isSpace(int line, int column); Q_INVOKABLE bool isSpace(const KTextEditor::Cursor &cursor); Q_INVOKABLE bool matchesAt(int line, int column, const QString &s); Q_INVOKABLE bool matchesAt(const KTextEditor::Cursor &cursor, const QString &s); Q_INVOKABLE bool setText(const QString &s); Q_INVOKABLE bool clear(); Q_INVOKABLE bool truncate(int line, int column); Q_INVOKABLE bool truncate(const KTextEditor::Cursor &cursor); Q_INVOKABLE bool insertText(int line, int column, const QString &s); Q_INVOKABLE bool insertText(const KTextEditor::Cursor &cursor, const QString &s); Q_INVOKABLE bool removeText(int fromLine, int fromColumn, int toLine, int toColumn); Q_INVOKABLE bool removeText(const KTextEditor::Cursor &from, const KTextEditor::Cursor &to); Q_INVOKABLE bool removeText(const KTextEditor::Range &range); Q_INVOKABLE bool insertLine(int line, const QString &s); Q_INVOKABLE bool removeLine(int line); Q_INVOKABLE bool wrapLine(int line, int column); Q_INVOKABLE bool wrapLine(const KTextEditor::Cursor &cursor); Q_INVOKABLE void joinLines(int startLine, int endLine); Q_INVOKABLE int lines(); Q_INVOKABLE bool isLineModified(int line); Q_INVOKABLE bool isLineSaved(int line); Q_INVOKABLE bool isLineTouched(int line); Q_INVOKABLE int findTouchedLine(int startLine, bool down); Q_INVOKABLE int length(); Q_INVOKABLE int lineLength(int line); Q_INVOKABLE void editBegin(); Q_INVOKABLE void editEnd(); Q_INVOKABLE int firstColumn(int line); Q_INVOKABLE int lastColumn(int line); Q_INVOKABLE int prevNonSpaceColumn(int line, int column); Q_INVOKABLE int prevNonSpaceColumn(const KTextEditor::Cursor &cursor); Q_INVOKABLE int nextNonSpaceColumn(int line, int column); Q_INVOKABLE int nextNonSpaceColumn(const KTextEditor::Cursor &cursor); Q_INVOKABLE int prevNonEmptyLine(int line); Q_INVOKABLE int nextNonEmptyLine(int line); Q_INVOKABLE bool isInWord(const QString &character, int attribute); Q_INVOKABLE bool canBreakAt(const QString &character, int attribute); Q_INVOKABLE bool canComment(int startAttribute, int endAttribute); Q_INVOKABLE QString commentMarker(int attribute); Q_INVOKABLE QString commentStart(int attribute); Q_INVOKABLE QString commentEnd(int attribute); Q_INVOKABLE KTextEditor::Range documentRange(); Q_INVOKABLE KTextEditor::Cursor documentEnd(); Q_INVOKABLE bool isValidTextPosition(int line, int column); Q_INVOKABLE bool isValidTextPosition(const KTextEditor::Cursor& cursor); /** * Get the syntax highlighting attribute at a given position in the document. */ Q_INVOKABLE int attribute(int line, int column); Q_INVOKABLE int attribute(const KTextEditor::Cursor &cursor); /** * Return true if the highlight attribute equals @p attr. */ Q_INVOKABLE bool isAttribute(int line, int column, int attr); Q_INVOKABLE bool isAttribute(const KTextEditor::Cursor &cursor, int attr); /** * Get the name of the syntax highlighting attribute at the given position. */ Q_INVOKABLE QString attributeName(int line, int column); Q_INVOKABLE QString attributeName(const KTextEditor::Cursor &cursor); /** * Return true is the name of the syntax attribute equals @p name. */ Q_INVOKABLE bool isAttributeName(int line, int column, const QString &name); Q_INVOKABLE bool isAttributeName(const KTextEditor::Cursor &cursor, const QString &name); Q_INVOKABLE QString variable(const QString &s); Q_INVOKABLE void setVariable(const QString &s, const QString &v); //END Q_INVOKABLE int firstVirtualColumn(int line); Q_INVOKABLE int lastVirtualColumn(int line); Q_INVOKABLE int toVirtualColumn(int line, int column); Q_INVOKABLE int toVirtualColumn(const KTextEditor::Cursor &cursor); Q_INVOKABLE KTextEditor::Cursor toVirtualCursor(const KTextEditor::Cursor &cursor); Q_INVOKABLE int fromVirtualColumn(int line, int virtualColumn); Q_INVOKABLE int fromVirtualColumn(const KTextEditor::Cursor &virtualCursor); Q_INVOKABLE KTextEditor::Cursor fromVirtualCursor(const KTextEditor::Cursor &virtualCursor); Q_INVOKABLE KTextEditor::Cursor anchor(int line, int column, QChar character); Q_INVOKABLE KTextEditor::Cursor anchor(const KTextEditor::Cursor &cursor, QChar character); Q_INVOKABLE KTextEditor::Cursor rfind(int line, int column, const QString &text, int attribute = -1); Q_INVOKABLE KTextEditor::Cursor rfind(const KTextEditor::Cursor &cursor, const QString &text, int attribute = -1); Q_INVOKABLE int defStyleNum(int line, int column); Q_INVOKABLE int defStyleNum(const KTextEditor::Cursor &cursor); Q_INVOKABLE bool isCode(int line, int column); Q_INVOKABLE bool isCode(const KTextEditor::Cursor &cursor); Q_INVOKABLE bool isComment(int line, int column); Q_INVOKABLE bool isComment(const KTextEditor::Cursor &cursor); Q_INVOKABLE bool isString(int line, int column); Q_INVOKABLE bool isString(const KTextEditor::Cursor &cursor); Q_INVOKABLE bool isRegionMarker(int line, int column); Q_INVOKABLE bool isRegionMarker(const KTextEditor::Cursor &cursor); Q_INVOKABLE bool isChar(int line, int column); Q_INVOKABLE bool isChar(const KTextEditor::Cursor &cursor); Q_INVOKABLE bool isOthers(int line, int column); Q_INVOKABLE bool isOthers(const KTextEditor::Cursor &cursor); Q_INVOKABLE bool startsWith(int line, const QString &pattern, bool skipWhiteSpaces); Q_INVOKABLE bool endsWith(int line, const QString &pattern, bool skipWhiteSpaces); Q_INVOKABLE void indent(KTextEditor::Range range, int change); private: bool _isCode(int defaultStyle); KTextEditor::DocumentPrivate *m_document; }; #endif diff --git a/src/script/katescriptmanager.cpp b/src/script/katescriptmanager.cpp index e175fa30..618e9f6d 100644 --- a/src/script/katescriptmanager.cpp +++ b/src/script/katescriptmanager.cpp @@ -1,341 +1,341 @@ // This file is part of the KDE libraries // Copyright (C) 2005 Christoph Cullmann // Copyright (C) 2005 Joseph Wenninger // Copyright (C) 2006, 2009 Dominik Haumann // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2010 Joseph Wenninger // // 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. #include "katescriptmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kateglobal.h" #include "katecmd.h" #include "katepartdebug.h" -KateScriptManager *KateScriptManager::m_instance = 0; +KateScriptManager *KateScriptManager::m_instance = nullptr; KateScriptManager::KateScriptManager() : KTextEditor::Command(QStringList() << QStringLiteral("reload-scripts")) { // use cached info collect(); } KateScriptManager::~KateScriptManager() { qDeleteAll(m_indentationScripts); qDeleteAll(m_commandLineScripts); - m_instance = 0; + m_instance = nullptr; } KateIndentScript *KateScriptManager::indenter(const QString &language) { - KateIndentScript *highestPriorityIndenter = 0; + KateIndentScript *highestPriorityIndenter = nullptr; foreach (KateIndentScript *indenter, m_languageToIndenters.value(language.toLower())) { // don't overwrite if there is already a result with a higher priority if (highestPriorityIndenter && indenter->indentHeader().priority() < highestPriorityIndenter->indentHeader().priority()) { #ifdef DEBUG_SCRIPTMANAGER qCDebug(LOG_KTE) << "Not overwriting indenter for" << language << "as the priority isn't big enough (" << indenter->indentHeader().priority() << '<' << highestPriorityIndenter->indentHeader().priority() << ')'; #endif } else { highestPriorityIndenter = indenter; } } #ifdef DEBUG_SCRIPTMANAGER if (highestPriorityIndenter) { qCDebug(LOG_KTE) << "Found indenter" << highestPriorityIndenter->url() << "for" << language; } else { qCDebug(LOG_KTE) << "No indenter for" << language; } #endif return highestPriorityIndenter; } /** * Small helper: QJsonValue to QStringList */ static QStringList jsonToStringList (const QJsonValue &value) { QStringList list; Q_FOREACH (const QJsonValue &value, value.toArray()) { if (value.isString()) { list.append(value.toString()); } } return list; } void KateScriptManager::collect() { // clear out the old scripts and reserve enough space qDeleteAll(m_indentationScripts); qDeleteAll(m_commandLineScripts); m_indentationScripts.clear(); m_commandLineScripts.clear(); m_languageToIndenters.clear(); m_indentationScriptMap.clear(); /** * now, we search all kinds of known scripts */ foreach (const QString &type, QStringList() << QLatin1String("indentation") << QLatin1String("commands")) { // basedir for filesystem lookup const QString basedir = QLatin1String("/katepart5/script/") + type; QStringList dirs; // first writable locations, e.g. stuff the user has provided dirs += QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + basedir; // then resources, e.g. the stuff we ship with us dirs.append(QLatin1String(":/ktexteditor/script/") + type); // then all other locations, this includes global stuff installed by other applications // this will not allow global stuff to overwrite the stuff we ship in our resources to allow to install a more up-to-date ktexteditor lib locally! foreach (const QString &dir, QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)) { dirs.append(dir + basedir); } QStringList list; foreach (const QString &dir, dirs) { const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.js")); foreach (const QString &file, fileNames) { list.append(dir + QLatin1Char('/') + file); } } // iterate through the files and read info out of cache or file, no double loading of same scripts QSet unique; foreach (const QString &fileName, list) { /** * get file basename */ const QString baseName = QFileInfo(fileName).baseName(); /** * only load scripts once, even if multiple installed variants found! */ if (unique.contains(baseName)) continue; /** * remember the script */ unique.insert (baseName); /** * open file or skip it */ QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { qCDebug(LOG_KTE) << "Script parse error: Cannot open file " << qPrintable(fileName) << '\n'; continue; } /** * search json header or skip this file */ QByteArray fileContent = file.readAll(); int startOfJson = fileContent.indexOf ('{'); if (startOfJson < 0) { qCDebug(LOG_KTE) << "Script parse error: Cannot find start of json header at start of file " << qPrintable(fileName) << '\n'; continue; } int endOfJson = fileContent.indexOf("\n};", startOfJson); if (endOfJson < 0) { // as fallback, check also mac os line ending endOfJson = fileContent.indexOf("\r};", startOfJson); } if (endOfJson < 0) { qCDebug(LOG_KTE) << "Script parse error: Cannot find end of json header at start of file " << qPrintable(fileName) << '\n'; continue; } endOfJson += 2; //we want the end including the } but not the ; /** * parse json header or skip this file */ QJsonParseError error; const QJsonDocument metaInfo (QJsonDocument::fromJson(fileContent.mid(startOfJson, endOfJson-startOfJson), &error)); if (error.error || !metaInfo.isObject()) { qCDebug(LOG_KTE) << "Script parse error: Cannot parse json header at start of file " << qPrintable(fileName) << error.errorString() << endOfJson << fileContent.mid(endOfJson-25, 25).replace('\n', ' '); continue; } /** * remember type */ KateScriptHeader generalHeader; if (type == QLatin1String("indentation")) { generalHeader.setScriptType(Kate::IndentationScript); } else if (type == QLatin1String("commands")) { generalHeader.setScriptType(Kate::CommandLineScript); } else { // should never happen, we dictate type by directory Q_ASSERT(false); } const QJsonObject metaInfoObject = metaInfo.object(); generalHeader.setLicense(metaInfoObject.value(QStringLiteral("license")).toString()); generalHeader.setAuthor(metaInfoObject.value(QStringLiteral("author")).toString()); generalHeader.setRevision(metaInfoObject.value(QStringLiteral("revision")).toInt()); generalHeader.setKateVersion(metaInfoObject.value(QStringLiteral("kate-version")).toString()); // now, cast accordingly based on type switch (generalHeader.scriptType()) { case Kate::IndentationScript: { KateIndentScriptHeader indentHeader; indentHeader.setName(metaInfoObject.value(QStringLiteral("name")).toString()); indentHeader.setBaseName(baseName); if (indentHeader.name().isNull()) { qCDebug(LOG_KTE) << "Script value error: No name specified in script meta data: " << qPrintable(fileName) << '\n' << "-> skipping indenter" << '\n'; continue; } // required style? indentHeader.setRequiredStyle(metaInfoObject.value(QStringLiteral("required-syntax-style")).toString()); // which languages does this support? QStringList indentLanguages = jsonToStringList(metaInfoObject.value(QStringLiteral("indent-languages"))); if (!indentLanguages.isEmpty()) { indentHeader.setIndentLanguages(indentLanguages); } else { indentHeader.setIndentLanguages(QStringList() << indentHeader.name()); #ifdef DEBUG_SCRIPTMANAGER qCDebug(LOG_KTE) << "Script value warning: No indent-languages specified for indent " << "script " << qPrintable(fileName) << ". Using the name (" << qPrintable(indentHeader.name()) << ")\n"; #endif } // priority indentHeader.setPriority(metaInfoObject.value(QStringLiteral("priority")).toInt()); KateIndentScript *script = new KateIndentScript(fileName, indentHeader); script->setGeneralHeader(generalHeader); foreach (const QString &language, indentHeader.indentLanguages()) { m_languageToIndenters[language.toLower()].push_back(script); } m_indentationScriptMap.insert(indentHeader.baseName(), script); m_indentationScripts.append(script); break; } case Kate::CommandLineScript: { KateCommandLineScriptHeader commandHeader; commandHeader.setFunctions(jsonToStringList(metaInfoObject.value(QStringLiteral("functions")))); commandHeader.setActions(metaInfoObject.value(QStringLiteral("actions")).toArray()); if (commandHeader.functions().isEmpty()) { qCDebug(LOG_KTE) << "Script value error: No functions specified in script meta data: " << qPrintable(fileName) << '\n' << "-> skipping script" << '\n'; continue; } KateCommandLineScript *script = new KateCommandLineScript(fileName, commandHeader); script->setGeneralHeader(generalHeader); m_commandLineScripts.push_back(script); break; } case Kate::UnknownScript: default: qCDebug(LOG_KTE) << "Script value warning: Unknown type ('" << qPrintable(type) << "'): " << qPrintable(fileName) << '\n'; } } } #ifdef DEBUG_SCRIPTMANAGER // XX Test if (indenter("Python")) { qCDebug(LOG_KTE) << "Python: " << indenter("Python")->global("triggerCharacters").isValid() << "\n"; qCDebug(LOG_KTE) << "Python: " << indenter("Python")->function("triggerCharacters").isValid() << "\n"; qCDebug(LOG_KTE) << "Python: " << indenter("Python")->global("blafldsjfklas").isValid() << "\n"; qCDebug(LOG_KTE) << "Python: " << indenter("Python")->function("indent").isValid() << "\n"; } if (indenter("C")) { qCDebug(LOG_KTE) << "C: " << qPrintable(indenter("C")->url()) << "\n"; } if (indenter("lisp")) { qCDebug(LOG_KTE) << "LISP: " << qPrintable(indenter("Lisp")->url()) << "\n"; } #endif } void KateScriptManager::reload() { collect(); emit reloaded(); } /// Kate::Command stuff bool KateScriptManager::exec(KTextEditor::View *view, const QString &_cmd, QString &errorMsg, const KTextEditor::Range &) { QStringList args(_cmd.split(QRegExp(QLatin1String("\\s+")), QString::SkipEmptyParts)); QString cmd(args.first()); args.removeFirst(); if (!view) { errorMsg = i18n("Could not access view"); return false; } if (cmd == QLatin1String("reload-scripts")) { reload(); return true; } else { errorMsg = i18n("Command not found: %1", cmd); return false; } } bool KateScriptManager::help(KTextEditor::View *view, const QString &cmd, QString &msg) { Q_UNUSED(view) if (cmd == QLatin1String("reload-scripts")) { msg = i18n("Reload all JavaScript files (indenters, command line scripts, etc)."); return true; } else { msg = i18n("Command not found: %1", cmd); return false; } } diff --git a/src/script/katescriptmanager.h b/src/script/katescriptmanager.h index f4d52536..d7df0e0e 100644 --- a/src/script/katescriptmanager.h +++ b/src/script/katescriptmanager.h @@ -1,140 +1,140 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2009 Dominik Haumann // Copyright (C) 2010 Joseph Wenninger // // 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) version 3. // // 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_SCRIPT_MANAGER_H #define KATE_SCRIPT_MANAGER_H #include #include #include #include "katescript.h" #include "kateindentscript.h" #include "katecommandlinescript.h" class QString; /** * Manage the scripts on disks -- find them and query them. * Provides access to loaded scripts too. */ class KateScriptManager : public KTextEditor::Command { Q_OBJECT KateScriptManager(); static KateScriptManager *m_instance; public: virtual ~KateScriptManager(); /** Get all scripts available in the command line */ const QVector &commandLineScripts() { return m_commandLineScripts; } /** * Get an indentation script for the given language -- if there is more than * one result, this will return the script with the highest priority. If * both have the same priority, an arbitrary script will be returned. * If no scripts are found, 0 is returned. */ KateIndentScript *indenter(const QString &language); // // KTextEditor::Command // public: /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &) Q_DECL_OVERRIDE; /** * get help for a command * @param view view to use * @param cmd cmd name * @param msg help message * @return help available or not */ bool help(KTextEditor::View *view, const QString &cmd, QString &msg) Q_DECL_OVERRIDE; static KateScriptManager *self() { - if (m_instance == 0) { + if (m_instance == nullptr) { m_instance = new KateScriptManager(); } return m_instance; } // // Helper methods // public: /** * Collect all scripts. */ void collect(); public: KateIndentScript *indentationScript(const QString &scriptname) { return m_indentationScriptMap.value(scriptname); } int indentationScriptCount() { return m_indentationScripts.size(); } KateIndentScript *indentationScriptByIndex(int index) { return m_indentationScripts[index]; } public: /** explicitly reload all scripts */ void reload(); Q_SIGNALS: /** this signal is emitted when all scripts are _deleted_ and reloaded again. */ void reloaded(); private: /** List of all command line scripts */ QVector m_commandLineScripts; /** list of all indentation scripts */ QList m_indentationScripts; /** hash of all existing indenter scripts, hashes basename -> script */ QHash m_indentationScriptMap; /** Map of language to indent scripts */ QHash > m_languageToIndenters; }; #endif diff --git a/src/script/katescriptview.cpp b/src/script/katescriptview.cpp index dd4bed2c..a43d1626 100644 --- a/src/script/katescriptview.cpp +++ b/src/script/katescriptview.cpp @@ -1,111 +1,111 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2008 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) version 3. // // 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 "katescriptview.h" #include "katedocument.h" #include "kateview.h" #include "katerenderer.h" #include "katescript.h" KateScriptView::KateScriptView(QObject *parent) - : QObject(parent), m_view(0) + : QObject(parent), m_view(nullptr) { } void KateScriptView::setView(KTextEditor::ViewPrivate *view) { m_view = view; } KTextEditor::ViewPrivate *KateScriptView::view() { return m_view; } KTextEditor::Cursor KateScriptView::cursorPosition() { return m_view->cursorPosition(); } void KateScriptView::setCursorPosition(int line, int column) { KTextEditor::Cursor c(line, column); m_view->setCursorPosition(c); } void KateScriptView::setCursorPosition(const KTextEditor::Cursor &cursor) { m_view->setCursorPosition(cursor); } KTextEditor::Cursor KateScriptView::virtualCursorPosition() { return m_view->cursorPositionVirtual(); } void KateScriptView::setVirtualCursorPosition(int line, int column) { setVirtualCursorPosition(KTextEditor::Cursor(line, column)); } void KateScriptView::setVirtualCursorPosition(const KTextEditor::Cursor &cursor) { m_view->setCursorPositionVisual(cursor); } QString KateScriptView::selectedText() { return m_view->selectionText(); } bool KateScriptView::hasSelection() { return m_view->selection(); } KTextEditor::Range KateScriptView::selection() { return m_view->selectionRange(); } void KateScriptView::setSelection(const KTextEditor::Range &range) { m_view->setSelection(range); } void KateScriptView::removeSelectedText() { m_view->removeSelectedText(); } void KateScriptView::selectAll() { m_view->selectAll(); } void KateScriptView::clearSelection() { m_view->clearSelection(); } void KateScriptView::align(const KTextEditor::Range &range) { m_view->doc()->align (m_view, range); } diff --git a/src/script/katescriptview.h b/src/script/katescriptview.h index 796ff3f1..7efd4ce0 100644 --- a/src/script/katescriptview.h +++ b/src/script/katescriptview.h @@ -1,80 +1,80 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2008 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) version 3. // // 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_SCRIPT_VIEW_H #define KATE_SCRIPT_VIEW_H #include #include #include #include #include namespace KTextEditor { class ViewPrivate; } /** * Thinish wrapping around KTextEditor::ViewPrivate, exposing the methods we want exposed * and adding some helper methods. * * We inherit from QScriptable to have more thight access to the scripting * engine. * * setView _must_ be called before using any other method. This is not checked * for the sake of speed. */ class KTEXTEDITOR_EXPORT KateScriptView : public QObject, protected QScriptable { /// Properties are accessible with a nicer syntax from JavaScript Q_OBJECT public: - KateScriptView(QObject *parent = 0); + KateScriptView(QObject *parent = nullptr); void setView(KTextEditor::ViewPrivate *view); KTextEditor::ViewPrivate *view(); Q_INVOKABLE KTextEditor::Cursor cursorPosition(); /** * Set the cursor position in the view. * @since 4.4 */ Q_INVOKABLE void setCursorPosition(int line, int column); Q_INVOKABLE void setCursorPosition(const KTextEditor::Cursor &cursor); Q_INVOKABLE KTextEditor::Cursor virtualCursorPosition(); Q_INVOKABLE void setVirtualCursorPosition(int line, int column); Q_INVOKABLE void setVirtualCursorPosition(const KTextEditor::Cursor &cursor); Q_INVOKABLE QString selectedText(); Q_INVOKABLE bool hasSelection(); Q_INVOKABLE KTextEditor::Range selection(); Q_INVOKABLE void setSelection(const KTextEditor::Range &range); Q_INVOKABLE void removeSelectedText(); Q_INVOKABLE void selectAll(); Q_INVOKABLE void clearSelection(); Q_INVOKABLE void align(const KTextEditor::Range &range); private: KTextEditor::ViewPrivate *m_view; }; #endif diff --git a/src/search/kateplaintextsearch.h b/src/search/kateplaintextsearch.h index 38d6659d..afb7f56c 100644 --- a/src/search/kateplaintextsearch.h +++ b/src/search/kateplaintextsearch.h @@ -1,72 +1,70 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2009-2010 Bernhard Beschow * Copyright (C) 2007 Sebastian Pipping * * 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_PLAINTEXTSEARCH_H_ #define _KATE_PLAINTEXTSEARCH_H_ #include #include #include namespace KTextEditor { class Document; } /** * Object to help to search for plain text. * This should be NO QObject, it is created too often! * I measured that, if you create it 20k times to replace for example " " in a document, that takes seconds on a modern machine! */ class KTEXTEDITOR_EXPORT KatePlainTextSearch { public: explicit KatePlainTextSearch(const KTextEditor::Document *document, Qt::CaseSensitivity caseSensitivity, bool wholeWords); ~KatePlainTextSearch(); public: /** * Search for the given \p text inside the range \p inputRange taking * into account whether to search \p casesensitive and \p backwards. * * \param text text to search for * \param inputRange Range to search in - * \param casesensitive if \e true, the search is performed case - * sensitive, otherwise case insensitive * \param backwards if \e true, the search will be backwards * \return The valid range of the matched text if \p text was found. If * the \p text was not found, the returned range is not valid * (see Range::isValid()). * \see KTextEditor::Range */ KTextEditor::Range search(const QString &text, const KTextEditor::Range &inputRange, bool backwards = false); private: const KTextEditor::Document *m_document; Qt::CaseSensitivity m_caseSensitivity; bool m_wholeWords; }; #endif diff --git a/src/search/kateregexp.cpp b/src/search/kateregexp.cpp index 7a4ee334..7399c3e6 100644 --- a/src/search/kateregexp.cpp +++ b/src/search/kateregexp.cpp @@ -1,301 +1,305 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2009 Bernhard Beschow * Copyright (C) 2007 Sebastian Pipping * * 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 "kateregexp.h" KateRegExp::KateRegExp(const QString &pattern, Qt::CaseSensitivity cs, QRegExp::PatternSyntax syntax) : m_regExp(pattern, cs, syntax) { } // these things can besides '.' and '\s' make apptern multi-line: // \n, \x000A, \x????-\x????, \0012, \0???-\0??? // a multi-line pattern must not pass as single-line, the other // way around will just result in slower searches and is therefore // not as critical int KateRegExp::repairPattern(bool &stillMultiLine) { const QString &text = pattern(); // read-only input for parsing // get input const int inputLen = text.length(); int input = 0; // walker index // prepare output QString output; output.reserve(2 * inputLen + 1); // twice should be enough for the average case // parser state stillMultiLine = false; int replaceCount = 0; bool insideClass = false; while (input < inputLen) { if (insideClass) { // wait for closing, unescaped ']' switch (text[input].unicode()) { case L'\\': switch (text[input + 1].unicode()) { case L'x': if (input + 5 < inputLen) { // copy "\x????" unmodified output.append(text.midRef(input, 6)); input += 6; } else { // copy "\x" unmodified output.append(text.midRef(input, 2)); input += 2; } stillMultiLine = true; break; case L'0': if (input + 4 < inputLen) { // copy "\0???" unmodified output.append(text.midRef(input, 5)); input += 5; } else { // copy "\0" unmodified output.append(text.midRef(input, 2)); input += 2; } stillMultiLine = true; break; case L's': // replace "\s" with "[ \t]" output.append(QLatin1String(" \\t")); input += 2; replaceCount++; break; case L'n': stillMultiLine = true; // FALLTROUGH - +#if QT_VERSION >= QT_VERSION_CHECK(5,8,0) + Q_FALLTHROUGH(); +#endif default: // copy "\?" unmodified output.append(text.midRef(input, 2)); input += 2; } break; case L']': // copy "]" unmodified insideClass = false; output.append(text[input]); input++; break; default: // copy "?" unmodified output.append(text[input]); input++; } } else { // search for real dots and \S switch (text[input].unicode()) { case L'\\': switch (text[input + 1].unicode()) { case L'x': if (input + 5 < inputLen) { // copy "\x????" unmodified output.append(text.midRef(input, 6)); input += 6; } else { // copy "\x" unmodified output.append(text.midRef(input, 2)); input += 2; } stillMultiLine = true; break; case L'0': if (input + 4 < inputLen) { // copy "\0???" unmodified output.append(text.midRef(input, 5)); input += 5; } else { // copy "\0" unmodified output.append(text.midRef(input, 2)); input += 2; } stillMultiLine = true; break; case L's': // replace "\s" with "[ \t]" output.append(QLatin1String("[ \\t]")); input += 2; replaceCount++; break; case L'n': stillMultiLine = true; // FALLTROUGH - +#if QT_VERSION >= QT_VERSION_CHECK(5,8,0) + Q_FALLTHROUGH(); +#endif default: // copy "\?" unmodified output.append(text.midRef(input, 2)); input += 2; } break; case L'.': // replace " with "[^\n]" output.append(QLatin1String("[^\\n]")); input++; replaceCount++; break; case L'[': // copy "]" unmodified insideClass = true; output.append(text[input]); input++; break; default: // copy "?" unmodified output.append(text[input]); input++; } } } // Overwrite with repaired pattern m_regExp.setPattern(output); return replaceCount; } bool KateRegExp::isMultiLine() const { const QString &text = pattern(); // parser state bool insideClass = false; for (int input = 0; input < text.length(); /*empty*/) { if (insideClass) { // wait for closing, unescaped ']' switch (text[input].unicode()) { case L'\\': switch (text[input + 1].unicode()) { case L'x': return true; case L'0': return true; case L's': // replace "\s" with "[ \t]" input += 2; break; case L'n': return true; // FALLTROUGH default: // copy "\?" unmodified input += 2; } break; case L']': // copy "]" unmodified insideClass = false; input++; break; default: // copy "?" unmodified input++; } } else { // search for real dots and \S switch (text[input].unicode()) { case L'\\': switch (text[input + 1].unicode()) { case L'x': return true; case L'0': return true; case L's': // replace "\s" with "[ \t]" input += 2; break; case L'n': return true; default: // copy "\?" unmodified input += 2; } break; case L'.': // replace " with "[^\n]" input++; break; case L'[': // copy "]" unmodified insideClass = true; input++; break; default: // copy "?" unmodified input++; } } } return false; } int KateRegExp::indexIn(const QString &str, int start, int end) const { return m_regExp.indexIn(str.left(end), start, QRegExp::CaretAtZero); } int KateRegExp::lastIndexIn(const QString &str, int start, int end) const { const int index = m_regExp.lastIndexIn(str.mid(start, end - start), -1, QRegExp::CaretAtZero); if (index == -1) { return -1; } const int index2 = m_regExp.indexIn(str.left(end), start + index, QRegExp::CaretAtZero); return index2; } diff --git a/src/search/kateregexpsearch.h b/src/search/kateregexpsearch.h index 236e52f0..4b3b8ace 100644 --- a/src/search/kateregexpsearch.h +++ b/src/search/kateregexpsearch.h @@ -1,107 +1,107 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2010 Bernhard Beschow * Copyright (C) 2007 Sebastian Pipping * * 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_REGEXPSEARCH_H_ #define _KATE_REGEXPSEARCH_H_ #include #include #include namespace KTextEditor { class Document; } /** * Object to help to search for regexp. * This should be NO QObject, it is created to often! * I measured that, if you create it 20k times to replace for example " " in a document, that takes seconds on a modern machine! */ class KTEXTEDITOR_EXPORT KateRegExpSearch { public: explicit KateRegExpSearch(const KTextEditor::Document *document, Qt::CaseSensitivity caseSensitivity); ~KateRegExpSearch(); // // KTextEditor::SearchInterface stuff // public: /** - * Search for the regular expression \p regexp inside the range + * Search for the regular expression \p pattern inside the range * \p inputRange. If \p backwards is \e true, the search direction will * be reversed. * - * \param regexp text to search for + * \param pattern text to search for * \param inputRange Range to search in * \param backwards if \e true, the search will be backwards * \return Vector of ranges, one for each capture. The first range (index zero) * spans the full match. If the pattern does not match the vector * has length 1 and holds the invalid range (see Range::isValid()). * \see KTextEditor::Range, QRegExp */ QVector search(const QString &pattern, const KTextEditor::Range &inputRange, bool backwards = false); /** * Returns a modified version of text where escape sequences are resolved, e.g. "\\n" to "\n". * * \param text text containing escape sequences * \return text with resolved escape sequences */ static QString escapePlaintext(const QString &text); /** * Returns a modified version of text where * \li escape sequences are resolved, e.g. "\\n" to "\n", * \li references are resolved, e.g. "\\1" to 1st entry in capturedTexts, and * \li counter sequences are resolved, e.g. "\\#...#" to replacementCounter. * * \param text text containing escape sequences, references, and counter sequences * \param capturedTexts list of substitutes for references * \param replacementCounter value for replacement counter * \return resolved text */ static QString buildReplacement(const QString &text, const QStringList &capturedTexts, int replacementCounter); private: /** * Implementation of escapePlainText() and public buildReplacement(). * * \param text text containing escape sequences and possibly references and counters * \param capturedTexts list of substitutes for references * \param replacementCounter value for replacement counter (only used when replacementGoodies == true) * \param replacementGoodies true for buildReplacement(), false for escapePlainText() * \return resolved text */ static QString buildReplacement(const QString &text, const QStringList &capturedTexts, int replacementCounter, bool replacementGoodies); private: const KTextEditor::Document *const m_document; Qt::CaseSensitivity m_caseSensitivity; class ReplacementStream; }; #endif diff --git a/src/search/katesearchbar.cpp b/src/search/katesearchbar.cpp index ae51663a..757416cb 100644 --- a/src/search/katesearchbar.cpp +++ b/src/search/katesearchbar.cpp @@ -1,1627 +1,1651 @@ /* This file is part of the KDE libraries Copyright (C) 2009-2010 Bernhard Beschow Copyright (C) 2007 Sebastian Pipping Copyright (C) 2007 Matthew Woehlke Copyright (C) 2007 Thomas Friedrichsmeier 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. */ #include "katesearchbar.h" #include "kateregexp.h" #include "katematch.h" #include "kateview.h" #include "katedocument.h" #include "kateundomanager.h" #include "kateconfig.h" #include "katerenderer.h" #include "kateglobal.h" #include #include #include #include "ui_searchbarincremental.h" #include "ui_searchbarpower.h" #include #include #include #include #include #include #include #include #include #include // Turn debug messages on/off here // #define FAST_DEBUG_ENABLE #ifdef FAST_DEBUG_ENABLE # define FAST_DEBUG(x) qCDebug(LOG_KTE) << x #else # define FAST_DEBUG(x) #endif using namespace KTextEditor; namespace { class AddMenuManager { private: QVector m_insertBefore; QVector m_insertAfter; QSet m_actionPointers; uint m_indexWalker; QMenu *m_menu; public: AddMenuManager(QMenu *parent, int expectedItemCount) : m_insertBefore(QVector(expectedItemCount)), m_insertAfter(QVector(expectedItemCount)), m_indexWalker(0), - m_menu(NULL) + m_menu(nullptr) { - Q_ASSERT(parent != NULL); + Q_ASSERT(parent != nullptr); m_menu = parent->addMenu(i18n("Add...")); - if (m_menu == NULL) { + if (m_menu == nullptr) { return; } m_menu->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); } void enableMenu(bool enabled) { - if (m_menu == NULL) { + if (m_menu == nullptr) { return; } m_menu->setEnabled(enabled); } void addEntry(const QString &before, const QString after, const QString description, const QString &realBefore = QString(), const QString &realAfter = QString()) { - if (m_menu == NULL) { + if (m_menu == nullptr) { return; } QAction *const action = m_menu->addAction(before + after + QLatin1Char('\t') + description); m_insertBefore[m_indexWalker] = QString(realBefore.isEmpty() ? before : realBefore); m_insertAfter[m_indexWalker] = QString(realAfter.isEmpty() ? after : realAfter); action->setData(QVariant(m_indexWalker++)); m_actionPointers.insert(action); } void addSeparator() { - if (m_menu == NULL) { + if (m_menu == nullptr) { return; } m_menu->addSeparator(); } void handle(QAction *action, QLineEdit *lineEdit) { if (!m_actionPointers.contains(action)) { return; } const int cursorPos = lineEdit->cursorPosition(); const int index = action->data().toUInt(); const QString &before = m_insertBefore[index]; const QString &after = m_insertAfter[index]; lineEdit->insert(before + after); lineEdit->setCursorPosition(cursorPos + before.count()); lineEdit->setFocus(); } }; } // anon namespace KateSearchBar::KateSearchBar(bool initAsPower, KTextEditor::ViewPrivate *view, KateViewConfig *config) : KateViewBarWidget(true, view), m_view(view), m_config(config), m_layout(new QVBoxLayout()), - m_widget(NULL), - m_incUi(NULL), + m_widget(nullptr), + m_incUi(nullptr), m_incInitCursor(view->cursorPosition()), - m_powerUi(NULL), + m_powerUi(nullptr), highlightMatchAttribute(new Attribute()), highlightReplacementAttribute(new Attribute()), m_incHighlightAll(false), m_incFromCursor(true), m_incMatchCase(false), m_powerMatchCase(true), m_powerFromCursor(false), m_powerHighlightAll(false), m_powerMode(0) { connect(view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updateIncInitCursor())); // init match attribute Attribute::Ptr mouseInAttribute(new Attribute()); mouseInAttribute->setFontBold(true); highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateMouseIn, mouseInAttribute); Attribute::Ptr caretInAttribute(new Attribute()); caretInAttribute->setFontItalic(true); highlightMatchAttribute->setDynamicAttribute(Attribute::ActivateCaretIn, caretInAttribute); updateHighlightColors(); // Modify parent QWidget *const widget = centralWidget(); widget->setLayout(m_layout); m_layout->setMargin(0); // allow to have small size, for e.g. Kile setMinimumWidth(100); // Copy global to local config backup const long searchFlags = m_config->searchFlags(); m_incHighlightAll = (searchFlags & KateViewConfig::IncHighlightAll) != 0; m_incFromCursor = (searchFlags & KateViewConfig::IncFromCursor) != 0; m_incMatchCase = (searchFlags & KateViewConfig::IncMatchCase) != 0; m_powerMatchCase = (searchFlags & KateViewConfig::PowerMatchCase) != 0; m_powerFromCursor = (searchFlags & KateViewConfig::PowerFromCursor) != 0; m_powerHighlightAll = (searchFlags & KateViewConfig::PowerHighlightAll) != 0; m_powerMode = ((searchFlags & KateViewConfig::PowerModeRegularExpression) != 0) ? MODE_REGEX : (((searchFlags & KateViewConfig::PowerModeEscapeSequences) != 0) ? MODE_ESCAPE_SEQUENCES : (((searchFlags & KateViewConfig::PowerModeWholeWords) != 0) ? MODE_WHOLE_WORDS : MODE_PLAIN_TEXT)); // Load one of either dialogs if (initAsPower) { enterPowerMode(); } else { enterIncrementalMode(); } updateSelectionOnly(); connect(view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); } KateSearchBar::~KateSearchBar() { clearHighlights(); delete m_layout; delete m_widget; delete m_incUi; delete m_powerUi; } void KateSearchBar::closed() { // remove search from the view bar, because it vertically bloats up the // stacked layout in KateViewBar. if (viewBar()) { viewBar()->removeBarWidget(this); } clearHighlights(); } void KateSearchBar::setReplacementPattern(const QString &replacementPattern) { Q_ASSERT(isPower()); if (this->replacementPattern() == replacementPattern) { return; } m_powerUi->replacement->setEditText(replacementPattern); } QString KateSearchBar::replacementPattern() const { Q_ASSERT(isPower()); return m_powerUi->replacement->currentText(); } void KateSearchBar::setSearchMode(KateSearchBar::SearchMode mode) { Q_ASSERT(isPower()); m_powerUi->searchMode->setCurrentIndex(mode); } void KateSearchBar::findNext() { const bool found = find(); if (found) { - QComboBox *combo = m_powerUi != 0 ? m_powerUi->pattern : m_incUi->pattern; + QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern; // Add to search history addCurrentTextToHistory(combo); } } void KateSearchBar::findPrevious() { const bool found = find(SearchBackward); if (found) { - QComboBox *combo = m_powerUi != 0 ? m_powerUi->pattern : m_incUi->pattern; + QComboBox *combo = m_powerUi != nullptr ? m_powerUi->pattern : m_incUi->pattern; // Add to search history addCurrentTextToHistory(combo); } } void KateSearchBar::showInfoMessage(const QString &text) { delete m_infoMessage; m_infoMessage = new KTextEditor::Message(text, KTextEditor::Message::Positive); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(3000); // 3 seconds m_infoMessage->setView(m_view); m_view->doc()->postMessage(m_infoMessage); } void KateSearchBar::highlightMatch(const Range &range) { KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand); highlight->setView(m_view); // show only in this view highlight->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface highlight->setZDepth(-10000.0); highlight->setAttribute(highlightMatchAttribute); m_hlRanges.append(highlight); } void KateSearchBar::highlightReplacement(const Range &range) { KTextEditor::MovingRange *const highlight = m_view->doc()->newMovingRange(range, Kate::TextRange::DoNotExpand); highlight->setView(m_view); // show only in this view highlight->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface highlight->setZDepth(-10000.0); highlight->setAttribute(highlightReplacementAttribute); m_hlRanges.append(highlight); } void KateSearchBar::indicateMatch(MatchResult matchResult) { QLineEdit *const lineEdit = isPower() ? m_powerUi->pattern->lineEdit() : m_incUi->pattern->lineEdit(); QPalette background(lineEdit->palette()); switch (matchResult) { case MatchFound: // FALLTHROUGH case MatchWrappedForward: case MatchWrappedBackward: // Green background for line edit KColorScheme::adjustBackground(background, KColorScheme::PositiveBackground); break; case MatchMismatch: // Red background for line edit KColorScheme::adjustBackground(background, KColorScheme::NegativeBackground); break; case MatchNothing: // Reset background of line edit background = QPalette(); break; case MatchNeutral: KColorScheme::adjustBackground(background, KColorScheme::NeutralBackground); break; } // Update status label - if (m_incUi != NULL) { + if (m_incUi != nullptr) { QPalette foreground(m_incUi->status->palette()); switch (matchResult) { case MatchFound: // FALLTHROUGH case MatchNothing: KColorScheme::adjustForeground(foreground, KColorScheme::NormalText, QPalette::WindowText, KColorScheme::Window); m_incUi->status->clear(); break; case MatchWrappedForward: case MatchWrappedBackward: KColorScheme::adjustForeground(foreground, KColorScheme::NormalText, QPalette::WindowText, KColorScheme::Window); if (matchResult == MatchWrappedBackward) { m_incUi->status->setText(i18n("Reached top, continued from bottom")); } else { m_incUi->status->setText(i18n("Reached bottom, continued from top")); } break; case MatchMismatch: KColorScheme::adjustForeground(foreground, KColorScheme::NegativeText, QPalette::WindowText, KColorScheme::Window); m_incUi->status->setText(i18n("Not found")); break; case MatchNeutral: /* do nothing */ break; } m_incUi->status->setPalette(foreground); } lineEdit->setPalette(background); } /*static*/ void KateSearchBar::selectRange(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range) { view->setCursorPositionInternal(range.end()); view->setSelection(range); } void KateSearchBar::selectRange2(const KTextEditor::Range &range) { disconnect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); selectRange(m_view, range); connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); } void KateSearchBar::onIncPatternChanged(const QString &pattern) { if (!m_incUi) { return; } // clear prior highlightings (deletes info message if present) clearHighlights(); m_incUi->next->setDisabled(pattern.isEmpty()); m_incUi->prev->setDisabled(pattern.isEmpty()); KateMatch match(m_view->doc(), searchOptions()); if (!pattern.isEmpty()) { // Find, first try const Range inputRange = KTextEditor::Range(m_incInitCursor, m_view->document()->documentEnd()); match.searchText(inputRange, pattern); } const bool wrap = !match.isValid() && !pattern.isEmpty(); if (wrap) { // Find, second try const KTextEditor::Range inputRange = m_view->document()->documentRange(); match.searchText(inputRange, pattern); } const MatchResult matchResult = match.isValid() ? (wrap ? MatchWrappedForward : MatchFound) : pattern.isEmpty() ? MatchNothing : MatchMismatch; const Range selectionRange = pattern.isEmpty() ? Range(m_incInitCursor, m_incInitCursor) : match.isValid() ? match.range() : Range::invalid(); // don't update m_incInitCursor when we move the cursor disconnect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updateIncInitCursor())); selectRange2(selectionRange); connect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(updateIncInitCursor())); indicateMatch(matchResult); } void KateSearchBar::setMatchCase(bool matchCase) { if (this->matchCase() == matchCase) { return; } if (isPower()) { m_powerUi->matchCase->setChecked(matchCase); } else { m_incUi->matchCase->setChecked(matchCase); } } void KateSearchBar::onMatchCaseToggled(bool /*matchCase*/) { sendConfig(); - if (m_incUi != 0) { + if (m_incUi != nullptr) { // Re-search with new settings const QString pattern = m_incUi->pattern->currentText(); onIncPatternChanged(pattern); } else { indicateMatch(MatchNothing); } } bool KateSearchBar::matchCase() const { return isPower() ? m_powerUi->matchCase->isChecked() : m_incUi->matchCase->isChecked(); } void KateSearchBar::fixForSingleLine(Range &range, SearchDirection searchDirection) { FAST_DEBUG("Single-line workaround checking BEFORE" << range); if (searchDirection == SearchForward) { const int line = range.start().line(); const int col = range.start().column(); const int maxColWithNewline = m_view->document()->lineLength(line) + 1; if (col == maxColWithNewline) { FAST_DEBUG("Starting on a newline" << range); const int maxLine = m_view->document()->lines() - 1; if (line < maxLine) { range.setRange(Cursor(line + 1, 0), range.end()); FAST_DEBUG("Search range fixed to " << range); } else { FAST_DEBUG("Already at last line"); range = Range::invalid(); } } } else { const int col = range.end().column(); if (col == 0) { FAST_DEBUG("Ending after a newline" << range); const int line = range.end().line(); if (line > 0) { const int maxColWithNewline = m_view->document()->lineLength(line - 1); range.setRange(range.start(), Cursor(line - 1, maxColWithNewline)); FAST_DEBUG("Search range fixed to " << range); } else { FAST_DEBUG("Already at first line"); range = Range::invalid(); } } } FAST_DEBUG("Single-line workaround checking AFTER" << range); } void KateSearchBar::onReturnPressed() { const Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); const bool shiftDown = (modifiers & Qt::ShiftModifier) != 0; const bool controlDown = (modifiers & Qt::ControlModifier) != 0; if (shiftDown) { // Shift down, search backwards findPrevious(); } else { // Shift up, search forwards findNext(); } if (controlDown) { emit hideMe(); } } bool KateSearchBar::find(SearchDirection searchDirection, const QString *replacement) { // What to find? if (searchPattern().isEmpty()) { return false; // == Pattern error } // don't let selectionChanged signal mess around in this routine disconnect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); // clear previous highlights if there are any clearHighlights(); const SearchOptions enabledOptions = searchOptions(searchDirection); // Where to find? Range inputRange; const Range selection = m_view->selection() ? m_view->selectionRange() : Range::invalid(); if (selection.isValid()) { if (selectionOnly()) { // First match in selection inputRange = selection; } else { // Next match after/before selection if a match was selected before if (searchDirection == SearchForward) { inputRange.setRange(selection.start(), m_view->document()->documentEnd()); } else { inputRange.setRange(Cursor(0, 0), selection.end()); } } } else { // No selection const Cursor cursorPos = m_view->cursorPosition(); if (searchDirection == SearchForward) { inputRange.setRange(cursorPos, m_view->document()->documentEnd()); } else { inputRange.setRange(Cursor(0, 0), cursorPos); } } FAST_DEBUG("Search range is" << inputRange); { const bool regexMode = enabledOptions.testFlag(Regex); const bool multiLinePattern = regexMode ? KateRegExp(searchPattern()).isMultiLine() : false; // Single-line pattern workaround if (regexMode && !multiLinePattern) { fixForSingleLine(inputRange, searchDirection); } } KateMatch match(m_view->doc(), enabledOptions); Range afterReplace = Range::invalid(); // Find, first try match.searchText(inputRange, searchPattern()); if (match.isValid() && match.range() == selection) { // Same match again - if (replacement != 0) { + if (replacement != nullptr) { // Selection is match -> replace KTextEditor::MovingRange *smartInputRange = m_view->doc()->newMovingRange(inputRange, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); afterReplace = match.replace(*replacement, m_view->blockSelection()); inputRange = *smartInputRange; delete smartInputRange; } if (!selectionOnly()) { // Find, second try after old selection if (searchDirection == SearchForward) { - const Cursor start = (replacement != 0) ? afterReplace.end() : selection.end(); + const Cursor start = (replacement != nullptr) ? afterReplace.end() : selection.end(); inputRange.setRange(start, inputRange.end()); } else { - const Cursor end = (replacement != 0) ? afterReplace.start() : selection.start(); + const Cursor end = (replacement != nullptr) ? afterReplace.start() : selection.start(); inputRange.setRange(inputRange.start(), end); } } // Single-line pattern workaround fixForSingleLine(inputRange, searchDirection); match.searchText(inputRange, searchPattern()); } bool askWrap = !match.isValid() && (!selection.isValid() || !selectionOnly()); bool wrap = false; if (askWrap) { askWrap = false; wrap = true; } if (askWrap) { QString question = searchDirection == SearchForward ? i18n("Bottom of file reached. Continue from top?") : i18n("Top of file reached. Continue from bottom?"); - wrap = (KMessageBox::questionYesNo(0, question, i18n("Continue search?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), + wrap = (KMessageBox::questionYesNo(nullptr, question, i18n("Continue search?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("DoNotShowAgainContinueSearchDialog")) == KMessageBox::Yes); } if (wrap) { // show message widget when wrapping (if not already present) if (searchDirection == SearchForward && !m_wrappedTopMessage) { const QString msg = i18n("Continuing search from top"); m_wrappedTopMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_wrappedTopMessage->setPosition(KTextEditor::Message::TopInView); m_wrappedTopMessage->setAutoHide(2000); m_wrappedTopMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_wrappedTopMessage->setView(m_view); m_view->doc()->postMessage(m_wrappedTopMessage); } else if (searchDirection == SearchBackward && !m_wrappedBottomMessage) { const QString msg = i18n("Continuing search from bottom"); m_wrappedBottomMessage = new KTextEditor::Message(msg, KTextEditor::Message::Information); m_wrappedBottomMessage->setPosition(KTextEditor::Message::BottomInView); m_wrappedBottomMessage->setAutoHide(2000); m_wrappedBottomMessage->setAutoHideMode(KTextEditor::Message::Immediate); m_wrappedBottomMessage->setView(m_view); m_view->doc()->postMessage(m_wrappedBottomMessage); } inputRange = m_view->document()->documentRange(); match.searchText(inputRange, searchPattern()); } if (match.isValid()) { selectRange2(match.range()); } const MatchResult matchResult = !match.isValid() ? MatchMismatch : !wrap ? MatchFound : searchDirection == SearchForward ? MatchWrappedForward : MatchWrappedBackward; indicateMatch(matchResult); // highlight replacements if applicable if (afterReplace.isValid()) { highlightReplacement(afterReplace); } // restore connection connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); return true; // == No pattern error } void KateSearchBar::findAll() { // clear highlightings of prior search&replace action clearHighlights(); Range inputRange = (m_view->selection() && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange(); - const int occurrences = findAll(inputRange, NULL); + const int occurrences = findAll(inputRange, nullptr); // send passive notification to view showInfoMessage(i18ncp("short translation", "1 match found", "%1 matches found", occurrences)); indicateMatch(occurrences > 0 ? MatchFound : MatchMismatch); } void KateSearchBar::onPowerPatternChanged(const QString & /*pattern*/) { givePatternFeedback(); indicateMatch(MatchNothing); } bool KateSearchBar::isPatternValid() const { if (searchPattern().isEmpty()) { return false; } return searchOptions().testFlag(WholeWords) ? searchPattern().trimmed() == searchPattern() : searchOptions().testFlag(Regex) ? QRegExp(searchPattern()).isValid() : true; } void KateSearchBar::givePatternFeedback() { // Enable/disable next/prev and replace next/all m_powerUi->findNext->setEnabled(isPatternValid()); m_powerUi->findPrev->setEnabled(isPatternValid()); m_powerUi->replaceNext->setEnabled(isPatternValid()); m_powerUi->replaceAll->setEnabled(isPatternValid()); m_powerUi->findAll->setEnabled(isPatternValid()); } void KateSearchBar::addCurrentTextToHistory(QComboBox *combo) { const QString text = combo->currentText(); const int index = combo->findText(text); if (index > 0) { combo->removeItem(index); } if (index != 0) { combo->insertItem(0, text); combo->setCurrentIndex(0); } // sync to application config KTextEditor::EditorPrivate::self()->saveSearchReplaceHistoryModels(); } void KateSearchBar::backupConfig(bool ofPower) { if (ofPower) { m_powerMatchCase = m_powerUi->matchCase->isChecked(); m_powerMode = m_powerUi->searchMode->currentIndex(); } else { m_incMatchCase = m_incUi->matchCase->isChecked(); } } void KateSearchBar::sendConfig() { const long pastFlags = m_config->searchFlags(); long futureFlags = pastFlags; - if (m_powerUi != NULL) { + if (m_powerUi != nullptr) { const bool OF_POWER = true; backupConfig(OF_POWER); // Update power search flags only const long incFlagsOnly = pastFlags & (KateViewConfig::IncHighlightAll | KateViewConfig::IncFromCursor | KateViewConfig::IncMatchCase); futureFlags = incFlagsOnly | (m_powerMatchCase ? KateViewConfig::PowerMatchCase : 0) | (m_powerFromCursor ? KateViewConfig::PowerFromCursor : 0) | (m_powerHighlightAll ? KateViewConfig::PowerHighlightAll : 0) | ((m_powerMode == MODE_REGEX) ? KateViewConfig::PowerModeRegularExpression : ((m_powerMode == MODE_ESCAPE_SEQUENCES) ? KateViewConfig::PowerModeEscapeSequences : ((m_powerMode == MODE_WHOLE_WORDS) ? KateViewConfig::PowerModeWholeWords : KateViewConfig::PowerModePlainText))); - } else if (m_incUi != NULL) { + } else if (m_incUi != nullptr) { const bool OF_INCREMENTAL = false; backupConfig(OF_INCREMENTAL); // Update incremental search flags only const long powerFlagsOnly = pastFlags & (KateViewConfig::PowerMatchCase | KateViewConfig::PowerFromCursor | KateViewConfig::PowerHighlightAll | KateViewConfig::PowerModeRegularExpression | KateViewConfig::PowerModeEscapeSequences | KateViewConfig::PowerModeWholeWords | KateViewConfig::PowerModePlainText); futureFlags = powerFlagsOnly | (m_incHighlightAll ? KateViewConfig::IncHighlightAll : 0) | (m_incFromCursor ? KateViewConfig::IncFromCursor : 0) | (m_incMatchCase ? KateViewConfig::IncMatchCase : 0); } // Adjust global config m_config->setSearchFlags(futureFlags); } void KateSearchBar::replaceNext() { const QString replacement = m_powerUi->replacement->currentText(); if (find(SearchForward, &replacement)) { // Never merge replace actions with other replace actions/user actions m_view->doc()->undoManager()->undoSafePoint(); // Add to search history addCurrentTextToHistory(m_powerUi->pattern); // Add to replace history addCurrentTextToHistory(m_powerUi->replacement); } } // replacement == NULL --> Highlight all matches // replacement != NULL --> Replace and highlight all matches int KateSearchBar::findAll(Range inputRange, const QString *replacement) { // don't let selectionChanged signal mess around in this routine disconnect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); const SearchOptions enabledOptions = searchOptions(SearchForward); const bool regexMode = enabledOptions.testFlag(Regex); const bool multiLinePattern = regexMode ? KateRegExp(searchPattern()).isMultiLine() : false; KTextEditor::MovingRange *workingRange = m_view->doc()->newMovingRange(inputRange); QList highlightRanges; int matchCounter = 0; bool block = m_view->selection() && m_view->blockSelection(); int line = inputRange.start().line(); do { if (block) { workingRange = m_view->doc()->newMovingRange(m_view->doc()->rangeOnLine(inputRange, line)); } for (;;) { KateMatch match(m_view->doc(), enabledOptions); match.searchText(*workingRange, searchPattern()); if (!match.isValid()) { break; } bool const originalMatchEmpty = match.isEmpty(); // Work with the match - if (replacement != NULL) { + if (replacement != nullptr) { if (matchCounter == 0) { static_cast(m_view->document())->startEditing(); } // Replace const Range afterReplace = match.replace(*replacement, false, ++matchCounter); // Highlight and continue after adjusted match //highlightReplacement(*afterReplace); highlightRanges << afterReplace; } else { // Highlight and continue after original match //highlightMatch(match); highlightRanges << match.range(); matchCounter++; } // Continue after match if (highlightRanges.last().end() >= workingRange->end()) { break; } KTextEditor::DocumentCursor workingStart(m_view->doc(), highlightRanges.last().end()); if (originalMatchEmpty) { // Can happen for regex patterns like "^". // If we don't advance here we will loop forever... workingStart.move(1); } else if (regexMode && !multiLinePattern && workingStart.atEndOfLine()) { // single-line regexps might match the naked line end // therefore we better advance to the next line workingStart.move(1); } workingRange->setRange(workingStart.toCursor(), workingRange->end()); // Are we done? if (!workingRange->toRange().isValid() || workingStart.atEndOfDocument()) { break; } } } while (block && ++line <= inputRange.end().line()); // After last match if (matchCounter > 0) { - if (replacement != NULL) { + if (replacement != nullptr) { static_cast(m_view->document())->finishEditing(); } } - if (replacement == NULL) + // Add ScrollBarMarks + KTextEditor::MarkInterface* iface = qobject_cast(m_view->document()); + if (iface) { + iface->setMarkDescription(KTextEditor::MarkInterface::SearchMatch, i18n("SearchHighLight")); + iface->setMarkPixmap(KTextEditor::MarkInterface::SearchMatch, QIcon().pixmap(0,0)); + foreach (Range r, highlightRanges) { + iface->addMark(r.start().line(), KTextEditor::MarkInterface::SearchMatch); + } + } + + // Add highlights + if (replacement == nullptr) foreach (Range r, highlightRanges) { highlightMatch(r); } else foreach (Range r, highlightRanges) { highlightReplacement(r); } delete workingRange; // restore connection connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(updateSelectionOnly())); return matchCounter; } void KateSearchBar::replaceAll() { // clear prior highlightings (deletes info message if present) clearHighlights(); // What to find/replace? const QString replacement = m_powerUi->replacement->currentText(); // Where to replace? Range selection; const bool selected = m_view->selection(); Range inputRange = (selected && selectionOnly()) ? m_view->selectionRange() : m_view->document()->documentRange(); // Pass on the hard work int replacementsDone = findAll(inputRange, &replacement); // send passive notification to view showInfoMessage(i18ncp("short translation", "1 replacement made", "%1 replacements made", replacementsDone)); // Never merge replace actions with other replace actions/user actions m_view->doc()->undoManager()->undoSafePoint(); // Add to search history addCurrentTextToHistory(m_powerUi->pattern); // Add to replace history addCurrentTextToHistory(m_powerUi->replacement); } void KateSearchBar::setSearchPattern(const QString &searchPattern) { if (searchPattern == this->searchPattern()) { return; } if (isPower()) { m_powerUi->pattern->setEditText(searchPattern); } else { m_incUi->pattern->setEditText(searchPattern); } } QString KateSearchBar::searchPattern() const { - return (m_powerUi != 0) ? m_powerUi->pattern->currentText() + return (m_powerUi != nullptr) ? m_powerUi->pattern->currentText() : m_incUi->pattern->currentText(); } void KateSearchBar::setSelectionOnly(bool selectionOnly) { if (this->selectionOnly() == selectionOnly) { return; } if (isPower()) { m_powerUi->selectionOnly->setChecked(selectionOnly); } } bool KateSearchBar::selectionOnly() const { return isPower() ? m_powerUi->selectionOnly->isChecked() : false; } KTextEditor::SearchOptions KateSearchBar::searchOptions(SearchDirection searchDirection) const { SearchOptions enabledOptions = KTextEditor::Default; if (!matchCase()) { enabledOptions |= CaseInsensitive; } if (searchDirection == SearchBackward) { enabledOptions |= Backwards; } - if (m_powerUi != NULL) { + if (m_powerUi != nullptr) { switch (m_powerUi->searchMode->currentIndex()) { case MODE_WHOLE_WORDS: enabledOptions |= WholeWords; break; case MODE_ESCAPE_SEQUENCES: enabledOptions |= EscapeSequences; break; case MODE_REGEX: enabledOptions |= Regex; break; case MODE_PLAIN_TEXT: // FALLTHROUGH default: break; } } return enabledOptions; } struct ParInfo { int openIndex; bool capturing; int captureNumber; // 1..9 }; QVector KateSearchBar::getCapturePatterns(const QString &pattern) const { QVector capturePatterns; capturePatterns.reserve(9); QStack parInfos; const int inputLen = pattern.length(); int input = 0; // walker index bool insideClass = false; int captureCount = 0; while (input < inputLen) { if (insideClass) { // Wait for closing, unescaped ']' if (pattern[input].unicode() == L']') { insideClass = false; } input++; } else { switch (pattern[input].unicode()) { case L'\\': // Skip this and any next character input += 2; break; case L'(': ParInfo curInfo; curInfo.openIndex = input; curInfo.capturing = (input + 1 >= inputLen) || (pattern[input + 1].unicode() != '?'); if (curInfo.capturing) { captureCount++; } curInfo.captureNumber = captureCount; parInfos.push(curInfo); input++; break; case L')': if (!parInfos.empty()) { ParInfo &top = parInfos.top(); if (top.capturing && (top.captureNumber <= 9)) { const int start = top.openIndex + 1; const int len = input - start; if (capturePatterns.size() < top.captureNumber) { capturePatterns.resize(top.captureNumber); } capturePatterns[top.captureNumber - 1] = pattern.mid(start, len); } parInfos.pop(); } input++; break; case L'[': input++; insideClass = true; break; default: input++; break; } } } return capturePatterns; } void KateSearchBar::showExtendedContextMenu(bool forPattern, const QPoint &pos) { // Make original menu QComboBox *comboBox = forPattern ? m_powerUi->pattern : m_powerUi->replacement; QMenu *const contextMenu = comboBox->lineEdit()->createStandardContextMenu(); - if (contextMenu == NULL) { + if (contextMenu == nullptr) { return; } bool extendMenu = false; bool regexMode = false; switch (m_powerUi->searchMode->currentIndex()) { case MODE_REGEX: regexMode = true; // FALLTHROUGH case MODE_ESCAPE_SEQUENCES: extendMenu = true; break; default: break; } AddMenuManager addMenuManager(contextMenu, 37); if (!extendMenu) { addMenuManager.enableMenu(extendMenu); } else { // Build menu if (forPattern) { if (regexMode) { addMenuManager.addEntry(QStringLiteral("^"), QString(), i18n("Beginning of line")); addMenuManager.addEntry(QStringLiteral("$"), QString(), i18n("End of line")); addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("."), QString(), i18n("Any single character (excluding line breaks)")); addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("+"), QString(), i18n("One or more occurrences")); addMenuManager.addEntry(QStringLiteral("*"), QString(), i18n("Zero or more occurrences")); addMenuManager.addEntry(QStringLiteral("?"), QString(), i18n("Zero or one occurrences")); addMenuManager.addEntry(QStringLiteral("{a"), QStringLiteral(",b}"), i18n(" through occurrences"), QStringLiteral("{"), QStringLiteral(",}")); addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("("), QStringLiteral(")"), i18n("Group, capturing")); addMenuManager.addEntry(QStringLiteral("|"), QString(), i18n("Or")); addMenuManager.addEntry(QStringLiteral("["), QStringLiteral("]"), i18n("Set of characters")); addMenuManager.addEntry(QStringLiteral("[^"), QStringLiteral("]"), i18n("Negative set of characters")); addMenuManager.addSeparator(); } } else { addMenuManager.addEntry(QStringLiteral("\\0"), QString(), i18n("Whole match reference")); addMenuManager.addSeparator(); if (regexMode) { const QString pattern = m_powerUi->pattern->currentText(); const QVector capturePatterns = getCapturePatterns(pattern); const int captureCount = capturePatterns.count(); for (int i = 1; i <= 9; i++) { const QString number = QString::number(i); const QString &captureDetails = (i <= captureCount) ? (QString::fromLatin1(" = (") + capturePatterns[i - 1].left(30)) + QLatin1String(")") : QString(); addMenuManager.addEntry(QLatin1String("\\") + number, QString(), i18n("Reference") + QLatin1Char(' ') + number + captureDetails); } addMenuManager.addSeparator(); } } addMenuManager.addEntry(QStringLiteral("\\n"), QString(), i18n("Line break")); addMenuManager.addEntry(QStringLiteral("\\t"), QString(), i18n("Tab")); if (forPattern && regexMode) { addMenuManager.addEntry(QStringLiteral("\\b"), QString(), i18n("Word boundary")); addMenuManager.addEntry(QStringLiteral("\\B"), QString(), i18n("Not word boundary")); addMenuManager.addEntry(QStringLiteral("\\d"), QString(), i18n("Digit")); addMenuManager.addEntry(QStringLiteral("\\D"), QString(), i18n("Non-digit")); addMenuManager.addEntry(QStringLiteral("\\s"), QString(), i18n("Whitespace (excluding line breaks)")); addMenuManager.addEntry(QStringLiteral("\\S"), QString(), i18n("Non-whitespace (excluding line breaks)")); addMenuManager.addEntry(QStringLiteral("\\w"), QString(), i18n("Word character (alphanumerics plus '_')")); addMenuManager.addEntry(QStringLiteral("\\W"), QString(), i18n("Non-word character")); } addMenuManager.addEntry(QStringLiteral("\\0???"), QString(), i18n("Octal character 000 to 377 (2^8-1)"), QStringLiteral("\\0")); addMenuManager.addEntry(QStringLiteral("\\x????"), QString(), i18n("Hex character 0000 to FFFF (2^16-1)"), QStringLiteral("\\x")); addMenuManager.addEntry(QStringLiteral("\\\\"), QString(), i18n("Backslash")); if (forPattern && regexMode) { addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("(?:E"), QStringLiteral(")"), i18n("Group, non-capturing"), QStringLiteral("(?:")); addMenuManager.addEntry(QStringLiteral("(?=E"), QStringLiteral(")"), i18n("Lookahead"), QStringLiteral("(?=")); addMenuManager.addEntry(QStringLiteral("(?!E"), QStringLiteral(")"), i18n("Negative lookahead"), QStringLiteral("(?!")); } if (!forPattern) { addMenuManager.addSeparator(); addMenuManager.addEntry(QStringLiteral("\\L"), QString(), i18n("Begin lowercase conversion")); addMenuManager.addEntry(QStringLiteral("\\U"), QString(), i18n("Begin uppercase conversion")); addMenuManager.addEntry(QStringLiteral("\\E"), QString(), i18n("End case conversion")); addMenuManager.addEntry(QStringLiteral("\\l"), QString(), i18n("Lowercase first character conversion")); addMenuManager.addEntry(QStringLiteral("\\u"), QString(), i18n("Uppercase first character conversion")); addMenuManager.addEntry(QStringLiteral("\\#[#..]"), QString(), i18n("Replacement counter (for Replace All)"), QStringLiteral("\\#")); } } // Show menu QAction *const result = contextMenu->exec(comboBox->mapToGlobal(pos)); - if (result != NULL) { + if (result != nullptr) { addMenuManager.handle(result, comboBox->lineEdit()); } } void KateSearchBar::onPowerModeChanged(int /*index*/) { if (m_powerUi->searchMode->currentIndex() == MODE_REGEX) { m_powerUi->matchCase->setChecked(true); } sendConfig(); indicateMatch(MatchNothing); givePatternFeedback(); } /*static*/ void KateSearchBar::nextMatchForSelection(KTextEditor::ViewPrivate *view, SearchDirection searchDirection) { const bool selected = view->selection(); if (selected) { const QString pattern = view->selectionText(); // How to find? SearchOptions enabledOptions(KTextEditor::Default); if (searchDirection == SearchBackward) { enabledOptions |= Backwards; } // Where to find? const Range selRange = view->selectionRange(); Range inputRange; if (searchDirection == SearchForward) { inputRange.setRange(selRange.end(), view->doc()->documentEnd()); } else { inputRange.setRange(Cursor(0, 0), selRange.start()); } // Find, first try KateMatch match(view->doc(), enabledOptions); match.searchText(inputRange, pattern); if (match.isValid()) { selectRange(view, match.range()); } else { // Find, second try if (searchDirection == SearchForward) { inputRange.setRange(Cursor(0, 0), selRange.start()); } else { inputRange.setRange(selRange.end(), view->doc()->documentEnd()); } KateMatch match2(view->doc(), enabledOptions); match2.searchText(inputRange, pattern); if (match2.isValid()) { selectRange(view, match2.range()); } } } else { // Select current word so we can search for that the next time const Cursor cursorPos = view->cursorPosition(); KTextEditor::Range wordRange = view->document()->wordRangeAt(cursorPos); if (wordRange.isValid()) { selectRange(view, wordRange); } } } void KateSearchBar::enterPowerMode() { QString initialPattern; bool selectionOnly = false; // Guess settings from context: init pattern with current selection const bool selected = m_view->selection(); if (selected) { const Range &selection = m_view->selectionRange(); if (selection.onSingleLine()) { // ... with current selection initialPattern = m_view->selectionText(); } else { // Enable selection only selectionOnly = true; } } // If there's no new selection, we'll use the existing pattern if (initialPattern.isNull()) { // Coming from power search? - const bool fromReplace = (m_powerUi != NULL) && (m_widget->isVisible()); + const bool fromReplace = (m_powerUi != nullptr) && (m_widget->isVisible()); if (fromReplace) { QLineEdit *const patternLineEdit = m_powerUi->pattern->lineEdit(); - Q_ASSERT(patternLineEdit != NULL); + Q_ASSERT(patternLineEdit != nullptr); patternLineEdit->selectAll(); m_powerUi->pattern->setFocus(Qt::MouseFocusReason); return; } // Coming from incremental search? - const bool fromIncremental = (m_incUi != NULL) && (m_widget->isVisible()); + const bool fromIncremental = (m_incUi != nullptr) && (m_widget->isVisible()); if (fromIncremental) { initialPattern = m_incUi->pattern->currentText(); } } // Create dialog - const bool create = (m_powerUi == NULL); + const bool create = (m_powerUi == nullptr); if (create) { // Kill incremental widget - if (m_incUi != NULL) { + if (m_incUi != nullptr) { // Backup current settings const bool OF_INCREMENTAL = false; backupConfig(OF_INCREMENTAL); // Kill widget delete m_incUi; - m_incUi = NULL; + m_incUi = nullptr; m_layout->removeWidget(m_widget); m_widget->deleteLater(); // I didn't get a crash here but for symmetrie to the other mutate slot^ } // Add power widget m_widget = new QWidget(this); m_powerUi = new Ui::PowerSearchBar; m_powerUi->setupUi(m_widget); m_layout->addWidget(m_widget); // Bind to shared history models m_powerUi->pattern->setDuplicatesEnabled(false); m_powerUi->pattern->setInsertPolicy(QComboBox::InsertAtTop); m_powerUi->pattern->setMaxCount(m_config->maxHistorySize()); m_powerUi->pattern->setModel(KTextEditor::EditorPrivate::self()->searchHistoryModel()); m_powerUi->pattern->lineEdit()->setClearButtonEnabled(true); - m_powerUi->pattern->setCompleter(0); + m_powerUi->pattern->setCompleter(nullptr); m_powerUi->replacement->setDuplicatesEnabled(false); m_powerUi->replacement->setInsertPolicy(QComboBox::InsertAtTop); m_powerUi->replacement->setMaxCount(m_config->maxHistorySize()); m_powerUi->replacement->setModel(KTextEditor::EditorPrivate::self()->replaceHistoryModel()); m_powerUi->replacement->lineEdit()->setClearButtonEnabled(true); - m_powerUi->replacement->setCompleter(0); + m_powerUi->replacement->setCompleter(nullptr); // Icons m_powerUi->mutate->setIcon(QIcon::fromTheme(QStringLiteral("games-config-options"))); m_powerUi->mutate->setChecked(true); m_powerUi->findNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); m_powerUi->findPrev->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); m_powerUi->findAll->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); m_powerUi->matchCase->setIcon(QIcon::fromTheme(QStringLiteral("format-text-superscript"))); m_powerUi->selectionOnly->setIcon(QIcon::fromTheme(QStringLiteral("edit-select-all"))); // Focus proxy centralWidget()->setFocusProxy(m_powerUi->pattern); } m_powerUi->selectionOnly->setChecked(selectionOnly); // Restore previous settings if (create) { m_powerUi->matchCase->setChecked(m_powerMatchCase); m_powerUi->searchMode->setCurrentIndex(m_powerMode); } // force current index of -1 --> shows 1st completion entry instead of 2nd m_powerUi->pattern->setCurrentIndex(-1); m_powerUi->replacement->setCurrentIndex(-1); // Set initial search pattern QLineEdit *const patternLineEdit = m_powerUi->pattern->lineEdit(); - Q_ASSERT(patternLineEdit != NULL); + Q_ASSERT(patternLineEdit != nullptr); patternLineEdit->setText(initialPattern); patternLineEdit->selectAll(); // Set initial replacement text QLineEdit *const replacementLineEdit = m_powerUi->replacement->lineEdit(); - Q_ASSERT(replacementLineEdit != NULL); + Q_ASSERT(replacementLineEdit != nullptr); replacementLineEdit->setText(QString()); // Propagate settings (slots are still inactive on purpose) onPowerPatternChanged(initialPattern); givePatternFeedback(); if (create) { // Slots connect(m_powerUi->mutate, SIGNAL(clicked()), this, SLOT(enterIncrementalMode())); connect(patternLineEdit, SIGNAL(textChanged(QString)), this, SLOT(onPowerPatternChanged(QString))); connect(m_powerUi->findNext, SIGNAL(clicked()), this, SLOT(findNext())); connect(m_powerUi->findPrev, SIGNAL(clicked()), this, SLOT(findPrevious())); connect(m_powerUi->replaceNext, SIGNAL(clicked()), this, SLOT(replaceNext())); connect(m_powerUi->replaceAll, SIGNAL(clicked()), this, SLOT(replaceAll())); connect(m_powerUi->searchMode, SIGNAL(currentIndexChanged(int)), this, SLOT(onPowerModeChanged(int))); connect(m_powerUi->matchCase, SIGNAL(toggled(bool)), this, SLOT(onMatchCaseToggled(bool))); connect(m_powerUi->findAll, SIGNAL(clicked()), this, SLOT(findAll())); // Make [return] in pattern line edit trigger action connect(patternLineEdit, SIGNAL(returnPressed()), this, SLOT(onReturnPressed())); connect(replacementLineEdit, SIGNAL(returnPressed()), this, SLOT(replaceNext())); // Hook into line edit context menus m_powerUi->pattern->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_powerUi->pattern, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onPowerPatternContextMenuRequest(QPoint))); m_powerUi->replacement->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_powerUi->replacement, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(onPowerReplacmentContextMenuRequest(QPoint))); } // Focus if (m_widget->isVisible()) { m_powerUi->pattern->setFocus(Qt::MouseFocusReason); } } void KateSearchBar::enterIncrementalMode() { QString initialPattern; // Guess settings from context: init pattern with current selection const bool selected = m_view->selection(); if (selected) { const Range &selection = m_view->selectionRange(); if (selection.onSingleLine()) { // ... with current selection initialPattern = m_view->selectionText(); } } // If there's no new selection, we'll use the existing pattern if (initialPattern.isNull()) { // Coming from incremental search? - const bool fromIncremental = (m_incUi != NULL) && (m_widget->isVisible()); + const bool fromIncremental = (m_incUi != nullptr) && (m_widget->isVisible()); if (fromIncremental) { m_incUi->pattern->lineEdit()->selectAll(); m_incUi->pattern->setFocus(Qt::MouseFocusReason); return; } // Coming from power search? - const bool fromReplace = (m_powerUi != NULL) && (m_widget->isVisible()); + const bool fromReplace = (m_powerUi != nullptr) && (m_widget->isVisible()); if (fromReplace) { initialPattern = m_powerUi->pattern->currentText(); } } // Still no search pattern? Use the word under the cursor if (initialPattern.isNull()) { const KTextEditor::Cursor cursorPosition = m_view->cursorPosition(); initialPattern = m_view->doc()->wordAt(cursorPosition); } // Create dialog - const bool create = (m_incUi == NULL); + const bool create = (m_incUi == nullptr); if (create) { // Kill power widget - if (m_powerUi != NULL) { + if (m_powerUi != nullptr) { // Backup current settings const bool OF_POWER = true; backupConfig(OF_POWER); // Kill widget delete m_powerUi; - m_powerUi = NULL; + m_powerUi = nullptr; m_layout->removeWidget(m_widget); m_widget->deleteLater(); //deleteLater, because it's not a good idea too delete the widget and there for the button triggering this slot } // Add incremental widget m_widget = new QWidget(this); m_incUi = new Ui::IncrementalSearchBar; m_incUi->setupUi(m_widget); m_layout->addWidget(m_widget); // new QShortcut(KStandardShortcut::paste().primary(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut); // if (!KStandardShortcut::paste().alternate().isEmpty()) // new QShortcut(KStandardShortcut::paste().alternate(), m_incUi->pattern, SLOT(paste()), 0, Qt::WidgetWithChildrenShortcut); // Icons m_incUi->mutate->setIcon(QIcon::fromTheme(QStringLiteral("games-config-options"))); m_incUi->next->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); m_incUi->prev->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); m_incUi->matchCase->setIcon(QIcon::fromTheme(QStringLiteral("format-text-superscript"))); // Ensure minimum size m_incUi->pattern->setMinimumWidth(12 * m_incUi->pattern->fontMetrics().height()); // Customize status area m_incUi->status->setTextElideMode(Qt::ElideLeft); // Focus proxy centralWidget()->setFocusProxy(m_incUi->pattern); m_incUi->pattern->setDuplicatesEnabled(false); m_incUi->pattern->setInsertPolicy(QComboBox::InsertAtTop); m_incUi->pattern->setMaxCount(m_config->maxHistorySize()); m_incUi->pattern->setModel(KTextEditor::EditorPrivate::self()->searchHistoryModel()); m_incUi->pattern->lineEdit()->setClearButtonEnabled(true); - m_incUi->pattern->setCompleter(0); + m_incUi->pattern->setCompleter(nullptr); } // Restore previous settings if (create) { m_incUi->matchCase->setChecked(m_incMatchCase); } // force current index of -1 --> shows 1st completion entry instead of 2nd m_incUi->pattern->setCurrentIndex(-1); // Set initial search pattern if (!create) { disconnect(m_incUi->pattern, SIGNAL(editTextChanged(QString)), this, SLOT(onIncPatternChanged(QString))); } m_incUi->pattern->setEditText(initialPattern); connect(m_incUi->pattern, SIGNAL(editTextChanged(QString)), this, SLOT(onIncPatternChanged(QString))); m_incUi->pattern->lineEdit()->selectAll(); // Propagate settings (slots are still inactive on purpose) if (initialPattern.isEmpty()) { // Reset edit color indicateMatch(MatchNothing); } // Enable/disable next/prev m_incUi->next->setDisabled(initialPattern.isEmpty()); m_incUi->prev->setDisabled(initialPattern.isEmpty()); if (create) { // Slots connect(m_incUi->mutate, SIGNAL(clicked()), this, SLOT(enterPowerMode())); connect(m_incUi->pattern->lineEdit(), SIGNAL(returnPressed()), this, SLOT(onReturnPressed())); connect(m_incUi->next, SIGNAL(clicked()), this, SLOT(findNext())); connect(m_incUi->prev, SIGNAL(clicked()), this, SLOT(findPrevious())); connect(m_incUi->matchCase, SIGNAL(toggled(bool)), this, SLOT(onMatchCaseToggled(bool))); } // Focus if (m_widget->isVisible()) { m_incUi->pattern->setFocus(Qt::MouseFocusReason); } } bool KateSearchBar::clearHighlights() { + // Remove ScrollBarMarks + KTextEditor::MarkInterface* iface = qobject_cast(m_view->document()); + if (iface) { + const QHash marks = iface->marks(); + QHashIterator i(marks); + while (i.hasNext()) { + i.next(); + if (i.value()->type & KTextEditor::MarkInterface::SearchMatch) { + iface->removeMark(i.value()->line, KTextEditor::MarkInterface::SearchMatch); + } + } + } + if (m_infoMessage) { delete m_infoMessage; } if (m_hlRanges.isEmpty()) { return false; } qDeleteAll(m_hlRanges); m_hlRanges.clear(); return true; } void KateSearchBar::updateHighlightColors() { const QColor foregroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor &searchColor = m_view->renderer()->config()->searchHighlightColor(); const QColor &replaceColor = m_view->renderer()->config()->replaceHighlightColor(); // init match attribute highlightMatchAttribute->setForeground(foregroundColor); highlightMatchAttribute->setBackground(searchColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateMouseIn)->setBackground(searchColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateMouseIn)->setForeground(foregroundColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateCaretIn)->setBackground(searchColor); highlightMatchAttribute->dynamicAttribute(Attribute::ActivateCaretIn)->setForeground(foregroundColor); // init replacement attribute highlightReplacementAttribute->setBackground(replaceColor); highlightReplacementAttribute->setForeground(foregroundColor); } void KateSearchBar::showEvent(QShowEvent *event) { // Update init cursor - if (m_incUi != NULL) { + if (m_incUi != nullptr) { m_incInitCursor = m_view->cursorPosition(); } updateSelectionOnly(); KateViewBarWidget::showEvent(event); } void KateSearchBar::updateSelectionOnly() { - if (m_powerUi == NULL) { + if (m_powerUi == nullptr) { return; } // Re-init "Selection only" checkbox if power search bar open const bool selected = m_view->selection(); bool selectionOnly = selected; if (selected) { Range const &selection = m_view->selectionRange(); selectionOnly = !selection.onSingleLine(); } m_powerUi->selectionOnly->setChecked(selectionOnly); } void KateSearchBar::updateIncInitCursor() { - if (m_incUi == NULL) { + if (m_incUi == nullptr) { return; } // Update init cursor m_incInitCursor = m_view->cursorPosition(); } void KateSearchBar::onPowerPatternContextMenuRequest(const QPoint &pos) { const bool FOR_PATTERN = true; showExtendedContextMenu(FOR_PATTERN, pos); } void KateSearchBar::onPowerPatternContextMenuRequest() { onPowerPatternContextMenuRequest(m_powerUi->pattern->mapFromGlobal(QCursor::pos())); } void KateSearchBar::onPowerReplacmentContextMenuRequest(const QPoint &pos) { const bool FOR_REPLACEMENT = false; showExtendedContextMenu(FOR_REPLACEMENT, pos); } void KateSearchBar::onPowerReplacmentContextMenuRequest() { onPowerReplacmentContextMenuRequest(m_powerUi->replacement->mapFromGlobal(QCursor::pos())); } bool KateSearchBar::isPower() const { - return m_powerUi != 0; + return m_powerUi != nullptr; } void KateSearchBar::slotReadWriteChanged() { if (!KateSearchBar::isPower()) { return; } // perhaps disable/enable m_powerUi->replaceNext->setEnabled(m_view->doc()->isReadWrite() && isPatternValid()); m_powerUi->replaceAll->setEnabled(m_view->doc()->isReadWrite() && isPatternValid()); } diff --git a/src/search/katesearchbar.h b/src/search/katesearchbar.h index f9935191..e70f9957 100644 --- a/src/search/katesearchbar.h +++ b/src/search/katesearchbar.h @@ -1,210 +1,210 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2009-2010 Bernhard Beschow * Copyright (C) 2007 Sebastian Pipping * * 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_SEARCH_BAR_H #define KATE_SEARCH_BAR_H 1 #include "kateviewhelpers.h" #include #include #include namespace KTextEditor { class ViewPrivate; } class KateViewConfig; class QVBoxLayout; class QComboBox; namespace Ui { class IncrementalSearchBar; class PowerSearchBar; } namespace KTextEditor { class MovingRange; class Message; } class KTEXTEDITOR_EXPORT KateSearchBar : public KateViewBarWidget { Q_OBJECT friend class SearchBarTest; public: enum SearchMode { // NOTE: Concrete values are important here // to work with the combobox index! MODE_PLAIN_TEXT = 0, MODE_WHOLE_WORDS = 1, MODE_ESCAPE_SEQUENCES = 2, MODE_REGEX = 3 }; enum MatchResult { MatchFound, MatchWrappedForward, MatchWrappedBackward, MatchMismatch, MatchNothing, MatchNeutral }; enum SearchDirection { SearchForward, SearchBackward }; public: explicit KateSearchBar(bool initAsPower, KTextEditor::ViewPrivate *view, KateViewConfig *config); ~KateSearchBar(); void closed() Q_DECL_OVERRIDE; bool isPower() const; QString searchPattern() const; QString replacementPattern() const; bool selectionOnly() const; bool matchCase() const; // Only used by KTextEditor::ViewPrivate static void nextMatchForSelection(KTextEditor::ViewPrivate *view, SearchDirection searchDirection); public Q_SLOTS: /** * Set the current search pattern. * @param searchPattern the search pattern */ void setSearchPattern(const QString &searchPattern); /** * Set the current replacement pattern. * @param replacementPattern the replacement pattern */ void setReplacementPattern(const QString &replacementPattern); void setSearchMode(SearchMode mode); void setSelectionOnly(bool selectionOnly); void setMatchCase(bool matchCase); // Called for and + void findNext(); void findPrevious(); void findAll(); void replaceNext(); void replaceAll(); // Also used by KTextEditor::ViewPrivate void enterPowerMode(); void enterIncrementalMode(); bool clearHighlights(); void updateHighlightColors(); // read write status of document changed void slotReadWriteChanged(); protected: // Overridden void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; private Q_SLOTS: void onIncPatternChanged(const QString &pattern); void onMatchCaseToggled(bool matchCase); void onReturnPressed(); void updateSelectionOnly(); void updateIncInitCursor(); void onPowerPatternChanged(const QString &pattern); void onPowerModeChanged(int index); void onPowerPatternContextMenuRequest(); void onPowerPatternContextMenuRequest(const QPoint &); void onPowerReplacmentContextMenuRequest(); void onPowerReplacmentContextMenuRequest(const QPoint &); private: // Helpers - bool find(SearchDirection searchDirection = SearchForward, const QString *replacement = 0); + bool find(SearchDirection searchDirection = SearchForward, const QString *replacement = nullptr); int findAll(KTextEditor::Range inputRange, const QString *replacement); bool isPatternValid() const; KTextEditor::SearchOptions searchOptions(SearchDirection searchDirection = SearchForward) const; void highlightMatch(const KTextEditor::Range &range); void highlightReplacement(const KTextEditor::Range &range); void indicateMatch(MatchResult matchResult); static void selectRange(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range); void selectRange2(const KTextEditor::Range &range); QVector getCapturePatterns(const QString &pattern) const; void showExtendedContextMenu(bool forPattern, const QPoint &pos); void givePatternFeedback(); void addCurrentTextToHistory(QComboBox *combo); void backupConfig(bool ofPower); void sendConfig(); void fixForSingleLine(KTextEditor::Range &range, SearchDirection searchDirection); void showInfoMessage(const QString &text); private: KTextEditor::ViewPrivate *const m_view; KateViewConfig *const m_config; QList m_hlRanges; QPointer m_infoMessage; QPointer m_wrappedTopMessage; QPointer m_wrappedBottomMessage; // Shared by both dialogs QVBoxLayout *const m_layout; QWidget *m_widget; // Incremental search related Ui::IncrementalSearchBar *m_incUi; KTextEditor::Cursor m_incInitCursor; // Power search related Ui::PowerSearchBar *m_powerUi; // attribute to highlight matches with KTextEditor::Attribute::Ptr highlightMatchAttribute; KTextEditor::Attribute::Ptr highlightReplacementAttribute; // Status backup bool m_incHighlightAll : 1; bool m_incFromCursor : 1; bool m_incMatchCase : 1; bool m_powerMatchCase : 1; bool m_powerFromCursor : 1; bool m_powerHighlightAll : 1; unsigned int m_powerMode : 2; }; #endif // KATE_SEARCH_BAR_H diff --git a/src/spellcheck/ontheflycheck.cpp b/src/spellcheck/ontheflycheck.cpp index 5960e643..05c871de 100644 --- a/src/spellcheck/ontheflycheck.cpp +++ b/src/spellcheck/ontheflycheck.cpp @@ -1,908 +1,908 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008-2010 by Michel Ludwig * Copyright (C) 2009 by Joseph Wenninger * * 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. */ /* If ever threads should be used again, thread communication and * synchronization ought to be done with blocking queued signal connections. */ #include "ontheflycheck.h" #include #include "kateconfig.h" #include "kateglobal.h" #include "katerenderer.h" #include "katebuffer.h" #include "kateview.h" #include "spellcheck.h" #include "spellingmenu.h" #include "katepartdebug.h" #define ON_THE_FLY_DEBUG qCDebug(LOG_KTE) namespace { inline const QPair& invalidSpellCheckQueueItem() { static const auto item = QPair(nullptr, QString()); return item; } } KateOnTheFlyChecker::KateOnTheFlyChecker(KTextEditor::DocumentPrivate *document) : QObject(document), m_document(document), - m_backgroundChecker(NULL), + m_backgroundChecker(nullptr), m_currentlyCheckedItem(invalidSpellCheckQueueItem()), - m_refreshView(NULL) + m_refreshView(nullptr) { ON_THE_FLY_DEBUG << "created"; m_viewRefreshTimer = new QTimer(this); m_viewRefreshTimer->setSingleShot(true); connect(m_viewRefreshTimer, SIGNAL(timeout()), this, SLOT(viewRefreshTimeout())); connect(document, &KTextEditor::DocumentPrivate::textInserted, this, &KateOnTheFlyChecker::textInserted); connect(document, &KTextEditor::DocumentPrivate::textRemoved, this, &KateOnTheFlyChecker::textRemoved); connect(document, SIGNAL(viewCreated(KTextEditor::Document*,KTextEditor::View*)), this, SLOT(addView(KTextEditor::Document*,KTextEditor::View*))); connect(document, SIGNAL(highlightingModeChanged(KTextEditor::Document*)), this, SLOT(updateConfig())); connect(&document->buffer(), SIGNAL(respellCheckBlock(int,int)), this, SLOT(handleRespellCheckBlock(int,int))); // load the settings for the speller updateConfig(); foreach (KTextEditor::View *view, document->views()) { addView(document, view); } refreshSpellCheck(); } KateOnTheFlyChecker::~KateOnTheFlyChecker() { freeDocument(); } QPair KateOnTheFlyChecker::getMisspelledItem(const KTextEditor::Cursor &cursor) const { foreach (const MisspelledItem &item, m_misspelledList) { KTextEditor::MovingRange *movingRange = item.first; if (movingRange->contains(cursor)) { return QPair(*movingRange, item.second); } } return QPair(KTextEditor::Range::invalid(), QString()); } QString KateOnTheFlyChecker::dictionaryForMisspelledRange(const KTextEditor::Range &range) const { foreach (const MisspelledItem &item, m_misspelledList) { KTextEditor::MovingRange *movingRange = item.first; if (*movingRange == range) { return item.second; } } return QString(); } void KateOnTheFlyChecker::clearMisspellingForWord(const QString &word) { MisspelledList misspelledList = m_misspelledList; // make a copy foreach (const MisspelledItem &item, misspelledList) { KTextEditor::MovingRange *movingRange = item.first; if (m_document->text(*movingRange) == word) { deleteMovingRange(movingRange); } } } void KateOnTheFlyChecker::handleRespellCheckBlock(int start, int end) { ON_THE_FLY_DEBUG << start << end; KTextEditor::Range range(start, 0, end, m_document->lineLength(end)); bool listEmpty = m_modificationList.isEmpty(); KTextEditor::MovingRange *movingRange = m_document->newMovingRange(range); movingRange->setFeedback(this); // we don't handle this directly as the highlighting information might not be up-to-date yet m_modificationList.push_back(ModificationItem(TEXT_INSERTED, movingRange)); ON_THE_FLY_DEBUG << "added" << *movingRange; if (listEmpty) { QTimer::singleShot(0, this, SLOT(handleModifiedRanges())); } } void KateOnTheFlyChecker::textInserted(KTextEditor::Document *document, const KTextEditor::Range &range) { Q_ASSERT(document == m_document); Q_UNUSED(document); if (!range.isValid()) { return; } bool listEmptyAtStart = m_modificationList.isEmpty(); // don't consider a range that is not within the document range const KTextEditor::Range documentIntersection = m_document->documentRange().intersect(range); if (!documentIntersection.isValid()) { return; } // for performance reasons we only want to schedule spellchecks for ranges that are visible foreach (KTextEditor::View *i, m_document->views()) { KTextEditor::ViewPrivate *view = static_cast(i); KTextEditor::Range visibleIntersection = documentIntersection.intersect(view->visibleRange()); if (visibleIntersection.isValid()) { // allow empty intersections // we don't handle this directly as the highlighting information might not be up-to-date yet KTextEditor::MovingRange *movingRange = m_document->newMovingRange(visibleIntersection); movingRange->setFeedback(this); m_modificationList.push_back(ModificationItem(TEXT_INSERTED, movingRange)); ON_THE_FLY_DEBUG << "added" << *movingRange; } } if (listEmptyAtStart && !m_modificationList.isEmpty()) { QTimer::singleShot(0, this, SLOT(handleModifiedRanges())); } } void KateOnTheFlyChecker::handleInsertedText(const KTextEditor::Range &range) { KTextEditor::Range consideredRange = range; ON_THE_FLY_DEBUG << m_document << range; bool spellCheckInProgress = m_currentlyCheckedItem != invalidSpellCheckQueueItem(); if (spellCheckInProgress) { KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first; if (spellCheckRange->contains(consideredRange)) { consideredRange = *spellCheckRange; stopCurrentSpellCheck(); deleteMovingRangeQuickly(spellCheckRange); } else if (consideredRange.contains(*spellCheckRange)) { stopCurrentSpellCheck(); deleteMovingRangeQuickly(spellCheckRange); } else if (consideredRange.overlaps(*spellCheckRange)) { consideredRange.expandToRange(*spellCheckRange); stopCurrentSpellCheck(); deleteMovingRangeQuickly(spellCheckRange); } else { spellCheckInProgress = false; } } for (QList::iterator i = m_spellCheckQueue.begin(); i != m_spellCheckQueue.end();) { KTextEditor::MovingRange *spellCheckRange = (*i).first; if (spellCheckRange->contains(consideredRange)) { consideredRange = *spellCheckRange; ON_THE_FLY_DEBUG << "erasing range " << *i; i = m_spellCheckQueue.erase(i); deleteMovingRangeQuickly(spellCheckRange); } else if (consideredRange.contains(*spellCheckRange)) { ON_THE_FLY_DEBUG << "erasing range " << *i; i = m_spellCheckQueue.erase(i); deleteMovingRangeQuickly(spellCheckRange); } else if (consideredRange.overlaps(*spellCheckRange)) { consideredRange.expandToRange(*spellCheckRange); ON_THE_FLY_DEBUG << "erasing range " << *i; i = m_spellCheckQueue.erase(i); deleteMovingRangeQuickly(spellCheckRange); } else { ++i; } } KTextEditor::Range spellCheckRange = findWordBoundaries(consideredRange.start(), consideredRange.end()); const bool emptyAtStart = m_spellCheckQueue.isEmpty(); queueSpellCheckVisibleRange(spellCheckRange); if (spellCheckInProgress || (emptyAtStart && !m_spellCheckQueue.isEmpty())) { QTimer::singleShot(0, this, SLOT(performSpellCheck())); } } void KateOnTheFlyChecker::textRemoved(KTextEditor::Document *document, const KTextEditor::Range &range) { Q_ASSERT(document == m_document); Q_UNUSED(document); if (!range.isValid()) { return; } bool listEmptyAtStart = m_modificationList.isEmpty(); // don't consider a range that is behind the end of the document const KTextEditor::Range documentIntersection = m_document->documentRange().intersect(range); if (!documentIntersection.isValid()) { // the intersection might however be empty if the last return; // word has been removed, for example } // for performance reasons we only want to schedule spellchecks for ranges that are visible foreach (KTextEditor::View *i, m_document->views()) { KTextEditor::ViewPrivate *view = static_cast(i); KTextEditor::Range visibleIntersection = documentIntersection.intersect(view->visibleRange()); if (visibleIntersection.isValid()) { // see above // we don't handle this directly as the highlighting information might not be up-to-date yet KTextEditor::MovingRange *movingRange = m_document->newMovingRange(visibleIntersection); movingRange->setFeedback(this); m_modificationList.push_back(ModificationItem(TEXT_REMOVED, movingRange)); ON_THE_FLY_DEBUG << "added" << *movingRange << view->visibleRange(); } } if (listEmptyAtStart && !m_modificationList.isEmpty()) { QTimer::singleShot(0, this, SLOT(handleModifiedRanges())); } } inline bool rangesAdjacent(const KTextEditor::Range &r1, const KTextEditor::Range &r2) { return (r1.end() == r2.start()) || (r2.end() == r1.start()); } void KateOnTheFlyChecker::handleRemovedText(const KTextEditor::Range &range) { ON_THE_FLY_DEBUG << range; QList rangesToReCheck; for (QList::iterator i = m_spellCheckQueue.begin(); i != m_spellCheckQueue.end();) { KTextEditor::MovingRange *spellCheckRange = (*i).first; if (rangesAdjacent(*spellCheckRange, range) || spellCheckRange->contains(range)) { ON_THE_FLY_DEBUG << "erasing range " << *i; if (!spellCheckRange->isEmpty()) { rangesToReCheck.push_back(*spellCheckRange); } deleteMovingRangeQuickly(spellCheckRange); i = m_spellCheckQueue.erase(i); } else { ++i; } } bool spellCheckInProgress = m_currentlyCheckedItem != invalidSpellCheckQueueItem(); const bool emptyAtStart = m_spellCheckQueue.isEmpty(); if (spellCheckInProgress) { KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first; ON_THE_FLY_DEBUG << *spellCheckRange; if (m_document->documentRange().contains(*spellCheckRange) && (rangesAdjacent(*spellCheckRange, range) || spellCheckRange->contains(range)) && !spellCheckRange->isEmpty()) { rangesToReCheck.push_back(*spellCheckRange); ON_THE_FLY_DEBUG << "added the range " << *spellCheckRange; stopCurrentSpellCheck(); deleteMovingRangeQuickly(spellCheckRange); } else if (spellCheckRange->isEmpty()) { stopCurrentSpellCheck(); deleteMovingRangeQuickly(spellCheckRange); } else { spellCheckInProgress = false; } } for (QList::iterator i = rangesToReCheck.begin(); i != rangesToReCheck.end(); ++i) { queueSpellCheckVisibleRange(*i); } KTextEditor::Range spellCheckRange = findWordBoundaries(range.start(), range.start()); KTextEditor::Cursor spellCheckEnd = spellCheckRange.end(); queueSpellCheckVisibleRange(spellCheckRange); if (range.numberOfLines() > 0) { //FIXME: there is no currently no way of doing this better as we only get notifications for removals of // of single lines, i.e. we don't know here how many lines have been removed in total KTextEditor::Cursor nextLineStart(spellCheckEnd.line() + 1, 0); const KTextEditor::Cursor documentEnd = m_document->documentEnd(); if (nextLineStart < documentEnd) { KTextEditor::Range rangeBelow = KTextEditor::Range(nextLineStart, documentEnd); const QList &viewList = m_document->views(); for (QList::const_iterator i = viewList.begin(); i != viewList.end(); ++i) { KTextEditor::ViewPrivate *view = static_cast(*i); const KTextEditor::Range visibleRange = view->visibleRange(); KTextEditor::Range intersection = visibleRange.intersect(rangeBelow); if (intersection.isValid()) { queueSpellCheckVisibleRange(view, intersection); } } } } ON_THE_FLY_DEBUG << "finished"; if (spellCheckInProgress || (emptyAtStart && !m_spellCheckQueue.isEmpty())) { QTimer::singleShot(0, this, SLOT(performSpellCheck())); } } void KateOnTheFlyChecker::freeDocument() { ON_THE_FLY_DEBUG; // empty the spell check queue for (QList::iterator i = m_spellCheckQueue.begin(); i != m_spellCheckQueue.end();) { ON_THE_FLY_DEBUG << "erasing range " << *i; KTextEditor::MovingRange *movingRange = (*i).first; deleteMovingRangeQuickly(movingRange); i = m_spellCheckQueue.erase(i); } if (m_currentlyCheckedItem != invalidSpellCheckQueueItem()) { KTextEditor::MovingRange *movingRange = m_currentlyCheckedItem.first; deleteMovingRangeQuickly(movingRange); } stopCurrentSpellCheck(); MisspelledList misspelledList = m_misspelledList; // make a copy! foreach (const MisspelledItem &i, misspelledList) { deleteMovingRange(i.first); } m_misspelledList.clear(); clearModificationList(); } void KateOnTheFlyChecker::performSpellCheck() { if (m_currentlyCheckedItem != invalidSpellCheckQueueItem()) { ON_THE_FLY_DEBUG << "exited as a check is currently in progress"; return; } if (m_spellCheckQueue.isEmpty()) { ON_THE_FLY_DEBUG << "exited as there is nothing to do"; return; } m_currentlyCheckedItem = m_spellCheckQueue.takeFirst(); KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first; const QString &language = m_currentlyCheckedItem.second; ON_THE_FLY_DEBUG << "for the range " << *spellCheckRange; // clear all the highlights that are currently present in the range that // is supposed to be checked const MovingRangeList highlightsList = installedMovingRanges(*spellCheckRange); // make a copy! deleteMovingRanges(highlightsList); m_currentDecToEncOffsetList.clear(); KTextEditor::DocumentPrivate::OffsetList encToDecOffsetList; QString text = m_document->decodeCharacters(*spellCheckRange, m_currentDecToEncOffsetList, encToDecOffsetList); ON_THE_FLY_DEBUG << "next spell checking" << text; if (text.isEmpty()) { // passing an empty string to Sonnet can lead to a bad allocation exception spellCheckDone(); // (bug 225867) return; } if (m_speller.language() != language) { m_speller.setLanguage(language); } if (!m_backgroundChecker) { m_backgroundChecker = new Sonnet::BackgroundChecker(m_speller, this); connect(m_backgroundChecker, SIGNAL(misspelling(QString,int)), this, SLOT(misspelling(QString,int))); connect(m_backgroundChecker, SIGNAL(done()), this, SLOT(spellCheckDone())); } m_backgroundChecker->setSpeller(m_speller); m_backgroundChecker->setText(text); // don't call 'start()' after this! } void KateOnTheFlyChecker::removeRangeFromEverything(KTextEditor::MovingRange *movingRange) { Q_ASSERT(m_document == movingRange->document()); ON_THE_FLY_DEBUG << *movingRange << "(" << movingRange << ")"; if (removeRangeFromModificationList(movingRange)) { return; // range was part of the modification queue, so we don't have // to look further for it } if (removeRangeFromSpellCheckQueue(movingRange)) { return; // range was part of the spell check queue, so it cannot have been // a misspelled range } for (MisspelledList::iterator i = m_misspelledList.begin(); i != m_misspelledList.end();) { if ((*i).first == movingRange) { i = m_misspelledList.erase(i); } else { ++i; } } } bool KateOnTheFlyChecker::removeRangeFromCurrentSpellCheck(KTextEditor::MovingRange *range) { if (m_currentlyCheckedItem != invalidSpellCheckQueueItem() && m_currentlyCheckedItem.first == range) { stopCurrentSpellCheck(); return true; } return false; } void KateOnTheFlyChecker::stopCurrentSpellCheck() { m_currentDecToEncOffsetList.clear(); m_currentlyCheckedItem = invalidSpellCheckQueueItem(); if (m_backgroundChecker) { m_backgroundChecker->stop(); } } bool KateOnTheFlyChecker::removeRangeFromSpellCheckQueue(KTextEditor::MovingRange *range) { if (removeRangeFromCurrentSpellCheck(range)) { if (!m_spellCheckQueue.isEmpty()) { QTimer::singleShot(0, this, SLOT(performSpellCheck())); } return true; } bool found = false; for (QList::iterator i = m_spellCheckQueue.begin(); i != m_spellCheckQueue.end();) { if ((*i).first == range) { i = m_spellCheckQueue.erase(i); found = true; } else { ++i; } } return found; } void KateOnTheFlyChecker::rangeEmpty(KTextEditor::MovingRange *range) { ON_THE_FLY_DEBUG << range->start() << range->end() << "(" << range << ")"; deleteMovingRange(range); } void KateOnTheFlyChecker::rangeInvalid(KTextEditor::MovingRange *range) { ON_THE_FLY_DEBUG << range->start() << range->end() << "(" << range << ")"; deleteMovingRange(range); } void KateOnTheFlyChecker::mouseEnteredRange(KTextEditor::MovingRange *range, KTextEditor::View *view) { KTextEditor::ViewPrivate *kateView = static_cast(view); kateView->spellingMenu()->mouseEnteredMisspelledRange(range); } void KateOnTheFlyChecker::mouseExitedRange(KTextEditor::MovingRange *range, KTextEditor::View *view) { KTextEditor::ViewPrivate *kateView = static_cast(view); kateView->spellingMenu()->mouseExitedMisspelledRange(range); } /** * It is not enough to use 'caret/Entered/ExitedRange' only as the cursor doesn't move when some * text has been selected. **/ void KateOnTheFlyChecker::caretEnteredRange(KTextEditor::MovingRange *range, KTextEditor::View *view) { KTextEditor::ViewPrivate *kateView = static_cast(view); kateView->spellingMenu()->caretEnteredMisspelledRange(range); } void KateOnTheFlyChecker::caretExitedRange(KTextEditor::MovingRange *range, KTextEditor::View *view) { KTextEditor::ViewPrivate *kateView = static_cast(view); kateView->spellingMenu()->caretExitedMisspelledRange(range); } void KateOnTheFlyChecker::deleteMovingRange(KTextEditor::MovingRange *range) { ON_THE_FLY_DEBUG << range; // remove it from all our structures removeRangeFromEverything(range); - range->setFeedback(NULL); + range->setFeedback(nullptr); foreach (KTextEditor::View *view, m_document->views()) { static_cast(view)->spellingMenu()->rangeDeleted(range); } delete(range); } void KateOnTheFlyChecker::deleteMovingRanges(const QList &list) { foreach (KTextEditor::MovingRange *r, list) { deleteMovingRange(r); } } KTextEditor::Range KateOnTheFlyChecker::findWordBoundaries(const KTextEditor::Cursor &begin, const KTextEditor::Cursor &end) { // FIXME: QTextBoundaryFinder should be ideally used for this, but it is currently // still broken in Qt const QRegExp boundaryRegExp(QLatin1String("\\b")); const QRegExp boundaryQuoteRegExp(QLatin1String("\\b\\w+'\\w*$")); // handle spell checking of QLatin1String("isn't"), QLatin1String("doesn't"), etc. const QRegExp extendedBoundaryRegExp(QLatin1String("(\\W|$)")); const QRegExp extendedBoundaryQuoteRegExp(QLatin1String("^\\w*'\\w+\\b")); // see above KTextEditor::DocumentPrivate::OffsetList decToEncOffsetList, encToDecOffsetList; const int startLine = begin.line(); const int startColumn = begin.column(); KTextEditor::Cursor boundaryStart, boundaryEnd; // first we take care of the start position const KTextEditor::Range startLineRange(startLine, 0, startLine, m_document->lineLength(startLine)); QString decodedLineText = m_document->decodeCharacters(startLineRange, decToEncOffsetList, encToDecOffsetList); int translatedColumn = m_document->computePositionWrtOffsets(encToDecOffsetList, startColumn); QString text = decodedLineText.mid(0, translatedColumn); boundaryStart.setLine(startLine); int match = text.lastIndexOf(boundaryQuoteRegExp); if (match < 0) { match = text.lastIndexOf(boundaryRegExp); } boundaryStart.setColumn(m_document->computePositionWrtOffsets(decToEncOffsetList, qMax(0, match))); // and now the end position const int endLine = end.line(); const int endColumn = end.column(); if (endLine != startLine) { decToEncOffsetList.clear(); encToDecOffsetList.clear(); const KTextEditor::Range endLineRange(endLine, 0, endLine, m_document->lineLength(endLine)); decodedLineText = m_document->decodeCharacters(endLineRange, decToEncOffsetList, encToDecOffsetList); } translatedColumn = m_document->computePositionWrtOffsets(encToDecOffsetList, endColumn); text = decodedLineText.mid(translatedColumn); boundaryEnd.setLine(endLine); match = extendedBoundaryQuoteRegExp.indexIn(text); if (match == 0) { match = extendedBoundaryQuoteRegExp.matchedLength(); } else { match = extendedBoundaryRegExp.indexIn(text); } boundaryEnd.setColumn(m_document->computePositionWrtOffsets(decToEncOffsetList, translatedColumn + qMax(0, match))); return KTextEditor::Range(boundaryStart, boundaryEnd); } void KateOnTheFlyChecker::misspelling(const QString &word, int start) { if (m_currentlyCheckedItem == invalidSpellCheckQueueItem()) { ON_THE_FLY_DEBUG << "exited as no spell check is taking place"; return; } int translatedStart = m_document->computePositionWrtOffsets(m_currentDecToEncOffsetList, start); // ON_THE_FLY_DEBUG << "misspelled " << word // << " at line " // << *m_currentlyCheckedItem.first // << " column " << start; KTextEditor::MovingRange *spellCheckRange = m_currentlyCheckedItem.first; int line = spellCheckRange->start().line(); int rangeStart = spellCheckRange->start().column(); int translatedEnd = m_document->computePositionWrtOffsets(m_currentDecToEncOffsetList, start + word.length()); KTextEditor::MovingRange *movingRange = m_document->newMovingRange(KTextEditor::Range(line, rangeStart + translatedStart, line, rangeStart + translatedEnd)); movingRange->setFeedback(this); KTextEditor::Attribute *attribute = new KTextEditor::Attribute(); attribute->setUnderlineStyle(QTextCharFormat::SpellCheckUnderline); attribute->setUnderlineColor(KateRendererConfig::global()->spellingMistakeLineColor()); // don't print this range movingRange->setAttributeOnlyForViews(true); movingRange->setAttribute(KTextEditor::Attribute::Ptr(attribute)); m_misspelledList.push_back(MisspelledItem(movingRange, m_currentlyCheckedItem.second)); if (m_backgroundChecker) { m_backgroundChecker->continueChecking(); } } void KateOnTheFlyChecker::spellCheckDone() { ON_THE_FLY_DEBUG << "on-the-fly spell check done, queue length " << m_spellCheckQueue.size(); if (m_currentlyCheckedItem == invalidSpellCheckQueueItem()) { return; } KTextEditor::MovingRange *movingRange = m_currentlyCheckedItem.first; stopCurrentSpellCheck(); deleteMovingRangeQuickly(movingRange); if (!m_spellCheckQueue.empty()) { QTimer::singleShot(0, this, SLOT(performSpellCheck())); } } QList KateOnTheFlyChecker::installedMovingRanges(const KTextEditor::Range &range) { ON_THE_FLY_DEBUG << range; MovingRangeList toReturn; for (QList::iterator i = m_misspelledList.begin(); i != m_misspelledList.end(); ++i) { KTextEditor::MovingRange *movingRange = (*i).first; if (movingRange->overlaps(range)) { toReturn.push_back(movingRange); } } return toReturn; } void KateOnTheFlyChecker::updateConfig() { ON_THE_FLY_DEBUG; //m_speller.restore(); } void KateOnTheFlyChecker::refreshSpellCheck(const KTextEditor::Range &range) { if (range.isValid()) { textInserted(m_document, range); } else { freeDocument(); textInserted(m_document, m_document->documentRange()); } } void KateOnTheFlyChecker::addView(KTextEditor::Document *document, KTextEditor::View *view) { Q_ASSERT(document == m_document); Q_UNUSED(document); ON_THE_FLY_DEBUG; connect(view, SIGNAL(destroyed(QObject*)), this, SLOT(viewDestroyed(QObject*))); connect(view, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(restartViewRefreshTimer(KTextEditor::ViewPrivate*))); updateInstalledMovingRanges(static_cast(view)); } void KateOnTheFlyChecker::viewDestroyed(QObject *obj) { ON_THE_FLY_DEBUG; KTextEditor::View *view = static_cast(obj); m_displayRangeMap.remove(view); } void KateOnTheFlyChecker::removeView(KTextEditor::View *view) { ON_THE_FLY_DEBUG; m_displayRangeMap.remove(view); } void KateOnTheFlyChecker::updateInstalledMovingRanges(KTextEditor::ViewPrivate *view) { Q_ASSERT(m_document == view->document()); ON_THE_FLY_DEBUG; KTextEditor::Range oldDisplayRange = m_displayRangeMap[view]; KTextEditor::Range newDisplayRange = view->visibleRange(); ON_THE_FLY_DEBUG << "new range: " << newDisplayRange; ON_THE_FLY_DEBUG << "old range: " << oldDisplayRange; QList toDelete; foreach (const MisspelledItem &item, m_misspelledList) { KTextEditor::MovingRange *movingRange = item.first; if (!movingRange->overlaps(newDisplayRange)) { bool stillVisible = false; foreach (KTextEditor::View *it2, m_document->views()) { KTextEditor::ViewPrivate *view2 = static_cast(it2); if (view != view2 && movingRange->overlaps(view2->visibleRange())) { stillVisible = true; break; } } if (!stillVisible) { toDelete.push_back(movingRange); } } } deleteMovingRanges(toDelete); m_displayRangeMap[view] = newDisplayRange; if (oldDisplayRange.isValid()) { bool emptyAtStart = m_spellCheckQueue.empty(); for (int line = newDisplayRange.end().line(); line >= newDisplayRange.start().line(); --line) { if (!oldDisplayRange.containsLine(line)) { bool visible = false; foreach (KTextEditor::View *it2, m_document->views()) { KTextEditor::ViewPrivate *view2 = static_cast(it2); if (view != view2 && view2->visibleRange().containsLine(line)) { visible = true; break; } } if (!visible) { queueLineSpellCheck(m_document, line); } } } if (emptyAtStart && !m_spellCheckQueue.isEmpty()) { QTimer::singleShot(0, this, SLOT(performSpellCheck())); } } } void KateOnTheFlyChecker::queueSpellCheckVisibleRange(const KTextEditor::Range &range) { const QList &viewList = m_document->views(); for (QList::const_iterator i = viewList.begin(); i != viewList.end(); ++i) { queueSpellCheckVisibleRange(static_cast(*i), range); } } void KateOnTheFlyChecker::queueSpellCheckVisibleRange(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range) { Q_ASSERT(m_document == view->doc()); KTextEditor::Range visibleRange = view->visibleRange(); KTextEditor::Range intersection = visibleRange.intersect(range); if (intersection.isEmpty()) { return; } // clear all the highlights that are currently present in the range that // is supposed to be checked, necessary due to highlighting const MovingRangeList highlightsList = installedMovingRanges(intersection); deleteMovingRanges(highlightsList); QList > spellCheckRanges = KTextEditor::EditorPrivate::self()->spellCheckManager()->spellCheckRanges(m_document, intersection, true); //we queue them up in reverse QListIterator > i(spellCheckRanges); i.toBack(); while (i.hasPrevious()) { QPair p = i.previous(); queueLineSpellCheck(p.first, p.second); } } void KateOnTheFlyChecker::queueLineSpellCheck(KTextEditor::DocumentPrivate *kateDocument, int line) { const KTextEditor::Range range = KTextEditor::Range(line, 0, line, kateDocument->lineLength(line)); // clear all the highlights that are currently present in the range that // is supposed to be checked, necessary due to highlighting const MovingRangeList highlightsList = installedMovingRanges(range); deleteMovingRanges(highlightsList); QList > spellCheckRanges = KTextEditor::EditorPrivate::self()->spellCheckManager()->spellCheckRanges(kateDocument, range, true); //we queue them up in reverse QListIterator > i(spellCheckRanges); i.toBack(); while (i.hasPrevious()) { QPair p = i.previous(); queueLineSpellCheck(p.first, p.second); } } void KateOnTheFlyChecker::queueLineSpellCheck(const KTextEditor::Range &range, const QString &dictionary) { ON_THE_FLY_DEBUG << m_document << range; Q_ASSERT(range.onSingleLine()); if (range.isEmpty()) { return; } addToSpellCheckQueue(range, dictionary); } void KateOnTheFlyChecker::addToSpellCheckQueue(const KTextEditor::Range &range, const QString &dictionary) { addToSpellCheckQueue(m_document->newMovingRange(range), dictionary); } void KateOnTheFlyChecker::addToSpellCheckQueue(KTextEditor::MovingRange *range, const QString &dictionary) { ON_THE_FLY_DEBUG << m_document << *range << dictionary; range->setFeedback(this); // if the queue contains a subrange of 'range', we remove that one for (QList::iterator i = m_spellCheckQueue.begin(); i != m_spellCheckQueue.end();) { KTextEditor::MovingRange *spellCheckRange = (*i).first; if (range->contains(*spellCheckRange)) { deleteMovingRangeQuickly(spellCheckRange); i = m_spellCheckQueue.erase(i); } else { ++i; } } // leave 'push_front' here as it is a LIFO queue, i.e. a stack m_spellCheckQueue.push_front(SpellCheckItem(range, dictionary)); ON_THE_FLY_DEBUG << "added" << *range << dictionary << "to the queue, which has a length of" << m_spellCheckQueue.size(); } void KateOnTheFlyChecker::viewRefreshTimeout() { if (m_refreshView) { updateInstalledMovingRanges(m_refreshView); } - m_refreshView = NULL; + m_refreshView = nullptr; } void KateOnTheFlyChecker::restartViewRefreshTimer(KTextEditor::ViewPrivate *view) { if (m_refreshView && view != m_refreshView) { // a new view should be refreshed updateInstalledMovingRanges(m_refreshView); // so refresh the old one first } m_refreshView = view; m_viewRefreshTimer->start(100); } void KateOnTheFlyChecker::deleteMovingRangeQuickly(KTextEditor::MovingRange *range) { - range->setFeedback(NULL); + range->setFeedback(nullptr); foreach (KTextEditor::View *view, m_document->views()) { static_cast(view)->spellingMenu()->rangeDeleted(range); } delete(range); } void KateOnTheFlyChecker::handleModifiedRanges() { foreach (const ModificationItem &item, m_modificationList) { KTextEditor::MovingRange *movingRange = item.second; KTextEditor::Range range = *movingRange; deleteMovingRangeQuickly(movingRange); if (item.first == TEXT_INSERTED) { handleInsertedText(range); } else { handleRemovedText(range); } } m_modificationList.clear(); } bool KateOnTheFlyChecker::removeRangeFromModificationList(KTextEditor::MovingRange *range) { bool found = false; for (ModificationList::iterator i = m_modificationList.begin(); i != m_modificationList.end();) { ModificationItem item = *i; KTextEditor::MovingRange *movingRange = item.second; if (movingRange == range) { found = true; i = m_modificationList.erase(i); } else { ++i; } } return found; } void KateOnTheFlyChecker::clearModificationList() { foreach (const ModificationItem &item, m_modificationList) { KTextEditor::MovingRange *movingRange = item.second; deleteMovingRangeQuickly(movingRange); } m_modificationList.clear(); } diff --git a/src/spellcheck/spellcheck.h b/src/spellcheck/spellcheck.h index df259be9..5b7570f4 100644 --- a/src/spellcheck/spellcheck.h +++ b/src/spellcheck/spellcheck.h @@ -1,72 +1,72 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008-2009 by Michel Ludwig * * 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 SPELLCHECK_H #define SPELLCHECK_H #include #include #include #include #include #include #include namespace KTextEditor { class DocumentPrivate; } class KateSpellCheckManager : public QObject { Q_OBJECT typedef QPair RangeDictionaryPair; public: - KateSpellCheckManager(QObject *parent = NULL); + KateSpellCheckManager(QObject *parent = nullptr); virtual ~KateSpellCheckManager(); QStringList suggestions(const QString &word, const QString &dictionary); void ignoreWord(const QString &word, const QString &dictionary); void addToDictionary(const QString &word, const QString &dictionary); /** * 'r2' is a subrange of 'r1', which is extracted from 'r1' and the remaining ranges are returned **/ static QList rangeDifference(const KTextEditor::Range &r1, const KTextEditor::Range &r2); public: QList > spellCheckLanguageRanges(KTextEditor::DocumentPrivate *doc, const KTextEditor::Range &range); QList > spellCheckWrtHighlightingRanges(KTextEditor::DocumentPrivate *doc, const KTextEditor::Range &range, const QString &dictionary = QString(), bool singleLine = false, bool returnSingleRange = false); QList > spellCheckRanges(KTextEditor::DocumentPrivate *doc, const KTextEditor::Range &range, bool singleLine = false); void replaceCharactersEncodedIfNecessary(const QString &newWord, KTextEditor::DocumentPrivate *doc, const KTextEditor::Range &replacementRange); private: void trimRange(KTextEditor::DocumentPrivate *doc, KTextEditor::Range &r); }; #endif diff --git a/src/spellcheck/spellcheckbar.cpp b/src/spellcheck/spellcheckbar.cpp index 6eecc974..053595f1 100644 --- a/src/spellcheck/spellcheckbar.cpp +++ b/src/spellcheck/spellcheckbar.cpp @@ -1,448 +1,448 @@ /** * Copyright (C) 2003 Zack Rusin * Copyright (C) 2009-2010 Michel Ludwig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #include "spellcheckbar.h" #include "ui_spellcheckbar.h" #include #include "sonnet/backgroundchecker.h" #include "sonnet/speller.h" /* #include "sonnet/filter_p.h" #include "sonnet/settings_p.h" */ #include #include #include #include #include #include #include #include #include //to initially disable sorting in the suggestions listview #define NONSORTINGCOLUMN 2 class ReadOnlyStringListModel: public QStringListModel { public: ReadOnlyStringListModel(QObject *parent): QStringListModel(parent) {} Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE { Q_UNUSED(index); return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } }; /** * Structure abstracts the word and its position in the * parent text. * * @author Zack Rusin * @short struct represents word */ struct Word { Word() : start(0), end(true) {} Word(const QString &w, int st, bool e = false) : word(w), start(st), end(e) {} Word(const Word &other) : word(other.word), start(other.start), end(other.end) {} QString word; int start; bool end; }; class SpellCheckBar::Private { public: Ui_SonnetUi ui; ReadOnlyStringListModel *suggestionsModel; QWidget *wdg; QDialogButtonBox *buttonBox; QProgressDialog *progressDialog; QString originalBuffer; Sonnet::BackgroundChecker *checker; Word currentWord; QMap replaceAllMap; bool restart;//used when text is distributed across several qtextedits, eg in KAider QMap dictsMap; int progressDialogTimeout; bool showCompletionMessageBox; bool spellCheckContinuedAfterReplacement; bool canceled; void deleteProgressDialog(bool directly) { if (progressDialog) { progressDialog->hide(); if (directly) { delete progressDialog; } else { progressDialog->deleteLater(); } - progressDialog = NULL; + progressDialog = nullptr; } } }; SpellCheckBar::SpellCheckBar(Sonnet::BackgroundChecker *checker, QWidget *parent) : KateViewBarWidget(true, parent), d(new Private) { d->checker = checker; d->canceled = false; d->showCompletionMessageBox = false; d->spellCheckContinuedAfterReplacement = true; d->progressDialogTimeout = -1; - d->progressDialog = NULL; + d->progressDialog = nullptr; initGui(); initConnections(); } SpellCheckBar::~SpellCheckBar() { delete d; } void SpellCheckBar::closed() { if (viewBar()) { viewBar()->removeBarWidget(this); } // called from hideMe, so don't call it again! d->canceled = true; d->deleteProgressDialog(false); // this method can be called in response to // pressing 'Cancel' on the dialog emit cancel(); emit spellCheckStatus(i18n("Spell check canceled.")); } void SpellCheckBar::initConnections() { connect(d->ui.m_addBtn, SIGNAL(clicked()), SLOT(slotAddWord())); connect(d->ui.m_replaceBtn, SIGNAL(clicked()), SLOT(slotReplaceWord())); connect(d->ui.m_replaceAllBtn, SIGNAL(clicked()), SLOT(slotReplaceAll())); connect(d->ui.m_skipBtn, SIGNAL(clicked()), SLOT(slotSkip())); connect(d->ui.m_skipAllBtn, SIGNAL(clicked()), SLOT(slotSkipAll())); connect(d->ui.m_suggestBtn, SIGNAL(clicked()), SLOT(slotSuggest())); connect(d->ui.m_language, SIGNAL(activated(QString)), SLOT(slotChangeLanguage(QString))); connect(d->checker, SIGNAL(misspelling(QString,int)), SLOT(slotMisspelling(QString,int))); connect(d->checker, SIGNAL(done()), SLOT(slotDone())); /* connect(d->ui.m_suggestions, SIGNAL(doubleClicked(QModelIndex)), SLOT(slotReplaceWord())); */ connect(d->ui.cmbReplacement, SIGNAL(returnPressed()), this, SLOT(slotReplaceWord())); connect(d->ui.m_autoCorrect, SIGNAL(clicked()), SLOT(slotAutocorrect())); // button use by kword/kpresenter // hide by default d->ui.m_autoCorrect->hide(); } void SpellCheckBar::initGui() { QVBoxLayout *layout = new QVBoxLayout; layout->setMargin(0); centralWidget()->setLayout(layout); d->wdg = new QWidget(this); d->ui.setupUi(d->wdg); layout->addWidget(d->wdg); setGuiEnabled(false); /* d->buttonBox = new QDialogButtonBox(this); d->buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); layout->addWidget(d->buttonBox); */ //d->ui.m_suggestions->setSorting( NONSORTINGCOLUMN ); fillDictionaryComboBox(); d->restart = false; d->suggestionsModel = new ReadOnlyStringListModel(this); d->ui.cmbReplacement->setModel(d->suggestionsModel); } void SpellCheckBar::activeAutoCorrect(bool _active) { if (_active) { d->ui.m_autoCorrect->show(); } else { d->ui.m_autoCorrect->hide(); } } void SpellCheckBar::showProgressDialog(int timeout) { d->progressDialogTimeout = timeout; } void SpellCheckBar::showSpellCheckCompletionMessage(bool b) { d->showCompletionMessageBox = b; } void SpellCheckBar::setSpellCheckContinuedAfterReplacement(bool b) { d->spellCheckContinuedAfterReplacement = b; } void SpellCheckBar::slotAutocorrect() { setGuiEnabled(false); setProgressDialogVisible(true); emit autoCorrect(d->currentWord.word, d->ui.cmbReplacement->lineEdit()->text()); slotReplaceWord(); } void SpellCheckBar::setGuiEnabled(bool b) { d->wdg->setEnabled(b); } void SpellCheckBar::setProgressDialogVisible(bool b) { if (!b) { d->deleteProgressDialog(true); } else if (d->progressDialogTimeout >= 0) { if (d->progressDialog) { return; } d->progressDialog = new QProgressDialog(this); d->progressDialog->setLabelText(i18nc("progress label", "Spell checking in progress...")); d->progressDialog->setWindowTitle(i18nc("@title:window", "Check Spelling")); d->progressDialog->setModal(true); d->progressDialog->setAutoClose(false); d->progressDialog->setAutoReset(false); // create an 'indefinite' progress box as we currently cannot get progress feedback from // the speller d->progressDialog->reset(); d->progressDialog->setRange(0, 0); d->progressDialog->setValue(0); connect(d->progressDialog, SIGNAL(canceled()), this, SLOT(slotCancel())); d->progressDialog->setMinimumDuration(d->progressDialogTimeout); } } void SpellCheckBar::slotCancel() { hideMe(); } QString SpellCheckBar::originalBuffer() const { return d->originalBuffer; } QString SpellCheckBar::buffer() const { return d->checker->text(); } void SpellCheckBar::setBuffer(const QString &buf) { d->originalBuffer = buf; //it is possible to change buffer inside slot connected to done() signal d->restart = true; } void SpellCheckBar::fillDictionaryComboBox() { Sonnet::Speller speller = d->checker->speller(); d->dictsMap = speller.availableDictionaries(); QStringList langs = d->dictsMap.keys(); d->ui.m_language->clear(); d->ui.m_language->addItems(langs); updateDictionaryComboBox(); } void SpellCheckBar::updateDictionaryComboBox() { Sonnet::Speller speller = d->checker->speller(); d->ui.m_language->setCurrentIndex(d->dictsMap.values().indexOf(speller.language())); } void SpellCheckBar::updateDialog(const QString &word) { d->ui.m_unknownWord->setText(word); //d->ui.m_contextLabel->setText(d->checker->currentContext()); const QStringList suggs = d->checker->suggest(word); if (suggs.isEmpty()) { d->ui.cmbReplacement->lineEdit()->clear(); } else { d->ui.cmbReplacement->lineEdit()->setText(suggs.first()); } fillSuggestions(suggs); } void SpellCheckBar::show() { d->canceled = false; fillDictionaryComboBox(); updateDictionaryComboBox(); if (d->originalBuffer.isEmpty()) { d->checker->start(); } else { d->checker->setText(d->originalBuffer); } setProgressDialogVisible(true); } void SpellCheckBar::slotAddWord() { setGuiEnabled(false); setProgressDialogVisible(true); d->checker->addWordToPersonal(d->currentWord.word); d->checker->continueChecking(); } void SpellCheckBar::slotReplaceWord() { setGuiEnabled(false); setProgressDialogVisible(true); const QString replacementText = d->ui.cmbReplacement->lineEdit()->text(); emit replace(d->currentWord.word, d->currentWord.start, replacementText); if (d->spellCheckContinuedAfterReplacement) { d->checker->replace(d->currentWord.start, d->currentWord.word, replacementText); d->checker->continueChecking(); } else { d->checker->stop(); } } void SpellCheckBar::slotReplaceAll() { setGuiEnabled(false); setProgressDialogVisible(true); d->replaceAllMap.insert(d->currentWord.word, d->ui.cmbReplacement->lineEdit()->text()); slotReplaceWord(); } void SpellCheckBar::slotSkip() { setGuiEnabled(false); setProgressDialogVisible(true); d->checker->continueChecking(); } void SpellCheckBar::slotSkipAll() { setGuiEnabled(false); setProgressDialogVisible(true); //### do we want that or should we have a d->ignoreAll list? Sonnet::Speller speller = d->checker->speller(); speller.addToPersonal(d->currentWord.word); d->checker->setSpeller(speller); d->checker->continueChecking(); } void SpellCheckBar::slotSuggest() { QStringList suggs = d->checker->suggest(d->ui.cmbReplacement->lineEdit()->text()); fillSuggestions(suggs); } void SpellCheckBar::slotChangeLanguage(const QString &lang) { Sonnet::Speller speller = d->checker->speller(); QString languageCode = d->dictsMap[lang]; if (!languageCode.isEmpty()) { d->checker->changeLanguage(languageCode); slotSuggest(); emit languageChanged(languageCode); } } void SpellCheckBar::fillSuggestions(const QStringList &suggs) { d->suggestionsModel->setStringList(suggs); } void SpellCheckBar::slotMisspelling(const QString &word, int start) { setGuiEnabled(true); setProgressDialogVisible(false); emit misspelling(word, start); //NOTE this is HACK I had to introduce because BackgroundChecker lacks 'virtual' marks on methods //this dramatically reduces spellchecking time in Lokalize //as this doesn't fetch suggestions for words that are present in msgid if (!updatesEnabled()) { return; } d->currentWord = Word(word, start); if (d->replaceAllMap.contains(word)) { d->ui.cmbReplacement->lineEdit()->setText(d->replaceAllMap[ word ]); slotReplaceWord(); } else { updateDialog(word); } } void SpellCheckBar::slotDone() { d->restart = false; emit done(d->checker->text()); if (d->restart) { updateDictionaryComboBox(); d->checker->setText(d->originalBuffer); d->restart = false; } else { setProgressDialogVisible(false); emit spellCheckStatus(i18n("Spell check complete.")); hideMe(); if (!d->canceled && d->showCompletionMessageBox) { QMessageBox::information(this, i18n("Spell check complete."), i18nc("@title:window", "Check Spelling")); } } } diff --git a/src/spellcheck/spellcheckbar.h b/src/spellcheck/spellcheckbar.h index 572e3eee..375fd8dc 100644 --- a/src/spellcheck/spellcheckbar.h +++ b/src/spellcheck/spellcheckbar.h @@ -1,160 +1,160 @@ /* * Copyright (C) 2003 Zack Rusin * Copyright (C) 2009-2010 Michel Ludwig * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ #ifndef SONNET_DIALOG_H #define SONNET_DIALOG_H #include "kateviewhelpers.h" class QListWidgetItem; class QModelIndex; namespace Sonnet { class BackgroundChecker; } /** * @short Spellcheck dialog * * \code * Sonnet::SpellCheckBar dlg = new Sonnet::SpellCheckBar( * new Sonnet::BackgroundChecker(this), this); * //connect signals * ... * dlg->setBuffer( someText ); * dlg->show(); * \endcode * * You can change buffer inside a slot connected to done() signal * and spellcheck will continue with new data automatically. */ class SpellCheckBar : public KateViewBarWidget { Q_OBJECT public: SpellCheckBar(Sonnet::BackgroundChecker *checker, QWidget *parent); ~SpellCheckBar(); QString originalBuffer() const; QString buffer() const; void closed() Q_DECL_OVERRIDE; void show(); void activeAutoCorrect(bool _active); /** * Controls whether an (indefinite) progress dialog is shown when the spell * checking takes longer than the given time to complete. By default no * progress dialog is shown. If the progress dialog is set to be shown, no * time consuming operation (for example, showing a notification message) should * be performed in a slot connected to the 'done' signal as this might trigger * the progress dialog unnecessarily. * * @param timeout time after which the progress dialog should appear; a negative * value can be used to hide it * @since 4.4 */ void showProgressDialog(int timeout = 500); /** * Controls whether a message box indicating the completion of the spell checking * is shown or not. By default it is not shown. * * @since 4.4 */ void showSpellCheckCompletionMessage(bool b = true); /** * Controls whether the spell checking is continued after the replacement of a * misspelled word has been performed. By default it is continued. * * @since 4.4 */ void setSpellCheckContinuedAfterReplacement(bool b); public Q_SLOTS: void setBuffer(const QString &); Q_SIGNALS: /** * The dialog won't be closed if you setBuffer() in slot connected to this signal * * Also emitted after stop() signal */ void done(const QString &newBuffer); void misspelling(const QString &word, int start); void replace(const QString &oldWord, int start, const QString &newWord); void stop(); void cancel(); void autoCorrect(const QString ¤tWord, const QString &replaceWord); /** * Signal sends when spell checking is finished/stopped/completed * @since 4.1 */ void spellCheckStatus(const QString &); /** * Emitted when the user changes the language used for spellchecking, * which is shown in a combobox of this dialog. * - * @param dictionary the new language the user selected + * @param language the new language the user selected * @since 4.1 */ void languageChanged(const QString &language); private Q_SLOTS: void slotMisspelling(const QString &word, int start); void slotDone(); void slotCancel(); void slotAddWord(); void slotReplaceWord(); void slotReplaceAll(); void slotSkip(); void slotSkipAll(); void slotSuggest(); void slotChangeLanguage(const QString &); void slotAutocorrect(); void setGuiEnabled(bool b); void setProgressDialogVisible(bool b); private: void updateDialog(const QString &word); void fillDictionaryComboBox(); void updateDictionaryComboBox(); void fillSuggestions(const QStringList &suggs); void initConnections(); void initGui(); void continueChecking(); private: class Private; Private *const d; Q_DISABLE_COPY(SpellCheckBar) }; #endif diff --git a/src/spellcheck/spellcheckdialog.cpp b/src/spellcheck/spellcheckdialog.cpp index b1c1c940..23391529 100644 --- a/src/spellcheck/spellcheckdialog.cpp +++ b/src/spellcheck/spellcheckdialog.cpp @@ -1,323 +1,323 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2009-2010 by Michel Ludwig * Copyright (C) 2008 Mirko Stocker * Copyright (C) 2004-2005 Anders Lund * Copyright (C) 2002 John Firebaugh * Copyright (C) 2001-2004 Christoph Cullmann * Copyright (C) 2001 Joseph Wenninger * Copyright (C) 1999 Jochen Wilhelmy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 "spellcheckdialog.h" #include "katedocument.h" #include "kateglobal.h" #include "kateview.h" #include "spellcheck/spellcheck.h" #include "spellcheck/spellcheckbar.h" #include #include #include #include #include KateSpellCheckDialog::KateSpellCheckDialog(KTextEditor::ViewPrivate *view) : QObject(view) , m_view(view) - , m_spellcheckSelection(NULL) - , m_speller(NULL) - , m_backgroundChecker(NULL) - , m_sonnetDialog(NULL) - , m_globalSpellCheckRange(NULL) + , m_spellcheckSelection(nullptr) + , m_speller(nullptr) + , m_backgroundChecker(nullptr) + , m_sonnetDialog(nullptr) + , m_globalSpellCheckRange(nullptr) , m_spellCheckCancelledByUser(false) { } KateSpellCheckDialog::~KateSpellCheckDialog() { delete m_globalSpellCheckRange; delete m_sonnetDialog; delete m_backgroundChecker; delete m_speller; } void KateSpellCheckDialog::createActions(KActionCollection *ac) { ac->addAction(KStandardAction::Spelling, this, SLOT(spellcheck())); QAction *a = new QAction(i18n("Spelling (from cursor)..."), this); ac->addAction(QStringLiteral("tools_spelling_from_cursor"), a); a->setIcon(QIcon::fromTheme(QStringLiteral("tools-check-spelling"))); a->setWhatsThis(i18n("Check the document's spelling from the cursor and forward")); connect(a, SIGNAL(triggered()), this, SLOT(spellcheckFromCursor())); m_spellcheckSelection = new QAction(i18n("Spellcheck Selection..."), this); ac->addAction(QStringLiteral("tools_spelling_selection"), m_spellcheckSelection); m_spellcheckSelection->setIcon(QIcon::fromTheme(QStringLiteral("tools-check-spelling"))); m_spellcheckSelection->setWhatsThis(i18n("Check spelling of the selected text")); connect(m_spellcheckSelection, SIGNAL(triggered()), this, SLOT(spellcheckSelection())); } void KateSpellCheckDialog::updateActions() { m_spellcheckSelection->setEnabled(m_view->selection()); } void KateSpellCheckDialog::spellcheckFromCursor() { spellcheck(m_view->cursorPosition()); } void KateSpellCheckDialog::spellcheckSelection() { spellcheck(m_view->selectionRange().start(), m_view->selectionRange().end()); } void KateSpellCheckDialog::spellcheck() { spellcheck(KTextEditor::Cursor(0, 0)); } void KateSpellCheckDialog::spellcheck(const KTextEditor::Cursor &from, const KTextEditor::Cursor &to) { KTextEditor::Cursor start = from; KTextEditor::Cursor end = to; if (end.line() == 0 && end.column() == 0) { end = m_view->doc()->documentEnd(); } if (!m_speller) { m_speller = new Sonnet::Speller(); } m_speller->restore(); if (!m_backgroundChecker) { m_backgroundChecker = new Sonnet::BackgroundChecker(*m_speller); } if (!m_sonnetDialog) { m_sonnetDialog = new SpellCheckBar(m_backgroundChecker, m_view); m_sonnetDialog->showProgressDialog(200); m_sonnetDialog->showSpellCheckCompletionMessage(); m_sonnetDialog->setSpellCheckContinuedAfterReplacement(false); m_view->bottomViewBar()->addBarWidget(m_sonnetDialog); connect(m_sonnetDialog, SIGNAL(done(QString)), this, SLOT(installNextSpellCheckRange())); connect(m_sonnetDialog, SIGNAL(replace(QString,int,QString)), this, SLOT(corrected(QString,int,QString))); connect(m_sonnetDialog, SIGNAL(misspelling(QString,int)), this, SLOT(misspelling(QString,int))); connect(m_sonnetDialog, SIGNAL(cancel()), this, SLOT(cancelClicked())); connect(m_sonnetDialog, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(QObject*))); connect(m_sonnetDialog, SIGNAL(languageChanged(QString)), this, SLOT(languageChanged(QString))); } m_userSpellCheckLanguage.clear(); m_previousGivenSpellCheckLanguage.clear(); delete m_globalSpellCheckRange; // we expand to handle the situation when the last word in the range is replace by a new one m_globalSpellCheckRange = m_view->doc()->newMovingRange(KTextEditor::Range(start, end), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); m_spellCheckCancelledByUser = false; performSpellCheck(*m_globalSpellCheckRange); } KTextEditor::Cursor KateSpellCheckDialog::locatePosition(int pos) { uint remains; while (m_spellLastPos < (uint)pos) { remains = pos - m_spellLastPos; uint l = m_view->doc()->lineLength(m_spellPosCursor.line()) - m_spellPosCursor.column(); if (l > remains) { m_spellPosCursor.setColumn(m_spellPosCursor.column() + remains); m_spellLastPos = pos; } else { m_spellPosCursor.setLine(m_spellPosCursor.line() + 1); m_spellPosCursor.setColumn(0); m_spellLastPos += l + 1; } } return m_spellPosCursor; } void KateSpellCheckDialog::misspelling(const QString &word, int pos) { KTextEditor::Cursor cursor; int length; int origPos = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos); cursor = locatePosition(origPos); length = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos + word.length()) - origPos; m_view->setCursorPositionInternal(cursor, 1); m_view->setSelection(KTextEditor::Range(cursor, length)); } void KateSpellCheckDialog::corrected(const QString &word, int pos, const QString &newWord) { int origPos = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos); int length = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos + word.length()) - origPos; KTextEditor::Cursor replacementStartCursor = locatePosition(origPos); KTextEditor::Range replacementRange = KTextEditor::Range(replacementStartCursor, length); KTextEditor::DocumentPrivate *doc = m_view->doc(); KTextEditor::EditorPrivate::self()->spellCheckManager()->replaceCharactersEncodedIfNecessary(newWord, doc, replacementRange); m_currentSpellCheckRange.setRange(KTextEditor::Range(replacementStartCursor, m_currentSpellCheckRange.end())); // we have to be careful here: due to static word wrapping the text might change in addition to simply // the misspelled word being replaced, i.e. new line breaks might be inserted as well. As such, the text // in the 'Sonnet::Dialog' might be eventually out of sync with the visible text. Therefore, we 'restart' // spell checking from the current position. performSpellCheck(KTextEditor::Range(replacementStartCursor, m_globalSpellCheckRange->end())); } void KateSpellCheckDialog::performSpellCheck(const KTextEditor::Range &range) { if (range.isEmpty()) { spellCheckDone(); } m_languagesInSpellCheckRange = KTextEditor::EditorPrivate::self()->spellCheckManager()->spellCheckLanguageRanges(m_view->doc(), range); m_currentLanguageRangeIterator = m_languagesInSpellCheckRange.begin(); m_currentSpellCheckRange = KTextEditor::Range::invalid(); installNextSpellCheckRange(); // first check if there is really something to spell check if (m_currentSpellCheckRange.isValid()) { m_view->bottomViewBar()->showBarWidget(m_sonnetDialog); m_sonnetDialog->show(); m_sonnetDialog->setFocus(); } } void KateSpellCheckDialog::installNextSpellCheckRange() { if (m_spellCheckCancelledByUser || m_currentLanguageRangeIterator == m_languagesInSpellCheckRange.end()) { spellCheckDone(); return; } KateSpellCheckManager *spellCheckManager = KTextEditor::EditorPrivate::self()->spellCheckManager(); KTextEditor::Cursor nextRangeBegin = (m_currentSpellCheckRange.isValid() ? m_currentSpellCheckRange.end() : KTextEditor::Cursor::invalid()); m_currentSpellCheckRange = KTextEditor::Range::invalid(); m_currentDecToEncOffsetList.clear(); QList > rangeDictionaryPairList; while (m_currentLanguageRangeIterator != m_languagesInSpellCheckRange.end()) { const KTextEditor::Range ¤tLanguageRange = (*m_currentLanguageRangeIterator).first; const QString &dictionary = (*m_currentLanguageRangeIterator).second; KTextEditor::Range languageSubRange = (nextRangeBegin.isValid() ? KTextEditor::Range(nextRangeBegin, currentLanguageRange.end()) : currentLanguageRange); rangeDictionaryPairList = spellCheckManager->spellCheckWrtHighlightingRanges(m_view->doc(), languageSubRange, dictionary, false, true); Q_ASSERT(rangeDictionaryPairList.size() <= 1); if (rangeDictionaryPairList.size() == 0) { ++m_currentLanguageRangeIterator; if (m_currentLanguageRangeIterator != m_languagesInSpellCheckRange.end()) { nextRangeBegin = (*m_currentLanguageRangeIterator).first.start(); } } else { m_currentSpellCheckRange = rangeDictionaryPairList.first().first; QString dictionary = rangeDictionaryPairList.first().second; const bool languageChanged = (dictionary != m_previousGivenSpellCheckLanguage); m_previousGivenSpellCheckLanguage = dictionary; // if there was no change of dictionary stemming from the document language ranges and // the user has set a dictionary in the dialog, we use that one if (!languageChanged && !m_userSpellCheckLanguage.isEmpty()) { dictionary = m_userSpellCheckLanguage; } // we only allow the user to override the preset dictionary within a language range // given by the document else if (languageChanged) { m_userSpellCheckLanguage.clear(); } m_spellPosCursor = m_currentSpellCheckRange.start(); m_spellLastPos = 0; m_currentDecToEncOffsetList.clear(); KTextEditor::DocumentPrivate::OffsetList encToDecOffsetList; QString text = m_view->doc()->decodeCharacters(m_currentSpellCheckRange, m_currentDecToEncOffsetList, encToDecOffsetList); // ensure that no empty string is passed on to Sonnet as this can lead to a crash // (bug 228789) if (text.isEmpty()) { nextRangeBegin = m_currentSpellCheckRange.end(); continue; } if (m_speller->language() != dictionary) { m_speller->setLanguage(dictionary); m_backgroundChecker->setSpeller(*m_speller); } m_sonnetDialog->setBuffer(text); break; } } if (m_currentLanguageRangeIterator == m_languagesInSpellCheckRange.end()) { spellCheckDone(); return; } } void KateSpellCheckDialog::cancelClicked() { m_spellCheckCancelledByUser = true; spellCheckDone(); } void KateSpellCheckDialog::spellCheckDone() { m_currentSpellCheckRange = KTextEditor::Range::invalid(); m_currentDecToEncOffsetList.clear(); m_view->clearSelection(); } void KateSpellCheckDialog::objectDestroyed(QObject *object) { Q_UNUSED(object); - m_sonnetDialog = NULL; + m_sonnetDialog = nullptr; } void KateSpellCheckDialog::languageChanged(const QString &language) { m_userSpellCheckLanguage = language; } //END diff --git a/src/spellcheck/spellingmenu.cpp b/src/spellcheck/spellingmenu.cpp index d16b2a41..d6039ccb 100644 --- a/src/spellcheck/spellingmenu.cpp +++ b/src/spellcheck/spellingmenu.cpp @@ -1,225 +1,225 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2009-2010 by Michel Ludwig * * 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 "spellingmenu.h" #include "katedocument.h" #include "kateglobal.h" #include "kateview.h" #include "spellcheck/spellcheck.h" #include #include "katepartdebug.h" KateSpellingMenu::KateSpellingMenu(KTextEditor::ViewPrivate *view) : QObject(view), m_view(view), - m_spellingMenuAction(NULL), - m_ignoreWordAction(NULL), - m_addToDictionaryAction(NULL), - m_spellingMenu(NULL), - m_currentMisspelledRange(NULL), // we have to use 'm_currentMisspelledRange' + m_spellingMenuAction(nullptr), + m_ignoreWordAction(nullptr), + m_addToDictionaryAction(nullptr), + m_spellingMenu(nullptr), + m_currentMisspelledRange(nullptr), // we have to use 'm_currentMisspelledRange' // as QSignalMapper doesn't work with pairs of objects; // it just points to the object pointed to by either // 'm_currentMouseMisspelledRange' or 'm_currentCaretMisspelledRange' - m_currentMouseMisspelledRange(NULL), - m_currentCaretMisspelledRange(NULL), + m_currentMouseMisspelledRange(nullptr), + m_currentCaretMisspelledRange(nullptr), m_useMouseForMisspelledRange(false), m_suggestionsSignalMapper(new QSignalMapper(this)) { connect(m_suggestionsSignalMapper, SIGNAL(mapped(QString)), this, SLOT(replaceWordBySuggestion(QString))); } KateSpellingMenu::~KateSpellingMenu() { - m_currentMisspelledRange = NULL; // it shouldn't be accessed anymore as it could + m_currentMisspelledRange = nullptr; // it shouldn't be accessed anymore as it could // point to a non-existing object (bug 226724) // (for example, when it pointed to m_currentCaretMisspelledRange // and that range got deleted after the caret had left) - m_currentCaretMisspelledRange = NULL; - m_currentMouseMisspelledRange = NULL; + m_currentCaretMisspelledRange = nullptr; + m_currentMouseMisspelledRange = nullptr; } bool KateSpellingMenu::isEnabled() const { if (!m_spellingMenuAction) { return false; } return m_spellingMenuAction->isEnabled(); } void KateSpellingMenu::setEnabled(bool b) { if (m_spellingMenuAction) { m_spellingMenuAction->setEnabled(b); } if (m_ignoreWordAction) { m_ignoreWordAction->setEnabled(b); } if (m_addToDictionaryAction) { m_addToDictionaryAction->setEnabled(b); } } void KateSpellingMenu::setVisible(bool b) { if (m_spellingMenuAction) { m_spellingMenuAction->setVisible(b); } if (m_ignoreWordAction) { m_ignoreWordAction->setVisible(b); } if (m_addToDictionaryAction) { m_addToDictionaryAction->setVisible(b); } } void KateSpellingMenu::createActions(KActionCollection *ac) { m_spellingMenuAction = new KActionMenu(i18n("Spelling"), this); ac->addAction(QStringLiteral("spelling_suggestions"), m_spellingMenuAction); m_spellingMenu = m_spellingMenuAction->menu(); connect(m_spellingMenu, SIGNAL(aboutToShow()), this, SLOT(populateSuggestionsMenu())); m_ignoreWordAction = new QAction(i18n("Ignore Word"), this); connect(m_ignoreWordAction, SIGNAL(triggered()), this, SLOT(ignoreCurrentWord())); m_addToDictionaryAction = new QAction(i18n("Add to Dictionary"), this); connect(m_addToDictionaryAction, SIGNAL(triggered()), this, SLOT(addCurrentWordToDictionary())); setEnabled(false); setVisible(false); } void KateSpellingMenu::caretEnteredMisspelledRange(KTextEditor::MovingRange *range) { if (m_currentCaretMisspelledRange == range) { return; } - m_currentCaretMisspelledRange = NULL; + m_currentCaretMisspelledRange = nullptr; setEnabled(true); m_currentCaretMisspelledRange = range; } void KateSpellingMenu::caretExitedMisspelledRange(KTextEditor::MovingRange *range) { if (range != m_currentCaretMisspelledRange) { // order of 'exit' and 'entered' signals return; // was wrong } setEnabled(false); - m_currentCaretMisspelledRange = NULL; + m_currentCaretMisspelledRange = nullptr; } void KateSpellingMenu::mouseEnteredMisspelledRange(KTextEditor::MovingRange *range) { if (m_currentMouseMisspelledRange == range) { return; } m_currentMouseMisspelledRange = range; } void KateSpellingMenu::mouseExitedMisspelledRange(KTextEditor::MovingRange *range) { if (range != m_currentMouseMisspelledRange) { // order of 'exit' and 'entered' signals return; // was wrong } - m_currentMouseMisspelledRange = NULL; + m_currentMouseMisspelledRange = nullptr; } void KateSpellingMenu::rangeDeleted(KTextEditor::MovingRange *range) { if (m_currentCaretMisspelledRange == range) { - m_currentCaretMisspelledRange = NULL; + m_currentCaretMisspelledRange = nullptr; } if (m_currentMouseMisspelledRange == range) { - m_currentMouseMisspelledRange = NULL; + m_currentMouseMisspelledRange = nullptr; } if (m_currentMisspelledRange == range) { - m_currentMisspelledRange = NULL; + m_currentMisspelledRange = nullptr; } - setEnabled(m_currentCaretMisspelledRange != NULL); + setEnabled(m_currentCaretMisspelledRange != nullptr); } void KateSpellingMenu::setUseMouseForMisspelledRange(bool b) { m_useMouseForMisspelledRange = b; if (m_useMouseForMisspelledRange) { - setEnabled(m_currentMouseMisspelledRange != NULL); + setEnabled(m_currentMouseMisspelledRange != nullptr); } else if (!m_useMouseForMisspelledRange) { - setEnabled(m_currentCaretMisspelledRange != NULL); + setEnabled(m_currentCaretMisspelledRange != nullptr); } } void KateSpellingMenu::populateSuggestionsMenu() { m_spellingMenu->clear(); if ((m_useMouseForMisspelledRange && !m_currentMouseMisspelledRange) || (!m_useMouseForMisspelledRange && !m_currentCaretMisspelledRange)) { return; } m_currentMisspelledRange = (m_useMouseForMisspelledRange ? m_currentMouseMisspelledRange : m_currentCaretMisspelledRange); m_spellingMenu->addAction(m_ignoreWordAction); m_spellingMenu->addAction(m_addToDictionaryAction); m_spellingMenu->addSeparator(); const QString &misspelledWord = m_view->doc()->text(*m_currentMisspelledRange); const QString dictionary = m_view->doc()->dictionaryForMisspelledRange(*m_currentMisspelledRange); m_currentSuggestions = KTextEditor::EditorPrivate::self()->spellCheckManager()->suggestions(misspelledWord, dictionary); int counter = 0; for (QStringList::iterator i = m_currentSuggestions.begin(); i != m_currentSuggestions.end() && counter < 10; ++i) { const QString &suggestion = *i; QAction *action = new QAction(suggestion, m_spellingMenu); connect(action, SIGNAL(triggered()), m_suggestionsSignalMapper, SLOT(map())); m_suggestionsSignalMapper->setMapping(action, suggestion); m_spellingMenu->addAction(action); ++counter; } } void KateSpellingMenu::replaceWordBySuggestion(const QString &suggestion) { KTextEditor::DocumentPrivate *doc = m_view->doc(); KTextEditor::EditorPrivate::self()->spellCheckManager()->replaceCharactersEncodedIfNecessary(suggestion, doc, *m_currentMisspelledRange); } void KateSpellingMenu::addCurrentWordToDictionary() { if (!m_currentMisspelledRange) { return; } const QString &misspelledWord = m_view->doc()->text(*m_currentMisspelledRange); const QString dictionary = m_view->doc()->dictionaryForMisspelledRange(*m_currentMisspelledRange); KTextEditor::EditorPrivate::self()->spellCheckManager()->addToDictionary(misspelledWord, dictionary); m_view->doc()->clearMisspellingForWord(misspelledWord); // WARNING: 'm_currentMisspelledRange' is deleted here! } void KateSpellingMenu::ignoreCurrentWord() { if (!m_currentMisspelledRange) { return; } const QString &misspelledWord = m_view->doc()->text(*m_currentMisspelledRange); const QString dictionary = m_view->doc()->dictionaryForMisspelledRange(*m_currentMisspelledRange); KTextEditor::EditorPrivate::self()->spellCheckManager()->ignoreWord(misspelledWord, dictionary); m_view->doc()->clearMisspellingForWord(misspelledWord); // WARNING: 'm_currentMisspelledRange' is deleted here! } diff --git a/src/swapfile/kateswapdiffcreator.cpp b/src/swapfile/kateswapdiffcreator.cpp index 765c9cee..3ad47c74 100644 --- a/src/swapfile/kateswapdiffcreator.cpp +++ b/src/swapfile/kateswapdiffcreator.cpp @@ -1,164 +1,164 @@ /* This file is part of the Kate project. * * Copyright (C) 2010-2012 Dominik Haumann * * 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 "kateswapdiffcreator.h" #include "kateswapfile.h" #include "katedocument.h" #include "katepartdebug.h" #include #include #include #include #include //BEGIN SwapDiffCreator SwapDiffCreator::SwapDiffCreator(Kate::SwapFile *swapFile) : QObject(swapFile) , m_swapFile(swapFile) - , m_proc(0) + , m_proc(nullptr) { } SwapDiffCreator::~SwapDiffCreator() { } void SwapDiffCreator::viewDiff() { QString path = m_swapFile->fileName(); if (path.isNull()) { return; } QFile swp(path); if (!swp.open(QIODevice::ReadOnly)) { qCWarning(LOG_KTE) << "Can't open swap file"; return; } // create all needed tempfiles m_originalFile.setFileTemplate(QDir::tempPath() + QLatin1String("/katepart_XXXXXX.original")); m_recoveredFile.setFileTemplate(QDir::tempPath() + QLatin1String("/katepart_XXXXXX.recovered")); m_diffFile.setFileTemplate(QDir::tempPath() + QLatin1String("/katepart_XXXXXX.diff")); if (!m_originalFile.open() || !m_recoveredFile.open() || !m_diffFile.open()) { qCWarning(LOG_KTE) << "Can't open temporary files needed for diffing"; return; } // truncate files, just in case m_originalFile.resize(0); m_recoveredFile.resize(0); m_diffFile.resize(0); // create a document with the recovered data KTextEditor::DocumentPrivate recoverDoc; recoverDoc.setText(m_swapFile->document()->text()); // store original text in a file as utf-8 and close it { QTextStream stream(&m_originalFile); stream.setCodec(QTextCodec::codecForName("UTF-8")); stream << recoverDoc.text(); } m_originalFile.close(); // recover data QDataStream stream(&swp); recoverDoc.swapFile()->recover(stream, false); // store recovered text in a file as utf-8 and close it { QTextStream stream(&m_recoveredFile); stream.setCodec(QTextCodec::codecForName("UTF-8")); stream << recoverDoc.text(); } m_recoveredFile.close(); // create a KProcess proc for diff m_proc = new KProcess(this); m_proc->setOutputChannelMode(KProcess::MergedChannels); *m_proc << QStringLiteral("diff") << QStringLiteral("-u") << m_originalFile.fileName() << m_recoveredFile.fileName(); connect(m_proc, SIGNAL(readyRead()), this, SLOT(slotDataAvailable())); connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotDiffFinished())); // setCursor(Qt::WaitCursor); m_proc->start(); QTextStream ts(m_proc); int lineCount = recoverDoc.lines(); for (int line = 0; line < lineCount; ++line) { ts << recoverDoc.line(line) << '\n'; } ts.flush(); m_proc->closeWriteChannel(); } void SwapDiffCreator::slotDataAvailable() { // collect diff output m_diffFile.write(m_proc->readAll()); } void SwapDiffCreator::slotDiffFinished() { // collect last diff output, if any m_diffFile.write(m_proc->readAll()); // get the exit status to check whether diff command run successfully const QProcess::ExitStatus es = m_proc->exitStatus(); delete m_proc; - m_proc = 0; + m_proc = nullptr; // check exit status if (es != QProcess::NormalExit) { - KMessageBox::sorry(0, + KMessageBox::sorry(nullptr, i18n("The diff command failed. Please make sure that " "diff(1) is installed and in your PATH."), i18n("Error Creating Diff")); deleteLater(); return; } // sanity check: is there any diff content? if (m_diffFile.size() == 0) { - KMessageBox::information(0, + KMessageBox::information(nullptr, i18n("The files are identical."), i18n("Diff Output")); deleteLater(); return; } // close diffFile and avoid removal, KRun will do that later! m_diffFile.close(); m_diffFile.setAutoRemove(false); // KRun::runUrl should delete the file, once the client exits KRun::runUrl(QUrl::fromLocalFile(m_diffFile.fileName()), QStringLiteral("text/x-patch"), m_swapFile->document()->activeView(), true); deleteLater(); } //END SwapDiffCreator diff --git a/src/swapfile/kateswapfile.cpp b/src/swapfile/kateswapfile.cpp index a07035d8..1e72eefe 100644 --- a/src/swapfile/kateswapfile.cpp +++ b/src/swapfile/kateswapfile.cpp @@ -1,676 +1,676 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Dominik Haumann * Copyright (C) 2010 Diana-Victoria Tiriplica * * 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 "config.h" #include "kateswapfile.h" #include "kateconfig.h" #include "kateswapdiffcreator.h" #include "kateundomanager.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include #endif // swap file version header const static char swapFileVersionString[] = "Kate Swap File 2.0"; // tokens for swap files const static qint8 EA_StartEditing = 'S'; const static qint8 EA_FinishEditing = 'E'; const static qint8 EA_WrapLine = 'W'; const static qint8 EA_UnwrapLine = 'U'; const static qint8 EA_InsertText = 'I'; const static qint8 EA_RemoveText = 'R'; namespace Kate { -QTimer *SwapFile::s_timer = 0; +QTimer *SwapFile::s_timer = nullptr; SwapFile::SwapFile(KTextEditor::DocumentPrivate *document) : QObject(document) , m_document(document) , m_trackingEnabled(false) , m_recovered(false) , m_needSync(false) { // fixed version of serialisation m_stream.setVersion(QDataStream::Qt_4_6); // conect the timer connect(syncTimer(), SIGNAL(timeout()), this, SLOT(writeFileToDisk()), Qt::DirectConnection); // connecting the signals connect(&m_document->buffer(), SIGNAL(saved(QString)), this, SLOT(fileSaved(QString))); connect(&m_document->buffer(), SIGNAL(loaded(QString,bool)), this, SLOT(fileLoaded(QString))); connect(m_document, SIGNAL(configChanged()), this, SLOT(configChanged())); // tracking on! setTrackingEnabled(true); } SwapFile::~SwapFile() { // only remove swap file after data recovery (bug #304576) if (!shouldRecover()) { removeSwapFile(); } } void SwapFile::configChanged() { } void SwapFile::setTrackingEnabled(bool enable) { if (m_trackingEnabled == enable) { return; } m_trackingEnabled = enable; TextBuffer &buffer = m_document->buffer(); if (m_trackingEnabled) { connect(&buffer, SIGNAL(editingStarted()), this, SLOT(startEditing())); connect(&buffer, SIGNAL(editingFinished()), this, SLOT(finishEditing())); connect(m_document, SIGNAL(modifiedChanged(KTextEditor::Document*)), this, SLOT(modifiedChanged())); connect(&buffer, SIGNAL(lineWrapped(KTextEditor::Cursor)), this, SLOT(wrapLine(KTextEditor::Cursor))); connect(&buffer, SIGNAL(lineUnwrapped(int)), this, SLOT(unwrapLine(int))); connect(&buffer, SIGNAL(textInserted(KTextEditor::Cursor,QString)), this, SLOT(insertText(KTextEditor::Cursor,QString))); connect(&buffer, SIGNAL(textRemoved(KTextEditor::Range,QString)), this, SLOT(removeText(KTextEditor::Range))); } else { disconnect(&buffer, SIGNAL(editingStarted()), this, SLOT(startEditing())); disconnect(&buffer, SIGNAL(editingFinished()), this, SLOT(finishEditing())); disconnect(m_document, SIGNAL(modifiedChanged(KTextEditor::Document*)), this, SLOT(modifiedChanged())); disconnect(&buffer, SIGNAL(lineWrapped(KTextEditor::Cursor)), this, SLOT(wrapLine(KTextEditor::Cursor))); disconnect(&buffer, SIGNAL(lineUnwrapped(int)), this, SLOT(unwrapLine(int))); disconnect(&buffer, SIGNAL(textInserted(KTextEditor::Cursor,QString)), this, SLOT(insertText(KTextEditor::Cursor,QString))); disconnect(&buffer, SIGNAL(textRemoved(KTextEditor::Range,QString)), this, SLOT(removeText(KTextEditor::Range))); } } void SwapFile::fileClosed() { // remove old swap file, file is now closed if (!shouldRecover()) { removeSwapFile(); } else { m_document->setReadWrite(true); } // purge filename updateFileName(); } KTextEditor::DocumentPrivate *SwapFile::document() { return m_document; } bool SwapFile::isValidSwapFile(QDataStream &stream, bool checkDigest) const { // read and check header QByteArray header; stream >> header; if (header != swapFileVersionString) { qCWarning(LOG_KTE) << "Can't open swap file, wrong version"; return false; } // read checksum QByteArray checksum; stream >> checksum; //qCDebug(LOG_KTE) << "DIGEST:" << checksum << m_document->checksum(); if (checkDigest && checksum != m_document->checksum()) { qCWarning(LOG_KTE) << "Can't recover from swap file, checksum of document has changed"; return false; } return true; } void SwapFile::fileLoaded(const QString &) { // look for swap file if (!updateFileName()) { return; } if (!m_swapfile.exists()) { //qCDebug(LOG_KTE) << "No swap file"; return; } if (!QFileInfo(m_swapfile).isReadable()) { qCWarning(LOG_KTE) << "Can't open swap file (missing permissions)"; return; } // sanity check QFile peekFile(fileName()); if (peekFile.open(QIODevice::ReadOnly)) { QDataStream stream(&peekFile); if (!isValidSwapFile(stream, true)) { removeSwapFile(); return; } peekFile.close(); } else { qCWarning(LOG_KTE) << "Can't open swap file:" << fileName(); return; } // show swap file message m_document->setReadWrite(false); showSwapFileMessage(); } void SwapFile::modifiedChanged() { if (!m_document->isModified() && !shouldRecover()) { m_needSync = false; // the file is not modified and we are not in recover mode removeSwapFile(); } } void SwapFile::recover() { m_document->setReadWrite(true); // if isOpen() returns true, the swap file likely changed already (appended data) // Example: The document was falsely marked as writable and the user changed // text even though the recover bar was visible. In this case, a replay of // the swap file across wrong document content would happen -> certainly wrong if (m_swapfile.isOpen()) { qCWarning(LOG_KTE) << "Attempt to recover an already modified document. Aborting"; removeSwapFile(); return; } // if the file doesn't exist, abort (user might have deleted it, or use two editor instances) if (!m_swapfile.open(QIODevice::ReadOnly)) { qCWarning(LOG_KTE) << "Can't open swap file"; return; } // remember that the file has recovered m_recovered = true; // open data stream m_stream.setDevice(&m_swapfile); // replay the swap file bool success = recover(m_stream); // close swap file - m_stream.setDevice(0); + m_stream.setDevice(nullptr); m_swapfile.close(); if (!success) { removeSwapFile(); } // recover can also be called through the KTE::RecoveryInterface. // Make sure, the message is hidden in this case as well. if (m_swapMessage) { m_swapMessage->deleteLater(); } } bool SwapFile::recover(QDataStream &stream, bool checkDigest) { if (!isValidSwapFile(stream, checkDigest)) { return false; } // disconnect current signals setTrackingEnabled(false); // needed to set undo/redo cursors in a sane way bool firstEditInGroup = false; QVector undoCursor, redoCursor; // replay swapfile bool editRunning = false; bool brokenSwapFile = false; while (!stream.atEnd()) { if (brokenSwapFile) { break; } qint8 type; stream >> type; switch (type) { case EA_StartEditing: { m_document->editStart(); editRunning = true; firstEditInGroup = true; undoCursor.clear(); redoCursor.clear(); break; } case EA_FinishEditing: { m_document->editEnd(); // empty editStart() / editEnd() groups exist: only set cursor if required if (!firstEditInGroup) { // set undo/redo cursor of last KateUndoGroup of the undo manager m_document->undoManager()->setUndoRedoCursorsOfLastGroup(undoCursor, redoCursor); m_document->undoManager()->undoSafePoint(); } firstEditInGroup = false; editRunning = false; break; } case EA_WrapLine: { if (!editRunning) { brokenSwapFile = true; break; } int line = 0, column = 0; stream >> line >> column; // emulate buffer unwrapLine with document m_document->editWrapLine(line, column, true); // track undo/redo cursor if (firstEditInGroup) { firstEditInGroup = false; #warning TODO: multicursor undoCursor.clear(); undoCursor.append(KTextEditor::Cursor(line, column)); } redoCursor.clear(); redoCursor.append(KTextEditor::Cursor(line + 1, 0)); break; } case EA_UnwrapLine: { if (!editRunning) { brokenSwapFile = true; break; } int line = 0; stream >> line; // assert valid line Q_ASSERT(line > 0); const int undoColumn = m_document->lineLength(line - 1); // emulate buffer unwrapLine with document m_document->editUnWrapLine(line - 1, true, 0); // track undo/redo cursor if (firstEditInGroup) { firstEditInGroup = false; undoCursor.clear(); undoCursor.append(KTextEditor::Cursor(line, 0)); } redoCursor.clear(); redoCursor.append(KTextEditor::Cursor(line - 1, undoColumn)); break; } case EA_InsertText: { if (!editRunning) { brokenSwapFile = true; break; } int line, column; QByteArray text; stream >> line >> column >> text; m_document->insertText(KTextEditor::Cursor(line, column), QString::fromUtf8(text.data(), text.size())); // track undo/redo cursor if (firstEditInGroup) { firstEditInGroup = false; undoCursor.clear(); undoCursor.append(KTextEditor::Cursor(line, column)); } redoCursor.clear(); redoCursor.append(KTextEditor::Cursor(line, column + text.size())); break; } case EA_RemoveText: { if (!editRunning) { brokenSwapFile = true; break; } int line, startColumn, endColumn; stream >> line >> startColumn >> endColumn; m_document->removeText(KTextEditor::Range(KTextEditor::Cursor(line, startColumn), KTextEditor::Cursor(line, endColumn))); // track undo/redo cursor if (firstEditInGroup) { firstEditInGroup = false; undoCursor.clear(); undoCursor.append(KTextEditor::Cursor(line, endColumn)); } redoCursor.clear(); redoCursor.append(KTextEditor::Cursor(line, startColumn)); break; } default: { qCWarning(LOG_KTE) << "Unknown type:" << type; } } } // balanced editStart and editEnd? if (editRunning) { brokenSwapFile = true; m_document->editEnd(); } // warn the user if the swap file is not complete if (brokenSwapFile) { qCWarning(LOG_KTE) << "Some data might be lost"; } else { // set sane final cursor, if possible KTextEditor::View *view = m_document->activeView(); redoCursor = m_document->undoManager()->lastRedoCursor(); if (view && ! redoCursor.isEmpty()) { view->setCursorPositions(redoCursor); } } // reconnect the signals setTrackingEnabled(true); return true; } void SwapFile::fileSaved(const QString &) { m_needSync = false; // remove old swap file (e.g. if a file A was "saved as" B) removeSwapFile(); // set the name for the new swap file updateFileName(); } void SwapFile::startEditing() { // no swap file, no work if (m_swapfile.fileName().isEmpty()) { return; } // if swap file doesn't exists, open it in WriteOnly mode // if it does, append the data to the existing swap file, // in case you recover and start editing again if (!m_swapfile.exists()) { // create path if not there if (KateDocumentConfig::global()->swapFileMode() == KateDocumentConfig::SwapFilePresetDirectory && !QDir(KateDocumentConfig::global()->swapDirectory()).exists()) { QDir().mkpath(KateDocumentConfig::global()->swapDirectory()); } m_swapfile.open(QIODevice::WriteOnly); m_swapfile.setPermissions(QFileDevice::ReadOwner|QFileDevice::WriteOwner); m_stream.setDevice(&m_swapfile); // write file header m_stream << QByteArray(swapFileVersionString); // write checksum m_stream << m_document->checksum(); - } else if (m_stream.device() == 0) { + } else if (m_stream.device() == nullptr) { m_swapfile.open(QIODevice::Append); m_swapfile.setPermissions(QFileDevice::ReadOwner|QFileDevice::WriteOwner); m_stream.setDevice(&m_swapfile); } // format: qint8 m_stream << EA_StartEditing; } void SwapFile::finishEditing() { // skip if not open if (!m_swapfile.isOpen()) { return; } // write the file to the disk every 15 seconds (default) // skip this if we disabled that if (m_document->config()->swapSyncInterval() != 0 && !syncTimer()->isActive()) { // important: we store the interval as seconds, start wants milliseconds! syncTimer()->start(m_document->config()->swapSyncInterval() * 1000); } // format: qint8 m_stream << EA_FinishEditing; m_swapfile.flush(); } void SwapFile::wrapLine(const KTextEditor::Cursor &position) { // skip if not open if (!m_swapfile.isOpen()) { return; } // format: qint8, int, int m_stream << EA_WrapLine << position.line() << position.column(); m_needSync = true; } void SwapFile::unwrapLine(int line) { // skip if not open if (!m_swapfile.isOpen()) { return; } // format: qint8, int m_stream << EA_UnwrapLine << line; m_needSync = true; } void SwapFile::insertText(const KTextEditor::Cursor &position, const QString &text) { // skip if not open if (!m_swapfile.isOpen()) { return; } // format: qint8, int, int, bytearray m_stream << EA_InsertText << position.line() << position.column() << text.toUtf8(); m_needSync = true; } void SwapFile::removeText(const KTextEditor::Range &range) { // skip if not open if (!m_swapfile.isOpen()) { return; } // format: qint8, int, int, int Q_ASSERT(range.start().line() == range.end().line()); m_stream << EA_RemoveText << range.start().line() << range.start().column() << range.end().column(); m_needSync = true; } bool SwapFile::shouldRecover() const { // should not recover if the file has already recovered in another view if (m_recovered) { return false; } - return !m_swapfile.fileName().isEmpty() && m_swapfile.exists() && m_stream.device() == 0; + return !m_swapfile.fileName().isEmpty() && m_swapfile.exists() && m_stream.device() == nullptr; } void SwapFile::discard() { m_document->setReadWrite(true); removeSwapFile(); // discard can also be called through the KTE::RecoveryInterface. // Make sure, the message is hidden in this case as well. if (m_swapMessage) { m_swapMessage->deleteLater(); } } void SwapFile::removeSwapFile() { if (!m_swapfile.fileName().isEmpty() && m_swapfile.exists()) { - m_stream.setDevice(0); + m_stream.setDevice(nullptr); m_swapfile.close(); m_swapfile.remove(); } } bool SwapFile::updateFileName() { // first clear filename m_swapfile.setFileName(QString()); // get the new path QString path = fileName(); if (path.isNull()) { return false; } m_swapfile.setFileName(path); return true; } QString SwapFile::fileName() { const QUrl &url = m_document->url(); if (url.isEmpty() || !url.isLocalFile()) { return QString(); } const QString fullLocalPath(url.toLocalFile()); QString path; if (KateDocumentConfig::global()->swapFileMode() == KateDocumentConfig::SwapFilePresetDirectory) { path = KateDocumentConfig::global()->swapDirectory(); path.append(QLatin1Char('/')); // append the sha1 sum of the full path + filename, to avoid "too long" paths created path.append(QString::fromLatin1(QCryptographicHash::hash(fullLocalPath.toUtf8(), QCryptographicHash::Sha1).toHex())); path.append(QLatin1String("-")); path.append(QFileInfo(fullLocalPath).fileName()); path.append(QLatin1String(".kate-swp")); } else { path = fullLocalPath; int poz = path.lastIndexOf(QLatin1Char('/')); path.insert(poz + 1, QLatin1String(".")); path.append(QLatin1String(".kate-swp")); } return path; } QTimer *SwapFile::syncTimer() { - if (s_timer == 0) { + if (s_timer == nullptr) { s_timer = new QTimer(QApplication::instance()); s_timer->setSingleShot(true); } return s_timer; } void SwapFile::writeFileToDisk() { if (m_needSync) { m_needSync = false; #ifndef Q_OS_WIN // ensure that the file is written to disk #ifdef HAVE_FDATASYNC fdatasync(m_swapfile.handle()); #else fsync(m_swapfile.handle()); #endif #endif } } void SwapFile::showSwapFileMessage() { m_swapMessage = new KTextEditor::Message(i18n("The file was not closed properly."), KTextEditor::Message::Warning); m_swapMessage->setWordWrap(true); - QAction *diffAction = new QAction(QIcon::fromTheme(QStringLiteral("split")), i18n("View Changes"), 0); - QAction *recoverAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-redo")), i18n("Recover Data"), 0); - QAction *discardAction = new QAction(KStandardGuiItem::discard().icon(), i18n("Discard"), 0); + QAction *diffAction = new QAction(QIcon::fromTheme(QStringLiteral("split")), i18n("View Changes"), nullptr); + QAction *recoverAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-redo")), i18n("Recover Data"), nullptr); + QAction *discardAction = new QAction(KStandardGuiItem::discard().icon(), i18n("Discard"), nullptr); m_swapMessage->addAction(diffAction, false); m_swapMessage->addAction(recoverAction); m_swapMessage->addAction(discardAction); connect(diffAction, SIGNAL(triggered()), SLOT(showDiff())); connect(recoverAction, SIGNAL(triggered()), SLOT(recover()), Qt::QueuedConnection); connect(discardAction, SIGNAL(triggered()), SLOT(discard()), Qt::QueuedConnection); m_document->postMessage(m_swapMessage); } void SwapFile::showDiff() { // the diff creator deletes itself thorugh deleteLater() when it's done SwapDiffCreator *diffCreator = new SwapDiffCreator(this); diffCreator->viewDiff(); } } diff --git a/src/syntax/katehighlight.cpp b/src/syntax/katehighlight.cpp index d09f9d05..efb06e68 100644 --- a/src/syntax/katehighlight.cpp +++ b/src/syntax/katehighlight.cpp @@ -1,2296 +1,2294 @@ /* 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 KSyntaxHighlighting::Definition &def) : refCount(0) , startctx(0) , base_startctx(0) { errorsAndWarnings = QString(); building = false; noHl = false; m_foldingIndentationSensitive = false; folding = false; if (!def.isValid()) { 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.translatedName(); iSection = def.translatedSection(); iHidden = def.isHidden(); identifier = def.filePath(); iVersion = QString::number(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 bool firstLine = (_prevLine == nullptr); 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; + KateHlItem *item = nullptr; // 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; + QHash *foldingStartToCount = nullptr; /** * 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; + item = nullptr; // 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; + foldingStartToCount = nullptr; } } /** * 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)); + col = tmp.toUInt(nullptr, 16); p->setForeground(QColor(col)); } tmp = s[2]; if (!tmp.isEmpty()) { - col = tmp.toUInt(0, 16); p->setSelectedForeground(QColor(col)); + col = tmp.toUInt(nullptr, 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)); + col = tmp.toUInt(nullptr, 16); p->setBackground(QColor(col)); } tmp = s[8]; if (!tmp.isEmpty()) { - col = tmp.toUInt(0, 16); p->setSelectedBackground(QColor(col)); + col = tmp.toUInt(nullptr, 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; + return nullptr; } // 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; + return nullptr; } // 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; + return nullptr; } 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 d1a7a1d2..381bfe19 100644 --- a/src/syntax/katehighlight.h +++ b/src/syntax/katehighlight.h @@ -1,469 +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 KSyntaxHighlighting::Definition &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); + QVector* contextChanges = nullptr); /** * 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); + KConfig *cfg = nullptr /*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); + void getKateExtendedAttributeList(const QString &schema, QList &, KConfig *cfg = nullptr); + void getKateExtendedAttributeListCopy(const QString &schema, QList &, KConfig *cfg = nullptr); 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 diff --git a/src/syntax/katehighlighthelpers.cpp b/src/syntax/katehighlighthelpers.cpp index b981a76e..fff189aa 100644 --- a/src/syntax/katehighlighthelpers.cpp +++ b/src/syntax/katehighlighthelpers.cpp @@ -1,883 +1,883 @@ /* This file is part of the KDE libraries 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 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. */ //BEGIN INCLUDES #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 //END //BEGIN KateHlItem KateHlItem::KateHlItem(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2) : attr(attribute), ctx(context), region(regionId), region2(regionId2), lookAhead(false), dynamic(false), dynamicChild(false), firstNonSpace(false), onlyConsume(false), column(-1), alwaysStartEnable(true), customStartEnable(false), haveCache(false), cachingHandled(false) { } KateHlItem::~KateHlItem() { } void KateHlItem::dynamicSubstitute(QString &str, const QStringList *args) { for (int i = 0; i < str.length() - 1; ++i) { if (str[i] == QLatin1Char('%')) { char c = str[i + 1].toLatin1(); if (c == '%') { str.remove(i, 1); } else if (c >= '0' && c <= '9') { if ((int)(c - '0') < args->size()) { str.replace(i, 2, (*args)[c - '0']); i += ((*args)[c - '0']).length() - 1; } else { str.remove(i, 2); --i; } } } } } //END //BEGIN KateHlCharDetect KateHlCharDetect::KateHlCharDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, QChar c) : KateHlItem(attribute, context, regionId, regionId2) , sChar(c) { } int KateHlCharDetect::checkHgl(const QString &text, int offset, int /*len*/) { if (text[offset] == sChar) { return offset + 1; } return 0; } KateHlItem *KateHlCharDetect::clone(const QStringList *args) { char c = sChar.toLatin1(); if (c < '0' || c > '9' || (c - '0') >= args->size()) { return this; } KateHlCharDetect *ret = new KateHlCharDetect(attr, ctx, region, region2, (*args)[c - '0'][0]); ret->dynamicChild = true; return ret; } //END //BEGIN KateHl2CharDetect KateHl2CharDetect::KateHl2CharDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, QChar ch1, QChar ch2) : KateHlItem(attribute, context, regionId, regionId2) , sChar1(ch1) , sChar2(ch2) { } int KateHl2CharDetect::checkHgl(const QString &text, int offset, int len) { if ((len >= 2) && text[offset++] == sChar1 && text[offset++] == sChar2) { return offset; } return 0; } KateHlItem *KateHl2CharDetect::clone(const QStringList *args) { char c1 = sChar1.toLatin1(); char c2 = sChar2.toLatin1(); if (c1 < '0' || c1 > '9' || (c1 - '0') >= args->size()) { return this; } if (c2 < '0' || c2 > '9' || (c2 - '0') >= args->size()) { return this; } KateHl2CharDetect *ret = new KateHl2CharDetect(attr, ctx, region, region2, (*args)[c1 - '0'][0], (*args)[c2 - '0'][0]); ret->dynamicChild = true; return ret; } //END //BEGIN KateHlStringDetect KateHlStringDetect::KateHlStringDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, const QString &s, bool inSensitive) : KateHlItem(attribute, context, regionId, regionId2) , str(inSensitive ? s.toUpper() : s) , strLen(str.length()) , _inSensitive(inSensitive) { } int KateHlStringDetect::checkHgl(const QString &text, int offset, int len) { if (len < strLen) { return 0; } if (_inSensitive) { for (int i = 0; i < strLen; i++) if (text[offset++].toUpper() != str[i]) { return 0; } return offset; } else { for (int i = 0; i < strLen; i++) if (text[offset++] != str[i]) { return 0; } return offset; } return 0; } KateHlItem *KateHlStringDetect::clone(const QStringList *args) { QString newstr = str; dynamicSubstitute(newstr, args); if (newstr == str) { return this; } KateHlStringDetect *ret = new KateHlStringDetect(attr, ctx, region, region2, newstr, _inSensitive); ret->dynamicChild = true; return ret; } //END //BEGIN KateHlWordDetect KateHlWordDetect::KateHlWordDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, const QString &s, bool inSensitive) : KateHlStringDetect(attribute, context, regionId, regionId2, s, inSensitive) { } inline bool isWordCharacter(const QChar &c) { // http://pcre.org/pcre.txt say for word characters: // \w any character that matches \p{L} or \p{N}, plus underscore return c.isLetterOrNumber() || c.isMark() || c.unicode() == '_'; } int KateHlWordDetect::checkHgl(const QString &text, int offset, int len) { //NOTE: word boundary means: any non-word character. // make sure there is no letter or number before the word starts if (offset > 0 && isWordCharacter(text.at(offset - 1))) { return 0; } offset = KateHlStringDetect::checkHgl(text, offset, len); // make sure there is no letter or number after the word ends if (offset && offset < text.length() && isWordCharacter(text.at(offset))) { return 0; } return offset; } KateHlItem *KateHlWordDetect::clone(const QStringList *args) { QString newstr = str; dynamicSubstitute(newstr, args); if (newstr == str) { return this; } KateHlWordDetect *ret = new KateHlWordDetect(attr, ctx, region, region2, newstr, _inSensitive); ret->dynamicChild = true; return ret; } //END KateHlWordDetect //BEGIN KateHlRangeDetect KateHlRangeDetect::KateHlRangeDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, QChar ch1, QChar ch2) : KateHlItem(attribute, context, regionId, regionId2) , sChar1(ch1) , sChar2(ch2) { } int KateHlRangeDetect::checkHgl(const QString &text, int offset, int len) { if (text[offset] == sChar1) { do { offset++; len--; if (len < 1) { return 0; } } while (text[offset] != sChar2); return offset + 1; } return 0; } //END //BEGIN KateHlKeyword KateHlKeyword::KateHlKeyword(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, bool insensitive, const QString &delims) : KateHlItem(attribute, context, regionId, regionId2) , _insensitive(insensitive) , minLen(0xFFFFFF) , maxLen(0) { alwaysStartEnable = false; customStartEnable = true; foreach (QChar c, delims) { deliminators << c; } } KateHlKeyword::~KateHlKeyword() { qDeleteAll(dict); } QSet KateHlKeyword::allKeywords() const { QSet result; foreach (const QSet* v, dict) { if (!v) { continue; } result.unite(*v); } return result; } void KateHlKeyword::addList(const QStringList &list) { for (int i = 0; i < list.count(); ++i) { int len = list[i].length(); if (minLen > len) { minLen = len; } if (maxLen < len) { maxLen = len; } if (len >= dict.size()) { uint oldSize = dict.size(); dict.resize(len + 1); for (int m = oldSize; m < dict.size(); ++m) { - dict[m] = 0; + dict[m] = nullptr; } } if (!dict[len]) { dict[len] = new QSet (); } if (!_insensitive) { dict[len]->insert(list[i]); } else { dict[len]->insert(list[i].toLower()); } } } int KateHlKeyword::checkHgl(const QString &text, int offset, int len) { int offset2 = offset; int wordLen = 0; while ((len > wordLen) && !deliminators.contains(text[offset2])) { offset2++; wordLen++; if (wordLen > maxLen) { return 0; } } if (wordLen < minLen || !dict[wordLen]) { return 0; } if (!_insensitive) { if (dict[wordLen]->contains(QString::fromRawData(text.unicode() + offset, wordLen))) { return offset2; } } else { if (dict[wordLen]->contains(QString::fromRawData(text.unicode() + offset, wordLen).toLower())) { return offset2; } } return 0; } //END //BEGIN KateHlInt KateHlInt::KateHlInt(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2) : KateHlItem(attribute, context, regionId, regionId2) { alwaysStartEnable = false; } int KateHlInt::checkHgl(const QString &text, int offset, int len) { int offset2 = offset; while ((len > 0) && text[offset2].isDigit()) { offset2++; len--; } if (offset2 > offset) { if (len > 0) { for (int i = 0; i < subItems.size(); i++) { if ((offset = subItems[i]->checkHgl(text, offset2, len))) { return offset; } } } return offset2; } return 0; } //END //BEGIN KateHlFloat KateHlFloat::KateHlFloat(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2) : KateHlItem(attribute, context, regionId, regionId2) { alwaysStartEnable = false; } int KateHlFloat::checkHgl(const QString &text, int offset, int len) { bool b = false; bool p = false; while ((len > 0) && text[offset].isDigit()) { offset++; len--; b = true; } if ((len > 0) && (p = (text[offset] == QLatin1Char('.')))) { offset++; len--; while ((len > 0) && text[offset].isDigit()) { offset++; len--; b = true; } } if (!b) { return 0; } if ((len > 0) && ((text[offset].toLatin1() & 0xdf) == 'E')) { offset++; len--; } else { if (!p) { return 0; } else { if (len > 0) { for (int i = 0; i < subItems.size(); ++i) { int offset2 = subItems[i]->checkHgl(text, offset, len); if (offset2) { return offset2; } } } return offset; } } if ((len > 0) && (text[offset] == QLatin1Char('-') || text[offset] == QLatin1Char('+'))) { offset++; len--; } b = false; while ((len > 0) && text[offset].isDigit()) { offset++; len--; b = true; } if (b) { if (len > 0) { for (int i = 0; i < subItems.size(); ++i) { int offset2 = subItems[i]->checkHgl(text, offset, len); if (offset2) { return offset2; } } } return offset; } return 0; } //END //BEGIN KateHlCOct KateHlCOct::KateHlCOct(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2) : KateHlItem(attribute, context, regionId, regionId2) { alwaysStartEnable = false; } int KateHlCOct::checkHgl(const QString &text, int offset, int len) { if (text[offset].toLatin1() == '0') { offset++; len--; int offset2 = offset; while ((len > 0) && (text[offset2].toLatin1() >= '0' && text[offset2].toLatin1() <= '7')) { offset2++; len--; } if (offset2 > offset) { if ((len > 0) && ((text[offset2].toLatin1() & 0xdf) == 'L' || (text[offset].toLatin1() & 0xdf) == 'U')) { offset2++; } return offset2; } } return 0; } //END //BEGIN KateHlCHex KateHlCHex::KateHlCHex(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2) : KateHlItem(attribute, context, regionId, regionId2) { alwaysStartEnable = false; } int KateHlCHex::checkHgl(const QString &text, int offset, int len) { if ((len > 1) && (text[offset++].toLatin1() == '0') && ((text[offset++].toLatin1() & 0xdf) == 'X')) { len -= 2; int offset2 = offset; while ((len > 0) && (text[offset2].isDigit() || ((text[offset2].toLatin1() & 0xdf) >= 'A' && (text[offset2].toLatin1() & 0xdf) <= 'F'))) { offset2++; len--; } if (offset2 > offset) { if ((len > 0) && ((text[offset2].toLatin1() & 0xdf) == 'L' || (text[offset2].toLatin1() & 0xdf) == 'U')) { offset2++; } return offset2; } } return 0; } //END //BEGIN KateHlCFloat KateHlCFloat::KateHlCFloat(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2) : KateHlFloat(attribute, context, regionId, regionId2) { alwaysStartEnable = false; } int KateHlCFloat::checkIntHgl(const QString &text, int offset, int len) { int offset2 = offset; while ((len > 0) && text[offset].isDigit()) { offset2++; len--; } if (offset2 > offset) { return offset2; } return 0; } int KateHlCFloat::checkHgl(const QString &text, int offset, int len) { int offset2 = KateHlFloat::checkHgl(text, offset, len); if (offset2) { if ((text[offset2].toLatin1() & 0xdf) == 'F') { offset2++; } return offset2; } else { offset2 = checkIntHgl(text, offset, len); if (offset2 && ((text[offset2].toLatin1() & 0xdf) == 'F')) { return ++offset2; } else { return 0; } } } //END //BEGIN KateHlAnyChar KateHlAnyChar::KateHlAnyChar(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, const QString &charList) : KateHlItem(attribute, context, regionId, regionId2) , _charList(charList) { } int KateHlAnyChar::checkHgl(const QString &text, int offset, int) { if (_charList.contains(text[offset])) { return ++offset; } return 0; } //END //BEGIN KateHlRegExpr KateHlRegExpr::KateHlRegExpr(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, const QString ®exp, bool insensitive, bool minimal) : KateHlItem(attribute, context, regionId, regionId2) , m_regularExpression (regexp, (insensitive ? QRegularExpression::CaseInsensitiveOption : QRegularExpression::NoPatternOption) | (minimal ? QRegularExpression::InvertedGreedinessOption : QRegularExpression::NoPatternOption)) , m_handlesLineStart (regexp.startsWith(QLatin1Char('^'))) { } int KateHlRegExpr::checkHgl(const QString &text, int offset, int /*len*/) { /** * skip any match, if we have ^ and offset is already > 0 */ if (m_handlesLineStart && (offset > 0)) { return 0; } /** * perhaps clear cache? * that is needed, if we had a match and our current offset is already too large! */ if (haveCache && m_lastMatch.hasMatch() && (offset > m_lastMatch.capturedStart())) { haveCache = false; } /** * try to match if not already cached * store result in member variable for later reuse */ if (!haveCache) { m_lastMatch = m_regularExpression.match(text, offset); haveCache = true; } /** * no match or we match at wrong position? * => bad match */ if (!m_lastMatch.hasMatch() || offset != m_lastMatch.capturedStart()) { return 0; } /** * else: return current capture end */ return m_lastMatch.capturedEnd(); } void KateHlRegExpr::capturedTexts(QStringList &list) { /** * return stored list, if any */ list = m_lastMatch.capturedTexts(); } KateHlItem *KateHlRegExpr::clone(const QStringList *args) { QString regexp = m_regularExpression.pattern(); QStringList escArgs = *args; for (QStringList::Iterator it = escArgs.begin(); it != escArgs.end(); ++it) { (*it).replace(QRegularExpression(QStringLiteral("(\\W)")), QStringLiteral("\\\\1")); } dynamicSubstitute(regexp, &escArgs); if (regexp == m_regularExpression.pattern()) { return this; } // qCDebug(LOG_KTE) << "clone regexp: " << regexp; KateHlRegExpr *ret = new KateHlRegExpr(attr, ctx, region, region2, regexp , m_regularExpression.patternOptions() & QRegularExpression::CaseInsensitiveOption , m_regularExpression.patternOptions() & QRegularExpression::InvertedGreedinessOption); ret->dynamicChild = true; return ret; } // //END //BEGIN KateHlLineContinue KateHlLineContinue::KateHlLineContinue(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, QChar c) : KateHlItem(attribute, context, regionId, regionId2) , m_trailer(c.isNull() ? QLatin1Char('\\') : c) { } int KateHlLineContinue::checkHgl(const QString &text, int offset, int len) { if ((len == 1) && (text[offset] == m_trailer)) { return ++offset; } return 0; } //END //BEGIN KateHlCStringChar KateHlCStringChar::KateHlCStringChar(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2) : KateHlItem(attribute, context, regionId, regionId2) { } // checks for C escaped chars \n and escaped hex/octal chars static int checkEscapedChar(const QString &text, int offset, int &len) { int i; if (text[offset] == QLatin1Char('\\') && len > 1) { offset++; len--; switch (text[offset].toLatin1()) { case 'a': // checks for control chars case 'b': // we want to fall through case 'e': case 'f': case 'n': case 'r': case 't': case 'v': case '\'': case '\"': case '?' : // added ? ANSI C classifies this as an escaped char case '\\': offset++; len--; break; case 'x': // if it's like \xff offset++; // eat the x len--; // these for loops can probably be // replaced with something else but // for right now they work // check for hexdigits for (i = 0; (len > 0) && (i < 2); i++) { const char ch = text[offset].toLatin1(); if (((ch >= '0') && (ch <= '9')) || (((ch & 0xdf) >= 'A') && ((ch & 0xdf) <= 'F'))) { offset++; len--; } else { break; } } if (i == 0) { return 0; // takes care of case '\x' } break; case '0': case '1': case '2': case '3' : case '4': case '5': case '6': case '7' : for (i = 0; (len > 0) && (i < 3) && (text[offset] >= QLatin1Char('0') && text[offset] <= QLatin1Char('7')); i++) { offset++; len--; } break; default: return 0; } return offset; } return 0; } int KateHlCStringChar::checkHgl(const QString &text, int offset, int len) { return checkEscapedChar(text, offset, len); } //END //BEGIN KateHlCChar KateHlCChar::KateHlCChar(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2) : KateHlItem(attribute, context, regionId, regionId2) { } int KateHlCChar::checkHgl(const QString &text, int offset, int len) { if ((len > 1) && (text[offset] == QLatin1Char('\'')) && (text[offset + 1] != QLatin1Char('\''))) { int oldl; oldl = len; len--; int offset2 = checkEscapedChar(text, offset + 1, len); if (!offset2) { if (oldl > 2) { offset2 = offset + 2; len = oldl - 2; } else { return 0; } } if ((len > 0) && (text[offset2] == QLatin1Char('\''))) { return ++offset2; } } return 0; } //END //BEGIN KateHl2CharDetect KateHl2CharDetect::KateHl2CharDetect(int attribute, KateHlContextModification context, signed char regionId, signed char regionId2, const QChar *s) : KateHlItem(attribute, context, regionId, regionId2) { sChar1 = s[0]; sChar2 = s[1]; } //END KateHl2CharDetect //BEGIN KateHlContext KateHlContext::KateHlContext(const QString &_hlId, int attribute, KateHlContextModification _lineEndContext, bool _fallthrough, KateHlContextModification _fallthroughContext, bool _dynamic, bool _noIndentationBasedFolding, bool _emptyLineContext, KateHlContextModification _emptyLineContextModification) { hlId = _hlId; attr = attribute; lineEndContext = _lineEndContext; fallthrough = _fallthrough; ftctx = _fallthroughContext; dynamic = _dynamic; dynamicChild = false; noIndentationBasedFolding = _noIndentationBasedFolding; emptyLineContext = _emptyLineContext; emptyLineContextModification = _emptyLineContextModification; if (_noIndentationBasedFolding) { qCDebug(LOG_KTE) << "**********************_noIndentationBasedFolding is TRUE*****************"; } } KateHlContext *KateHlContext::clone(const QStringList *args) { KateHlContext *ret = new KateHlContext(hlId, attr, lineEndContext, fallthrough, ftctx, false, noIndentationBasedFolding , emptyLineContext, emptyLineContextModification ); for (int n = 0; n < items.size(); ++n) { KateHlItem *item = items[n]; KateHlItem *i = (item->dynamic ? item->clone(args) : item); ret->items.append(i); } ret->dynamicChild = true; return ret; } KateHlContext::~KateHlContext() { if (dynamicChild) { for (int n = 0; n < items.size(); ++n) { if (items[n]->dynamicChild) { delete items[n]; } } } } //END diff --git a/src/syntax/katehighlightingcmds.cpp b/src/syntax/katehighlightingcmds.cpp index 22925726..1f18b38e 100644 --- a/src/syntax/katehighlightingcmds.cpp +++ b/src/syntax/katehighlightingcmds.cpp @@ -1,63 +1,63 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2014 Christoph Rüßler * * 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 "katehighlightingcmds.h" #include "katesyntaxmanager.h" #include "katedocument.h" #include "kateview.h" #include "kateglobal.h" #include "katehighlight.h" #include -KateCommands::Highlighting *KateCommands::Highlighting::m_instance = 0; +KateCommands::Highlighting *KateCommands::Highlighting::m_instance = nullptr; bool KateCommands::Highlighting::exec(KTextEditor::View *view, const QString &cmd, QString &, const KTextEditor::Range &) { if(cmd.startsWith(QLatin1String("reload-highlighting"))) { KateHlManager *manager = KTextEditor::EditorPrivate::self()->hlManager(); manager->reload(); return true; } else if(cmd.startsWith(QLatin1String("edit-highlighting"))) { KTextEditor::DocumentPrivate* document = static_cast(view->document()); KateHighlighting *highlighting = document->highlight(); if(!highlighting->noHighlighting()) { QUrl url = QUrl::fromLocalFile(highlighting->getIdentifier()); KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); app->openUrl(url); } return true; } return true; } bool KateCommands::Highlighting::help(KTextEditor::View*, const QString&, QString&) { return false; } diff --git a/src/syntax/katehighlightingcmds.h b/src/syntax/katehighlightingcmds.h index bb2dbbc1..94d3a0e2 100644 --- a/src/syntax/katehighlightingcmds.h +++ b/src/syntax/katehighlightingcmds.h @@ -1,68 +1,68 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2014 Christoph Rüßler * * 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_RELOAD_H #define KATE_HIGHLIGHT_RELOAD_H #include namespace KateCommands { class Highlighting : public KTextEditor::Command { Highlighting() : KTextEditor::Command(QStringList() << QStringLiteral("reload-highlighting") << QStringLiteral("edit-highlighting")) { } static Highlighting* m_instance; public: ~Highlighting() { - m_instance = 0; + m_instance = nullptr; } static Highlighting *self() { - if (m_instance == 0) { + if (m_instance == nullptr) { m_instance = new Highlighting(); } return m_instance; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) Q_DECL_OVERRIDE; /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; }; } #endif diff --git a/src/syntax/katehighlightmenu.cpp b/src/syntax/katehighlightmenu.cpp index d6d449dd..589e7927 100644 --- a/src/syntax/katehighlightmenu.cpp +++ b/src/syntax/katehighlightmenu.cpp @@ -1,107 +1,107 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2003 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. */ //BEGIN Includes #include "katehighlightmenu.h" #include "katedocument.h" #include "kateconfig.h" #include "kateview.h" #include "kateglobal.h" #include "katesyntaxmanager.h" #include "katesyntaxdocument.h" #include "katepartdebug.h" //END Includes KateHighlightingMenu::~KateHighlightingMenu() { qDeleteAll(subMenus); } void KateHighlightingMenu::init() { - m_doc = 0; + m_doc = nullptr; connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); m_actionGroup = new QActionGroup(menu()); } void KateHighlightingMenu::updateMenu(KTextEditor::DocumentPrivate *doc) { m_doc = doc; } void KateHighlightingMenu::slotAboutToShow() { for (int z = 0; z < KateHlManager::self()->highlights(); z++) { QString hlName = KateHlManager::self()->hlNameTranslated(z); QString hlSection = KateHlManager::self()->hlSection(z); if (!KateHlManager::self()->hlHidden(z)) { if (!hlSection.isEmpty() && !names.contains(hlName)) { if (!subMenusName.contains(hlSection)) { subMenusName << hlSection; QMenu *qmenu = new QMenu(QLatin1Char('&') + hlSection); subMenus.append(qmenu); menu()->addMenu(qmenu); } int m = subMenusName.indexOf(hlSection); names << hlName; QAction *a = subMenus.at(m)->addAction(QLatin1Char('&') + hlName, this, SLOT(setHl())); m_actionGroup->addAction(a); a->setData(KateHlManager::self()->hlName(z)); a->setCheckable(true); subActions.append(a); } else if (!names.contains(hlName)) { names << hlName; QAction *a = menu()->addAction(QLatin1Char('&') + hlName, this, SLOT(setHl())); m_actionGroup->addAction(a); a->setData(KateHlManager::self()->hlName(z)); a->setCheckable(true); subActions.append(a); } } } if (!m_doc) { return; } QString mode = m_doc->highlightingMode(); for (int i = 0; i < subActions.count(); i++) { subActions[i]->setChecked(subActions[i]->data().toString() == mode); } } void KateHighlightingMenu::setHl() { if (!m_doc || !sender()) { return; } QAction *action = qobject_cast(sender()); if (!action) { return; } QString mode = action->data().toString(); m_doc->setHighlightingMode(mode); // use change, honor this m_doc->setDontChangeHlOnSave(); } diff --git a/src/syntax/katesyntaxdocument.cpp b/src/syntax/katesyntaxdocument.cpp index 690d5a67..3a4ae058 100644 --- a/src/syntax/katesyntaxdocument.cpp +++ b/src/syntax/katesyntaxdocument.cpp @@ -1,336 +1,336 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Joseph Wenninger Copyright (C) 2000 Scott Manson 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 "katesyntaxdocument.h" #include "katepartdebug.h" #include "kateglobal.h" #include #include #include #include #include #include #include // use this to turn on over verbose debug output... #undef KSD_OVER_VERBOSE KateSyntaxDocument::KateSyntaxDocument() { } KateSyntaxDocument::~KateSyntaxDocument() { // cleanup, else we have a memory leak! clearCache(); } /** If the open hl file is different from the one needed, it opens the new one and assign some other things. identifier = File name and path of the new xml needed */ bool KateSyntaxDocument::setIdentifier(const QString &identifier) { // already existing in cache? be done if (m_domDocuments.contains(identifier)) { currentFile = identifier; return true; } // else: try to open QFile f(identifier); if (!f.open(QIODevice::ReadOnly)) { // Oh o, we couldn't open the file. KMessageBox::error(QApplication::activeWindow(), i18n("Unable to open %1", identifier)); return false; } // try to parse QDomDocument *document = new QDomDocument(); QString errorMsg; int line, col; if (!document->setContent(&f, &errorMsg, &line, &col)) { KMessageBox::error(QApplication::activeWindow(), i18n("The error %4
has been detected in the file %1 at %2/%3
", identifier, line, col, i18nc("QXml", errorMsg.toUtf8().data()))); delete document; return false; } // cache and be done currentFile = identifier; m_domDocuments[currentFile] = document; return true; } void KateSyntaxDocument::clearCache() { qDeleteAll(m_domDocuments); m_domDocuments.clear(); currentFile.clear(); m_data.clear(); } /** * Jump to the next group, KateSyntaxContextData::currentGroup will point to the next group */ bool KateSyntaxDocument::nextGroup(KateSyntaxContextData *data) { if (!data) { return false; } // No group yet so go to first child if (data->currentGroup.isNull()) { // Skip over non-elements. So far non-elements are just comments QDomNode node = data->parent.firstChild(); while (node.isComment()) { node = node.nextSibling(); } data->currentGroup = node.toElement(); } else { // common case, iterate over siblings, skipping comments as we go QDomNode node = data->currentGroup.nextSibling(); while (node.isComment()) { node = node.nextSibling(); } data->currentGroup = node.toElement(); } return !data->currentGroup.isNull(); } /** * Jump to the next item, KateSyntaxContextData::item will point to the next item */ bool KateSyntaxDocument::nextItem(KateSyntaxContextData *data) { if (!data) { return false; } if (data->item.isNull()) { QDomNode node = data->currentGroup.firstChild(); while (node.isComment()) { node = node.nextSibling(); } data->item = node.toElement(); } else { QDomNode node = data->item.nextSibling(); while (node.isComment()) { node = node.nextSibling(); } data->item = node.toElement(); } return !data->item.isNull(); } /** * This function is used to fetch the atributes of the tags of the item in a KateSyntaxContextData. */ QString KateSyntaxDocument::groupItemData(const KateSyntaxContextData *data, const QString &name) { if (!data) { return QString(); } // If there's no name just return the tag name of data->item if ((!data->item.isNull()) && (name.isEmpty())) { return data->item.tagName(); } // if name is not empty return the value of the attribute name if (!data->item.isNull()) { return data->item.attribute(name); } return QString(); } QString KateSyntaxDocument::groupData(const KateSyntaxContextData *data, const QString &name) { if (!data) { return QString(); } if (!data->currentGroup.isNull()) { return data->currentGroup.attribute(name); } else { return QString(); } } void KateSyntaxDocument::freeGroupInfo(KateSyntaxContextData *data) { delete data; } KateSyntaxContextData *KateSyntaxDocument::getSubItems(KateSyntaxContextData *data) { KateSyntaxContextData *retval = new KateSyntaxContextData; - if (data != 0) { + if (data != nullptr) { retval->parent = data->currentGroup; retval->currentGroup = data->item; } return retval; } bool KateSyntaxDocument::getElement(QDomElement &element, const QString &mainGroupName, const QString &config) { #ifdef KSD_OVER_VERBOSE qCDebug(LOG_KTE) << "Looking for \"" << mainGroupName << "\" -> \"" << config << "\"."; #endif QDomNodeList nodes; if (m_domDocuments.contains(currentFile)) nodes = m_domDocuments[currentFile]->documentElement().childNodes(); // Loop over all these child nodes looking for mainGroupName for (int i = 0; i < nodes.count(); i++) { QDomElement elem = nodes.item(i).toElement(); if (elem.tagName() == mainGroupName) { // Found mainGroupName ... QDomNodeList subNodes = elem.childNodes(); // ... so now loop looking for config for (int j = 0; j < subNodes.count(); j++) { QDomElement subElem = subNodes.item(j).toElement(); if (subElem.tagName() == config) { // Found it! element = subElem; return true; } } #ifdef KSD_OVER_VERBOSE qCDebug(LOG_KTE) << "WARNING: \"" << config << "\" wasn't found!"; #endif return false; } } #ifdef KSD_OVER_VERBOSE qCDebug(LOG_KTE) << "WARNING: \"" << mainGroupName << "\" wasn't found!"; #endif return false; } /** * Get the KateSyntaxContextData of the QDomElement Config inside mainGroupName * KateSyntaxContextData::item will contain the QDomElement found */ KateSyntaxContextData *KateSyntaxDocument::getConfig(const QString &mainGroupName, const QString &config) { QDomElement element; if (getElement(element, mainGroupName, config)) { KateSyntaxContextData *data = new KateSyntaxContextData; data->item = element; return data; } - return 0; + return nullptr; } /** * Get the KateSyntaxContextData of the QDomElement Config inside mainGroupName * KateSyntaxContextData::parent will contain the QDomElement found */ KateSyntaxContextData *KateSyntaxDocument::getGroupInfo(const QString &mainGroupName, const QString &group) { QDomElement element; if (getElement(element, mainGroupName, group + QLatin1Char('s'))) { KateSyntaxContextData *data = new KateSyntaxContextData; data->parent = element; return data; } - return 0; + return nullptr; } /** * Returns a list with all the keywords inside the list type */ QStringList &KateSyntaxDocument::finddata(const QString &mainGroup, const QString &type, bool clearList) { #ifdef KSD_OVER_VERBOSE qCDebug(LOG_KTE) << "Create a list of keywords \"" << type << "\" from \"" << mainGroup << "\"."; #endif if (clearList) { m_data.clear(); } if (!m_domDocuments.contains(currentFile)) return m_data; for (QDomNode node = m_domDocuments[currentFile]->documentElement().firstChild(); !node.isNull(); node = node.nextSibling()) { QDomElement elem = node.toElement(); if (elem.tagName() == mainGroup) { #ifdef KSD_OVER_VERBOSE qCDebug(LOG_KTE) << "\"" << mainGroup << "\" found."; #endif QDomNodeList nodelist1 = elem.elementsByTagName(QStringLiteral("list")); for (int l = 0; l < nodelist1.count(); l++) { if (nodelist1.item(l).toElement().attribute(QStringLiteral("name")) == type) { #ifdef KSD_OVER_VERBOSE qCDebug(LOG_KTE) << "List with attribute name=\"" << type << "\" found."; #endif QDomNodeList childlist = nodelist1.item(l).toElement().childNodes(); for (int i = 0; i < childlist.count(); i++) { QString element = childlist.item(i).toElement().text().trimmed(); if (element.isEmpty()) { continue; } #ifdef KSD_OVER_VERBOSE if (i < 6) { qCDebug(LOG_KTE) << "\"" << element << "\" added to the list \"" << type << "\""; } else if (i == 6) { qCDebug(LOG_KTE) << "... The list continues ..."; } #endif m_data += element; } break; } } break; } } return m_data; } diff --git a/src/syntax/katesyntaxmanager.cpp b/src/syntax/katesyntaxmanager.cpp index 8d57594e..0488a0b9 100644 --- a/src/syntax/katesyntaxmanager.cpp +++ b/src/syntax/katesyntaxmanager.cpp @@ -1,720 +1,720 @@ /* This file is part of the KDE libraries 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 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. */ //BEGIN INCLUDES #include "katesyntaxmanager.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 "katehighlight.h" #include "katepartdebug.h" #include "katedefaultcolors.h" #include #include #include #include #include #include #include #include #include #include #include #include //END using namespace KTextEditor; //BEGIN KateHlManager KateHlManager::KateHlManager() : QObject() , m_config(KTextEditor::EditorPrivate::unitTestMode() ? QString() :QStringLiteral("katesyntaxhighlightingrc") , KTextEditor::EditorPrivate::unitTestMode() ? KConfig::SimpleConfig : KConfig::NoGlobals) // skip config for unit tests! , commonSuffixes({QStringLiteral(".orig"), QStringLiteral(".new"), QStringLiteral("~"), QStringLiteral(".bak"), QStringLiteral(".BAK")}) , dynamicCtxsCount(0) , forceNoDCReset(false) { // Let's build the Mode List setupModeList(); lastCtxsReset.start(); } KateHlManager::~KateHlManager() { qDeleteAll(hlList); } void KateHlManager::setupModeList() { const auto defs = m_repository.definitions(); hlList.reserve(defs.size() + 1); hlDict.reserve(defs.size() + 1); for (int i = 0; i < defs.count(); i++) { KateHighlighting *hl = new KateHighlighting(defs.at(i)); hlList.push_back(hl); hlDict.insert(hl->name(), hl); } // Normal HL KateHighlighting *hl = new KateHighlighting(KSyntaxHighlighting::Definition()); hlList.prepend(hl); hlDict.insert(hl->name(), hl); } KateHlManager *KateHlManager::self() { return KTextEditor::EditorPrivate::self()->hlManager(); } KateHighlighting *KateHlManager::getHl(int n) { if (n < 0 || n >= hlList.count()) { n = 0; } return hlList.at(n); } int KateHlManager::nameFind(const QString &name) { for (int i = 0; i < hlList.count(); ++i) { if (hlList.at(i)->name().compare(name, Qt::CaseInsensitive) == 0) { return i; } } return -1; } int KateHlManager::defaultStyleCount() { return KTextEditor::dsError + 1; } QString KateHlManager::defaultStyleName(int n, bool translateNames) { static QStringList names; static QStringList translatedNames; if (names.isEmpty()) { names << QStringLiteral("Normal"); names << QStringLiteral("Keyword"); names << QStringLiteral("Function"); names << QStringLiteral("Variable"); names << QStringLiteral("Control Flow"); names << QStringLiteral("Operator"); names << QStringLiteral("Built-in"); names << QStringLiteral("Extension"); names << QStringLiteral("Preprocessor"); names << QStringLiteral("Attribute"); names << QStringLiteral("Character"); names << QStringLiteral("Special Character"); names << QStringLiteral("String"); names << QStringLiteral("Verbatim String"); names << QStringLiteral("Special String"); names << QStringLiteral("Import"); names << QStringLiteral("Data Type"); names << QStringLiteral("Decimal/Value"); names << QStringLiteral("Base-N Integer"); names << QStringLiteral("Floating Point"); names << QStringLiteral("Constant"); names << QStringLiteral("Comment"); names << QStringLiteral("Documentation"); names << QStringLiteral("Annotation"); names << QStringLiteral("Comment Variable"); // this next one is for denoting the beginning/end of a user defined folding region names << QStringLiteral("Region Marker"); names << QStringLiteral("Information"); names << QStringLiteral("Warning"); names << QStringLiteral("Alert"); names << QStringLiteral("Others"); // this one is for marking invalid input names << QStringLiteral("Error"); translatedNames << i18nc("@item:intable Text context", "Normal"); translatedNames << i18nc("@item:intable Text context", "Keyword"); translatedNames << i18nc("@item:intable Text context", "Function"); translatedNames << i18nc("@item:intable Text context", "Variable"); translatedNames << i18nc("@item:intable Text context", "Control Flow"); translatedNames << i18nc("@item:intable Text context", "Operator"); translatedNames << i18nc("@item:intable Text context", "Built-in"); translatedNames << i18nc("@item:intable Text context", "Extension"); translatedNames << i18nc("@item:intable Text context", "Preprocessor"); translatedNames << i18nc("@item:intable Text context", "Attribute"); translatedNames << i18nc("@item:intable Text context", "Character"); translatedNames << i18nc("@item:intable Text context", "Special Character"); translatedNames << i18nc("@item:intable Text context", "String"); translatedNames << i18nc("@item:intable Text context", "Verbatim String"); translatedNames << i18nc("@item:intable Text context", "Special String"); translatedNames << i18nc("@item:intable Text context", "Imports, Modules, Includes"); translatedNames << i18nc("@item:intable Text context", "Data Type"); translatedNames << i18nc("@item:intable Text context", "Decimal/Value"); translatedNames << i18nc("@item:intable Text context", "Base-N Integer"); translatedNames << i18nc("@item:intable Text context", "Floating Point"); translatedNames << i18nc("@item:intable Text context", "Constant"); translatedNames << i18nc("@item:intable Text context", "Comment"); translatedNames << i18nc("@item:intable Text context", "Documentation"); translatedNames << i18nc("@item:intable Text context", "Annotation"); translatedNames << i18nc("@item:intable Text context", "Comment Variable"); // this next one is for denoting the beginning/end of a user defined folding region translatedNames << i18nc("@item:intable Text context", "Region Marker"); translatedNames << i18nc("@item:intable Text context", "Information"); translatedNames << i18nc("@item:intable Text context", "Warning"); translatedNames << i18nc("@item:intable Text context", "Alert"); translatedNames << i18nc("@item:intable Text context", "Others"); // this one is for marking invalid input translatedNames << i18nc("@item:intable Text context", "Error"); } // sanity checks Q_ASSERT(n >= 0); Q_ASSERT(n < names.size()); return translateNames ? translatedNames[n] : names[n]; } int KateHlManager::defaultStyleNameToIndex(const QString &name) { // // Normal text and source code // if (name == QLatin1String("dsNormal")) { return KTextEditor::dsNormal; } else if (name == QLatin1String("dsKeyword")) { return KTextEditor::dsKeyword; } else if (name == QLatin1String("dsFunction")) { return KTextEditor::dsFunction; } else if (name == QLatin1String("dsVariable")) { return KTextEditor::dsVariable; } else if (name == QLatin1String("dsControlFlow")) { return KTextEditor::dsControlFlow; } else if (name == QLatin1String("dsOperator")) { return KTextEditor::dsOperator; } else if (name == QLatin1String("dsBuiltIn")) { return KTextEditor::dsBuiltIn; } else if (name == QLatin1String("dsExtension")) { return KTextEditor::dsExtension; } else if (name == QLatin1String("dsPreprocessor")) { return KTextEditor::dsPreprocessor; } else if (name == QLatin1String("dsAttribute")) { return KTextEditor::dsAttribute; } // // Strings & Characters // if (name == QLatin1String("dsChar")) { return KTextEditor::dsChar; } else if (name == QLatin1String("dsSpecialChar")) { return KTextEditor::dsSpecialChar; } else if (name == QLatin1String("dsString")) { return KTextEditor::dsString; } else if (name == QLatin1String("dsVerbatimString")) { return KTextEditor::dsVerbatimString; } else if (name == QLatin1String("dsSpecialString")) { return KTextEditor::dsSpecialString; } else if (name == QLatin1String("dsImport")) { return KTextEditor::dsImport; } // // Numbers, Types & Constants // if (name == QLatin1String("dsDataType")) { return KTextEditor::dsDataType; } else if (name == QLatin1String("dsDecVal")) { return KTextEditor::dsDecVal; } else if (name == QLatin1String("dsBaseN")) { return KTextEditor::dsBaseN; } else if (name == QLatin1String("dsFloat")) { return KTextEditor::dsFloat; } else if (name == QLatin1String("dsConstant")) { return KTextEditor::dsConstant; } // // Comments & Documentation // if (name == QLatin1String("dsComment")) { return KTextEditor::dsComment; } else if (name == QLatin1String("dsDocumentation")) { return KTextEditor::dsDocumentation; } else if (name == QLatin1String("dsAnnotation")) { return KTextEditor::dsAnnotation; } else if (name == QLatin1String("dsCommentVar")) { return KTextEditor::dsCommentVar; } else if (name == QLatin1String("dsRegionMarker")) { return KTextEditor::dsRegionMarker; } else if (name == QLatin1String("dsInformation")) { return KTextEditor::dsInformation; } else if (name == QLatin1String("dsWarning")) { return KTextEditor::dsWarning; } else if (name == QLatin1String("dsAlert")) { return KTextEditor::dsAlert; } // // Misc // if (name == QLatin1String("dsOthers")) { return KTextEditor::dsOthers; } else if (name == QLatin1String("dsError")) { return KTextEditor::dsError; } return KTextEditor::dsNormal; } void KateHlManager::getDefaults(const QString &schema, KateAttributeList &list, KConfig *cfg) { const KColorScheme &scheme(KTextEditor::EditorPrivate::self()->defaultColors().view()); const KColorScheme &schemeSelected(KTextEditor::EditorPrivate::self()->defaultColors().selection()); ///NOTE: it's important to append in the order of the KTextEditor::DefaultStyle /// enum, to make KTextEditor::DocumentPrivate::defaultStyle() work properly. { // dsNormal Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground().color()); attrib->setSelectedForeground(schemeSelected.foreground().color()); list.append(attrib); } { // dsKeyword Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground().color()); attrib->setSelectedForeground(schemeSelected.foreground().color()); attrib->setFontBold(true); list.append(attrib); } { // dsFunction Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NeutralText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NeutralText).color()); list.append(attrib); } { // dsVariable Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::LinkText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::LinkText).color()); list.append(attrib); } { // dsControlFlow Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground().color()); attrib->setSelectedForeground(schemeSelected.foreground().color()); attrib->setFontBold(true); list.append(attrib); } { // dsOperator Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground().color()); attrib->setSelectedForeground(schemeSelected.foreground().color()); list.append(attrib); } { // dsBuiltIn Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::VisitedText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::VisitedText).color()); list.append(attrib); } { // dsExtension Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(QColor(0, 149, 255)); attrib->setSelectedForeground(schemeSelected.foreground().color()); attrib->setFontBold(true); list.append(attrib); } { // dsPreprocessor Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::PositiveText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::PositiveText).color()); list.append(attrib); } { // dsAttribute Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::LinkText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::LinkText).color()); list.append(attrib); } { // dsChar Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::ActiveText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::ActiveText).color()); list.append(attrib); } { // dsSpecialChar Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::ActiveText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::ActiveText).color()); list.append(attrib); } { // dsString Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); list.append(attrib); } { // dsVerbatimString Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); list.append(attrib); } { // dsSpecialString Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); list.append(attrib); } { // dsImport Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::VisitedText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::VisitedText).color()); list.append(attrib); } { // dsDataType Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::LinkText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::LinkText).color()); list.append(attrib); } { // dsDecVal Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NeutralText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NeutralText).color()); list.append(attrib); } { // dsBaseN Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NeutralText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NeutralText).color()); list.append(attrib); } { // dsFloat Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NeutralText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NeutralText).color()); list.append(attrib); } { // dsConstant Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground().color()); attrib->setSelectedForeground(schemeSelected.foreground().color()); attrib->setFontBold(true); list.append(attrib); } { // dsComment Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::InactiveText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::InactiveText).color()); list.append(attrib); } { // dsDocumentation Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); list.append(attrib); } { // dsAnnotation Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::VisitedText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::VisitedText).color()); list.append(attrib); } { // dsCommentVar Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::VisitedText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::VisitedText).color()); list.append(attrib); } { // dsRegionMarker Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::LinkText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::LinkText).color()); attrib->setBackground(scheme.background(KColorScheme::LinkBackground).color()); list.append(attrib); } { // dsInformation Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NeutralText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NeutralText).color()); list.append(attrib); } { // dsWarning Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); list.append(attrib); } { // dsAlert Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); attrib->setFontBold(true); attrib->setBackground(scheme.background(KColorScheme::NegativeBackground).color()); list.append(attrib); } { // dsOthers Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::PositiveText).color()); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::PositiveText).color()); list.append(attrib); } { // dsError Attribute::Ptr attrib(new KTextEditor::Attribute()); attrib->setForeground(scheme.foreground(KColorScheme::NegativeText)); attrib->setSelectedForeground(schemeSelected.foreground(KColorScheme::NegativeText).color()); attrib->setFontUnderline(true); list.append(attrib); } KConfigGroup config(cfg ? cfg : KateHlManager::self()->self()->getKConfig(), QLatin1String("Default Item Styles - Schema ") + schema); for (int z = 0; z < defaultStyleCount(); z++) { KTextEditor::Attribute::Ptr i = list.at(z); QStringList s = config.readEntry(defaultStyleName(z), QStringList()); if (!s.isEmpty()) { while (s.count() < 9) { s << QString(); } QString tmp; QRgb col; tmp = s[0]; if (!tmp.isEmpty()) { - col = tmp.toUInt(0, 16); i->setForeground(QColor(col)); + col = tmp.toUInt(nullptr, 16); i->setForeground(QColor(col)); } tmp = s[1]; if (!tmp.isEmpty()) { - col = tmp.toUInt(0, 16); i->setSelectedForeground(QColor(col)); + col = tmp.toUInt(nullptr, 16); i->setSelectedForeground(QColor(col)); } tmp = s[2]; if (!tmp.isEmpty()) { i->setFontBold(tmp != QLatin1String("0")); } tmp = s[3]; if (!tmp.isEmpty()) { i->setFontItalic(tmp != QLatin1String("0")); } tmp = s[4]; if (!tmp.isEmpty()) { i->setFontStrikeOut(tmp != QLatin1String("0")); } tmp = s[5]; if (!tmp.isEmpty()) { i->setFontUnderline(tmp != QLatin1String("0")); } tmp = s[6]; if (!tmp.isEmpty()) { if (tmp != QLatin1String("-")) { - col = tmp.toUInt(0, 16); + col = tmp.toUInt(nullptr, 16); i->setBackground(QColor(col)); } else { i->clearBackground(); } } tmp = s[7]; if (!tmp.isEmpty()) { if (tmp != QLatin1String("-")) { - col = tmp.toUInt(0, 16); + col = tmp.toUInt(nullptr, 16); i->setSelectedBackground(QColor(col)); } else { i->clearProperty(SelectedBackground); } } tmp = s[8]; if (!tmp.isEmpty() && tmp != QLatin1String("---")) { i->setFontFamily(tmp); } } } } void KateHlManager::setDefaults(const QString &schema, KateAttributeList &list, KConfig *cfg) { cfg = cfg ? cfg : KateHlManager::self()->self()->getKConfig(); KConfigGroup config(cfg, QLatin1String("Default Item Styles - Schema ") + schema); const QString zero = QStringLiteral("0"); const QString one = QStringLiteral("1"); const QString dash = QStringLiteral("-"); for (int z = 0; z < defaultStyleCount(); z++) { QStringList settings; KTextEditor::Attribute::Ptr p = list.at(z); settings << (p->hasProperty(QTextFormat::ForegroundBrush) ? QString::number(p->foreground().color().rgb(), 16) : QString()); settings << (p->hasProperty(SelectedForeground) ? QString::number(p->selectedForeground().color().rgb(), 16) : QString()); settings << (p->hasProperty(QTextFormat::FontWeight) ? (p->fontBold() ? one : zero) : QString()); settings << (p->hasProperty(QTextFormat::FontItalic) ? (p->fontItalic() ? one : zero) : QString()); settings << (p->hasProperty(QTextFormat::FontStrikeOut) ? (p->fontStrikeOut() ? one : zero) : QString()); settings << (p->hasProperty(QTextFormat::FontUnderline) ? (p->fontUnderline() ? one : zero) : QString()); settings << (p->hasProperty(QTextFormat::BackgroundBrush) ? QString::number(p->background().color().rgb(), 16) : dash); settings << (p->hasProperty(SelectedBackground) ? QString::number(p->selectedBackground().color().rgb(), 16) : dash); settings << (p->hasProperty(QTextFormat::FontFamily) ? (p->fontFamily()) : QString()); settings << QStringLiteral("---"); config.writeEntry(defaultStyleName(z), settings); } emit changed(); } int KateHlManager::highlights() { return (int) hlList.count(); } QString KateHlManager::hlName(int n) { return hlList.at(n)->name(); } QString KateHlManager::hlNameTranslated(int n) { return hlList.at(n)->nameTranslated(); } QString KateHlManager::hlSection(int n) { return hlList.at(n)->section(); } bool KateHlManager::hlHidden(int n) { return hlList.at(n)->hidden(); } QString KateHlManager::identifierForName(const QString &name) { if (hlDict.contains(name)) { return hlDict[name]->getIdentifier(); } return QString(); } QString KateHlManager::nameForIdentifier(const QString &identifier) { for (QHash::iterator it = hlDict.begin(); it != hlDict.end(); ++it) { if ((*it)->getIdentifier() == identifier) { return it.key(); } } return QString(); } bool KateHlManager::resetDynamicCtxs() { if (forceNoDCReset) { return false; } if (lastCtxsReset.elapsed() < KATE_DYNAMIC_CONTEXTS_RESET_DELAY) { return false; } foreach (KateHighlighting *hl, hlList) { hl->dropDynamicContexts(); } dynamicCtxsCount = 0; lastCtxsReset.start(); return true; } void KateHlManager::reload() { // clear syntax document cache syntax.clearCache(); resetDynamicCtxs(); for(int i = 0; i < highlights(); i++) { getHl(i)->reload(); } foreach(KTextEditor::DocumentPrivate* doc, KTextEditor::EditorPrivate::self()->kateDocuments()) { doc->makeAttribs(); } } //END diff --git a/src/syntax/katesyntaxmanager.h b/src/syntax/katesyntaxmanager.h index ffb5b724..a4fcc8f4 100644 --- a/src/syntax/katesyntaxmanager.h +++ b/src/syntax/katesyntaxmanager.h @@ -1,167 +1,167 @@ /* 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_SYNTAXMANAGER_H__ #define __KATE_SYNTAXMANAGER_H__ #include "katetextline.h" #include "kateextendedattribute.h" #include "katesyntaxdocument.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class KateHighlighting; class KateHlManager : public QObject { Q_OBJECT public: KateHlManager(); ~KateHlManager(); static KateHlManager *self(); KateSyntaxDocument *syntaxDocument() { return &syntax; } inline KConfig *getKConfig() { return &m_config; } KateHighlighting *getHl(int n); int nameFind(const QString &name); QString identifierForName(const QString &); /** * Returns the mode name for a given identifier, as e.g. * returned by KateHighlighting::hlKeyForAttrib(). */ QString nameForIdentifier(const QString &); - void getDefaults(const QString &schema, KateAttributeList &, KConfig *cfg = 0); - void setDefaults(const QString &schema, KateAttributeList &, KConfig *cfg = 0); + void getDefaults(const QString &schema, KateAttributeList &, KConfig *cfg = nullptr); + void setDefaults(const QString &schema, KateAttributeList &, KConfig *cfg = nullptr); int highlights(); QString hlName(int n); QString hlNameTranslated(int n); QString hlSection(int n); bool hlHidden(int n); void incDynamicCtxs() { ++dynamicCtxsCount; } int countDynamicCtxs() { return dynamicCtxsCount; } void setForceNoDCReset(bool b) { forceNoDCReset = b; } // be carefull: all documents hl should be invalidated after having successfully called this method! bool resetDynamicCtxs(); void reload(); Q_SIGNALS: void changed(); // // methodes to get the default style count + names // public: /** * Return the number of default styles. */ static int defaultStyleCount(); /** * Return the name of default style @p n. If @p translateNames is @e true, * the default style name is translated. */ static QString defaultStyleName(int n, bool translateNames = false); /** * Return the index for the default style @p name. * @param name @e untranslated default style * @see KTextEditor::DefaultStyles) */ static int defaultStyleNameToIndex(const QString &name); /** * Get the mode list * @return mode list */ QVector modeList() const { return m_repository.definitions(); } private: friend class KateHighlighting; /** * Generate the list of hl modes, store them in myModeList */ void setupModeList(); /** * Syntax highlighting definitions. */ KSyntaxHighlighting::Repository m_repository; // This list owns objects it holds, thus they should be deleted when the object is removed QList hlList; // This hash does not own the objects it holds, thus they should not be deleted QHash hlDict; KConfig m_config; QStringList commonSuffixes; KateSyntaxDocument syntax; int dynamicCtxsCount; QTime lastCtxsReset; bool forceNoDCReset; }; #endif diff --git a/src/undo/kateundo.cpp b/src/undo/kateundo.cpp index ff538177..4585617c 100644 --- a/src/undo/kateundo.cpp +++ b/src/undo/kateundo.cpp @@ -1,416 +1,416 @@ /* This file is part of the KDE libraries Copyright (C) 2011 Dominik Haumann Copyright (C) 2009-2010 Bernhard Beschow Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger * * 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 "kateundo.h" #include "kateundomanager.h" #include "katedocument.h" #include #include KateUndo::KateUndo(KTextEditor::DocumentPrivate *document) : m_document(document) , m_lineModFlags(0x00) { } KateUndo::~KateUndo() { } KateEditInsertTextUndo::KateEditInsertTextUndo(KTextEditor::DocumentPrivate *document, int line, int col, const QString &text) : KateUndo(document) , m_line(line) , m_col(col) , m_text(text) { } KateEditRemoveTextUndo::KateEditRemoveTextUndo(KTextEditor::DocumentPrivate *document, int line, int col, const QString &text) : KateUndo(document) , m_line(line) , m_col(col) , m_text(text) { } KateEditWrapLineUndo::KateEditWrapLineUndo(KTextEditor::DocumentPrivate *document, int line, int col, int len, bool newLine) : KateUndo(document) , m_line(line) , m_col(col) , m_len(len) , m_newLine(newLine) { } KateEditUnWrapLineUndo::KateEditUnWrapLineUndo(KTextEditor::DocumentPrivate *document, int line, int col, int len, bool removeLine) : KateUndo(document) , m_line(line) , m_col(col) , m_len(len) , m_removeLine(removeLine) { } KateEditInsertLineUndo::KateEditInsertLineUndo(KTextEditor::DocumentPrivate *document, int line, const QString &text) : KateUndo(document) , m_line(line) , m_text(text) { } KateEditRemoveLineUndo::KateEditRemoveLineUndo(KTextEditor::DocumentPrivate *document, int line, const QString &text) : KateUndo(document) , m_line(line) , m_text(text) { } bool KateUndo::isEmpty() const { return false; } bool KateEditInsertTextUndo::isEmpty() const { return len() == 0; } bool KateEditRemoveTextUndo::isEmpty() const { return len() == 0; } bool KateUndo::mergeWith(const KateUndo * /*undo*/) { return false; } bool KateEditInsertTextUndo::mergeWith(const KateUndo *undo) { const KateEditInsertTextUndo *u = dynamic_cast(undo); - if (u != 0 + if (u != nullptr && m_line == u->m_line && (m_col + len()) == u->m_col) { m_text += u->m_text; return true; } return false; } bool KateEditRemoveTextUndo::mergeWith(const KateUndo *undo) { const KateEditRemoveTextUndo *u = dynamic_cast(undo); - if (u != 0 + if (u != nullptr && m_line == u->m_line && m_col == (u->m_col + u->len())) { m_text.prepend(u->m_text); m_col = u->m_col; return true; } return false; } void KateEditInsertTextUndo::undo() { KTextEditor::DocumentPrivate *doc = document(); doc->editRemoveText(m_line, m_col, len()); } void KateEditRemoveTextUndo::undo() { KTextEditor::DocumentPrivate *doc = document(); doc->editInsertText(m_line, m_col, m_text); } void KateEditWrapLineUndo::undo() { KTextEditor::DocumentPrivate *doc = document(); doc->editUnWrapLine(m_line, m_newLine, m_len); } void KateEditUnWrapLineUndo::undo() { KTextEditor::DocumentPrivate *doc = document(); doc->editWrapLine(m_line, m_col, m_removeLine); } void KateEditInsertLineUndo::undo() { KTextEditor::DocumentPrivate *doc = document(); doc->editRemoveLine(m_line); } void KateEditRemoveLineUndo::undo() { KTextEditor::DocumentPrivate *doc = document(); doc->editInsertLine(m_line, m_text); } void KateEditMarkLineAutoWrappedUndo::undo() { KTextEditor::DocumentPrivate *doc = document(); doc->editMarkLineAutoWrapped(m_line, m_autowrapped); } void KateEditRemoveTextUndo::redo() { KTextEditor::DocumentPrivate *doc = document(); doc->editRemoveText(m_line, m_col, len()); } void KateEditInsertTextUndo::redo() { KTextEditor::DocumentPrivate *doc = document(); doc->editInsertText(m_line, m_col, m_text); } void KateEditUnWrapLineUndo::redo() { KTextEditor::DocumentPrivate *doc = document(); doc->editUnWrapLine(m_line, m_removeLine, m_len); } void KateEditWrapLineUndo::redo() { KTextEditor::DocumentPrivate *doc = document(); doc->editWrapLine(m_line, m_col, m_newLine); } void KateEditRemoveLineUndo::redo() { KTextEditor::DocumentPrivate *doc = document(); doc->editRemoveLine(m_line); } void KateEditInsertLineUndo::redo() { KTextEditor::DocumentPrivate *doc = document(); doc->editInsertLine(m_line, m_text); } void KateEditMarkLineAutoWrappedUndo::redo() { KTextEditor::DocumentPrivate *doc = document(); doc->editMarkLineAutoWrapped(m_line, m_autowrapped); } KateUndoGroup::KateUndoGroup(KateUndoManager *manager, const QVector &cursorPosition, const QVector &selectionRange) : m_manager(manager) , m_safePoint(false) , m_undoSelection(selectionRange) , m_redoSelection() , m_undoCursor(cursorPosition) , m_redoCursor() { Q_ASSERT(m_undoCursor.size() == m_undoSelection.size()); } KateUndoGroup::~KateUndoGroup() { qDeleteAll(m_items); } void KateUndoGroup::undo(KTextEditor::View *view) { if (m_items.isEmpty()) { return; } m_manager->startUndo(); for (int i = m_items.size() - 1; i >= 0; --i) { m_items[i]->undo(); } - if (view != 0) { + if (view != nullptr) { view->setSelections(m_undoSelection, m_undoCursor); } m_manager->endUndo(); } void KateUndoGroup::redo(KTextEditor::View *view) { if (m_items.isEmpty()) { return; } m_manager->startUndo(); for (int i = 0; i < m_items.size(); ++i) { m_items[i]->redo(); } - if (view != 0) { + if (view != nullptr) { view->setSelections(m_redoSelection, m_redoCursor); } m_manager->endUndo(); } void KateUndoGroup::editEnd(const QVector &cursorPosition, const QVector &selectionRange) { m_redoCursor = cursorPosition; m_redoSelection = selectionRange; } void KateUndoGroup::addItem(KateUndo *u) { if (u->isEmpty()) { delete u; } else if (!m_items.isEmpty() && m_items.last()->mergeWith(u)) { delete u; } else { m_items.append(u); } } bool KateUndoGroup::merge(KateUndoGroup *newGroup, bool complex) { if (m_safePoint) { return false; } if (newGroup->isOnlyType(singleType()) || complex) { // Take all of its items first -> last - KateUndo *u = newGroup->m_items.isEmpty() ? 0 : newGroup->m_items.takeFirst(); + KateUndo *u = newGroup->m_items.isEmpty() ? nullptr : newGroup->m_items.takeFirst(); while (u) { addItem(u); - u = newGroup->m_items.isEmpty() ? 0 : newGroup->m_items.takeFirst(); + u = newGroup->m_items.isEmpty() ? nullptr : newGroup->m_items.takeFirst(); } if (newGroup->m_safePoint) { safePoint(); } m_redoCursor = newGroup->m_redoCursor; m_redoSelection = newGroup->m_redoSelection; Q_ASSERT(m_redoCursor.size() == m_redoSelection.size()); return true; } return false; } void KateUndoGroup::safePoint(bool safePoint) { m_safePoint = safePoint; } void KateUndoGroup::flagSavedAsModified() { foreach (KateUndo *item, m_items) { if (item->isFlagSet(KateUndo::UndoLine1Saved)) { item->unsetFlag(KateUndo::UndoLine1Saved); item->setFlag(KateUndo::UndoLine1Modified); } if (item->isFlagSet(KateUndo::UndoLine2Saved)) { item->unsetFlag(KateUndo::UndoLine2Saved); item->setFlag(KateUndo::UndoLine2Modified); } if (item->isFlagSet(KateUndo::RedoLine1Saved)) { item->unsetFlag(KateUndo::RedoLine1Saved); item->setFlag(KateUndo::RedoLine1Modified); } if (item->isFlagSet(KateUndo::RedoLine2Saved)) { item->unsetFlag(KateUndo::RedoLine2Saved); item->setFlag(KateUndo::RedoLine2Modified); } } } void KateUndoGroup::markUndoAsSaved(QBitArray &lines) { for (int i = m_items.size() - 1; i >= 0; --i) { KateUndo *item = m_items[i]; item->updateUndoSavedOnDiskFlag(lines); } } void KateUndoGroup::markRedoAsSaved(QBitArray &lines) { for (int i = m_items.size() - 1; i >= 0; --i) { KateUndo *item = m_items[i]; item->updateRedoSavedOnDiskFlag(lines); } } KTextEditor::Document *KateUndoGroup::document() { return m_manager->document(); } KateUndo::UndoType KateUndoGroup::singleType() const { KateUndo::UndoType ret = KateUndo::editInvalid; Q_FOREACH (const KateUndo *item, m_items) { if (ret == KateUndo::editInvalid) { ret = item->type(); } else if (ret != item->type()) { return KateUndo::editInvalid; } } return ret; } bool KateUndoGroup::isOnlyType(KateUndo::UndoType type) const { if (type == KateUndo::editInvalid) { return false; } Q_FOREACH (const KateUndo *item, m_items) if (item->type() != type) { return false; } return true; } diff --git a/src/undo/kateundomanager.cpp b/src/undo/kateundomanager.cpp index 54076159..d765dc7c 100644 --- a/src/undo/kateundomanager.cpp +++ b/src/undo/kateundomanager.cpp @@ -1,458 +1,458 @@ /* This file is part of the KDE libraries Copyright (C) 2009-2010 Bernhard Beschow * * 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 "kateundomanager.h" #include #include "katedocument.h" #include "katemodifiedundo.h" #include "katepartdebug.h" #include KateUndoManager::KateUndoManager(KTextEditor::DocumentPrivate *doc) : QObject(doc) , m_document(doc) , m_undoComplexMerge(false) , m_isActive(true) - , m_editCurrentUndo(0) - , lastUndoGroupWhenSaved(0) - , lastRedoGroupWhenSaved(0) + , m_editCurrentUndo(nullptr) + , lastUndoGroupWhenSaved(nullptr) + , lastRedoGroupWhenSaved(nullptr) , docWasSavedWhenUndoWasEmpty(true) , docWasSavedWhenRedoWasEmpty(true) { connect(this, SIGNAL(undoEnd(KTextEditor::Document*)), this, SIGNAL(undoChanged())); connect(this, SIGNAL(redoEnd(KTextEditor::Document*)), this, SIGNAL(undoChanged())); connect(doc, SIGNAL(viewCreated(KTextEditor::Document*,KTextEditor::View*)), SLOT(viewCreated(KTextEditor::Document*,KTextEditor::View*))); } KateUndoManager::~KateUndoManager() { delete m_editCurrentUndo; // cleanup the undo/redo items, very important, truee :/ qDeleteAll(undoItems); undoItems.clear(); qDeleteAll(redoItems); redoItems.clear(); } KTextEditor::Document *KateUndoManager::document() { return m_document; } void KateUndoManager::viewCreated(KTextEditor::Document *, KTextEditor::View *newView) { connect(newView, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), SLOT(undoCancel())); } void KateUndoManager::editStart() { if (!m_isActive) { return; } // editStart() and editEnd() must be called in alternating fashion - Q_ASSERT(m_editCurrentUndo == 0); // make sure to enter a clean state + Q_ASSERT(m_editCurrentUndo == nullptr); // make sure to enter a clean state const auto cursorPosition = activeView() ? activeView()->cursorPositions() : QVector(); const auto selectionRange = activeView() ? activeView()->selectionRanges() : QVector(); // new current undo item m_editCurrentUndo = new KateUndoGroup(this, cursorPosition, selectionRange); - Q_ASSERT(m_editCurrentUndo != 0); // a new undo group must be created by this method + Q_ASSERT(m_editCurrentUndo != nullptr); // a new undo group must be created by this method } void KateUndoManager::editEnd() { if (!m_isActive) { return; } // editStart() and editEnd() must be called in alternating fashion - Q_ASSERT(m_editCurrentUndo != 0); // an undo group must have been created by editStart() + Q_ASSERT(m_editCurrentUndo != nullptr); // an undo group must have been created by editStart() const auto cursorPosition = activeView() ? activeView()->cursorPositions() : QVector(); const auto selectionRange = activeView() ? activeView()->selectionRanges() : QVector(); m_editCurrentUndo->editEnd(cursorPosition, selectionRange); bool changedUndo = false; if (m_editCurrentUndo->isEmpty()) { delete m_editCurrentUndo; } else if (!undoItems.isEmpty() && undoItems.last()->merge(m_editCurrentUndo, m_undoComplexMerge)) { delete m_editCurrentUndo; } else { undoItems.append(m_editCurrentUndo); changedUndo = true; } - m_editCurrentUndo = 0L; + m_editCurrentUndo = nullptr; if (changedUndo) { emit undoChanged(); } - Q_ASSERT(m_editCurrentUndo == 0); // must be 0 after calling this method + Q_ASSERT(m_editCurrentUndo == nullptr); // must be 0 after calling this method } void KateUndoManager::inputMethodStart() { setActive(false); m_document->editStart(); } void KateUndoManager::inputMethodEnd() { m_document->editEnd(); setActive(true); } void KateUndoManager::startUndo() { setActive(false); m_document->editStart(); } void KateUndoManager::endUndo() { m_document->editEnd(); setActive(true); } void KateUndoManager::slotTextInserted(int line, int col, const QString &s) { - if (m_editCurrentUndo != 0) { // do we care about notifications? + if (m_editCurrentUndo != nullptr) { // do we care about notifications? addUndoItem(new KateModifiedInsertText(m_document, line, col, s)); } } void KateUndoManager::slotTextRemoved(int line, int col, const QString &s) { - if (m_editCurrentUndo != 0) { // do we care about notifications? + if (m_editCurrentUndo != nullptr) { // do we care about notifications? addUndoItem(new KateModifiedRemoveText(m_document, line, col, s)); } } void KateUndoManager::slotMarkLineAutoWrapped(int line, bool autowrapped) { - if (m_editCurrentUndo != 0) { // do we care about notifications? + if (m_editCurrentUndo != nullptr) { // do we care about notifications? addUndoItem(new KateEditMarkLineAutoWrappedUndo(m_document, line, autowrapped)); } } void KateUndoManager::slotLineWrapped(int line, int col, int length, bool newLine) { - if (m_editCurrentUndo != 0) { // do we care about notifications? + if (m_editCurrentUndo != nullptr) { // do we care about notifications? addUndoItem(new KateModifiedWrapLine(m_document, line, col, length, newLine)); } } void KateUndoManager::slotLineUnWrapped(int line, int col, int length, bool lineRemoved) { - if (m_editCurrentUndo != 0) { // do we care about notifications? + if (m_editCurrentUndo != nullptr) { // do we care about notifications? addUndoItem(new KateModifiedUnWrapLine(m_document, line, col, length, lineRemoved)); } } void KateUndoManager::slotLineInserted(int line, const QString &s) { - if (m_editCurrentUndo != 0) { // do we care about notifications? + if (m_editCurrentUndo != nullptr) { // do we care about notifications? addUndoItem(new KateModifiedInsertLine(m_document, line, s)); } } void KateUndoManager::slotLineRemoved(int line, const QString &s) { - if (m_editCurrentUndo != 0) { // do we care about notifications? + if (m_editCurrentUndo != nullptr) { // do we care about notifications? addUndoItem(new KateModifiedRemoveLine(m_document, line, s)); } } void KateUndoManager::undoCancel() { // Don't worry about this when an edit is in progress if (m_document->isEditRunning()) { return; } undoSafePoint(); } void KateUndoManager::undoSafePoint() { KateUndoGroup *undoGroup = m_editCurrentUndo; - if (undoGroup == 0 && !undoItems.isEmpty()) { + if (undoGroup == nullptr && !undoItems.isEmpty()) { undoGroup = undoItems.last(); } - if (undoGroup == 0) { + if (undoGroup == nullptr) { return; } undoGroup->safePoint(); } void KateUndoManager::addUndoItem(KateUndo *undo) { - Q_ASSERT(undo != 0); // don't add null pointers to our history - Q_ASSERT(m_editCurrentUndo != 0); // make sure there is an undo group for our item + Q_ASSERT(undo != nullptr); // don't add null pointers to our history + Q_ASSERT(m_editCurrentUndo != nullptr); // make sure there is an undo group for our item m_editCurrentUndo->addItem(undo); // Clear redo buffer qDeleteAll(redoItems); redoItems.clear(); } void KateUndoManager::setActive(bool enabled) { - Q_ASSERT(m_editCurrentUndo == 0); // must not already be in edit mode + Q_ASSERT(m_editCurrentUndo == nullptr); // must not already be in edit mode Q_ASSERT(m_isActive != enabled); m_isActive = enabled; emit isActiveChanged(enabled); } uint KateUndoManager::undoCount() const { return undoItems.count(); } uint KateUndoManager::redoCount() const { return redoItems.count(); } void KateUndoManager::undo() { - Q_ASSERT(m_editCurrentUndo == 0); // undo is not supported while we care about notifications (call editEnd() first) + Q_ASSERT(m_editCurrentUndo == nullptr); // undo is not supported while we care about notifications (call editEnd() first) if (undoItems.count() > 0) { emit undoStart(document()); undoItems.last()->undo(activeView()); redoItems.append(undoItems.last()); undoItems.removeLast(); updateModified(); emit undoEnd(document()); } } void KateUndoManager::redo() { - Q_ASSERT(m_editCurrentUndo == 0); // redo is not supported while we care about notifications (call editEnd() first) + Q_ASSERT(m_editCurrentUndo == nullptr); // redo is not supported while we care about notifications (call editEnd() first) if (redoItems.count() > 0) { emit redoStart(document()); redoItems.last()->redo(activeView()); undoItems.append(redoItems.last()); redoItems.removeLast(); updateModified(); emit redoEnd(document()); } } void KateUndoManager::updateModified() { /* How this works: After noticing that there where to many scenarios to take into consideration when using 'if's to toggle the "Modified" flag I came up with this baby, flexible and repetitive calls are minimal. A numeric unique pattern is generated by toggling a set of bits, each bit symbolizes a different state in the Undo Redo structure. undoItems.isEmpty() != null BIT 1 redoItems.isEmpty() != null BIT 2 docWasSavedWhenUndoWasEmpty == true BIT 3 docWasSavedWhenRedoWasEmpty == true BIT 4 lastUndoGroupWhenSavedIsLastUndo BIT 5 lastUndoGroupWhenSavedIsLastRedo BIT 6 lastRedoGroupWhenSavedIsLastUndo BIT 7 lastRedoGroupWhenSavedIsLastRedo BIT 8 If you find a new pattern, please add it to the patterns array */ unsigned char currentPattern = 0; const unsigned char patterns[] = {5, 16, 21, 24, 26, 88, 90, 93, 133, 144, 149, 154, 165}; const unsigned char patternCount = sizeof(patterns); - KateUndoGroup *undoLast = 0; - KateUndoGroup *redoLast = 0; + KateUndoGroup *undoLast = nullptr; + KateUndoGroup *redoLast = nullptr; if (undoItems.isEmpty()) { currentPattern |= 1; } else { undoLast = undoItems.last(); } if (redoItems.isEmpty()) { currentPattern |= 2; } else { redoLast = redoItems.last(); } if (docWasSavedWhenUndoWasEmpty) { currentPattern |= 4; } if (docWasSavedWhenRedoWasEmpty) { currentPattern |= 8; } if (lastUndoGroupWhenSaved == undoLast) { currentPattern |= 16; } if (lastUndoGroupWhenSaved == redoLast) { currentPattern |= 32; } if (lastRedoGroupWhenSaved == undoLast) { currentPattern |= 64; } if (lastRedoGroupWhenSaved == redoLast) { currentPattern |= 128; } // This will print out the pattern information qCDebug(LOG_KTE) << "Pattern:" << static_cast(currentPattern); for (uint patternIndex = 0; patternIndex < patternCount; ++patternIndex) { if (currentPattern == patterns[patternIndex]) { // Note: m_document->setModified() calls KateUndoManager::setModified! m_document->setModified(false); // (dominik) whenever the doc is not modified, succeeding edits // should not be merged undoSafePoint(); qCDebug(LOG_KTE) << "setting modified to false!"; break; } } } void KateUndoManager::clearUndo() { qDeleteAll(undoItems); undoItems.clear(); - lastUndoGroupWhenSaved = 0; + lastUndoGroupWhenSaved = nullptr; docWasSavedWhenUndoWasEmpty = false; emit undoChanged(); } void KateUndoManager::clearRedo() { qDeleteAll(redoItems); redoItems.clear(); - lastRedoGroupWhenSaved = 0; + lastRedoGroupWhenSaved = nullptr; docWasSavedWhenRedoWasEmpty = false; emit undoChanged(); } void KateUndoManager::setModified(bool modified) { if (!modified) { if (! undoItems.isEmpty()) { lastUndoGroupWhenSaved = undoItems.last(); } if (! redoItems.isEmpty()) { lastRedoGroupWhenSaved = redoItems.last(); } docWasSavedWhenUndoWasEmpty = undoItems.isEmpty(); docWasSavedWhenRedoWasEmpty = redoItems.isEmpty(); } } void KateUndoManager::updateLineModifications() { // change LineSaved flag of all undo & redo items to LineModified foreach (KateUndoGroup *undoGroup, undoItems) { undoGroup->flagSavedAsModified(); } foreach (KateUndoGroup *undoGroup, redoItems) { undoGroup->flagSavedAsModified(); } // iterate all undo/redo items to find out, which item sets the flag LineSaved QBitArray lines(document()->lines(), false); for (int i = undoItems.size() - 1; i >= 0; --i) { undoItems[i]->markRedoAsSaved(lines); } lines.fill(false); for (int i = redoItems.size() - 1; i >= 0; --i) { redoItems[i]->markUndoAsSaved(lines); } } void KateUndoManager::setUndoRedoCursorsOfLastGroup(const QVector& undoCursor, const QVector& redoCursor) { - Q_ASSERT(m_editCurrentUndo == 0); + Q_ASSERT(m_editCurrentUndo == nullptr); if (!undoItems.isEmpty()) { KateUndoGroup *last = undoItems.last(); last->setUndoCursor(undoCursor); last->setRedoCursor(redoCursor); } } QVector KateUndoManager::lastRedoCursor() const { - Q_ASSERT(m_editCurrentUndo == 0); + Q_ASSERT(m_editCurrentUndo == nullptr); if (!undoItems.isEmpty()) { KateUndoGroup *last = undoItems.last(); return last->redoCursor(); } return {}; } void KateUndoManager::updateConfig() { emit undoChanged(); } void KateUndoManager::setAllowComplexMerge(bool allow) { m_undoComplexMerge = allow; } KTextEditor::View *KateUndoManager::activeView() { return m_document->activeView(); } diff --git a/src/utils/application.cpp b/src/utils/application.cpp index d4c4ab6d..0fe7c44e 100644 --- a/src/utils/application.cpp +++ b/src/utils/application.cpp @@ -1,169 +1,169 @@ /* * This file is part of the KDE project. * * Copyright (C) 2013 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 #include #include #include "kateglobal.h" namespace KTextEditor { Application::Application(QObject *parent) : QObject(parent) - , d(Q_NULLPTR) + , d(nullptr) { } Application::~Application() { } bool Application::quit() { /** * dispatch to parent */ bool success = false; QMetaObject::invokeMethod(parent() , "quit" , Qt::DirectConnection , Q_RETURN_ARG(bool, success)); return success; } QList Application::mainWindows() { /** * dispatch to parent */ QList mainWindow; QMetaObject::invokeMethod(parent() , "mainWindows" , Qt::DirectConnection , Q_RETURN_ARG(QList, mainWindow)); return mainWindow; } KTextEditor::MainWindow *Application::activeMainWindow() { /** * dispatch to parent */ - KTextEditor::MainWindow *window = Q_NULLPTR; + KTextEditor::MainWindow *window = nullptr; QMetaObject::invokeMethod(parent() , "activeMainWindow" , Qt::DirectConnection , Q_RETURN_ARG(KTextEditor::MainWindow *, window)); /** * always return some kind of window to not need to check for valid pointer */ return window ? window : KTextEditor::EditorPrivate::self()->dummyMainWindow(); } QList Application::documents() { /** * dispatch to parent */ QList documents; QMetaObject::invokeMethod(parent() , "documents" , Qt::DirectConnection , Q_RETURN_ARG(QList, documents)); return documents; } KTextEditor::Document *Application::findUrl(const QUrl &url) { /** * dispatch to parent */ - KTextEditor::Document *document = Q_NULLPTR; + KTextEditor::Document *document = nullptr; QMetaObject::invokeMethod(parent() , "findUrl" , Qt::DirectConnection , Q_RETURN_ARG(KTextEditor::Document *, document) , Q_ARG(const QUrl &, url)); return document; } KTextEditor::Document *Application::openUrl(const QUrl &url, const QString &encoding) { /** * dispatch to parent */ - KTextEditor::Document *document = Q_NULLPTR; + KTextEditor::Document *document = nullptr; QMetaObject::invokeMethod(parent() , "openUrl" , Qt::DirectConnection , Q_RETURN_ARG(KTextEditor::Document *, document) , Q_ARG(const QUrl &, url) , Q_ARG(const QString &, encoding)); return document; } bool Application::closeDocument(KTextEditor::Document *document) { /** * dispatch to parent */ bool success = false; QMetaObject::invokeMethod(parent() , "closeDocument" , Qt::DirectConnection , Q_RETURN_ARG(bool, success) , Q_ARG(KTextEditor::Document *, document)); return success; } bool Application::closeDocuments(const QList &documents) { /** * dispatch to parent */ bool success = false; QMetaObject::invokeMethod(parent() , "closeDocuments" , Qt::DirectConnection , Q_RETURN_ARG(bool, success) , Q_ARG(const QList &, documents)); return success; } KTextEditor::Plugin *Application::plugin(const QString &name) { /** * dispatch to parent */ - Plugin *plugin = Q_NULLPTR; + Plugin *plugin = nullptr; QMetaObject::invokeMethod(parent() , "plugin" , Qt::DirectConnection , Q_RETURN_ARG(KTextEditor::Plugin *, plugin) , Q_ARG(const QString &, name)); return plugin; } } // namespace KTextEditor diff --git a/src/utils/codecompletionmodel.cpp b/src/utils/codecompletionmodel.cpp index 120ea7fd..81f068a7 100644 --- a/src/utils/codecompletionmodel.cpp +++ b/src/utils/codecompletionmodel.cpp @@ -1,119 +1,119 @@ /* This file is part of the KDE libraries Copyright (C) 2005-2006 Hamish Rodda * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 "codecompletionmodel.h" #include "document.h" #include "view.h" using namespace KTextEditor; class KTextEditor::CodeCompletionModelPrivate { public: CodeCompletionModelPrivate() : rowCount(0), hasGroups(false) {} int rowCount; bool hasGroups; }; CodeCompletionModel::CodeCompletionModel(QObject *parent) : QAbstractItemModel(parent) , d(new CodeCompletionModelPrivate) { } CodeCompletionModel::~ CodeCompletionModel() { delete d; } int CodeCompletionModel::columnCount(const QModelIndex &) const { return ColumnCount; } QModelIndex CodeCompletionModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || row >= d->rowCount || column < 0 || column >= ColumnCount || parent.isValid()) { return QModelIndex(); } - return createIndex(row, column, (void *)0); + return createIndex(row, column, (void *)nullptr); } QMap< int, QVariant > CodeCompletionModel::itemData(const QModelIndex &index) const { QMap ret = QAbstractItemModel::itemData(index); for (int i = CompletionRole; i <= AccessibilityAccept; ++i) { QVariant v = data(index, i); if (v.isValid()) { ret.insert(i, v); } } return ret; } QModelIndex CodeCompletionModel::parent(const QModelIndex &) const { return QModelIndex(); } void CodeCompletionModel::setRowCount(int rowCount) { d->rowCount = rowCount; } int CodeCompletionModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->rowCount; } void CodeCompletionModel::completionInvoked(KTextEditor::View *view, const Range &range, InvocationType invocationType) { Q_UNUSED(view) Q_UNUSED(range) Q_UNUSED(invocationType) } void CodeCompletionModel::executeCompletionItem (KTextEditor::View *view, const Range &word, const QModelIndex &index) const { view->document()->replaceText(word, data(index.sibling(index.row(), Name)).toString()); } bool CodeCompletionModel::hasGroups() const { return d->hasGroups; } void CodeCompletionModel::setHasGroups(bool hasGroups) { if (d->hasGroups != hasGroups) { d->hasGroups = hasGroups; emit hasGroupsChanged(this, hasGroups); } } diff --git a/src/utils/configinterface.cpp b/src/utils/configinterface.cpp index bfe678c7..01c25814 100644 --- a/src/utils/configinterface.cpp +++ b/src/utils/configinterface.cpp @@ -1,31 +1,31 @@ /* This file is part of the KDE project Copyright (C) 2006 Matt Broadstone (mbroadst@gmail.com) 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 using namespace KTextEditor; ConfigInterface::ConfigInterface() - : d(0) + : d(nullptr) { } ConfigInterface::~ConfigInterface() { } diff --git a/src/utils/document.cpp b/src/utils/document.cpp index 44dc7cf0..176304f6 100644 --- a/src/utils/document.cpp +++ b/src/utils/document.cpp @@ -1,135 +1,135 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Bernhard Beschow * * 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 "document.h" #include "katedocument.h" using namespace KTextEditor; Document::Document(DocumentPrivate *impl, QObject *parent) : KParts::ReadWritePart(parent) , d (impl) { } Document::~Document() { } namespace KTextEditor { /** * Private d-pointer type for EditingTransaction */ class EditingTransactionPrivate { public: /** * real document implementation */ DocumentPrivate *document; /** * Indicator for running editing transaction */ bool transactionRunning; }; } Document::EditingTransaction::EditingTransaction(Document *document) : d (new EditingTransactionPrivate()) { // Alghouth it works in release-mode, we usually want a valid document - Q_ASSERT(document != Q_NULLPTR); + Q_ASSERT(document != nullptr); // initialize d-pointer d->document = qobject_cast (document); d->transactionRunning = false; // start the editing transaction start(); } void Document::EditingTransaction::start() { if (d->document && !d->transactionRunning) { d->document->startEditing (); d->transactionRunning = true; } } void Document::EditingTransaction::finish() { if (d->document && d->transactionRunning) { d->document->finishEditing (); d->transactionRunning = false; } } Document::EditingTransaction::~EditingTransaction() { /** * finish the editing transaction */ finish(); /** * delete our d-pointer */ delete d; } bool Document::openingError() const { return d->m_openingError; } QString Document::openingErrorMessage() const { return d->m_openingErrorMessage; } bool KTextEditor::Document::replaceText(const Range &range, const QString &text, bool block) { bool success = true; EditingTransaction transaction(this); success &= removeText(range, block); success &= insertText(range.start(), text, block); return success; } bool Document::replaceText(const Range &range, const QStringList &text, bool block) { bool success = true; EditingTransaction transaction(this); success &= removeText(range, block); success &= insertText(range.start(), text, block); return success; } bool Document::isEmpty() const { return documentEnd() == Cursor::start(); } QVector Document::searchText(const KTextEditor::Range &range, const QString &pattern, const SearchOptions options) const { return d->searchText(range, pattern, options); } diff --git a/src/utils/kateautoindent.cpp b/src/utils/kateautoindent.cpp index 5aee7cdb..344f01d4 100644 --- a/src/utils/kateautoindent.cpp +++ b/src/utils/kateautoindent.cpp @@ -1,498 +1,498 @@ /* This file is part of the KDE libraries Copyright (C) 2003 Jesse Yurkovich Copyright (C) 2004 >Anders Lund (KateVarIndent class) Copyright (C) 2005 Dominik Haumann (basic support for config page) 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. */ #include "kateautoindent.h" #include "kateconfig.h" #include "katehighlight.h" #include "kateglobal.h" #include "kateindentscript.h" #include "katescriptmanager.h" #include "kateview.h" #include "kateextendedattribute.h" #include "katedocument.h" #include "katepartdebug.h" #include #include namespace { inline const QString MODE_NONE() { return QStringLiteral("none"); } inline const QString MODE_NORMAL() { return QStringLiteral("normal"); } } //BEGIN KateAutoIndent QStringList KateAutoIndent::listModes() { QStringList l; for (int i = 0; i < modeCount(); ++i) { l << modeDescription(i); } return l; } QStringList KateAutoIndent::listIdentifiers() { QStringList l; for (int i = 0; i < modeCount(); ++i) { l << modeName(i); } return l; } int KateAutoIndent::modeCount() { // inbuild modes + scripts return 2 + KTextEditor::EditorPrivate::self()->scriptManager()->indentationScriptCount(); } QString KateAutoIndent::modeName(int mode) { if (mode == 0 || mode >= modeCount()) { return MODE_NONE(); } if (mode == 1) { return MODE_NORMAL(); } return KTextEditor::EditorPrivate::self()->scriptManager()->indentationScriptByIndex(mode - 2)->indentHeader().baseName(); } QString KateAutoIndent::modeDescription(int mode) { if (mode == 0 || mode >= modeCount()) { return i18nc("Autoindent mode", "None"); } if (mode == 1) { return i18nc("Autoindent mode", "Normal"); } const QString &name = KTextEditor::EditorPrivate::self()->scriptManager()->indentationScriptByIndex(mode - 2)->indentHeader().name(); return i18nc("Autoindent mode", name.toUtf8().constData()); } QString KateAutoIndent::modeRequiredStyle(int mode) { if (mode == 0 || mode == 1 || mode >= modeCount()) { return QString(); } return KTextEditor::EditorPrivate::self()->scriptManager()->indentationScriptByIndex(mode - 2)->indentHeader().requiredStyle(); } uint KateAutoIndent::modeNumber(const QString &name) { for (int i = 0; i < modeCount(); ++i) if (modeName(i) == name) { return i; } return 0; } KateAutoIndent::KateAutoIndent(KTextEditor::DocumentPrivate *_doc) - : QObject(_doc), doc(_doc), m_script(0) + : QObject(_doc), doc(_doc), m_script(nullptr) { // don't call updateConfig() here, document might is not ready for that.... // on script reload, the script pointer is invalid -> force reload connect(KTextEditor::EditorPrivate::self()->scriptManager(), SIGNAL(reloaded()), this, SLOT(reloadScript())); } KateAutoIndent::~KateAutoIndent() { } QString KateAutoIndent::tabString(int length, int align) const { QString s; length = qMin(length, 256); // sanity check for large values of pos int spaces = qBound(0, align - length, 256); if (!useSpaces) { s.append(QString(length / tabWidth, QLatin1Char('\t'))); length = length % tabWidth; } s.append(QString(length + spaces, QLatin1Char(' '))); return s; } bool KateAutoIndent::doIndent(int line, int indentDepth, int align) { Kate::TextLine textline = doc->plainKateTextLine(line); // textline not found, cu if (!textline) { return false; } // sanity check if (indentDepth < 0) { indentDepth = 0; } const QString oldIndentation = textline->leadingWhitespace(); // Preserve existing "tabs then spaces" alignment if and only if: // - no alignment was passed to doIndent and // - we aren't using spaces for indentation and // - we aren't rounding indentation up to the next multiple of the indentation width and // - we aren't using a combination to tabs and spaces for alignment, or in other words // the indent width is a multiple of the tab width. bool preserveAlignment = !useSpaces && keepExtra && indentWidth % tabWidth == 0; if (align == 0 && preserveAlignment) { // Count the number of consecutive spaces at the end of the existing indentation int i = oldIndentation.size() - 1; while (i >= 0 && oldIndentation.at(i) == QLatin1Char(' ')) { --i; } // Use the passed indentDepth as the alignment, and set the indentDepth to // that value minus the number of spaces found (but don't let it get negative). align = indentDepth; indentDepth = qMax(0, align - (oldIndentation.size() - 1 - i)); } QString indentString = tabString(indentDepth, align); // Modify the document *ONLY* if smth has really changed! if (oldIndentation != indentString) { // remove leading whitespace, then insert the leading indentation doc->editStart(); doc->editRemoveText(line, 0, oldIndentation.length()); doc->editInsertText(line, 0, indentString); doc->editEnd(); } return true; } bool KateAutoIndent::doIndentRelative(int line, int change) { Kate::TextLine textline = doc->plainKateTextLine(line); // get indent width of current line int indentDepth = textline->indentDepth(tabWidth); int extraSpaces = indentDepth % indentWidth; // add change indentDepth += change; // if keepExtra is off, snap to a multiple of the indentWidth if (!keepExtra && extraSpaces > 0) { if (change < 0) { indentDepth += indentWidth - extraSpaces; } else { indentDepth -= extraSpaces; } } // do indent return doIndent(line, indentDepth); } void KateAutoIndent::keepIndent(int line) { // no line in front, no work... if (line <= 0) { return; } // keep indentation: find line with content int nonEmptyLine = line - 1; while (nonEmptyLine >= 0) { if (doc->lineLength(nonEmptyLine) > 0) { break; } --nonEmptyLine; } Kate::TextLine prevTextLine = doc->plainKateTextLine(nonEmptyLine); Kate::TextLine textLine = doc->plainKateTextLine(line); // textline not found, cu if (!prevTextLine || !textLine) { return; } const QString previousWhitespace = prevTextLine->leadingWhitespace(); // remove leading whitespace, then insert the leading indentation doc->editStart(); if (!keepExtra) { const QString currentWhitespace = textLine->leadingWhitespace(); doc->editRemoveText(line, 0, currentWhitespace.length()); } doc->editInsertText(line, 0, previousWhitespace); doc->editEnd(); } void KateAutoIndent::reloadScript() { // small trick to force reload - m_script = 0; // prevent dangling pointer + m_script = nullptr; // prevent dangling pointer QString currentMode = m_mode; m_mode = QString(); setMode(currentMode); } void KateAutoIndent::scriptIndent(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &position, QChar typedChar) { // start edit doc->pushEditState(); doc->editStart(); QPair result = m_script->indent(view, position, typedChar, indentWidth); int newIndentInChars = result.first; // handle negative values special if (newIndentInChars < -1) { // do nothing atm } // reuse indentation of the previous line, just like the "normal" indenter else if (newIndentInChars == -1) { // keep indent of previous line keepIndent(position.line()); } // get align else { // we got a positive or zero indent to use... doIndent(position.line(), newIndentInChars, result.second); } // end edit in all cases doc->editEnd(); doc->popEditState(); } bool KateAutoIndent::isStyleProvided(const KateIndentScript *script, const KateHighlighting *highlight) { QString requiredStyle = script->indentHeader().requiredStyle(); return (requiredStyle.isEmpty() || requiredStyle == highlight->style()); } void KateAutoIndent::setMode(const QString &name) { // bail out, already set correct mode... if (m_mode == name) { return; } // cleanup - m_script = 0; + m_script = nullptr; // first, catch easy stuff... normal mode and none, easy... if (name.isEmpty() || name == MODE_NONE()) { m_mode = MODE_NONE(); return; } if (name == MODE_NORMAL()) { m_mode = MODE_NORMAL(); return; } // handle script indenters, if any for this name... KateIndentScript *script = KTextEditor::EditorPrivate::self()->scriptManager()->indentationScript(name); if (script) { if (isStyleProvided(script, doc->highlight())) { m_script = script; m_mode = name; return; } else { qCWarning(LOG_KTE) << "mode" << name << "requires a different highlight style: highlighting '" << doc->highlight()->name() << "' (" << doc->highlight()->version() << "), style '" << doc->highlight()->style() << "'" ", but script require '" << script->indentHeader().requiredStyle() << "'" ; } } else { qCWarning(LOG_KTE) << "mode" << name << "does not exist"; } // Fall back to normal m_mode = MODE_NORMAL(); } void KateAutoIndent::checkRequiredStyle() { if (m_script) { if (!isStyleProvided(m_script, doc->highlight())) { qCDebug(LOG_KTE) << "mode" << m_mode << "requires a different highlight style: highlighting '" << doc->highlight()->name() << "' (" << doc->highlight()->version() << "), style '" << doc->highlight()->style() << "'" ", but script require '" << m_script->indentHeader().requiredStyle() << "'" ; doc->config()->setIndentationMode(MODE_NORMAL()); } } } void KateAutoIndent::updateConfig() { KateDocumentConfig *config = doc->config(); useSpaces = config->replaceTabsDyn(); keepExtra = config->keepExtraSpaces(); tabWidth = config->tabWidth(); indentWidth = config->indentationWidth(); } bool KateAutoIndent::changeIndent(const KTextEditor::Range &range, int change) { QList skippedLines; // loop over all lines given... for (int line = range.start().line() < 0 ? 0 : range.start().line(); line <= qMin(range.end().line(), doc->lines() - 1); ++line) { // don't indent empty lines if (doc->line(line).isEmpty()) { skippedLines.append(line); continue; } // don't indent the last line when the cursor is on the first column if (line == range.end().line() && range.end().column() == 0) { skippedLines.append(line); continue; } doIndentRelative(line, change * indentWidth); } if (skippedLines.count() > range.numberOfLines()) { // all lines were empty, so indent them nevertheless foreach (int line, skippedLines) { doIndentRelative(line, change * indentWidth); } } return true; } void KateAutoIndent::indent(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range) { // no script, do nothing... if (!m_script) { return; } // we want one undo action >= START doc->setUndoMergeAllEdits(true); // loop over all lines given... for (int line = range.start().line() < 0 ? 0 : range.start().line(); line <= qMin(range.end().line(), doc->lines() - 1); ++line) { // let the script indent for us... scriptIndent(view, KTextEditor::Cursor(line, 0), QChar()); } // we want one undo action => END doc->setUndoMergeAllEdits(false); } void KateAutoIndent::userTypedChar(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &position, QChar typedChar) { // normal mode if (m_mode == MODE_NORMAL()) { // only indent on new line, per default if (typedChar != QLatin1Char('\n')) { return; } // keep indent of previous line keepIndent(position.line()); return; } // no script, do nothing... if (!m_script) { return; } // does the script allow this char as trigger? if (typedChar != QLatin1Char('\n') && !m_script->triggerCharacters().contains(typedChar)) { return; } // let the script indent for us... scriptIndent(view, position, typedChar); } //END KateAutoIndent //BEGIN KateViewIndentAction KateViewIndentationAction::KateViewIndentationAction(KTextEditor::DocumentPrivate *_doc, const QString &text, QObject *parent) : KActionMenu(text, parent), doc(_doc) { connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); actionGroup = new QActionGroup(menu()); } void KateViewIndentationAction::slotAboutToShow() { QStringList modes = KateAutoIndent::listModes(); menu()->clear(); foreach (QAction *action, actionGroup->actions()) { actionGroup->removeAction(action); } for (int z = 0; z < modes.size(); ++z) { QAction *action = menu()->addAction(QLatin1Char('&') + KateAutoIndent::modeDescription(z).replace(QLatin1Char('&'), QLatin1String("&&"))); actionGroup->addAction(action); action->setCheckable(true); action->setData(z); QString requiredStyle = KateAutoIndent::modeRequiredStyle(z); action->setEnabled(requiredStyle.isEmpty() || requiredStyle == doc->highlight()->style()); if (doc->config()->indentationMode() == KateAutoIndent::modeName(z)) { action->setChecked(true); } } disconnect(menu(), SIGNAL(triggered(QAction*)), this, SLOT(setMode(QAction*))); connect(menu(), SIGNAL(triggered(QAction*)), this, SLOT(setMode(QAction*))); } void KateViewIndentationAction::setMode(QAction *action) { // set new mode doc->config()->setIndentationMode(KateAutoIndent::modeName(action->data().toInt())); doc->rememberUserDidSetIndentationMode(); } //END KateViewIndentationAction diff --git a/src/utils/kateautoindent.h b/src/utils/kateautoindent.h index fab81597..fefb9f5a 100644 --- a/src/utils/kateautoindent.h +++ b/src/utils/kateautoindent.h @@ -1,257 +1,257 @@ /* This file is part of the KDE libraries Copyright (C) 2003 Jesse Yurkovich Copyright (C) 2004 >Anders Lund (KateVarIndent class) Copyright (C) 2005 Dominik Haumann (basic support for config page) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KATE_AUTO_INDENT_H__ #define __KATE_AUTO_INDENT_H__ #include "kateconfig.h" #include #include #include namespace KTextEditor { class DocumentPrivate; } class KateIndentScript; class KateHighlighting; /** * Provides Auto-Indent functionality for katepart. * This baseclass is a real dummy, does nothing beside remembering the document it belongs too, * only to have the object around */ class KateAutoIndent : public QObject { Q_OBJECT /* * Static methods to list indention modes */ public: /** * List all possible modes by name, i.e. "C Style", "XML Style", ... * @return list of modes */ static QStringList listModes(); /** * List all possible names, i.e. "cstyle", "xml", ... * @return list of indenter identifiers */ static QStringList listIdentifiers(); /** * Return the mode name given the mode * @param mode mode index * @return name for this mode index */ static QString modeName(int mode); /** * Return the mode description * @param mode mode index * @return mode index */ static QString modeDescription(int mode); /** * Return the syntax highlighting style required to use this mode * @param mode mode index * @return required style, or empty if the mode doesn't require any style */ static QString modeRequiredStyle(int mode); /** * Maps name -> index * @param name mode name * @return mode index */ static uint modeNumber(const QString &name); /** * count of modes * @return number of existing modes */ static int modeCount(); /* * Construction + Destruction */ public: /** * Constructor, creates dummy indenter "None" * \param doc parent document */ explicit KateAutoIndent(KTextEditor::DocumentPrivate *doc); /** * Destructor */ ~KateAutoIndent(); /* * Internal helper for the subclasses and itself */ private: /** * Produces a string with the proper indentation characters for its length. * * @param length The length of the indention in characters. * @param align Length of alignment, ignored if less of equal to length * @return A QString representing @p length characters (factoring in tabs and spaces) */ QString tabString(int length, int align) const; /** * Set the indent level of the line. * \param line line to change indent for - * \param change set indentation to given number of spaces + * \param indentDepth set indentation to given number of spaces * \param align if align is higher than indentDepth, the difference * represents a number of spaces to be added after the indent */ bool doIndent(int line, int indentDepth, int align = 0); /** * Change the indent of the specified line by the number of levels * specified by change. Positive values will indent more, negative values * will indent less. * \param line line to change indent for * \param change change the indentation by given number of spaces */ bool doIndentRelative(int line, int change); /** * Reuse the indent of the previous line * \param line line to change indent for */ void keepIndent(int line); /** * Call the indentation script, this is a helper to be used in userTypedChar and indent * \param view the view the user work at * \param position current cursor position, after the inserted char... * \param typedChar the inserted char, indent will just give the script '\n' */ void scriptIndent(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &position, QChar typedChar); /** * Return true if the required style for the script is provided by the highlighter. */ static bool isStyleProvided(const KateIndentScript *script, const KateHighlighting *highlight); public: /** * Switch indenter * Nop if already set to given mode * Otherwise switch to given indenter or to "None" if no suitable found... * @param name indention mode wanted */ void setMode(const QString &name); /** * Check if the current highlighting mode provides the style required by the * current indenter. If not, deactivate the indenter by changing to "normal" * mode. */ void checkRequiredStyle(); /** * mode name */ const QString &modeName() const { return m_mode; } /** * Update indenter's configuration (indention width, etc.) * Is called in the updateConfig() of the document and after creation of the indenter... */ void updateConfig(); /** * Function to provide the common indent/unindent/clean indent functionality to the document * This should be generic for all indenters, internally it uses the doIndent function. * This works equal for all indenters, even for "none" or the scripts * \param range range of text to change indent for * \param change level of indents to add or remove, zero will still trigger cleaning of indentation * and removal of extra spaces, if option set * \return \e true on success, otherwise \e false */ bool changeIndent(const KTextEditor::Range &range, int change); /** * The document requests the indenter to indent the given range of existing text. * This may happen to indent text pasted or to reindent existing text. * For "none" and "normal" this is a nop, for the scripts, the expression * will be asked for indent level for each line * \param view the view the user work at * \param range the range of text to indent... */ void indent(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range); /** * The user typed some char, the indenter can react on this * '\n' will be send as char if the user wraps a line * \param view the view the user work at * \param position current cursor position, after the inserted char... * \param typedChar the inserted char */ void userTypedChar(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &position, QChar typedChar); public Q_SLOTS: void reloadScript(); /* * needed data */ private: KTextEditor::DocumentPrivate *doc; //!< the document the indenter works on int tabWidth; //!< The number of characters simulated for a tab int indentWidth; //!< The number of characters used when tabs are replaced by spaces bool useSpaces; //!< Should we use spaces or tabs to indent bool keepExtra; //!< Keep indentation that is not on indentation boundaries QString m_mode; KateIndentScript *m_script; }; /** * This action provides a list of available indenters and gets plugged * into the KTextEditor::ViewPrivate's KActionCollection. */ class KateViewIndentationAction : public KActionMenu { Q_OBJECT public: KateViewIndentationAction(KTextEditor::DocumentPrivate *_doc, const QString &text, QObject *parent); private: KTextEditor::DocumentPrivate *doc; QActionGroup *actionGroup; public Q_SLOTS: void slotAboutToShow(); private Q_SLOTS: void setMode(QAction *); }; #endif diff --git a/src/utils/katebookmarks.cpp b/src/utils/katebookmarks.cpp index 6c7408cc..47e1ace5 100644 --- a/src/utils/katebookmarks.cpp +++ b/src/utils/katebookmarks.cpp @@ -1,299 +1,299 @@ /* This file is part of the KDE libraries Copyright (C) 2002, 2003, 2004 Anders Lund Copyright (C) 2002 John Firebaugh 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. */ #include "katebookmarks.h" #include "katedocument.h" #include "kateview.h" #include "kateabstractinputmode.h" #include #include #include #include #include #include #include #include #include #include #include namespace KTextEditor { class Document; } KateBookmarks::KateBookmarks(KTextEditor::ViewPrivate *view, Sorting sort) : QObject(view) , m_view(view) - , m_bookmarkClear(0) + , m_bookmarkClear(nullptr) , m_sorting(sort) { setObjectName(QStringLiteral("kate bookmarks")); connect(view->doc(), SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(marksChanged())); _tries = 0; - m_bookmarksMenu = 0L; + m_bookmarksMenu = nullptr; } KateBookmarks::~KateBookmarks() { } void KateBookmarks::createActions(KActionCollection *ac) { m_bookmarkToggle = new KToggleAction(i18n("Set &Bookmark"), this); ac->addAction(QStringLiteral("bookmarks_toggle"), m_bookmarkToggle); m_bookmarkToggle->setIcon(QIcon::fromTheme(QStringLiteral("bookmark-new"))); ac->setDefaultShortcut(m_bookmarkToggle, Qt::CTRL + Qt::Key_B); m_bookmarkToggle->setWhatsThis(i18n("If a line has no bookmark then add one, otherwise remove it.")); connect(m_bookmarkToggle, SIGNAL(triggered()), this, SLOT(toggleBookmark())); m_bookmarkClear = new QAction(i18n("Clear &All Bookmarks"), this); ac->addAction(QStringLiteral("bookmarks_clear"), m_bookmarkClear); m_bookmarkClear->setWhatsThis(i18n("Remove all bookmarks of the current document.")); connect(m_bookmarkClear, SIGNAL(triggered()), this, SLOT(clearBookmarks())); m_goNext = new QAction(i18n("Next Bookmark"), this); ac->addAction(QStringLiteral("bookmarks_next"), m_goNext); m_goNext->setIcon(QIcon::fromTheme(QStringLiteral("go-down-search"))); ac->setDefaultShortcut(m_goNext, Qt::ALT + Qt::Key_PageDown); m_goNext->setWhatsThis(i18n("Go to the next bookmark.")); connect(m_goNext, SIGNAL(triggered()), this, SLOT(goNext())); m_goPrevious = new QAction(i18n("Previous Bookmark"), this); ac->addAction(QStringLiteral("bookmarks_previous"), m_goPrevious); m_goPrevious->setIcon(QIcon::fromTheme(QStringLiteral("go-up-search"))); ac->setDefaultShortcut(m_goPrevious, Qt::ALT + Qt::Key_PageUp); m_goPrevious->setWhatsThis(i18n("Go to the previous bookmark.")); connect(m_goPrevious, SIGNAL(triggered()), this, SLOT(goPrevious())); KActionMenu *actionMenu = new KActionMenu(i18n("&Bookmarks"), this); ac->addAction(QStringLiteral("bookmarks"), actionMenu); m_bookmarksMenu = actionMenu->menu(); connect(m_bookmarksMenu, SIGNAL(aboutToShow()), this, SLOT(bookmarkMenuAboutToShow())); marksChanged(); // Always want the actions with shortcuts plugged into something so their shortcuts can work m_view->addAction(m_bookmarkToggle); m_view->addAction(m_bookmarkClear); m_view->addAction(m_goNext); m_view->addAction(m_goPrevious); } void KateBookmarks::toggleBookmark() { uint mark = m_view->doc()->mark(m_view->cursorPosition().line()); if (mark & KTextEditor::MarkInterface::markType01) m_view->doc()->removeMark(m_view->cursorPosition().line(), KTextEditor::MarkInterface::markType01); else m_view->doc()->addMark(m_view->cursorPosition().line(), KTextEditor::MarkInterface::markType01); } void KateBookmarks::clearBookmarks() { QHash m = m_view->doc()->marks(); for (QHash::const_iterator i = m.constBegin(); i != m.constEnd(); ++i) { m_view->doc()->removeMark(i.value()->line, KTextEditor::MarkInterface::markType01); } // just to be sure ;) // dominik: the following line can be deleted afaics, as Document::removeMark emits this signal. marksChanged(); } void KateBookmarks::insertBookmarks(QMenu &menu) { int line = m_view->cursorPosition().line(); const QRegExp re(QLatin1String("&(?!&)")); int next = -1; // -1 means next bookmark doesn't exist int prev = -1; // -1 means previous bookmark doesn't exist const QHash &m = m_view->doc()->marks(); QVector bookmarkLineArray; // Array of line numbers which have bookmarks if (m.isEmpty()) { return; } // Find line numbers where bookmarks are set & store those line numbers in bookmarkLineArray for (QHash::const_iterator it = m.constBegin(); it != m.constEnd(); ++it) { if (it.value()->type & KTextEditor::MarkInterface::markType01) { bookmarkLineArray.append(it.value()->line); } } if (m_sorting == Position) { qSort(bookmarkLineArray.begin(), bookmarkLineArray.end()); } QAction *firstNewAction = menu.addSeparator(); // Consider each line with a bookmark one at a time for (int i = 0; i < bookmarkLineArray.size(); ++i) { // Get text in this particular line in a QString QString bText = menu.fontMetrics().elidedText (m_view->doc()->line(bookmarkLineArray.at(i)), Qt::ElideRight, menu.fontMetrics().maxWidth() * 32); bText.replace(re, QStringLiteral("&&")); // kill undesired accellerators! bText.replace(QLatin1Char('\t'), QLatin1Char(' ')); // kill tabs, as they are interpreted as shortcuts - QAction *before = 0; + QAction *before = nullptr; if (m_sorting == Position) { // 3 actions already present if (menu.actions().size() <= i + 3) { - before = 0; + before = nullptr; } else { before = menu.actions().at(i + 3); } } // Adding action for this bookmark in menu if (before) { QAction *a = new QAction(QStringLiteral("%1 %3 - \"%2\"") .arg(bookmarkLineArray.at(i) + 1).arg(bText) .arg(m_view->currentInputMode()->bookmarkLabel(bookmarkLineArray.at(i))), &menu); menu.insertAction(before, a); connect(a, SIGNAL(activated()), this, SLOT(gotoLine())); a->setData(bookmarkLineArray.at(i)); if (!firstNewAction) { firstNewAction = a; } } else { QAction *a = menu.addAction(QStringLiteral("%1 %3 - \"%2\"") .arg(bookmarkLineArray.at(i) + 1).arg(bText) .arg(m_view->currentInputMode()->bookmarkLabel(bookmarkLineArray.at(i))), this, SLOT(gotoLine())); a->setData(bookmarkLineArray.at(i)); } // Find the line number of previous & next bookmark (if present) in relation to the cursor if (bookmarkLineArray.at(i) < line) { if ((prev == -1) || prev < (bookmarkLineArray.at(i))) { prev = bookmarkLineArray.at(i); } } else if (bookmarkLineArray.at(i) > line) { if ((next == -1) || next > (bookmarkLineArray.at(i))) { next = bookmarkLineArray.at(i); } } } if (next != -1) { // Insert action for next bookmark m_goNext->setText(i18n("&Next: %1 - \"%2\"", next + 1, KStringHandler::rsqueeze(m_view->doc()->line(next), 24))); menu.insertAction(firstNewAction, m_goNext); firstNewAction = m_goNext; } if (prev != -1) { // Insert action for previous bookmark m_goPrevious->setText(i18n("&Previous: %1 - \"%2\"", prev + 1, KStringHandler::rsqueeze(m_view->doc()->line(prev), 24))); menu.insertAction(firstNewAction, m_goPrevious); firstNewAction = m_goPrevious; } if (next != -1 || prev != -1) { menu.insertSeparator(firstNewAction); } } void KateBookmarks::gotoLine() { if (!sender()) { return; } gotoLine(((QAction *)(sender()))->data().toInt()); } void KateBookmarks::gotoLine(int line) { m_view->setCursorPosition(KTextEditor::Cursor(line, 0)); } void KateBookmarks::bookmarkMenuAboutToShow() { m_bookmarksMenu->clear(); m_bookmarkToggle->setChecked(m_view->doc()->mark(m_view->cursorPosition().line()) & KTextEditor::MarkInterface::markType01); m_bookmarksMenu->addAction(m_bookmarkToggle); m_bookmarksMenu->addAction(m_bookmarkClear); m_goNext->setText(i18n("Next Bookmark")); m_goPrevious->setText(i18n("Previous Bookmark")); insertBookmarks(*m_bookmarksMenu); } void KateBookmarks::goNext() { const QHash &m = m_view->doc()->marks(); if (m.isEmpty()) { return; } int line = m_view->cursorPosition().line(); int found = -1; for (QHash::const_iterator it = m.constBegin(); it != m.constEnd(); ++it) { if ((it.value()->line > line) && ((found == -1) || (found > it.value()->line))) { found = it.value()->line; } } if (found != -1) { gotoLine(found); } } void KateBookmarks::goPrevious() { const QHash &m = m_view->doc()->marks(); if (m.isEmpty()) { return; } int line = m_view->cursorPosition().line(); int found = -1; for (QHash::const_iterator it = m.constBegin(); it != m.constEnd(); ++it) { if ((it.value()->line < line) && ((found == -1) || (found < it.value()->line))) { found = it.value()->line; } } if (found != -1) { gotoLine(found); } } void KateBookmarks::marksChanged() { if (m_bookmarkClear) { m_bookmarkClear->setEnabled(!m_view->doc()->marks().isEmpty()); } } diff --git a/src/utils/katecmds.cpp b/src/utils/katecmds.cpp index 2a7be9d0..0c2ad5e5 100644 --- a/src/utils/katecmds.cpp +++ b/src/utils/katecmds.cpp @@ -1,593 +1,593 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * 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 "katecmds.h" #include "katedocument.h" #include "kateview.h" #include "kateautoindent.h" #include "katetextline.h" #include "katesyntaxmanager.h" #include "katerenderer.h" #include "katecmd.h" #include "katepartdebug.h" #include #include //BEGIN CoreCommands -KateCommands::CoreCommands *KateCommands::CoreCommands::m_instance = 0; +KateCommands::CoreCommands *KateCommands::CoreCommands::m_instance = nullptr; // this returns wheather the string s could be converted to // a bool value, one of on|off|1|0|true|false. the argument val is // set to the extracted value in case of success static bool getBoolArg(const QString &t, bool *val) { bool res(false); QString s = t.toLower(); res = (s == QLatin1String("on") || s == QLatin1String("1") || s == QLatin1String("true")); if (res) { *val = true; return true; } res = (s == QLatin1String("off") || s == QLatin1String("0") || s == QLatin1String("false")); if (res) { *val = false; return true; } return false; } bool KateCommands::CoreCommands::help(KTextEditor::View *, const QString &cmd, QString &msg) { QString realcmd = cmd.trimmed(); if (realcmd == QLatin1String("indent")) { msg = i18n("

indent

" "

Indents the selected lines or the current line

"); return true; } else if (realcmd == QLatin1String("unindent")) { msg = i18n("

unindent

" "

Unindents the selected lines or current line.

"); return true; } else if (realcmd == QLatin1String("cleanindent")) { msg = i18n("

cleanindent

" "

Cleans up the indentation of the selected lines or current line according to the indentation settings in the document.

"); return true; } else if (realcmd == QLatin1String("comment")) { msg = i18n("

comment

" "

Inserts comment markers to make the selection or selected lines or current line a comment according to the text format as defined by the syntax highlight definition for the document.

"); return true; } else if (realcmd == QLatin1String("uncomment")) { msg = i18n("

uncomment

" "

Removes comment markers from the selection or selected lines or current line according to the text format as defined by the syntax highlight definition for the document.

"); return true; } else if (realcmd == QLatin1String("goto")) { msg = i18n("

goto line number

" "

This command navigates to the specified line number.

"); return true; } else if (realcmd == QLatin1String("set-indent-pasted-text")) { msg = i18n("

set-indent-pasted-text enable

" "

If enabled, indentation of text pasted from the clipboard is adjusted using the current indenter.

" "

Possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("kill-line")) { msg = i18n("Deletes the current line."); return true; } else if (realcmd == QLatin1String("set-tab-width")) { msg = i18n("

set-tab-width width

" "

Sets the tab width to the number width

"); return true; } else if (realcmd == QLatin1String("set-replace-tab")) { msg = i18n("

set-replace-tab enable

" "

If enabled, tabs are replaced with spaces as you type.

" "

Possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-show-tabs")) { msg = i18n("

set-show-tabs enable

" "

If enabled, TAB characters and trailing whitespace will be visualized by a small dot.

" "

Possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-remove-trailing-spaces")) { msg = i18n("

set-remove-trailing-spaces mode

" "

Removes the trailing spaces in the document depending on the mode.

" "

Possible values:" "

    " "
  • none: never remove trailing spaces.
  • " "
  • modified: remove trailing spaces only of modified lines.
  • " "
  • all: remove trailing spaces in the entire document.
  • " "

"); return true; } else if (realcmd == QLatin1String("set-indent-width")) { msg = i18n("

set-indent-width width

" "

Sets the indentation width to the number width. Used only if you are indenting with spaces.

"); return true; } else if (realcmd == QLatin1String("set-indent-mode")) { msg = i18n("

set-indent-mode mode

" "

The mode parameter is a value as seen in the Tools - Indentation menu

"); return true; } else if (realcmd == QLatin1String("set-auto-indent")) { msg = i18n("

set-auto-indent enable

" "

Enable or disable autoindentation.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-line-numbers")) { msg = i18n("

set-line-numbers enable

" "

Sets the visibility of the line numbers pane.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-folding-markers")) { msg = i18n("

set-folding-markers enable

" "

Sets the visibility of the folding markers pane.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-icon-border")) { msg = i18n("

set-icon-border enable

" "

Sets the visibility of the icon border.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-word-wrap")) { msg = i18n("

set-word-wrap enable

" "

Enables dynamic word wrap according to enable

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-word-wrap-column")) { msg = i18n("

set-word-wrap-column width

" "

Sets the line width for hard wrapping to width. This is used if you are having your text wrapped automatically.

"); return true; } else if (realcmd == QLatin1String("set-replace-tabs-save")) { msg = i18n("

set-replace-tabs-save enable

" "

When enabled, tabs will be replaced with whitespace whenever the document is saved.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-highlight")) { msg = i18n("

set-highlight highlight

" "

Sets the syntax highlighting system for the document. The argument must be a valid highlight name, as seen in the Tools → Highlighting menu. This command provides an autocompletion list for its argument.

"); return true; } else if (realcmd == QLatin1String("set-mode")) { msg = i18n("

set-mode mode

" "

Sets the mode as seen in Tools - Mode

"); return true; } else if (realcmd == QLatin1String("set-show-indent")) { msg = i18n("

set-show-indent enable

" "

If enabled, indentation will be visualized by a vertical dotted line.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("print")) { msg = i18n("

Open the Print dialog to print the current document.

"); return true; } else { return false; } } bool KateCommands::CoreCommands::exec(KTextEditor::View *view, const QString &_cmd, QString &errorMsg, const KTextEditor::Range &range) { #define KCC_ERR(s) { errorMsg=s; return false; } // cast it hardcore, we know that it is really a kateview :) KTextEditor::ViewPrivate *v = static_cast(view); if (! v) { KCC_ERR(i18n("Could not access view")); } //create a list of args QStringList args(_cmd.split(QRegExp(QLatin1String("\\s+")), QString::SkipEmptyParts)); QString cmd(args.takeFirst()); // ALL commands that takes no arguments. if (cmd == QLatin1String("indent")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->indent(KTextEditor::Range(line, 0, line, 0), 1); } v->doc()->editEnd(); } else { v->indent(); } return true; } else if (cmd == QLatin1String("unindent")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->indent(KTextEditor::Range(line, 0, line, 0), -1); } v->doc()->editEnd(); } else { v->unIndent(); } return true; } else if (cmd == QLatin1String("cleanindent")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->indent(KTextEditor::Range(line, 0, line, 0), 0); } v->doc()->editEnd(); } else { v->cleanIndent(); } return true; } else if (cmd == QLatin1String("fold")) { return (v->textFolding().newFoldingRange(range.isValid() ? range : v->selectionRange(), Kate::TextFolding::Persistent | Kate::TextFolding::Folded) != -1); } else if (cmd == QLatin1String("tfold")) { return (v->textFolding().newFoldingRange(range.isValid() ? range : v->selectionRange(), Kate::TextFolding::Folded) != -1); } else if (cmd == QLatin1String("unfold")) { QVector > startingRanges = v->textFolding().foldingRangesStartingOnLine(v->cursorPosition().line()); bool unfolded = false; for (int i = 0; i < startingRanges.size(); ++i) { if (startingRanges[i].second & Kate::TextFolding::Folded) { unfolded = v->textFolding().unfoldRange(startingRanges[i].first) || unfolded; } } return unfolded; } else if (cmd == QLatin1String("comment")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->comment(v, line, 0, 1); } v->doc()->editEnd(); } else { v->comment(); } return true; } else if (cmd == QLatin1String("uncomment")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->comment(v, line, 0, -1); } v->doc()->editEnd(); } else { v->uncomment(); } return true; } else if (cmd == QLatin1String("kill-line")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->removeLine(range.start().line()); } v->doc()->editEnd(); } else { v->killLine(); } return true; } else if (cmd == QLatin1String("print")) { v->print(); return true; } // ALL commands that take a string argument else if (cmd == QLatin1String("set-indent-mode") || cmd == QLatin1String("set-highlight") || cmd == QLatin1String("set-mode")) { // need at least one item, otherwise args.first() crashes if (args.isEmpty()) { KCC_ERR(i18n("Missing argument. Usage: %1 ", cmd)); } if (cmd == QLatin1String("set-indent-mode")) { v->doc()->config()->setIndentationMode(args.join(QLatin1Char(' '))); v->doc()->rememberUserDidSetIndentationMode(); return true; } else if (cmd == QLatin1String("set-highlight")) { if (v->doc()->setHighlightingMode(args.first())) { static_cast(v->doc())->setDontChangeHlOnSave(); return true; } KCC_ERR(i18n("No such highlighting '%1'", args.first())); } else if (cmd == QLatin1String("set-mode")) { if (v->doc()->setMode(args.first())) { return true; } KCC_ERR(i18n("No such mode '%1'", args.first())); } } // ALL commands that takes exactly one integer argument. else if (cmd == QLatin1String("set-tab-width") || cmd == QLatin1String("set-indent-width") || cmd == QLatin1String("set-word-wrap-column") || cmd == QLatin1String("goto")) { // find a integer value > 0 if (args.isEmpty()) { KCC_ERR(i18n("Missing argument. Usage: %1 ", cmd)); } bool ok; int val(args.first().toInt(&ok, 10)); // use base 10 even if the string starts with '0' if (!ok) KCC_ERR(i18n("Failed to convert argument '%1' to integer.", args.first())); if (cmd == QLatin1String("set-tab-width")) { if (val < 1) { KCC_ERR(i18n("Width must be at least 1.")); } v->doc()->config()->setTabWidth(val); } else if (cmd == QLatin1String("set-indent-width")) { if (val < 1) { KCC_ERR(i18n("Width must be at least 1.")); } v->doc()->config()->setIndentationWidth(val); } else if (cmd == QLatin1String("set-word-wrap-column")) { if (val < 2) { KCC_ERR(i18n("Column must be at least 1.")); } v->doc()->setWordWrapAt(val); } else if (cmd == QLatin1String("goto")) { if (args.first().at(0) == QLatin1Char('-') || args.first().at(0) == QLatin1Char('+')) { // if the number starts with a minus or plus sign, add/subract the number val = v->cursorPosition().line() + val; } else { val--; // convert given line number to the internal representation of line numbers } // constrain cursor to the range [0, number of lines] if (val < 0) { val = 0; } else if (val > v->doc()->lines() - 1) { val = v->doc()->lines() - 1; } v->setCursorPosition(KTextEditor::Cursor(val, 0)); return true; } return true; } // ALL commands that takes 1 boolean argument. else if (cmd == QLatin1String("set-icon-border") || cmd == QLatin1String("set-folding-markers") || cmd == QLatin1String("set-indent-pasted-text") || cmd == QLatin1String("set-line-numbers") || cmd == QLatin1String("set-replace-tabs") || cmd == QLatin1String("set-show-tabs") || cmd == QLatin1String("set-word-wrap") || cmd == QLatin1String("set-wrap-cursor") || cmd == QLatin1String("set-replace-tabs-save") || cmd == QLatin1String("set-show-indent")) { if (args.isEmpty()) { KCC_ERR(i18n("Usage: %1 on|off|1|0|true|false", cmd)); } bool enable = false; KateDocumentConfig *const config = v->doc()->config(); if (getBoolArg(args.first(), &enable)) { if (cmd == QLatin1String("set-icon-border")) { v->setIconBorder(enable); } else if (cmd == QLatin1String("set-folding-markers")) { v->setFoldingMarkersOn(enable); } else if (cmd == QLatin1String("set-line-numbers")) { v->setLineNumbersOn(enable); } else if (cmd == QLatin1String("set-show-indent")) { v->renderer()->setShowIndentLines(enable); } else if (cmd == QLatin1String("set-indent-pasted-text")) { config->setIndentPastedText(enable); } else if (cmd == QLatin1String("set-replace-tabs")) { config->setReplaceTabsDyn(enable); } else if (cmd == QLatin1String("set-show-tabs")) { config->setShowTabs(enable); } else if (cmd == QLatin1String("set-show-trailing-spaces")) { config->setShowSpaces(enable); } else if (cmd == QLatin1String("set-word-wrap")) { v->doc()->setWordWrap(enable); } return true; } else KCC_ERR(i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false", args.first(), cmd)); } else if (cmd == QLatin1String("set-remove-trailing-spaces")) { // need at least one item, otherwise args.first() crashes if (args.count() != 1) { KCC_ERR(i18n("Usage: set-remove-trailing-spaces 0|-|none or 1|+|mod|modified or 2|*|all")); } QString tmp = args.first().toLower().trimmed(); if (tmp == QLatin1String("1") || tmp == QLatin1String("modified") || tmp == QLatin1String("mod") || tmp == QLatin1String("+")) { v->doc()->config()->setRemoveSpaces(1); } else if (tmp == QLatin1String("2") || tmp == QLatin1String("all") || tmp == QLatin1String("*")) { v->doc()->config()->setRemoveSpaces(2); } else { v->doc()->config()->setRemoveSpaces(0); } } // unlikely.. KCC_ERR(i18n("Unknown command '%1'", cmd)); } bool KateCommands::CoreCommands::supportsRange(const QString &range) { static QStringList l; if (l.isEmpty()) l << QStringLiteral("indent") << QStringLiteral("unindent") << QStringLiteral("cleanindent") << QStringLiteral("comment") << QStringLiteral("uncomment") << QStringLiteral("kill-line") << QStringLiteral("fold") << QStringLiteral("tfold"); return l.contains(range); } KCompletion *KateCommands::CoreCommands::completionObject(KTextEditor::View *view, const QString &cmd) { Q_UNUSED(view) if (cmd == QLatin1String("set-highlight")) { QStringList l; for (int i = 0; i < KateHlManager::self()->highlights(); i++) { l << KateHlManager::self()->hlName(i); } KateCmdShellCompletion *co = new KateCmdShellCompletion(); co->setItems(l); co->setIgnoreCase(true); return co; } else if (cmd == QLatin1String("set-remove-trailing-spaces")) { QStringList l; l << QStringLiteral("none") << QStringLiteral("modified") << QStringLiteral("all"); KateCmdShellCompletion *co = new KateCmdShellCompletion(); co->setItems(l); co->setIgnoreCase(true); return co; } else if (cmd == QLatin1String("set-indent-mode")) { QStringList l = KateAutoIndent::listIdentifiers(); KateCmdShellCompletion *co = new KateCmdShellCompletion(); co->setItems(l); co->setIgnoreCase(true); return co; } - return 0L; + return nullptr; } //END CoreCommands //BEGIN Character -KateCommands::Character *KateCommands::Character::m_instance = 0; +KateCommands::Character *KateCommands::Character::m_instance = nullptr; bool KateCommands::Character::help(class KTextEditor::View *, const QString &cmd, QString &msg) { if (cmd.trimmed() == QLatin1String("char")) { msg = i18n("

char identifier

" "

This command allows you to insert literal characters by their numerical identifier, in decimal, octal or hexadecimal form.

" "

Examples:

    " "
  • char 234
  • " "
  • char 0x1234
  • " "

"); return true; } return false; } bool KateCommands::Character::exec(KTextEditor::View *view, const QString &_cmd, QString &, const KTextEditor::Range &) { QString cmd = _cmd; // hex, octal, base 9+1 QRegExp num(QLatin1String("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,5})$")); if (num.indexIn(cmd) == -1) { return false; } cmd = num.cap(1); // identify the base unsigned short int number = 0; int base = 10; if (cmd[0] == QLatin1Char('x') || cmd.startsWith(QLatin1String("0x"))) { cmd.remove(QRegExp(QLatin1String("^0?x"))); base = 16; } else if (cmd[0] == QLatin1Char('0')) { base = 8; } bool ok; number = cmd.toUShort(&ok, base); if (!ok || number == 0) { return false; } if (number <= 255) { char buf[2]; buf[0] = (char)number; buf[1] = 0; view->document()->insertText(view->cursorPosition(), QString::fromLatin1(buf)); } else { // do the unicode thing QChar c(number); view->document()->insertText(view->cursorPosition(), QString(&c, 1)); } return true; } //END Character //BEGIN Date -KateCommands::Date *KateCommands::Date::m_instance = 0; +KateCommands::Date *KateCommands::Date::m_instance = nullptr; bool KateCommands::Date::help(class KTextEditor::View *, const QString &cmd, QString &msg) { if (cmd.trimmed() == QLatin1String("date")) { msg = i18n("

date or date format

" "

Inserts a date/time string as defined by the specified format, or the format yyyy-MM-dd hh:mm:ss if none is specified.

" "

Possible format specifiers are:" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "
dThe day as number without a leading zero (1-31).
ddThe day as number with a leading zero (01-31).
dddThe abbreviated localized day name (e.g. 'Mon'..'Sun').
ddddThe long localized day name (e.g. 'Monday'..'Sunday').
MThe month as number without a leading zero (1-12).
MMThe month as number with a leading zero (01-12).
MMMThe abbreviated localized month name (e.g. 'Jan'..'Dec').
yyThe year as two digit number (00-99).
yyyyThe year as four digit number (1752-8000).
hThe hour without a leading zero (0..23 or 1..12 if AM/PM display).
hhThe hour with a leading zero (00..23 or 01..12 if AM/PM display).
mThe minute without a leading zero (0..59).
mmThe minute with a leading zero (00..59).
sThe second without a leading zero (0..59).
ssThe second with a leading zero (00..59).
zThe milliseconds without leading zeroes (0..999).
zzzThe milliseconds with leading zeroes (000..999).
APUse AM/PM display. AP will be replaced by either \"AM\" or \"PM\".
apUse am/pm display. ap will be replaced by either \"am\" or \"pm\".

"); return true; } return false; } bool KateCommands::Date::exec(KTextEditor::View *view, const QString &cmd, QString &, const KTextEditor::Range &) { if (!cmd.startsWith(QLatin1String("date"))) { return false; } if (QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length() - 5)).length() > 0) { view->document()->insertText(view->cursorPosition(), QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length() - 5))); } else { view->document()->insertText(view->cursorPosition(), QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss"))); } return true; } //END Date diff --git a/src/utils/katecmds.h b/src/utils/katecmds.h index 3305f1f8..03d16abc 100644 --- a/src/utils/katecmds.h +++ b/src/utils/katecmds.h @@ -1,194 +1,193 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * 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_CMDS_H__ #define __KATE_CMDS_H__ #include #include class KCompletion; /** * The KateCommands namespace collects subclasses of KTextEditor::Command * for specific use in kate. */ namespace KateCommands { /** * This KTextEditor::Command provides access to a lot of the core functionality * of kate part, settings, utilities, navigation etc. * it needs to get a kateview pointer, it will cast the kate::view pointer * hard to kateview */ class CoreCommands : public KTextEditor::Command { CoreCommands() : KTextEditor::Command(QStringList() << QStringLiteral("indent") << QStringLiteral("unindent") << QStringLiteral("cleanindent") << QStringLiteral("fold") << QStringLiteral("tfold") << QStringLiteral("unfold") << QStringLiteral("comment") << QStringLiteral("uncomment") << QStringLiteral("goto") << QStringLiteral("kill-line") << QStringLiteral("set-tab-width") << QStringLiteral("set-replace-tabs") << QStringLiteral("set-show-tabs") << QStringLiteral("set-indent-width") << QStringLiteral("set-indent-mode") << QStringLiteral("set-auto-indent") << QStringLiteral("set-line-numbers") << QStringLiteral("set-folding-markers") << QStringLiteral("set-icon-border") << QStringLiteral("set-indent-pasted-text") << QStringLiteral("set-word-wrap") << QStringLiteral("set-word-wrap-column") << QStringLiteral("set-replace-tabs-save") << QStringLiteral("set-remove-trailing-spaces") << QStringLiteral("set-highlight") << QStringLiteral("set-mode") << QStringLiteral("set-show-indent") << QStringLiteral("print")) { } static CoreCommands *m_instance; public: ~CoreCommands() { - m_instance = 0; + m_instance = nullptr; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg); /** * execute command on given range * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success - * @param rangeStart first line in range - * @param rangeEnd last line in range + * @param range range to execute command on * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; bool supportsRange(const QString &range) Q_DECL_OVERRIDE; /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; /** override from KTextEditor::Command */ KCompletion *completionObject(KTextEditor::View *, const QString &) Q_DECL_OVERRIDE; static CoreCommands *self() { - if (m_instance == 0) { + if (m_instance == nullptr) { m_instance = new CoreCommands(); } return m_instance; } }; /** * insert a unicode or ascii character * base 9+1: 1234 * hex: 0x1234 or x1234 * octal: 01231 * * prefixed with "char:" **/ class Character : public KTextEditor::Command { Character() : KTextEditor::Command(QStringList() << QStringLiteral("char")) { } static Character *m_instance; public: ~Character() { - m_instance = 0; + m_instance = nullptr; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; static Character *self() { - if (m_instance == 0) { + if (m_instance == nullptr) { m_instance = new Character(); } return m_instance; } }; /** * insert the current date/time in the given format */ class Date : public KTextEditor::Command { Date() : KTextEditor::Command(QStringList() << QStringLiteral("date")) { } static Date *m_instance; public: ~Date() { - m_instance = 0; + m_instance = nullptr; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; static Date *self() { - if (m_instance == 0) { + if (m_instance == nullptr) { m_instance = new Date(); } return m_instance; } }; } // namespace KateCommands #endif diff --git a/src/utils/kateconfig.cpp b/src/utils/kateconfig.cpp index 30dd4fe4..7f6d3d80 100644 --- a/src/utils/kateconfig.cpp +++ b/src/utils/kateconfig.cpp @@ -1,3051 +1,3051 @@ /* This file is part of the KDE libraries Copyright (C) 2007, 2008 Matthew Woehlke Copyright (C) 2003 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 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. */ #include "kateconfig.h" #include "kateglobal.h" #include "katedefaultcolors.h" #include "katerenderer.h" #include "kateview.h" #include "katedocument.h" #include "kateschema.h" #include "katepartdebug.h" #include #include #include #include #include #include //BEGIN KateConfig KateConfig::KateConfig() : configSessionNumber(0), configIsRunning(false) { } KateConfig::~KateConfig() { } void KateConfig::configStart() { configSessionNumber++; if (configSessionNumber > 1) { return; } configIsRunning = true; } void KateConfig::configEnd() { if (configSessionNumber == 0) { return; } configSessionNumber--; if (configSessionNumber > 0) { return; } configIsRunning = false; updateConfig(); } //END //BEGIN KateDocumentConfig -KateGlobalConfig *KateGlobalConfig::s_global = 0; -KateDocumentConfig *KateDocumentConfig::s_global = 0; -KateViewConfig *KateViewConfig::s_global = 0; -KateRendererConfig *KateRendererConfig::s_global = 0; +KateGlobalConfig *KateGlobalConfig::s_global = nullptr; +KateDocumentConfig *KateDocumentConfig::s_global = nullptr; +KateViewConfig *KateViewConfig::s_global = nullptr; +KateRendererConfig *KateRendererConfig::s_global = nullptr; KateGlobalConfig::KateGlobalConfig() { s_global = this; // init with defaults from config or really hardcoded ones KConfigGroup cg(KTextEditor::EditorPrivate::config(), "Editor"); readConfig(cg); } KateGlobalConfig::~KateGlobalConfig() { } namespace { const char KEY_PROBER_TYPE[] = "Encoding Prober Type"; const char KEY_FALLBACK_ENCODING[] = "Fallback Encoding"; } void KateGlobalConfig::readConfig(const KConfigGroup &config) { configStart(); setProberType((KEncodingProber::ProberType)config.readEntry(KEY_PROBER_TYPE, (int)KEncodingProber::Universal)); setFallbackEncoding(config.readEntry(KEY_FALLBACK_ENCODING, "")); configEnd(); } void KateGlobalConfig::writeConfig(KConfigGroup &config) { config.writeEntry(KEY_PROBER_TYPE, (int)proberType()); config.writeEntry(KEY_FALLBACK_ENCODING, fallbackEncoding()); } void KateGlobalConfig::updateConfig() { // write config KConfigGroup cg(KTextEditor::EditorPrivate::config(), "Editor"); writeConfig(cg); KTextEditor::EditorPrivate::config()->sync(); } void KateGlobalConfig::setProberType(KEncodingProber::ProberType proberType) { configStart(); m_proberType = proberType; configEnd(); } const QString &KateGlobalConfig::fallbackEncoding() const { return m_fallbackEncoding; } QTextCodec *KateGlobalConfig::fallbackCodec() const { if (m_fallbackEncoding.isEmpty()) { return QTextCodec::codecForName("ISO 8859-15"); } return KCharsets::charsets()->codecForName(m_fallbackEncoding); } bool KateGlobalConfig::setFallbackEncoding(const QString &encoding) { QTextCodec *codec; bool found = false; if (encoding.isEmpty()) { codec = s_global->fallbackCodec(); found = true; } else { codec = KCharsets::charsets()->codecForName(encoding, found); } if (!found || !codec) { return false; } configStart(); m_fallbackEncoding = QString::fromLatin1(codec->name()); configEnd(); return true; } KateDocumentConfig::KateDocumentConfig() : m_indentationWidth(2), m_tabWidth(4), m_tabHandling(tabSmart), m_configFlags(0), m_wordWrapAt(80), m_tabWidthSet(false), m_indentationWidthSet(false), m_indentationModeSet(false), m_wordWrapSet(false), m_wordWrapAtSet(false), m_pageUpDownMovesCursorSet(false), m_keepExtraSpacesSet(false), m_indentPastedTextSet(false), m_backspaceIndentsSet(false), m_smartHomeSet(false), m_showTabsSet(false), m_showSpacesSet(false), m_replaceTabsDynSet(false), m_removeSpacesSet(false), m_newLineAtEofSet(false), m_overwiteModeSet(false), m_tabIndentsSet(false), m_encodingSet(false), m_eolSet(false), m_bomSet(false), m_allowEolDetectionSet(false), m_backupFlagsSet(false), m_backupPrefixSet(false), m_backupSuffixSet(false), m_swapFileModeSet(false), m_swapDirectorySet(false), m_swapSyncIntervalSet(false), m_onTheFlySpellCheckSet(false), m_lineLengthLimitSet(false), - m_doc(0) + m_doc(nullptr) { s_global = this; // init with defaults from config or really hardcoded ones KConfigGroup cg(KTextEditor::EditorPrivate::config(), "Document"); readConfig(cg); } KateDocumentConfig::KateDocumentConfig(const KConfigGroup &cg) : m_indentationWidth(2), m_tabWidth(4), m_tabHandling(tabSmart), m_configFlags(0), m_wordWrapAt(80), m_tabWidthSet(false), m_indentationWidthSet(false), m_indentationModeSet(false), m_wordWrapSet(false), m_wordWrapAtSet(false), m_pageUpDownMovesCursorSet(false), m_keepExtraSpacesSet(false), m_indentPastedTextSet(false), m_backspaceIndentsSet(false), m_smartHomeSet(false), m_showTabsSet(false), m_showSpacesSet(false), m_replaceTabsDynSet(false), m_removeSpacesSet(false), m_newLineAtEofSet(false), m_overwiteModeSet(false), m_tabIndentsSet(false), m_encodingSet(false), m_eolSet(false), m_bomSet(false), m_allowEolDetectionSet(false), m_backupFlagsSet(false), m_backupPrefixSet(false), m_backupSuffixSet(false), m_swapFileModeSet(false), m_swapDirectorySet(false), m_swapSyncIntervalSet(false), m_onTheFlySpellCheckSet(false), m_lineLengthLimitSet(false), - m_doc(0) + m_doc(nullptr) { // init with defaults from config or really hardcoded ones readConfig(cg); } KateDocumentConfig::KateDocumentConfig(KTextEditor::DocumentPrivate *doc) : m_tabHandling(tabSmart), m_configFlags(0), m_tabWidthSet(false), m_indentationWidthSet(false), m_indentationModeSet(false), m_wordWrapSet(false), m_wordWrapAtSet(false), m_pageUpDownMovesCursorSet(false), m_keepExtraSpacesSet(false), m_indentPastedTextSet(false), m_backspaceIndentsSet(false), m_smartHomeSet(false), m_showTabsSet(false), m_showSpacesSet(false), m_replaceTabsDynSet(false), m_removeSpacesSet(false), m_newLineAtEofSet(false), m_overwiteModeSet(false), m_tabIndentsSet(false), m_encodingSet(false), m_eolSet(false), m_bomSet(false), m_allowEolDetectionSet(false), m_backupFlagsSet(false), m_backupPrefixSet(false), m_backupSuffixSet(false), m_swapFileModeSet(false), m_swapDirectorySet(false), m_swapSyncIntervalSet(false), m_onTheFlySpellCheckSet(false), m_lineLengthLimitSet(false), m_doc(doc) { } KateDocumentConfig::~KateDocumentConfig() { } namespace { const char KEY_TAB_WIDTH[] = "Tab Width"; const char KEY_INDENTATION_WIDTH[] = "Indentation Width"; const char KEY_INDENTATION_MODE[] = "Indentation Mode"; const char KEY_TAB_HANDLING[] = "Tab Handling"; const char KEY_WORD_WRAP[] = "Word Wrap"; const char KEY_WORD_WRAP_AT[] = "Word Wrap Column"; const char KEY_PAGEUP_DOWN_MOVES_CURSOR[] = "PageUp/PageDown Moves Cursor"; const char KEY_SMART_HOME[] = "Smart Home"; const char KEY_SHOW_TABS[] = "Show Tabs"; const char KEY_TAB_INDENTS[] = "Indent On Tab"; const char KEY_KEEP_EXTRA_SPACES[] = "Keep Extra Spaces"; const char KEY_INDENT_PASTED_TEXT[] = "Indent On Text Paste"; const char KEY_BACKSPACE_INDENTS[] = "Indent On Backspace"; const char KEY_SHOW_SPACES[] = "Show Spaces"; const char KEY_REPLACE_TABS_DYN[] = "ReplaceTabsDyn"; const char KEY_REMOVE_SPACES[] = "Remove Spaces"; const char KEY_NEWLINE_AT_EOF[] = "Newline at End of File"; const char KEY_OVR[] = "Overwrite Mode"; const char KEY_ENCODING[] = "Encoding"; const char KEY_EOL[] = "End of Line"; const char KEY_ALLOW_EOL_DETECTION[] = "Allow End of Line Detection"; const char KEY_BOM[] = "BOM"; const char KEY_BACKUP_FLAGS[] = "Backup Flags"; const char KEY_BACKUP_PREFIX[] = "Backup Prefix"; const char KEY_BACKUP_SUFFIX[] = "Backup Suffix"; const char KEY_SWAP_FILE_MODE[] = "Swap File Mode"; const char KEY_SWAP_DIRECTORY[] = "Swap Directory"; const char KEY_SWAP_SYNC_INTERVAL[] = "Swap Sync Interval"; const char KEY_ON_THE_FLY_SPELLCHECK[] = "On-The-Fly Spellcheck"; const char KEY_LINE_LENGTH_LIMIT[] = "Line Length Limit"; } void KateDocumentConfig::readConfig(const KConfigGroup &config) { configStart(); setTabWidth(config.readEntry(KEY_TAB_WIDTH, 4)); setIndentationWidth(config.readEntry(KEY_INDENTATION_WIDTH, 4)); setIndentationMode(config.readEntry(KEY_INDENTATION_MODE, "normal")); setTabHandling(config.readEntry(KEY_TAB_HANDLING, int(KateDocumentConfig::tabSmart))); setWordWrap(config.readEntry(KEY_WORD_WRAP, false)); setWordWrapAt(config.readEntry(KEY_WORD_WRAP_AT, 80)); setPageUpDownMovesCursor(config.readEntry(KEY_PAGEUP_DOWN_MOVES_CURSOR, false)); setSmartHome(config.readEntry(KEY_SMART_HOME, true)); setShowTabs(config.readEntry(KEY_SHOW_TABS, true)); setTabIndents(config.readEntry(KEY_TAB_INDENTS, true)); setKeepExtraSpaces(config.readEntry(KEY_KEEP_EXTRA_SPACES, false)); setIndentPastedText(config.readEntry(KEY_INDENT_PASTED_TEXT, false)); setBackspaceIndents(config.readEntry(KEY_BACKSPACE_INDENTS, true)); setShowSpaces(config.readEntry(KEY_SHOW_SPACES, false)); setReplaceTabsDyn(config.readEntry(KEY_REPLACE_TABS_DYN, true)); setRemoveSpaces(config.readEntry(KEY_REMOVE_SPACES, 0)); setNewLineAtEof(config.readEntry(KEY_NEWLINE_AT_EOF, true)); setOvr(config.readEntry(KEY_OVR, false)); setEncoding(config.readEntry(KEY_ENCODING, "")); setEol(config.readEntry(KEY_EOL, 0)); setAllowEolDetection(config.readEntry(KEY_ALLOW_EOL_DETECTION, true)); setBom(config.readEntry(KEY_BOM, false)); setBackupFlags(config.readEntry(KEY_BACKUP_FLAGS, 0)); setBackupPrefix(config.readEntry(KEY_BACKUP_PREFIX, QString())); setBackupSuffix(config.readEntry(KEY_BACKUP_SUFFIX, QStringLiteral("~"))); setSwapFileMode(config.readEntry(KEY_SWAP_FILE_MODE, (uint)EnableSwapFile)); setSwapDirectory(config.readEntry(KEY_SWAP_DIRECTORY, QString())); setSwapSyncInterval(config.readEntry(KEY_SWAP_SYNC_INTERVAL, 15)); setOnTheFlySpellCheck(config.readEntry(KEY_ON_THE_FLY_SPELLCHECK, false)); setLineLengthLimit(config.readEntry(KEY_LINE_LENGTH_LIMIT, 4096)); configEnd(); } void KateDocumentConfig::writeConfig(KConfigGroup &config) { config.writeEntry(KEY_TAB_WIDTH, tabWidth()); config.writeEntry(KEY_INDENTATION_WIDTH, indentationWidth()); config.writeEntry(KEY_INDENTATION_MODE, indentationMode()); config.writeEntry(KEY_TAB_HANDLING, tabHandling()); config.writeEntry(KEY_WORD_WRAP, wordWrap()); config.writeEntry(KEY_WORD_WRAP_AT, wordWrapAt()); config.writeEntry(KEY_PAGEUP_DOWN_MOVES_CURSOR, pageUpDownMovesCursor()); config.writeEntry(KEY_SMART_HOME, smartHome()); config.writeEntry(KEY_SHOW_TABS, showTabs()); config.writeEntry(KEY_TAB_INDENTS, tabIndentsEnabled()); config.writeEntry(KEY_KEEP_EXTRA_SPACES, keepExtraSpaces()); config.writeEntry(KEY_INDENT_PASTED_TEXT, indentPastedText()); config.writeEntry(KEY_BACKSPACE_INDENTS, backspaceIndents()); config.writeEntry(KEY_SHOW_SPACES, showSpaces()); config.writeEntry(KEY_REPLACE_TABS_DYN, replaceTabsDyn()); config.writeEntry(KEY_REMOVE_SPACES, removeSpaces()); config.writeEntry(KEY_NEWLINE_AT_EOF, newLineAtEof()); config.writeEntry(KEY_OVR, ovr()); config.writeEntry(KEY_ENCODING, encoding()); config.writeEntry(KEY_EOL, eol()); config.writeEntry(KEY_ALLOW_EOL_DETECTION, allowEolDetection()); config.writeEntry(KEY_BOM, bom()); config.writeEntry(KEY_BACKUP_FLAGS, backupFlags()); config.writeEntry(KEY_BACKUP_PREFIX, backupPrefix()); config.writeEntry(KEY_BACKUP_SUFFIX, backupSuffix()); config.writeEntry(KEY_SWAP_FILE_MODE, swapFileModeRaw()); config.writeEntry(KEY_SWAP_DIRECTORY, swapDirectory()); config.writeEntry(KEY_SWAP_SYNC_INTERVAL, swapSyncInterval()); config.writeEntry(KEY_ON_THE_FLY_SPELLCHECK, onTheFlySpellCheck()); config.writeEntry(KEY_LINE_LENGTH_LIMIT, lineLengthLimit()); } void KateDocumentConfig::updateConfig() { if (m_doc) { m_doc->updateConfig(); return; } if (isGlobal()) { for (int z = 0; z < KTextEditor::EditorPrivate::self()->kateDocuments().size(); ++z) { (KTextEditor::EditorPrivate::self()->kateDocuments())[z]->updateConfig(); } // write config KConfigGroup cg(KTextEditor::EditorPrivate::config(), "Document"); writeConfig(cg); KTextEditor::EditorPrivate::config()->sync(); } } int KateDocumentConfig::tabWidth() const { if (m_tabWidthSet || isGlobal()) { return m_tabWidth; } return s_global->tabWidth(); } void KateDocumentConfig::setTabWidth(int tabWidth) { if (tabWidth < 1) { return; } if (m_tabWidthSet && m_tabWidth == tabWidth) { return; } configStart(); m_tabWidthSet = true; m_tabWidth = tabWidth; configEnd(); } int KateDocumentConfig::indentationWidth() const { if (m_indentationWidthSet || isGlobal()) { return m_indentationWidth; } return s_global->indentationWidth(); } void KateDocumentConfig::setIndentationWidth(int indentationWidth) { if (indentationWidth < 1) { return; } if (m_indentationWidthSet && m_indentationWidth == indentationWidth) { return; } configStart(); m_indentationWidthSet = true; m_indentationWidth = indentationWidth; configEnd(); } const QString &KateDocumentConfig::indentationMode() const { if (m_indentationModeSet || isGlobal()) { return m_indentationMode; } return s_global->indentationMode(); } void KateDocumentConfig::setIndentationMode(const QString &indentationMode) { if (m_indentationModeSet && m_indentationMode == indentationMode) { return; } configStart(); m_indentationModeSet = true; m_indentationMode = indentationMode; configEnd(); } uint KateDocumentConfig::tabHandling() const { // This setting is purly a user preference, // hence, there exists only the global setting. if (isGlobal()) { return m_tabHandling; } return s_global->tabHandling(); } void KateDocumentConfig::setTabHandling(uint tabHandling) { configStart(); m_tabHandling = tabHandling; configEnd(); } bool KateDocumentConfig::wordWrap() const { if (m_wordWrapSet || isGlobal()) { return m_wordWrap; } return s_global->wordWrap(); } void KateDocumentConfig::setWordWrap(bool on) { if (m_wordWrapSet && m_wordWrap == on) { return; } configStart(); m_wordWrapSet = true; m_wordWrap = on; configEnd(); } int KateDocumentConfig::wordWrapAt() const { if (m_wordWrapAtSet || isGlobal()) { return m_wordWrapAt; } return s_global->wordWrapAt(); } void KateDocumentConfig::setWordWrapAt(int col) { if (col < 1) { return; } if (m_wordWrapAtSet && m_wordWrapAt == col) { return; } configStart(); m_wordWrapAtSet = true; m_wordWrapAt = col; configEnd(); } bool KateDocumentConfig::pageUpDownMovesCursor() const { if (m_pageUpDownMovesCursorSet || isGlobal()) { return m_pageUpDownMovesCursor; } return s_global->pageUpDownMovesCursor(); } void KateDocumentConfig::setPageUpDownMovesCursor(bool on) { if (m_pageUpDownMovesCursorSet && m_pageUpDownMovesCursor == on) { return; } configStart(); m_pageUpDownMovesCursorSet = true; m_pageUpDownMovesCursor = on; configEnd(); } void KateDocumentConfig::setKeepExtraSpaces(bool on) { if (m_keepExtraSpacesSet && m_keepExtraSpaces == on) { return; } configStart(); m_keepExtraSpacesSet = true; m_keepExtraSpaces = on; configEnd(); } bool KateDocumentConfig::keepExtraSpaces() const { if (m_keepExtraSpacesSet || isGlobal()) { return m_keepExtraSpaces; } return s_global->keepExtraSpaces(); } void KateDocumentConfig::setIndentPastedText(bool on) { if (m_indentPastedTextSet && m_indentPastedText == on) { return; } configStart(); m_indentPastedTextSet = true; m_indentPastedText = on; configEnd(); } bool KateDocumentConfig::indentPastedText() const { if (m_indentPastedTextSet || isGlobal()) { return m_indentPastedText; } return s_global->indentPastedText(); } void KateDocumentConfig::setBackspaceIndents(bool on) { if (m_backspaceIndentsSet && m_backspaceIndents == on) { return; } configStart(); m_backspaceIndentsSet = true; m_backspaceIndents = on; configEnd(); } bool KateDocumentConfig::backspaceIndents() const { if (m_backspaceIndentsSet || isGlobal()) { return m_backspaceIndents; } return s_global->backspaceIndents(); } void KateDocumentConfig::setSmartHome(bool on) { if (m_smartHomeSet && m_smartHome == on) { return; } configStart(); m_smartHomeSet = true; m_smartHome = on; configEnd(); } bool KateDocumentConfig::smartHome() const { if (m_smartHomeSet || isGlobal()) { return m_smartHome; } return s_global->smartHome(); } void KateDocumentConfig::setShowTabs(bool on) { if (m_showTabsSet && m_showTabs == on) { return; } configStart(); m_showTabsSet = true; m_showTabs = on; configEnd(); } bool KateDocumentConfig::showTabs() const { if (m_showTabsSet || isGlobal()) { return m_showTabs; } return s_global->showTabs(); } void KateDocumentConfig::setShowSpaces(bool on) { if (m_showSpacesSet && m_showSpaces == on) { return; } configStart(); m_showSpacesSet = true; m_showSpaces = on; configEnd(); } bool KateDocumentConfig::showSpaces() const { if (m_showSpacesSet || isGlobal()) { return m_showSpaces; } return s_global->showSpaces(); } void KateDocumentConfig::setReplaceTabsDyn(bool on) { if (m_replaceTabsDynSet && m_replaceTabsDyn == on) { return; } configStart(); m_replaceTabsDynSet = true; m_replaceTabsDyn = on; configEnd(); } bool KateDocumentConfig::replaceTabsDyn() const { if (m_replaceTabsDynSet || isGlobal()) { return m_replaceTabsDyn; } return s_global->replaceTabsDyn(); } void KateDocumentConfig::setRemoveSpaces(int triState) { if (m_removeSpacesSet && m_removeSpaces == triState) { return; } configStart(); m_removeSpacesSet = true; m_removeSpaces = triState; configEnd(); } int KateDocumentConfig::removeSpaces() const { if (m_removeSpacesSet || isGlobal()) { return m_removeSpaces; } return s_global->removeSpaces(); } void KateDocumentConfig::setNewLineAtEof(bool on) { if (m_newLineAtEofSet && m_newLineAtEof == on) { return; } configStart(); m_newLineAtEofSet = true; m_newLineAtEof = on; configEnd(); } bool KateDocumentConfig::newLineAtEof() const { if (m_newLineAtEofSet || isGlobal()) { return m_newLineAtEof; } return s_global->newLineAtEof(); } void KateDocumentConfig::setOvr(bool on) { if (m_overwiteModeSet && m_overwiteMode == on) { return; } configStart(); m_overwiteModeSet = true; m_overwiteMode = on; configEnd(); } bool KateDocumentConfig::ovr() const { if (m_overwiteModeSet || isGlobal()) { return m_overwiteMode; } return s_global->ovr(); } void KateDocumentConfig::setTabIndents(bool on) { if (m_tabIndentsSet && m_tabIndents == on) { return; } configStart(); m_tabIndentsSet = true; m_tabIndents = on; configEnd(); } bool KateDocumentConfig::tabIndentsEnabled() const { if (m_tabIndentsSet || isGlobal()) { return m_tabIndents; } return s_global->tabIndentsEnabled(); } const QString &KateDocumentConfig::encoding() const { if (m_encodingSet || isGlobal()) { return m_encoding; } return s_global->encoding(); } QTextCodec *KateDocumentConfig::codec() const { if (m_encodingSet || isGlobal()) { if (m_encoding.isEmpty() && isGlobal()) { // default to UTF-8, this makes sense to have a usable encoding detection // else for people that have by bad luck some encoding like latin1 as default, no encoding detection will work // see e.g. bug 362604 for windows return QTextCodec::codecForName("UTF-8"); } else if (m_encoding.isEmpty()) { return s_global->codec(); } else { return KCharsets::charsets()->codecForName(m_encoding); } } return s_global->codec(); } bool KateDocumentConfig::setEncoding(const QString &encoding) { QTextCodec *codec; bool found = false; if (encoding.isEmpty()) { codec = s_global->codec(); found = true; } else { codec = KCharsets::charsets()->codecForName(encoding, found); } if (!found || !codec) { return false; } configStart(); m_encodingSet = true; m_encoding = QString::fromLatin1(codec->name()); configEnd(); return true; } bool KateDocumentConfig::isSetEncoding() const { return m_encodingSet; } int KateDocumentConfig::eol() const { if (m_eolSet || isGlobal()) { return m_eol; } return s_global->eol(); } QString KateDocumentConfig::eolString() { if (eol() == KateDocumentConfig::eolUnix) { return QStringLiteral("\n"); } else if (eol() == KateDocumentConfig::eolDos) { return QStringLiteral("\r\n"); } else if (eol() == KateDocumentConfig::eolMac) { return QStringLiteral("\r"); } return QStringLiteral("\n"); } void KateDocumentConfig::setEol(int mode) { if (m_eolSet && m_eol == mode) { return; } configStart(); m_eolSet = true; m_eol = mode; configEnd(); } void KateDocumentConfig::setBom(bool bom) { if (m_bomSet && m_bom == bom) { return; } configStart(); m_bomSet = true; m_bom = bom; configEnd(); } bool KateDocumentConfig::bom() const { if (m_bomSet || isGlobal()) { return m_bom; } return s_global->bom(); } bool KateDocumentConfig::allowEolDetection() const { if (m_allowEolDetectionSet || isGlobal()) { return m_allowEolDetection; } return s_global->allowEolDetection(); } void KateDocumentConfig::setAllowEolDetection(bool on) { if (m_allowEolDetectionSet && m_allowEolDetection == on) { return; } configStart(); m_allowEolDetectionSet = true; m_allowEolDetection = on; configEnd(); } uint KateDocumentConfig::backupFlags() const { if (m_backupFlagsSet || isGlobal()) { return m_backupFlags; } return s_global->backupFlags(); } void KateDocumentConfig::setBackupFlags(uint flags) { if (m_backupFlagsSet && m_backupFlags == flags) { return; } configStart(); m_backupFlagsSet = true; m_backupFlags = flags; configEnd(); } const QString &KateDocumentConfig::backupPrefix() const { if (m_backupPrefixSet || isGlobal()) { return m_backupPrefix; } return s_global->backupPrefix(); } const QString &KateDocumentConfig::backupSuffix() const { if (m_backupSuffixSet || isGlobal()) { return m_backupSuffix; } return s_global->backupSuffix(); } void KateDocumentConfig::setBackupPrefix(const QString &prefix) { if (m_backupPrefixSet && m_backupPrefix == prefix) { return; } configStart(); m_backupPrefixSet = true; m_backupPrefix = prefix; configEnd(); } void KateDocumentConfig::setBackupSuffix(const QString &suffix) { if (m_backupSuffixSet && m_backupSuffix == suffix) { return; } configStart(); m_backupSuffixSet = true; m_backupSuffix = suffix; configEnd(); } uint KateDocumentConfig::swapSyncInterval() const { if (m_swapSyncIntervalSet || isGlobal()) { return m_swapSyncInterval; } return s_global->swapSyncInterval(); } void KateDocumentConfig::setSwapSyncInterval(uint interval) { if (m_swapSyncIntervalSet && m_swapSyncInterval == interval) { return; } configStart(); m_swapSyncIntervalSet = true; m_swapSyncInterval = interval; configEnd(); } uint KateDocumentConfig::swapFileModeRaw() const { if (m_swapFileModeSet || isGlobal()) { return m_swapFileMode; } return s_global->swapFileModeRaw(); } KateDocumentConfig::SwapFileMode KateDocumentConfig::swapFileMode() const { return static_cast(swapFileModeRaw()); } void KateDocumentConfig::setSwapFileMode(uint mode) { if (m_swapFileModeSet && m_swapFileMode == mode) { return; } configStart(); m_swapFileModeSet = true; m_swapFileMode = mode; configEnd(); } const QString &KateDocumentConfig::swapDirectory() const { if (m_swapDirectorySet || isGlobal()) { return m_swapDirectory; } return s_global->swapDirectory(); } void KateDocumentConfig::setSwapDirectory(const QString &directory) { if (m_swapDirectorySet && m_swapDirectory == directory) { return; } configStart(); m_swapDirectorySet = true; m_swapDirectory = directory; configEnd(); } bool KateDocumentConfig::onTheFlySpellCheck() const { if (isGlobal()) { // WARNING: this is slightly hackish, but it's currently the only way to // do it, see also the KTextEdit class QSettings settings(QStringLiteral("KDE"), QStringLiteral("Sonnet")); return settings.value(QStringLiteral("checkerEnabledByDefault"), false).toBool(); //KConfigGroup configGroup(KSharedConfig::openConfig(), "Spelling"); //return configGroup.readEntry("checkerEnabledByDefault", false); } if (m_onTheFlySpellCheckSet) { return m_onTheFlySpellCheck; } return s_global->onTheFlySpellCheck(); } void KateDocumentConfig::setOnTheFlySpellCheck(bool on) { if (m_onTheFlySpellCheckSet && m_onTheFlySpellCheck == on) { return; } configStart(); m_onTheFlySpellCheckSet = true; m_onTheFlySpellCheck = on; configEnd(); } int KateDocumentConfig::lineLengthLimit() const { if (m_lineLengthLimitSet || isGlobal()) { return m_lineLengthLimit; } return s_global->lineLengthLimit(); } void KateDocumentConfig::setLineLengthLimit(int lineLengthLimit) { if (m_lineLengthLimitSet && m_lineLengthLimit == lineLengthLimit) { return; } configStart(); m_lineLengthLimitSet = true; m_lineLengthLimit = lineLengthLimit; configEnd(); } //END //BEGIN KateViewConfig KateViewConfig::KateViewConfig() : m_showWordCount(false), m_dynWordWrapSet(false), m_dynWordWrapIndicatorsSet(false), m_dynWordWrapAlignIndentSet(false), m_lineNumbersSet(false), m_scrollBarMarksSet(false), m_scrollBarPreviewSet(false), m_scrollBarMiniMapSet(false), m_scrollBarMiniMapAllSet(false), m_scrollBarMiniMapWidthSet(false), m_showScrollbarsSet(false), m_iconBarSet(false), m_foldingBarSet(false), m_foldingPreviewSet(false), m_lineModificationSet(false), m_bookmarkSortSet(false), m_autoCenterLinesSet(false), m_searchFlagsSet(false), m_defaultMarkTypeSet(false), m_persistentSelectionSet(false), m_inputModeSet(false), m_viInputModeStealKeysSet(false), m_viRelativeLineNumbersSet(false), m_automaticCompletionInvocationSet(false), m_wordCompletionSet(false), m_keywordCompletionSet(false), m_wordCompletionMinimalWordLengthSet(false), m_smartCopyCutSet(false), m_scrollPastEndSet(false), m_allowMarkMenu(true), m_wordCompletionRemoveTailSet(false), m_foldFirstLineSet (false), m_autoBracketsSet(false), - m_view(0) + m_view(nullptr) { s_global = this; // init with defaults from config or really hardcoded ones KConfigGroup config(KTextEditor::EditorPrivate::config(), "View"); readConfig(config); } KateViewConfig::KateViewConfig(KTextEditor::ViewPrivate *view) : m_searchFlags(PowerModePlainText), m_maxHistorySize(100), m_showWordCount(false), m_dynWordWrapSet(false), m_dynWordWrapIndicatorsSet(false), m_dynWordWrapAlignIndentSet(false), m_lineNumbersSet(false), m_scrollBarMarksSet(false), m_scrollBarPreviewSet(false), m_scrollBarMiniMapSet(false), m_scrollBarMiniMapAllSet(false), m_scrollBarMiniMapWidthSet(false), m_showScrollbarsSet(false), m_iconBarSet(false), m_foldingBarSet(false), m_foldingPreviewSet(false), m_lineModificationSet(false), m_bookmarkSortSet(false), m_autoCenterLinesSet(false), m_searchFlagsSet(false), m_defaultMarkTypeSet(false), m_persistentSelectionSet(false), m_inputModeSet(false), m_viInputModeStealKeysSet(false), m_viRelativeLineNumbersSet(false), m_automaticCompletionInvocationSet(false), m_wordCompletionSet(false), m_keywordCompletionSet(false), m_wordCompletionMinimalWordLengthSet(false), m_smartCopyCutSet(false), m_scrollPastEndSet(false), m_allowMarkMenu(true), m_wordCompletionRemoveTailSet(false), m_foldFirstLineSet (false), m_autoBracketsSet(false), m_view(view) { } KateViewConfig::~KateViewConfig() { } namespace { const char KEY_SEARCH_REPLACE_FLAGS[] = "Search/Replace Flags"; const char KEY_DYN_WORD_WRAP[] = "Dynamic Word Wrap"; const char KEY_DYN_WORD_WRAP_INDICATORS[] = "Dynamic Word Wrap Indicators"; const char KEY_DYN_WORD_WRAP_ALIGN_INDENT[] = "Dynamic Word Wrap Align Indent"; const char KEY_LINE_NUMBERS[] = "Line Numbers"; const char KEY_SCROLL_BAR_MARKS[] = "Scroll Bar Marks"; const char KEY_SCROLL_BAR_PREVIEW[] = "Scroll Bar Preview"; const char KEY_SCROLL_BAR_MINI_MAP[] = "Scroll Bar MiniMap"; const char KEY_SCROLL_BAR_MINI_MAP_ALL[] = "Scroll Bar Mini Map All"; const char KEY_SCROLL_BAR_MINI_MAP_WIDTH[] = "Scroll Bar Mini Map Width"; const char KEY_SHOW_SCROLLBARS[] = "Show Scrollbars"; const char KEY_ICON_BAR[] = "Icon Bar"; const char KEY_FOLDING_BAR[] = "Folding Bar"; const char KEY_FOLDING_PREVIEW[] = "Folding Preview"; const char KEY_LINE_MODIFICATION[] = "Line Modification"; const char KEY_BOOKMARK_SORT[] = "Bookmark Menu Sorting"; const char KEY_AUTO_CENTER_LINES[] = "Auto Center Lines"; const char KEY_MAX_HISTORY_SIZE[] = "Maximum Search History Size"; const char KEY_DEFAULT_MARK_TYPE[] = "Default Mark Type"; const char KEY_ALLOW_MARK_MENU[] = "Allow Mark Menu"; const char KEY_PERSISTENT_SELECTION[] = "Persistent Selection"; const char KEY_INPUT_MODE[] = "Input Mode"; const char KEY_VI_INPUT_MODE_STEAL_KEYS[] = "Vi Input Mode Steal Keys"; const char KEY_VI_RELATIVE_LINE_NUMBERS[] = "Vi Relative Line Numbers"; const char KEY_AUTOMATIC_COMPLETION_INVOCATION[] = "Auto Completion"; const char KEY_WORD_COMPLETION[] = "Word Completion"; const char KEY_KEYWORD_COMPLETION[] = "Keyword Completion"; const char KEY_WORD_COMPLETION_MINIMAL_WORD_LENGTH[] = "Word Completion Minimal Word Length"; const char KEY_WORD_COMPLETION_REMOVE_TAIL[] = "Word Completion Remove Tail"; const char KEY_SMART_COPY_CUT[] = "Smart Copy Cut"; const char KEY_SCROLL_PAST_END[] = "Scroll Past End"; const char KEY_FOLD_FIRST_LINE[] = "Fold First Line"; const char KEY_SHOW_WORD_COUNT[] = "Show Word Count"; const char KEY_AUTO_BRACKETS[] = "Auto Brackets"; } void KateViewConfig::readConfig(const KConfigGroup &config) { configStart(); // default on setDynWordWrap(config.readEntry(KEY_DYN_WORD_WRAP, true)); setDynWordWrapIndicators(config.readEntry(KEY_DYN_WORD_WRAP_INDICATORS, 1)); setDynWordWrapAlignIndent(config.readEntry(KEY_DYN_WORD_WRAP_ALIGN_INDENT, 80)); setLineNumbers(config.readEntry(KEY_LINE_NUMBERS, false)); setScrollBarMarks(config.readEntry(KEY_SCROLL_BAR_MARKS, false)); setScrollBarPreview(config.readEntry(KEY_SCROLL_BAR_PREVIEW, true)); setScrollBarMiniMap(config.readEntry(KEY_SCROLL_BAR_MINI_MAP, true)); setScrollBarMiniMapAll(config.readEntry(KEY_SCROLL_BAR_MINI_MAP_ALL, false)); setScrollBarMiniMapWidth(config.readEntry(KEY_SCROLL_BAR_MINI_MAP_WIDTH, 60)); setShowScrollbars(config.readEntry(KEY_SHOW_SCROLLBARS, static_cast(AlwaysOn))); setIconBar(config.readEntry(KEY_ICON_BAR, false)); setFoldingBar(config.readEntry(KEY_FOLDING_BAR, true)); setFoldingPreview(config.readEntry(KEY_FOLDING_PREVIEW, true)); setLineModification(config.readEntry(KEY_LINE_MODIFICATION, false)); setBookmarkSort(config.readEntry(KEY_BOOKMARK_SORT, 0)); setAutoCenterLines(config.readEntry(KEY_AUTO_CENTER_LINES, 0)); setSearchFlags(config.readEntry(KEY_SEARCH_REPLACE_FLAGS, IncFromCursor | PowerMatchCase | PowerModePlainText)); m_maxHistorySize = config.readEntry(KEY_MAX_HISTORY_SIZE, 100); setDefaultMarkType(config.readEntry(KEY_DEFAULT_MARK_TYPE, int(KTextEditor::MarkInterface::markType01))); setAllowMarkMenu(config.readEntry(KEY_ALLOW_MARK_MENU, true)); setPersistentSelection(config.readEntry(KEY_PERSISTENT_SELECTION, false)); setInputModeRaw(config.readEntry(KEY_INPUT_MODE, 0)); setViInputModeStealKeys(config.readEntry(KEY_VI_INPUT_MODE_STEAL_KEYS, false)); setViRelativeLineNumbers(config.readEntry(KEY_VI_RELATIVE_LINE_NUMBERS, false)); setAutomaticCompletionInvocation(config.readEntry(KEY_AUTOMATIC_COMPLETION_INVOCATION, true)); setWordCompletion(config.readEntry(KEY_WORD_COMPLETION, true)); setKeywordCompletion(config.readEntry(KEY_KEYWORD_COMPLETION, true)); setWordCompletionMinimalWordLength(config.readEntry(KEY_WORD_COMPLETION_MINIMAL_WORD_LENGTH, 3)); setWordCompletionRemoveTail(config.readEntry(KEY_WORD_COMPLETION_REMOVE_TAIL, true)); setSmartCopyCut(config.readEntry(KEY_SMART_COPY_CUT, false)); setScrollPastEnd(config.readEntry(KEY_SCROLL_PAST_END, false)); setFoldFirstLine(config.readEntry(KEY_FOLD_FIRST_LINE, false)); setShowWordCount(config.readEntry(KEY_SHOW_WORD_COUNT, false)); setAutoBrackets(config.readEntry(KEY_AUTO_BRACKETS, false)); configEnd(); } void KateViewConfig::writeConfig(KConfigGroup &config) { config.writeEntry(KEY_DYN_WORD_WRAP, dynWordWrap()); config.writeEntry(KEY_DYN_WORD_WRAP_INDICATORS, dynWordWrapIndicators()); config.writeEntry(KEY_DYN_WORD_WRAP_ALIGN_INDENT, dynWordWrapAlignIndent()); config.writeEntry(KEY_LINE_NUMBERS, lineNumbers()); config.writeEntry(KEY_SCROLL_BAR_MARKS, scrollBarMarks()); config.writeEntry(KEY_SCROLL_BAR_PREVIEW, scrollBarPreview()); config.writeEntry(KEY_SCROLL_BAR_MINI_MAP, scrollBarMiniMap()); config.writeEntry(KEY_SCROLL_BAR_MINI_MAP_ALL, scrollBarMiniMapAll()); config.writeEntry(KEY_SCROLL_BAR_MINI_MAP_WIDTH, scrollBarMiniMapWidth()); config.writeEntry(KEY_SHOW_SCROLLBARS, showScrollbars()); config.writeEntry(KEY_ICON_BAR, iconBar()); config.writeEntry(KEY_FOLDING_BAR, foldingBar()); config.writeEntry(KEY_FOLDING_PREVIEW, foldingPreview()); config.writeEntry(KEY_LINE_MODIFICATION, lineModification()); config.writeEntry(KEY_BOOKMARK_SORT, bookmarkSort()); config.writeEntry(KEY_AUTO_CENTER_LINES, autoCenterLines()); config.writeEntry(KEY_SEARCH_REPLACE_FLAGS, int(searchFlags())); config.writeEntry(KEY_MAX_HISTORY_SIZE, m_maxHistorySize); config.writeEntry(KEY_DEFAULT_MARK_TYPE, defaultMarkType()); config.writeEntry(KEY_ALLOW_MARK_MENU, allowMarkMenu()); config.writeEntry(KEY_PERSISTENT_SELECTION, persistentSelection()); config.writeEntry(KEY_AUTOMATIC_COMPLETION_INVOCATION, automaticCompletionInvocation()); config.writeEntry(KEY_WORD_COMPLETION, wordCompletion()); config.writeEntry(KEY_KEYWORD_COMPLETION, keywordCompletion()); config.writeEntry(KEY_WORD_COMPLETION_MINIMAL_WORD_LENGTH, wordCompletionMinimalWordLength()); config.writeEntry(KEY_WORD_COMPLETION_REMOVE_TAIL, wordCompletionRemoveTail()); config.writeEntry(KEY_SMART_COPY_CUT, smartCopyCut()); config.writeEntry(KEY_SCROLL_PAST_END, scrollPastEnd()); config.writeEntry(KEY_FOLD_FIRST_LINE, foldFirstLine()); config.writeEntry(KEY_INPUT_MODE, static_cast(inputMode())); config.writeEntry(KEY_VI_INPUT_MODE_STEAL_KEYS, viInputModeStealKeys()); config.writeEntry(KEY_VI_RELATIVE_LINE_NUMBERS, viRelativeLineNumbers()); config.writeEntry(KEY_SHOW_WORD_COUNT, showWordCount()); config.writeEntry(KEY_AUTO_BRACKETS, autoBrackets()); } void KateViewConfig::updateConfig() { if (m_view) { m_view->updateConfig(); return; } if (isGlobal()) { foreach (KTextEditor::ViewPrivate* view, KTextEditor::EditorPrivate::self()->views()) { view->updateConfig(); } // write config KConfigGroup cg(KTextEditor::EditorPrivate::config(), "View"); writeConfig(cg); KTextEditor::EditorPrivate::config()->sync(); } } bool KateViewConfig::dynWordWrap() const { if (m_dynWordWrapSet || isGlobal()) { return m_dynWordWrap; } return s_global->dynWordWrap(); } void KateViewConfig::setDynWordWrap(bool wrap) { if (m_dynWordWrapSet && m_dynWordWrap == wrap) { return; } configStart(); m_dynWordWrapSet = true; m_dynWordWrap = wrap; configEnd(); } int KateViewConfig::dynWordWrapIndicators() const { if (m_dynWordWrapIndicatorsSet || isGlobal()) { return m_dynWordWrapIndicators; } return s_global->dynWordWrapIndicators(); } void KateViewConfig::setDynWordWrapIndicators(int mode) { if (m_dynWordWrapIndicatorsSet && m_dynWordWrapIndicators == mode) { return; } configStart(); m_dynWordWrapIndicatorsSet = true; m_dynWordWrapIndicators = qBound(0, mode, 80); configEnd(); } int KateViewConfig::dynWordWrapAlignIndent() const { if (m_dynWordWrapAlignIndentSet || isGlobal()) { return m_dynWordWrapAlignIndent; } return s_global->dynWordWrapAlignIndent(); } void KateViewConfig::setDynWordWrapAlignIndent(int indent) { if (m_dynWordWrapAlignIndentSet && m_dynWordWrapAlignIndent == indent) { return; } configStart(); m_dynWordWrapAlignIndentSet = true; m_dynWordWrapAlignIndent = indent; configEnd(); } bool KateViewConfig::lineNumbers() const { if (m_lineNumbersSet || isGlobal()) { return m_lineNumbers; } return s_global->lineNumbers(); } void KateViewConfig::setLineNumbers(bool on) { if (m_lineNumbersSet && m_lineNumbers == on) { return; } configStart(); m_lineNumbersSet = true; m_lineNumbers = on; configEnd(); } bool KateViewConfig::scrollBarMarks() const { if (m_scrollBarMarksSet || isGlobal()) { return m_scrollBarMarks; } return s_global->scrollBarMarks(); } void KateViewConfig::setScrollBarMarks(bool on) { if (m_scrollBarMarksSet && m_scrollBarMarks == on) { return; } configStart(); m_scrollBarMarksSet = true; m_scrollBarMarks = on; configEnd(); } bool KateViewConfig::scrollBarPreview() const { if (m_scrollBarPreviewSet || isGlobal()) { return m_scrollBarPreview; } return s_global->scrollBarPreview(); } void KateViewConfig::setScrollBarPreview(bool on) { if (m_scrollBarPreviewSet && m_scrollBarPreview == on) { return; } configStart(); m_scrollBarPreviewSet = true; m_scrollBarPreview = on; configEnd(); } bool KateViewConfig::scrollBarMiniMap() const { if (m_scrollBarMiniMapSet || isGlobal()) { return m_scrollBarMiniMap; } return s_global->scrollBarMiniMap(); } void KateViewConfig::setScrollBarMiniMap(bool on) { if (m_scrollBarMiniMapSet && m_scrollBarMiniMap == on) { return; } configStart(); m_scrollBarMiniMapSet = true; m_scrollBarMiniMap = on; configEnd(); } bool KateViewConfig::scrollBarMiniMapAll() const { if (m_scrollBarMiniMapAllSet || isGlobal()) { return m_scrollBarMiniMapAll; } return s_global->scrollBarMiniMapAll(); } void KateViewConfig::setScrollBarMiniMapAll(bool on) { if (m_scrollBarMiniMapAllSet && m_scrollBarMiniMapAll == on) { return; } configStart(); m_scrollBarMiniMapAllSet = true; m_scrollBarMiniMapAll = on; configEnd(); } int KateViewConfig::scrollBarMiniMapWidth() const { if (m_scrollBarMiniMapWidthSet || isGlobal()) { return m_scrollBarMiniMapWidth; } return s_global->scrollBarMiniMapWidth(); } void KateViewConfig::setScrollBarMiniMapWidth(int width) { if (m_scrollBarMiniMapWidthSet && m_scrollBarMiniMapWidth == width) { return; } configStart(); m_scrollBarMiniMapWidthSet = true; m_scrollBarMiniMapWidth = width; configEnd(); } int KateViewConfig::showScrollbars() const { if (m_showScrollbarsSet || isGlobal()) { return m_showScrollbars; } return s_global->showScrollbars(); } void KateViewConfig::setShowScrollbars(int mode) { if (m_showScrollbarsSet && m_showScrollbars == mode) { return; } configStart(); m_showScrollbarsSet = true; m_showScrollbars = qBound(0, mode, 80); configEnd(); } bool KateViewConfig::autoBrackets() const { if (m_autoBracketsSet || isGlobal()) { return m_autoBrackets; } return s_global->autoBrackets(); } void KateViewConfig::setAutoBrackets(bool on) { if (m_autoBracketsSet && m_autoBrackets == on) { return; } configStart(); m_autoBracketsSet = true; m_autoBrackets = on; configEnd(); } bool KateViewConfig::iconBar() const { if (m_iconBarSet || isGlobal()) { return m_iconBar; } return s_global->iconBar(); } void KateViewConfig::setIconBar(bool on) { if (m_iconBarSet && m_iconBar == on) { return; } configStart(); m_iconBarSet = true; m_iconBar = on; configEnd(); } bool KateViewConfig::foldingBar() const { if (m_foldingBarSet || isGlobal()) { return m_foldingBar; } return s_global->foldingBar(); } void KateViewConfig::setFoldingBar(bool on) { if (m_foldingBarSet && m_foldingBar == on) { return; } configStart(); m_foldingBarSet = true; m_foldingBar = on; configEnd(); } bool KateViewConfig::foldingPreview() const { if (m_foldingPreviewSet || isGlobal()) { return m_foldingPreview; } return s_global->foldingPreview(); } void KateViewConfig::setFoldingPreview(bool on) { if (m_foldingPreviewSet && m_foldingPreview == on) { return; } configStart(); m_foldingPreviewSet = true; m_foldingPreview = on; configEnd(); } bool KateViewConfig::lineModification() const { if (m_lineModificationSet || isGlobal()) { return m_lineModification; } return s_global->lineModification(); } void KateViewConfig::setLineModification(bool on) { if (m_lineModificationSet && m_lineModification == on) { return; } configStart(); m_lineModificationSet = true; m_lineModification = on; configEnd(); } int KateViewConfig::bookmarkSort() const { if (m_bookmarkSortSet || isGlobal()) { return m_bookmarkSort; } return s_global->bookmarkSort(); } void KateViewConfig::setBookmarkSort(int mode) { if (m_bookmarkSortSet && m_bookmarkSort == mode) { return; } configStart(); m_bookmarkSortSet = true; m_bookmarkSort = mode; configEnd(); } int KateViewConfig::autoCenterLines() const { if (m_autoCenterLinesSet || isGlobal()) { return m_autoCenterLines; } return s_global->autoCenterLines(); } void KateViewConfig::setAutoCenterLines(int lines) { if (lines < 0) { return; } if (m_autoCenterLinesSet && m_autoCenterLines == lines) { return; } configStart(); m_autoCenterLinesSet = true; m_autoCenterLines = lines; configEnd(); } long KateViewConfig::searchFlags() const { if (m_searchFlagsSet || isGlobal()) { return m_searchFlags; } return s_global->searchFlags(); } void KateViewConfig::setSearchFlags(long flags) { if (m_searchFlagsSet && m_searchFlags == flags) { return; } configStart(); m_searchFlagsSet = true; m_searchFlags = flags; configEnd(); } int KateViewConfig::maxHistorySize() const { return m_maxHistorySize; } uint KateViewConfig::defaultMarkType() const { if (m_defaultMarkTypeSet || isGlobal()) { return m_defaultMarkType; } return s_global->defaultMarkType(); } void KateViewConfig::setDefaultMarkType(uint type) { if (m_defaultMarkTypeSet && m_defaultMarkType == type) { return; } configStart(); m_defaultMarkTypeSet = true; m_defaultMarkType = type; configEnd(); } bool KateViewConfig::allowMarkMenu() const { return m_allowMarkMenu; } void KateViewConfig::setAllowMarkMenu(bool allow) { m_allowMarkMenu = allow; } bool KateViewConfig::persistentSelection() const { if (m_persistentSelectionSet || isGlobal()) { return m_persistentSelection; } return s_global->persistentSelection(); } void KateViewConfig::setPersistentSelection(bool on) { if (m_persistentSelectionSet && m_persistentSelection == on) { return; } configStart(); m_persistentSelectionSet = true; m_persistentSelection = on; configEnd(); } KTextEditor::View::InputMode KateViewConfig::inputMode() const { if (m_inputModeSet || isGlobal()) { return m_inputMode; } return s_global->inputMode(); } void KateViewConfig::setInputMode(KTextEditor::View::InputMode mode) { if (m_inputModeSet && m_inputMode == mode) { return; } configStart(); m_inputModeSet = true; m_inputMode = mode; configEnd(); } void KateViewConfig::setInputModeRaw(int rawmode) { setInputMode(static_cast(rawmode)); } bool KateViewConfig::viInputModeStealKeys() const { if (m_viInputModeStealKeysSet || isGlobal()) { return m_viInputModeStealKeys; } return s_global->viInputModeStealKeys(); } void KateViewConfig::setViInputModeStealKeys(bool on) { if (m_viInputModeStealKeysSet && m_viInputModeStealKeys == on) { return; } configStart(); m_viInputModeStealKeysSet = true; m_viInputModeStealKeys = on; configEnd(); } bool KateViewConfig::viRelativeLineNumbers() const { if (m_viRelativeLineNumbersSet || isGlobal()) { return m_viRelativeLineNumbers; } return s_global->viRelativeLineNumbers(); } void KateViewConfig::setViRelativeLineNumbers(bool on) { if (m_viRelativeLineNumbersSet && m_viRelativeLineNumbers == on) { return; } configStart(); m_viRelativeLineNumbersSet = true; m_viRelativeLineNumbers = on; configEnd(); } bool KateViewConfig::automaticCompletionInvocation() const { if (m_automaticCompletionInvocationSet || isGlobal()) { return m_automaticCompletionInvocation; } return s_global->automaticCompletionInvocation(); } void KateViewConfig::setAutomaticCompletionInvocation(bool on) { if (m_automaticCompletionInvocationSet && m_automaticCompletionInvocation == on) { return; } configStart(); m_automaticCompletionInvocationSet = true; m_automaticCompletionInvocation = on; configEnd(); } bool KateViewConfig::wordCompletion() const { if (m_wordCompletionSet || isGlobal()) { return m_wordCompletion; } return s_global->wordCompletion(); } void KateViewConfig::setWordCompletion(bool on) { if (m_wordCompletionSet && m_wordCompletion == on) { return; } configStart(); m_wordCompletionSet = true; m_wordCompletion = on; configEnd(); } bool KateViewConfig::keywordCompletion() const { if (m_keywordCompletionSet || isGlobal()) return m_keywordCompletion; return s_global->keywordCompletion(); } void KateViewConfig::setKeywordCompletion(bool on) { if (m_keywordCompletionSet && m_keywordCompletion == on) return; configStart(); m_keywordCompletionSet = true; m_keywordCompletion = on; configEnd(); } int KateViewConfig::wordCompletionMinimalWordLength() const { if (m_wordCompletionMinimalWordLengthSet || isGlobal()) { return m_wordCompletionMinimalWordLength; } return s_global->wordCompletionMinimalWordLength(); } void KateViewConfig::setWordCompletionMinimalWordLength(int length) { if (m_wordCompletionMinimalWordLengthSet && m_wordCompletionMinimalWordLength == length) { return; } configStart(); m_wordCompletionMinimalWordLengthSet = true; m_wordCompletionMinimalWordLength = length; configEnd(); } bool KateViewConfig::wordCompletionRemoveTail() const { if (m_wordCompletionRemoveTailSet || isGlobal()) { return m_wordCompletionRemoveTail; } return s_global->wordCompletionRemoveTail(); } void KateViewConfig::setWordCompletionRemoveTail(bool on) { if (m_wordCompletionRemoveTailSet && m_wordCompletionRemoveTail == on) { return; } configStart(); m_wordCompletionRemoveTailSet = true; m_wordCompletionRemoveTail = on; configEnd(); } bool KateViewConfig::smartCopyCut() const { if (m_smartCopyCutSet || isGlobal()) { return m_smartCopyCut; } return s_global->smartCopyCut(); } void KateViewConfig::setSmartCopyCut(bool on) { if (m_smartCopyCutSet && m_smartCopyCut == on) { return; } configStart(); m_smartCopyCutSet = true; m_smartCopyCut = on; configEnd(); } bool KateViewConfig::scrollPastEnd() const { if (m_scrollPastEndSet || isGlobal()) { return m_scrollPastEnd; } return s_global->scrollPastEnd(); } void KateViewConfig::setScrollPastEnd(bool on) { if (m_scrollPastEndSet && m_scrollPastEnd == on) { return; } configStart(); m_scrollPastEndSet = true; m_scrollPastEnd = on; configEnd(); } bool KateViewConfig::foldFirstLine() const { if (m_foldFirstLineSet || isGlobal()) { return m_foldFirstLine; } return s_global->foldFirstLine(); } void KateViewConfig::setFoldFirstLine(bool on) { if (m_foldFirstLineSet && m_foldFirstLine == on) { return; } configStart(); m_foldFirstLineSet = true; m_foldFirstLine = on; configEnd(); } bool KateViewConfig::showWordCount() { return m_showWordCount; } void KateViewConfig::setShowWordCount(bool on) { if (m_showWordCount == on) { return; } configStart(); m_showWordCount = on; configEnd(); } //END //BEGIN KateRendererConfig KateRendererConfig::KateRendererConfig() : m_fontMetrics(QFont()), m_lineMarkerColor(KTextEditor::MarkInterface::reservedMarkersCount()), m_wordWrapMarker(false), m_showIndentationLines(false), m_showWholeBracketExpression(false), m_animateBracketMatching(false), m_schemaSet(false), m_fontSet(false), m_wordWrapMarkerSet(false), m_showIndentationLinesSet(false), m_showWholeBracketExpressionSet(false), m_backgroundColorSet(false), m_selectionColorSet(false), m_highlightedLineColorSet(false), m_highlightedBracketColorSet(false), m_wordWrapMarkerColorSet(false), m_tabMarkerColorSet(false), m_indentationLineColorSet(false), m_iconBarColorSet(false), m_foldingColorSet(false), m_lineNumberColorSet(false), m_currentLineNumberColorSet(false), m_separatorColorSet(false), m_spellingMistakeLineColorSet(false), m_templateColorsSet(false), m_modifiedLineColorSet(false), m_savedLineColorSet(false), m_searchHighlightColorSet(false), m_replaceHighlightColorSet(false), m_lineMarkerColorSet(m_lineMarkerColor.size()), - m_renderer(0) + m_renderer(nullptr) { // init bitarray m_lineMarkerColorSet.fill(true); s_global = this; // init with defaults from config or really hardcoded ones KConfigGroup config(KTextEditor::EditorPrivate::config(), "Renderer"); readConfig(config); } KateRendererConfig::KateRendererConfig(KateRenderer *renderer) : m_fontMetrics(QFont()), m_lineMarkerColor(KTextEditor::MarkInterface::reservedMarkersCount()), m_schemaSet(false), m_fontSet(false), m_wordWrapMarkerSet(false), m_showIndentationLinesSet(false), m_showWholeBracketExpressionSet(false), m_backgroundColorSet(false), m_selectionColorSet(false), m_highlightedLineColorSet(false), m_highlightedBracketColorSet(false), m_wordWrapMarkerColorSet(false), m_tabMarkerColorSet(false), m_indentationLineColorSet(false), m_iconBarColorSet(false), m_foldingColorSet(false), m_lineNumberColorSet(false), m_currentLineNumberColorSet(false), m_separatorColorSet(false), m_spellingMistakeLineColorSet(false), m_templateColorsSet(false), m_modifiedLineColorSet(false), m_savedLineColorSet(false), m_searchHighlightColorSet(false), m_replaceHighlightColorSet(false), m_lineMarkerColorSet(m_lineMarkerColor.size()), m_renderer(renderer) { // init bitarray m_lineMarkerColorSet.fill(false); } KateRendererConfig::~KateRendererConfig() { } namespace { const char KEY_SCHEMA[] = "Schema"; const char KEY_WORD_WRAP_MARKER[] = "Word Wrap Marker"; const char KEY_SHOW_INDENTATION_LINES[] = "Show Indentation Lines"; const char KEY_SHOW_WHOLE_BRACKET_EXPRESSION[] = "Show Whole Bracket Expression"; const char KEY_ANIMATE_BRACKET_MATCHING[] = "Animate Bracket Matching"; } void KateRendererConfig::readConfig(const KConfigGroup &config) { configStart(); // "Normal" Schema MUST BE THERE, see global kateschemarc setSchema(config.readEntry(KEY_SCHEMA, "Normal")); setWordWrapMarker(config.readEntry(KEY_WORD_WRAP_MARKER, false)); setShowIndentationLines(config.readEntry(KEY_SHOW_INDENTATION_LINES, false)); setShowWholeBracketExpression(config.readEntry(KEY_SHOW_WHOLE_BRACKET_EXPRESSION, false)); setAnimateBracketMatching(config.readEntry(KEY_ANIMATE_BRACKET_MATCHING, false)); configEnd(); } void KateRendererConfig::writeConfig(KConfigGroup &config) { config.writeEntry(KEY_SCHEMA, schema()); config.writeEntry(KEY_WORD_WRAP_MARKER, wordWrapMarker()); config.writeEntry(KEY_SHOW_INDENTATION_LINES, showIndentationLines()); config.writeEntry(KEY_SHOW_WHOLE_BRACKET_EXPRESSION, showWholeBracketExpression()); config.writeEntry(KEY_ANIMATE_BRACKET_MATCHING, animateBracketMatching()); } void KateRendererConfig::updateConfig() { if (m_renderer) { m_renderer->updateConfig(); return; } if (isGlobal()) { for (int z = 0; z < KTextEditor::EditorPrivate::self()->views().size(); ++z) { (KTextEditor::EditorPrivate::self()->views())[z]->renderer()->updateConfig(); } // write config KConfigGroup cg(KTextEditor::EditorPrivate::config(), "Renderer"); writeConfig(cg); KTextEditor::EditorPrivate::config()->sync(); } } const QString &KateRendererConfig::schema() const { if (m_schemaSet || isGlobal()) { return m_schema; } return s_global->schema(); } void KateRendererConfig::setSchema(const QString &schema) { if (m_schemaSet && m_schema == schema) { return; } configStart(); m_schemaSet = true; m_schema = schema; setSchemaInternal(schema); configEnd(); } void KateRendererConfig::reloadSchema() { if (isGlobal()) { setSchemaInternal(m_schema); foreach (KTextEditor::ViewPrivate *view, KTextEditor::EditorPrivate::self()->views()) { view->renderer()->config()->reloadSchema(); } } else if (m_renderer && m_schemaSet) { setSchemaInternal(m_schema); } // trigger renderer/view update if (m_renderer) { m_renderer->updateConfig(); } } void KateRendererConfig::setSchemaInternal(const QString &schema) { m_schemaSet = true; m_schema = schema; KConfigGroup config = KTextEditor::EditorPrivate::self()->schemaManager()->schema(schema); // use global color instance, creation is expensive! const KateDefaultColors &colors(KTextEditor::EditorPrivate::self()->defaultColors()); m_backgroundColor = config.readEntry("Color Background", colors.color(Kate::Background)); m_backgroundColorSet = true; m_selectionColor = config.readEntry("Color Selection", colors.color(Kate::SelectionBackground)); m_selectionColorSet = true; m_highlightedLineColor = config.readEntry("Color Highlighted Line", colors.color(Kate::HighlightedLineBackground)); m_highlightedLineColorSet = true; m_highlightedBracketColor = config.readEntry("Color Highlighted Bracket", colors.color(Kate::HighlightedBracket)); m_highlightedBracketColorSet = true; m_wordWrapMarkerColor = config.readEntry("Color Word Wrap Marker", colors.color(Kate::WordWrapMarker)); m_wordWrapMarkerColorSet = true; m_tabMarkerColor = config.readEntry("Color Tab Marker", colors.color(Kate::TabMarker)); m_tabMarkerColorSet = true; m_indentationLineColor = config.readEntry("Color Indentation Line", colors.color(Kate::IndentationLine)); m_indentationLineColorSet = true; m_iconBarColor = config.readEntry("Color Icon Bar", colors.color(Kate::IconBar)); m_iconBarColorSet = true; m_foldingColor = config.readEntry("Color Code Folding", colors.color(Kate::CodeFolding)); m_foldingColorSet = true; m_lineNumberColor = config.readEntry("Color Line Number", colors.color(Kate::LineNumber)); m_lineNumberColorSet = true; m_currentLineNumberColor = config.readEntry("Color Current Line Number", colors.color(Kate::CurrentLineNumber)); m_currentLineNumberColorSet = true; m_separatorColor = config.readEntry("Color Separator", colors.color(Kate::Separator)); m_separatorColorSet = true; m_spellingMistakeLineColor = config.readEntry("Color Spelling Mistake Line", colors.color(Kate::SpellingMistakeLine)); m_spellingMistakeLineColorSet = true; m_modifiedLineColor = config.readEntry("Color Modified Lines", colors.color(Kate::ModifiedLine)); m_modifiedLineColorSet = true; m_savedLineColor = config.readEntry("Color Saved Lines", colors.color(Kate::SavedLine)); m_savedLineColorSet = true; m_searchHighlightColor = config.readEntry("Color Search Highlight", colors.color(Kate::SearchHighlight)); m_searchHighlightColorSet = true; m_replaceHighlightColor = config.readEntry("Color Replace Highlight", colors.color(Kate::ReplaceHighlight)); m_replaceHighlightColorSet = true; for (int i = Kate::FIRST_MARK; i <= Kate::LAST_MARK; i++) { QColor col = config.readEntry(QStringLiteral("Color MarkType %1").arg(i + 1), colors.mark(i)); m_lineMarkerColorSet[i] = true; m_lineMarkerColor[i] = col; } QFont f(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_font = config.readEntry("Font", f); m_fontMetrics = QFontMetricsF(m_font); m_fontSet = true; m_templateBackgroundColor = config.readEntry(QStringLiteral("Color Template Background"), colors.color(Kate::TemplateBackground)); m_templateFocusedEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Focused Editable Placeholder"), colors.color(Kate::TemplateFocusedEditablePlaceholder)); m_templateEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Editable Placeholder"), colors.color(Kate::TemplateEditablePlaceholder)); m_templateNotEditablePlaceholderColor = config.readEntry(QStringLiteral("Color Template Not Editable Placeholder"), colors.color(Kate::TemplateNotEditablePlaceholder)); m_templateColorsSet = true; } const QFont &KateRendererConfig::font() const { if (m_fontSet || isGlobal()) { return m_font; } return s_global->font(); } const QFontMetricsF &KateRendererConfig::fontMetrics() const { if (m_fontSet || isGlobal()) { return m_fontMetrics; } return s_global->fontMetrics(); } void KateRendererConfig::setFont(const QFont &font) { if (m_fontSet && m_font == font) { return; } configStart(); m_fontSet = true; m_font = font; m_fontMetrics = QFontMetricsF(m_font); configEnd(); } bool KateRendererConfig::wordWrapMarker() const { if (m_wordWrapMarkerSet || isGlobal()) { return m_wordWrapMarker; } return s_global->wordWrapMarker(); } void KateRendererConfig::setWordWrapMarker(bool on) { if (m_wordWrapMarkerSet && m_wordWrapMarker == on) { return; } configStart(); m_wordWrapMarkerSet = true; m_wordWrapMarker = on; configEnd(); } const QColor &KateRendererConfig::backgroundColor() const { if (m_backgroundColorSet || isGlobal()) { return m_backgroundColor; } return s_global->backgroundColor(); } void KateRendererConfig::setBackgroundColor(const QColor &col) { if (m_backgroundColorSet && m_backgroundColor == col) { return; } configStart(); m_backgroundColorSet = true; m_backgroundColor = col; configEnd(); } const QColor &KateRendererConfig::selectionColor() const { if (m_selectionColorSet || isGlobal()) { return m_selectionColor; } return s_global->selectionColor(); } void KateRendererConfig::setSelectionColor(const QColor &col) { if (m_selectionColorSet && m_selectionColor == col) { return; } configStart(); m_selectionColorSet = true; m_selectionColor = col; configEnd(); } const QColor &KateRendererConfig::highlightedLineColor() const { if (m_highlightedLineColorSet || isGlobal()) { return m_highlightedLineColor; } return s_global->highlightedLineColor(); } void KateRendererConfig::setHighlightedLineColor(const QColor &col) { if (m_highlightedLineColorSet && m_highlightedLineColor == col) { return; } configStart(); m_highlightedLineColorSet = true; m_highlightedLineColor = col; configEnd(); } const QColor &KateRendererConfig::lineMarkerColor(KTextEditor::MarkInterface::MarkTypes type) const { int index = 0; if (type > 0) { while ((type >> index++) ^ 1) {} } index -= 1; if (index < 0 || index >= KTextEditor::MarkInterface::reservedMarkersCount()) { static QColor dummy; return dummy; } if (m_lineMarkerColorSet[index] || isGlobal()) { return m_lineMarkerColor[index]; } return s_global->lineMarkerColor(type); } void KateRendererConfig::setLineMarkerColor(const QColor &col, KTextEditor::MarkInterface::MarkTypes type) { int index = static_cast(log(static_cast(type)) / log(2.0)); Q_ASSERT(index >= 0 && index < KTextEditor::MarkInterface::reservedMarkersCount()); if (m_lineMarkerColorSet[index] && m_lineMarkerColor[index] == col) { return; } configStart(); m_lineMarkerColorSet[index] = true; m_lineMarkerColor[index] = col; configEnd(); } const QColor &KateRendererConfig::highlightedBracketColor() const { if (m_highlightedBracketColorSet || isGlobal()) { return m_highlightedBracketColor; } return s_global->highlightedBracketColor(); } void KateRendererConfig::setHighlightedBracketColor(const QColor &col) { if (m_highlightedBracketColorSet && m_highlightedBracketColor == col) { return; } configStart(); m_highlightedBracketColorSet = true; m_highlightedBracketColor = col; configEnd(); } const QColor &KateRendererConfig::wordWrapMarkerColor() const { if (m_wordWrapMarkerColorSet || isGlobal()) { return m_wordWrapMarkerColor; } return s_global->wordWrapMarkerColor(); } void KateRendererConfig::setWordWrapMarkerColor(const QColor &col) { if (m_wordWrapMarkerColorSet && m_wordWrapMarkerColor == col) { return; } configStart(); m_wordWrapMarkerColorSet = true; m_wordWrapMarkerColor = col; configEnd(); } const QColor &KateRendererConfig::tabMarkerColor() const { if (m_tabMarkerColorSet || isGlobal()) { return m_tabMarkerColor; } return s_global->tabMarkerColor(); } void KateRendererConfig::setTabMarkerColor(const QColor &col) { if (m_tabMarkerColorSet && m_tabMarkerColor == col) { return; } configStart(); m_tabMarkerColorSet = true; m_tabMarkerColor = col; configEnd(); } const QColor &KateRendererConfig::indentationLineColor() const { if (m_indentationLineColorSet || isGlobal()) { return m_indentationLineColor; } return s_global->indentationLineColor(); } void KateRendererConfig::setIndentationLineColor(const QColor &col) { if (m_indentationLineColorSet && m_indentationLineColor == col) { return; } configStart(); m_indentationLineColorSet = true; m_indentationLineColor = col; configEnd(); } const QColor &KateRendererConfig::iconBarColor() const { if (m_iconBarColorSet || isGlobal()) { return m_iconBarColor; } return s_global->iconBarColor(); } void KateRendererConfig::setIconBarColor(const QColor &col) { if (m_iconBarColorSet && m_iconBarColor == col) { return; } configStart(); m_iconBarColorSet = true; m_iconBarColor = col; configEnd(); } const QColor &KateRendererConfig::foldingColor() const { if (m_foldingColorSet || isGlobal()) { return m_foldingColor; } return s_global->foldingColor(); } void KateRendererConfig::setFoldingColor(const QColor &col) { if (m_foldingColorSet && m_foldingColor == col) { return; } configStart(); m_foldingColorSet = true; m_foldingColor = col; configEnd(); } const QColor &KateRendererConfig::templateBackgroundColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateBackgroundColor; } return s_global->templateBackgroundColor(); } const QColor &KateRendererConfig::templateEditablePlaceholderColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateEditablePlaceholderColor; } return s_global->templateEditablePlaceholderColor(); } const QColor &KateRendererConfig::templateFocusedEditablePlaceholderColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateFocusedEditablePlaceholderColor; } return s_global->templateFocusedEditablePlaceholderColor(); } const QColor &KateRendererConfig::templateNotEditablePlaceholderColor() const { if (m_templateColorsSet || isGlobal()) { return m_templateNotEditablePlaceholderColor; } return s_global->templateNotEditablePlaceholderColor(); } const QColor &KateRendererConfig::lineNumberColor() const { if (m_lineNumberColorSet || isGlobal()) { return m_lineNumberColor; } return s_global->lineNumberColor(); } void KateRendererConfig::setLineNumberColor(const QColor &col) { if (m_lineNumberColorSet && m_lineNumberColor == col) { return; } configStart(); m_lineNumberColorSet = true; m_lineNumberColor = col; configEnd(); } const QColor &KateRendererConfig::currentLineNumberColor() const { if (m_currentLineNumberColorSet || isGlobal()) { return m_currentLineNumberColor; } return s_global->currentLineNumberColor(); } void KateRendererConfig::setCurrentLineNumberColor(const QColor &col) { if (m_currentLineNumberColorSet && m_currentLineNumberColor == col) { return; } configStart(); m_currentLineNumberColorSet = true; m_currentLineNumberColor = col; configEnd(); } const QColor &KateRendererConfig::separatorColor() const { if (m_separatorColorSet || isGlobal()) { return m_separatorColor; } return s_global->separatorColor(); } void KateRendererConfig::setSeparatorColor(const QColor &col) { if (m_separatorColorSet && m_separatorColor == col) { return; } configStart(); m_separatorColorSet = true; m_separatorColor = col; configEnd(); } const QColor &KateRendererConfig::spellingMistakeLineColor() const { if (m_spellingMistakeLineColorSet || isGlobal()) { return m_spellingMistakeLineColor; } return s_global->spellingMistakeLineColor(); } void KateRendererConfig::setSpellingMistakeLineColor(const QColor &col) { if (m_spellingMistakeLineColorSet && m_spellingMistakeLineColor == col) { return; } configStart(); m_spellingMistakeLineColorSet = true; m_spellingMistakeLineColor = col; configEnd(); } const QColor &KateRendererConfig::modifiedLineColor() const { if (m_modifiedLineColorSet || isGlobal()) { return m_modifiedLineColor; } return s_global->modifiedLineColor(); } void KateRendererConfig::setModifiedLineColor(const QColor &col) { if (m_modifiedLineColorSet && m_modifiedLineColor == col) { return; } configStart(); m_modifiedLineColorSet = true; m_modifiedLineColor = col; configEnd(); } const QColor &KateRendererConfig::savedLineColor() const { if (m_savedLineColorSet || isGlobal()) { return m_savedLineColor; } return s_global->savedLineColor(); } void KateRendererConfig::setSavedLineColor(const QColor &col) { if (m_savedLineColorSet && m_savedLineColor == col) { return; } configStart(); m_savedLineColorSet = true; m_savedLineColor = col; configEnd(); } const QColor &KateRendererConfig::searchHighlightColor() const { if (m_searchHighlightColorSet || isGlobal()) { return m_searchHighlightColor; } return s_global->searchHighlightColor(); } void KateRendererConfig::setSearchHighlightColor(const QColor &col) { if (m_searchHighlightColorSet && m_searchHighlightColor == col) { return; } configStart(); m_searchHighlightColorSet = true; m_searchHighlightColor = col; configEnd(); } const QColor &KateRendererConfig::replaceHighlightColor() const { if (m_replaceHighlightColorSet || isGlobal()) { return m_replaceHighlightColor; } return s_global->replaceHighlightColor(); } void KateRendererConfig::setReplaceHighlightColor(const QColor &col) { if (m_replaceHighlightColorSet && m_replaceHighlightColor == col) { return; } configStart(); m_replaceHighlightColorSet = true; m_replaceHighlightColor = col; configEnd(); } bool KateRendererConfig::showIndentationLines() const { if (m_showIndentationLinesSet || isGlobal()) { return m_showIndentationLines; } return s_global->showIndentationLines(); } void KateRendererConfig::setShowIndentationLines(bool on) { if (m_showIndentationLinesSet && m_showIndentationLines == on) { return; } configStart(); m_showIndentationLinesSet = true; m_showIndentationLines = on; configEnd(); } bool KateRendererConfig::showWholeBracketExpression() const { if (m_showWholeBracketExpressionSet || isGlobal()) { return m_showWholeBracketExpression; } return s_global->showWholeBracketExpression(); } void KateRendererConfig::setShowWholeBracketExpression(bool on) { if (m_showWholeBracketExpressionSet && m_showWholeBracketExpression == on) { return; } configStart(); m_showWholeBracketExpressionSet = true; m_showWholeBracketExpression = on; configEnd(); } bool KateRendererConfig::animateBracketMatching() const { return s_global->m_animateBracketMatching; } void KateRendererConfig::setAnimateBracketMatching(bool on) { if (!isGlobal()) { s_global->setAnimateBracketMatching(on); } else if (on != m_animateBracketMatching) { configStart(); m_animateBracketMatching = on; configEnd(); } } //END diff --git a/src/utils/kateglobal.cpp b/src/utils/kateglobal.cpp index 38ddd8a1..1cea28da 100644 --- a/src/utils/kateglobal.cpp +++ b/src/utils/kateglobal.cpp @@ -1,521 +1,521 @@ /* This file is part of the KDE libraries and the Kate part. * * 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. */ #include "kateglobal.h" #include "config.h" #include #include "katedocument.h" #include "kateview.h" #include "katerenderer.h" #include "katecmd.h" #include "katecmds.h" #include "katesedcmd.h" #include "katehighlightingcmds.h" #include "katemodemanager.h" #include "kateschema.h" #include "kateschemaconfig.h" #include "kateconfig.h" #include "katescriptmanager.h" #include "katebuffer.h" #include "katewordcompletion.h" #include "katekeywordcompletion.h" #include "spellcheck/spellcheck.h" #include "katepartdebug.h" #include "katedefaultcolors.h" #include "katenormalinputmodefactory.h" #include "kateviinputmodefactory.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef LIBGIT2_FOUND #include #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) // logging category for this framework, default: log stuff >= warning Q_LOGGING_CATEGORY(LOG_KTE, "org.kde.ktexteditor", QtWarningMsg) #else Q_LOGGING_CATEGORY(LOG_KTE, "org.kde.ktexteditor") #endif //BEGIN unit test mode static bool kateUnitTestMode = false; void KTextEditor::EditorPrivate::enableUnitTestMode() { kateUnitTestMode = true; } bool KTextEditor::EditorPrivate::unitTestMode() { return kateUnitTestMode; } //END unit test mode KTextEditor::EditorPrivate::EditorPrivate(QPointer &staticInstance) : KTextEditor::Editor (this) , m_aboutData(QStringLiteral("katepart"), i18n("Kate Part"), QStringLiteral(KTEXTEDITOR_VERSION_STRING), i18n("Embeddable editor component"), KAboutLicense::LGPL_V2, - i18n("(c) 2000-2016 The Kate Authors"), QString(), QStringLiteral("http://kate-editor.org")) - , m_dummyApplication(Q_NULLPTR) + i18n("(c) 2000-2017 The Kate Authors"), QString(), QStringLiteral("http://kate-editor.org")) + , m_dummyApplication(nullptr) , m_application(&m_dummyApplication) - , m_dummyMainWindow(Q_NULLPTR) + , m_dummyMainWindow(nullptr) , m_defaultColors(new KateDefaultColors()) - , m_searchHistoryModel(Q_NULLPTR) - , m_replaceHistoryModel(Q_NULLPTR) + , m_searchHistoryModel(nullptr) + , m_replaceHistoryModel(nullptr) { // remember this staticInstance = this; // init libgit2, we require at least 0.22 which has this function! #ifdef LIBGIT2_FOUND git_libgit2_init(); #endif /** * register some datatypes */ qRegisterMetaType("KTextEditor::Cursor"); qRegisterMetaType("KTextEditor::Document*"); qRegisterMetaType("KTextEditor::View*"); // // fill about data // m_aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("http://www.cullmann.io")); m_aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org")); m_aboutData.addAuthor(i18n("Milian Wolff"), i18n("Core Developer"), QStringLiteral("mail@milianw.de"), QStringLiteral("http://milianw.de")); m_aboutData.addAuthor(i18n("Joseph Wenninger"), i18n("Core Developer"), QStringLiteral("jowenn@kde.org"), QStringLiteral("http://stud3.tuwien.ac.at/~e9925371")); m_aboutData.addAuthor(i18n("Erlend Hamberg"), i18n("Vi Input Mode"), QStringLiteral("ehamberg@gmail.com"), QStringLiteral("http://hamberg.no/erlend")); m_aboutData.addAuthor(i18n("Bernhard Beschow"), i18n("Developer"), QStringLiteral("bbeschow@cs.tu-berlin.de"), QStringLiteral("https://user.cs.tu-berlin.de/~bbeschow")); m_aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("http://www.alweb.dk")); m_aboutData.addAuthor(i18n("Michel Ludwig"), i18n("On-the-fly spell checking"), QStringLiteral("michel.ludwig@kdemail.net")); m_aboutData.addAuthor(i18n("Pascal Létourneau"), i18n("Large scale bug fixing"), QStringLiteral("pascal.letourneau@gmail.com")); m_aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org")); m_aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org")); m_aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org")); m_aboutData.addAuthor(i18n("Matt Newell"), i18n("Testing, ..."), QStringLiteral("newellm@proaxis.com")); m_aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at")); m_aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz")); m_aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org")); m_aboutData.addAuthor(i18n("Christian Gebauer"), QString(), QStringLiteral("gebauer@kde.org")); m_aboutData.addAuthor(i18n("Simon Hausmann"), QString(), QStringLiteral("hausmann@kde.org")); m_aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com")); m_aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net")); m_aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org")); m_aboutData.addAuthor(i18n("Andreas Kling"), i18n("Developer"), QStringLiteral("kling@impul.se")); m_aboutData.addAuthor(i18n("Mirko Stocker"), i18n("Various bugfixes"), QStringLiteral("me@misto.ch"), QStringLiteral("http://misto.ch/")); m_aboutData.addAuthor(i18n("Matthew Woehlke"), i18n("Selection, KColorScheme integration"), QStringLiteral("mw_triad@users.sourceforge.net")); m_aboutData.addAuthor(i18n("Sebastian Pipping"), i18n("Search bar back- and front-end"), QStringLiteral("webmaster@hartwork.org"), QStringLiteral("http://www.hartwork.org/")); m_aboutData.addAuthor(i18n("Jochen Wilhelmy"), i18n("Original KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de")); m_aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"), i18n("QA and Scripting"), QStringLiteral("oss@senarclens.eu"), QStringLiteral("http://find-santa.eu/")); m_aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it")); m_aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu")); m_aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL"), QString()); m_aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite"), QString()); m_aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG"), QString()); m_aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX"), QString()); m_aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python"), QString()); m_aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python"), QString()); m_aboutData.addCredit(i18n("Daniel Naber")); m_aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme"), QString()); m_aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list"), QString()); m_aboutData.addCredit(i18n("Carsten Pfeiffer"), i18n("Very nice help"), QString()); m_aboutData.addCredit(i18n("Bruno Massa"), i18n("Highlighting for Lua"), QStringLiteral("brmassa@gmail.com")); m_aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention")); m_aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); /** * set the new Kate mascot */ m_aboutData.setProgramLogo (QImage(QStringLiteral(":/ktexteditor/mascot.png"))); // // dir watch // m_dirWatch = new KDirWatch(); // // command manager // m_cmdManager = new KateCmd(); // // hl manager // m_hlManager = new KateHlManager(); // // mode man // m_modeManager = new KateModeManager(); // // schema man // m_schemaManager = new KateSchemaManager(); // // input mode factories // KateAbstractInputModeFactory *fact; fact = new KateNormalInputModeFactory(); m_inputModeFactories.insert(KTextEditor::View::NormalInputMode, fact); #ifdef BUILD_VIMODE fact = new KateViInputModeFactory(); m_inputModeFactories.insert(KTextEditor::View::ViInputMode, fact); #endif // // spell check manager // m_spellCheckManager = new KateSpellCheckManager(); // config objects m_globalConfig = new KateGlobalConfig(); m_documentConfig = new KateDocumentConfig(); m_viewConfig = new KateViewConfig(); m_rendererConfig = new KateRendererConfig(); // create script manager (search scripts) m_scriptManager = KateScriptManager::self(); // // init the cmds // m_cmds.push_back(KateCommands::CoreCommands::self()); m_cmds.push_back(KateCommands::Character::self()); m_cmds.push_back(KateCommands::Date::self()); m_cmds.push_back(KateCommands::SedReplace::self()); m_cmds.push_back(KateCommands::Highlighting::self()); // global word completion model m_wordCompletionModel = new KateWordCompletionModel(this); // global keyword completion model m_keywordCompletionModel = new KateKeywordCompletionModel (this); // tap to QApplication object for color palette changes qApp->installEventFilter(this); } KTextEditor::EditorPrivate::~EditorPrivate() { delete m_globalConfig; delete m_documentConfig; delete m_viewConfig; delete m_rendererConfig; delete m_modeManager; delete m_schemaManager; delete m_dirWatch; // cu managers delete m_scriptManager; delete m_hlManager; delete m_spellCheckManager; // cu model delete m_wordCompletionModel; // delete the commands before we delete the cmd manager qDeleteAll(m_cmds); delete m_cmdManager; qDeleteAll(m_inputModeFactories); // shutdown libgit2, we require at least 0.22 which has this function! #ifdef LIBGIT2_FOUND git_libgit2_shutdown(); #endif } KTextEditor::Document *KTextEditor::EditorPrivate::createDocument(QObject *parent) { - KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(false, false, 0, parent); + KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(false, false, nullptr, parent); emit documentCreated(this, doc); return doc; } //END KTextEditor::Editor config stuff void KTextEditor::EditorPrivate::configDialog(QWidget *parent) { QPointer kd = new KPageDialog(parent); kd->setWindowTitle(i18n("Configure")); kd->setFaceType(KPageDialog::List); kd->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply | QDialogButtonBox::Help); QList editorPages; for (int i = 0; i < configPages(); ++i) { QFrame *page = new QFrame(); KTextEditor::ConfigPage *cp = configPage(i, page); KPageWidgetItem *item = kd->addPage(page, cp->name()); item->setHeader(cp->fullName()); item->setIcon(cp->icon()); QVBoxLayout *topLayout = new QVBoxLayout(page); topLayout->setMargin(0); connect(kd->button(QDialogButtonBox::Apply), SIGNAL(clicked()), cp, SLOT(apply())); topLayout->addWidget(cp); editorPages.append(cp); } if (kd->exec() && kd) { KateGlobalConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); KateViewConfig::global()->configStart(); KateRendererConfig::global()->configStart(); for (int i = 0; i < editorPages.count(); ++i) { editorPages.at(i)->apply(); } KateGlobalConfig::global()->configEnd(); KateDocumentConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); KateRendererConfig::global()->configEnd(); } delete kd; } int KTextEditor::EditorPrivate::configPages() const { return 4; } KTextEditor::ConfigPage *KTextEditor::EditorPrivate::configPage(int number, QWidget *parent) { switch (number) { case 0: return new KateViewDefaultsConfig(parent); case 1: return new KateSchemaConfigPage(parent); case 2: return new KateEditConfigTab(parent); case 3: return new KateSaveConfigTab(parent); default: break; } - return 0; + return nullptr; } /** * Cleanup the KTextEditor::EditorPrivate during QCoreApplication shutdown */ static void cleanupGlobal() { /** * delete if there */ delete KTextEditor::EditorPrivate::self(); } KTextEditor::EditorPrivate *KTextEditor::EditorPrivate::self() { /** * remember the static instance in a QPointer */ static bool inited = false; static QPointer staticInstance; /** * just return it, if already inited */ if (inited) { return staticInstance.data(); } /** * start init process */ inited = true; /** * now create the object and store it */ new KTextEditor::EditorPrivate(staticInstance); /** * register cleanup * let use be deleted during QCoreApplication shutdown */ qAddPostRoutine(cleanupGlobal); /** * return instance */ return staticInstance.data(); } void KTextEditor::EditorPrivate::registerDocument(KTextEditor::DocumentPrivate *doc) { Q_ASSERT (!m_documents.contains(doc)); m_documents.insert(doc, doc); } void KTextEditor::EditorPrivate::deregisterDocument(KTextEditor::DocumentPrivate *doc) { Q_ASSERT (m_documents.contains(doc)); m_documents.remove(doc); } void KTextEditor::EditorPrivate::registerView(KTextEditor::ViewPrivate *view) { Q_ASSERT (!m_views.contains(view)); m_views.insert(view); } void KTextEditor::EditorPrivate::deregisterView(KTextEditor::ViewPrivate *view) { Q_ASSERT (m_views.contains(view)); m_views.remove(view); } KTextEditor::Command *KTextEditor::EditorPrivate::queryCommand(const QString &cmd) const { return m_cmdManager->queryCommand(cmd); } QList KTextEditor::EditorPrivate::commands() const { return m_cmdManager->commands(); } QStringList KTextEditor::EditorPrivate::commandList() const { return m_cmdManager->commandList(); } void KTextEditor::EditorPrivate::updateColorPalette() { // update default color cache m_defaultColors.reset(new KateDefaultColors()); // reload the global schema (triggers reload for every view as well) m_rendererConfig->reloadSchema(); // force full update of all view caches and colors m_rendererConfig->updateConfig(); } void KTextEditor::EditorPrivate::updateClipboardHistory(const QVector &text) { /** * empty => nop */ if (text.isEmpty()) { return; } /** * remember in history * cut after 10 entries */ m_clipboardHistory.prepend(text); if (m_clipboardHistory.size() > 10) { m_clipboardHistory.removeLast(); } /** * notify about change */ emit clipboardHistoryChanged(); } bool KTextEditor::EditorPrivate::eventFilter(QObject *obj, QEvent *event) { if (obj == qApp && event->type() == QEvent::ApplicationPaletteChange) { // only update the color once for the event that belongs to the qApp updateColorPalette(); } return false; // always continue processing } QList< KateAbstractInputModeFactory *> KTextEditor::EditorPrivate::inputModeFactories() { return m_inputModeFactories.values(); } QStringListModel *KTextEditor::EditorPrivate::searchHistoryModel() { if (!m_searchHistoryModel) { KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search"); const QStringList history = cg.readEntry(QStringLiteral("Search History"), QStringList()); m_searchHistoryModel = new QStringListModel(history, this); } return m_searchHistoryModel; } QStringListModel *KTextEditor::EditorPrivate::replaceHistoryModel() { if (!m_replaceHistoryModel) { KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search"); const QStringList history = cg.readEntry(QStringLiteral("Replace History"), QStringList()); m_replaceHistoryModel = new QStringListModel(history, this); } return m_replaceHistoryModel; } void KTextEditor::EditorPrivate::saveSearchReplaceHistoryModels() { KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search"); if (m_searchHistoryModel) { cg.writeEntry(QStringLiteral("Search History"), m_searchHistoryModel->stringList()); } if (m_replaceHistoryModel) { cg.writeEntry(QStringLiteral("Replace History"), m_replaceHistoryModel->stringList()); } } diff --git a/src/utils/katesedcmd.cpp b/src/utils/katesedcmd.cpp index 56febcf5..bd55f48f 100644 --- a/src/utils/katesedcmd.cpp +++ b/src/utils/katesedcmd.cpp @@ -1,302 +1,302 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * 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 "katesedcmd.h" #include "katedocument.h" #include "kateview.h" #include "kateglobal.h" #include "katecmd.h" #include "katepartdebug.h" #include #include #include #include -KateCommands::SedReplace *KateCommands::SedReplace::m_instance = 0; +KateCommands::SedReplace *KateCommands::SedReplace::m_instance = nullptr; static int backslashString(const QString &haystack, const QString &needle, int index) { int len = haystack.length(); int searchlen = needle.length(); bool evenCount = true; while (index < len) { if (haystack[index] == QLatin1Char('\\')) { evenCount = !evenCount; } else { // isn't a slash if (!evenCount) { if (haystack.mid(index, searchlen) == needle) { return index - 1; } } evenCount = true; } ++index; } return -1; } // exchange "\t" for the actual tab character, for example static void exchangeAbbrevs(QString &str) { // the format is (findreplace)*[nullzero] const char *magic = "a\x07t\tn\n"; while (*magic) { int index = 0; char replace = magic[1]; while ((index = backslashString(str, QString(QChar::fromLatin1(*magic)), index)) != -1) { str.replace(index, 2, QChar::fromLatin1(replace)); ++index; } ++magic; ++magic; } } bool KateCommands::SedReplace::exec(class KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &r) { qCDebug(LOG_KTE) << "SedReplace::execCmd( " << cmd << " )"; if (r.isValid()) { qCDebug(LOG_KTE) << "Range: " << r; } int findBeginPos = -1; int findEndPos = -1; int replaceBeginPos = -1; int replaceEndPos = -1; QString delimiter; if (!parse(cmd, delimiter, findBeginPos, findEndPos, replaceBeginPos, replaceEndPos)) { return false; } const QString searchParamsString = cmd.mid(cmd.lastIndexOf(delimiter)); const bool noCase = searchParamsString.contains(QLatin1Char('i')); const bool repeat = searchParamsString.contains(QLatin1Char('g')); const bool interactive = searchParamsString.contains(QLatin1Char('c')); QString find = cmd.mid(findBeginPos, findEndPos - findBeginPos + 1); qCDebug(LOG_KTE) << "SedReplace: find =" << find; QString replace = cmd.mid(replaceBeginPos, replaceEndPos - replaceBeginPos + 1); exchangeAbbrevs(replace); qCDebug(LOG_KTE) << "SedReplace: replace =" << replace; if (find.isEmpty()) { // Nothing to do. return true; } KTextEditor::ViewPrivate *kateView = static_cast(view); KTextEditor::DocumentPrivate *doc = kateView->doc(); if (!doc) { return false; } // Only current line ... int startLine = kateView->cursorPosition().line(); int endLine = kateView->cursorPosition().line(); // ... unless a range was provided. if (r.isValid()) { startLine = r.start().line(); endLine = r.end().line(); } QSharedPointer interactiveSedReplacer(new InteractiveSedReplacer(doc, find, replace, !noCase, !repeat, startLine, endLine)); if (interactive) { const bool hasInitialMatch = interactiveSedReplacer->currentMatch().isValid(); if (!hasInitialMatch) { // Can't start an interactive sed replace if there is no initial match! msg = interactiveSedReplacer->finalStatusReportMessage(); return false; } interactiveSedReplace(kateView, interactiveSedReplacer); return true; } interactiveSedReplacer->replaceAllRemaining(); msg = interactiveSedReplacer->finalStatusReportMessage(); return true; } bool KateCommands::SedReplace::interactiveSedReplace(KTextEditor::ViewPrivate *, QSharedPointer) { qCDebug(LOG_KTE) << "Interactive sedreplace is only currently supported with Vi mode plus Vi emulated command bar."; return false; } bool KateCommands::SedReplace::parse(const QString &sedReplaceString, QString &destDelim, int &destFindBeginPos, int &destFindEndPos, int &destReplaceBeginPos, int &destReplaceEndPos) { // valid delimiters are all non-word, non-space characters plus '_' QRegExp delim(QLatin1String("^s\\s*([^\\w\\s]|_)")); if (delim.indexIn(sedReplaceString) < 0) { return false; } QString d = delim.cap(1); qCDebug(LOG_KTE) << "SedReplace: delimiter is '" << d << "'"; QRegExp splitter(QStringLiteral("^s\\s*") + d + QLatin1String("((?:[^\\\\\\") + d + QLatin1String("]|\\\\.)*)\\") + d + QLatin1String("((?:[^\\\\\\") + d + QLatin1String("]|\\\\.)*)(\\") + d + QLatin1String("[igc]{0,3})?$")); if (splitter.indexIn(sedReplaceString) < 0) { return false; } const QString find = splitter.cap(1); const QString replace = splitter.cap(2); destDelim = d; destFindBeginPos = splitter.pos(1); destFindEndPos = splitter.pos(1) + find.length() - 1; destReplaceBeginPos = splitter.pos(2); destReplaceEndPos = splitter.pos(2) + replace.length() - 1; return true; } KateCommands::SedReplace::InteractiveSedReplacer::InteractiveSedReplacer(KTextEditor::DocumentPrivate *doc, const QString &findPattern, const QString &replacePattern, bool caseSensitive, bool onlyOnePerLine, int startLine, int endLine) : m_findPattern(findPattern), m_replacePattern(replacePattern), m_onlyOnePerLine(onlyOnePerLine), m_endLine(endLine), m_doc(doc), m_regExpSearch(doc, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive), m_numReplacementsDone(0), m_numLinesTouched(0), m_lastChangedLineNum(-1) { m_currentSearchPos = KTextEditor::Cursor(startLine, 0); } KTextEditor::Range KateCommands::SedReplace::InteractiveSedReplacer::currentMatch() { QVector matches = fullCurrentMatch(); if (matches.isEmpty()) { return KTextEditor::Range::invalid(); } if (matches.first().start().line() > m_endLine) { return KTextEditor::Range::invalid(); } return matches.first(); } void KateCommands::SedReplace::InteractiveSedReplacer::skipCurrentMatch() { const KTextEditor::Range currentMatch = this->currentMatch(); m_currentSearchPos = currentMatch.end(); if (m_onlyOnePerLine && currentMatch.start().line() == m_currentSearchPos.line()) { m_currentSearchPos = KTextEditor::Cursor(m_currentSearchPos.line() + 1, 0); } } void KateCommands::SedReplace::InteractiveSedReplacer::replaceCurrentMatch() { const KTextEditor::Range currentMatch = this->currentMatch(); const QString currentMatchText = m_doc->text(currentMatch); const QString replacementText = replacementTextForCurrentMatch(); m_doc->editBegin(); m_doc->removeText(currentMatch); m_doc->insertText(currentMatch.start(), replacementText); m_doc->editEnd(); // Begin next search from directly after replacement. if (!replacementText.contains(QLatin1Char('\n'))) { const int moveChar = currentMatch.isEmpty() ? 1 : 0; // if the search was for \s*, make sure we advance a char const int col = currentMatch.start().column() + replacementText.length() + moveChar; m_currentSearchPos = KTextEditor::Cursor(currentMatch.start().line(), col); } else { m_currentSearchPos = KTextEditor::Cursor(currentMatch.start().line() + replacementText.count(QLatin1Char('\n')), replacementText.length() - replacementText.lastIndexOf(QLatin1Char('\n')) - 1); } if (m_onlyOnePerLine) { // Drop down to next line. m_currentSearchPos = KTextEditor::Cursor(m_currentSearchPos.line() + 1, 0); } // Adjust end line down by the number of new newlines just added, minus the number taken away. m_endLine += replacementText.count(QLatin1Char('\n')); m_endLine -= currentMatchText.count(QLatin1Char('\n')); m_numReplacementsDone++; if (m_lastChangedLineNum != currentMatch.start().line()) { // Counting "swallowed" lines as being "touched". m_numLinesTouched += currentMatchText.count(QLatin1Char('\n')) + 1; } m_lastChangedLineNum = m_currentSearchPos.line(); } void KateCommands::SedReplace::InteractiveSedReplacer::replaceAllRemaining() { m_doc->editBegin(); while (currentMatch().isValid()) { replaceCurrentMatch(); } m_doc->editEnd(); } QString KateCommands::SedReplace::InteractiveSedReplacer::currentMatchReplacementConfirmationMessage() { return i18n("replace with %1?", replacementTextForCurrentMatch().replace(QLatin1Char('\n'), QLatin1String("\\n"))); } QString KateCommands::SedReplace::InteractiveSedReplacer::finalStatusReportMessage() { return i18ncp("%2 is the translation of the next message", "1 replacement done on %2", "%1 replacements done on %2", m_numReplacementsDone, i18ncp("substituted into the previous message", "1 line", "%1 lines", m_numLinesTouched)); } const QVector KateCommands::SedReplace::InteractiveSedReplacer::fullCurrentMatch() { if (m_currentSearchPos > m_doc->documentEnd()) { return QVector (); } return m_regExpSearch.search(m_findPattern, KTextEditor::Range(m_currentSearchPos, m_doc->documentEnd())); } QString KateCommands::SedReplace::InteractiveSedReplacer::replacementTextForCurrentMatch() { const QVector captureRanges = fullCurrentMatch(); QStringList captureTexts; foreach (KTextEditor::Range captureRange, captureRanges) { captureTexts << m_doc->text(captureRange); } const QString replacementText = m_regExpSearch.buildReplacement(m_replacePattern, captureTexts, 0); return replacementText; } diff --git a/src/utils/katesedcmd.h b/src/utils/katesedcmd.h index d5dbf318..af6dd8e7 100644 --- a/src/utils/katesedcmd.h +++ b/src/utils/katesedcmd.h @@ -1,145 +1,145 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * 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_SED_CMD_H__ #define __KATE_SED_CMD_H__ #include #include "kateregexpsearch.h" #include #include namespace KTextEditor { class DocumentPrivate; class ViewPrivate; } /** * The KateCommands namespace collects subclasses of KTextEditor::Command * for specific use in kate. */ namespace KateCommands { /** * Support vim/sed style search and replace * @author Charles Samuels **/ class SedReplace : public KTextEditor::Command { static SedReplace *m_instance; protected: SedReplace() : KTextEditor::Command(QStringList() << QStringLiteral("s") << QStringLiteral("%s") << QStringLiteral("$s")) { } public: ~SedReplace() { - m_instance = 0; + m_instance = nullptr; } /** * Execute command. Valid command strings are: * - s/search/replace/ find @c search, replace it with @c replace * on this line * - \%s/search/replace/ do the same to the whole file * - s/search/replace/i do the search and replace case insensitively * - $s/search/replace/ do the search are replacement to the * selection only * * @note $s/// is currently unsupported * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &r) Q_DECL_OVERRIDE; bool supportsRange(const QString &) Q_DECL_OVERRIDE { return true; } /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE { return false; } static SedReplace *self() { - if (m_instance == 0) { + if (m_instance == nullptr) { m_instance = new SedReplace(); } return m_instance; } /** * Parses @c sedReplaceString to see if it is a valid sed replace expression (e.g. "s/find/replace/gi"). * If it is, returns true and sets @c delimiter to the delimiter used in the expression; * @c destFindBeginPos and @c destFindEndPos to the positions in * @c sedReplaceString of the * begin and end of the "find" term; and @c destReplaceBeginPos and @c destReplaceEndPos to the begin * and end positions of the "replace" term. */ static bool parse(const QString &sedReplaceString, QString &destDelim, int &destFindBeginPos, int &destFindEndPos, int &destReplaceBeginPos, int &destReplaceEndPos); class InteractiveSedReplacer { public: InteractiveSedReplacer(KTextEditor::DocumentPrivate *doc, const QString &findPattern, const QString &replacePattern, bool caseSensitive, bool onlyOnePerLine, int startLine, int endLine); /** * Will return invalid Range if there are no further matches. */ KTextEditor::Range currentMatch(); void skipCurrentMatch(); void replaceCurrentMatch(); void replaceAllRemaining(); QString currentMatchReplacementConfirmationMessage(); QString finalStatusReportMessage(); private: const QString m_findPattern; const QString m_replacePattern; bool m_onlyOnePerLine; int m_endLine; KTextEditor::DocumentPrivate *m_doc; KateRegExpSearch m_regExpSearch; int m_numReplacementsDone; int m_numLinesTouched; int m_lastChangedLineNum; KTextEditor::Cursor m_currentSearchPos; const QVector fullCurrentMatch(); QString replacementTextForCurrentMatch(); }; protected: virtual bool interactiveSedReplace(KTextEditor::ViewPrivate *kateView, QSharedPointer interactiveSedReplace); }; } // namespace KateCommands #endif diff --git a/src/utils/katetemplatehandler.cpp b/src/utils/katetemplatehandler.cpp index 6c103c4c..a168445d 100644 --- a/src/utils/katetemplatehandler.cpp +++ b/src/utils/katetemplatehandler.cpp @@ -1,561 +1,561 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2004,2010 Joseph Wenninger * Copyright (C) 2009 Milian Wolff * 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 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 #include #include #include #include #include "katetemplatehandler.h" #include "katedocument.h" #include "kateview.h" #include "kateconfig.h" #include "katerenderer.h" #include "kateundomanager.h" #include "kateregexpsearch.h" #include "kateglobal.h" #include "script/katescriptmanager.h" #include "katepartdebug.h" using namespace KTextEditor; #define ifDebug(x) KateTemplateHandler::KateTemplateHandler(KTextEditor::ViewPrivate *view, Cursor position, const QString& templateString, const QString& script, KateUndoManager* undoManager) : QObject(view) , m_view(view) , m_undoManager(undoManager) - , m_wholeTemplateRange(nullptr) + , m_wholeTemplateRange() , m_internalEdit(false) , m_templateScript(script, KateScript::InputSCRIPT) { Q_ASSERT(m_view); m_templateScript.setView(m_view); // remember selection, it will be lost when inserting the template QScopedPointer selection(doc()->newMovingRange(m_view->selectionRange(), MovingRange::DoNotExpand)); m_undoManager->setAllowComplexMerge(true); { connect(doc(), &KTextEditor::DocumentPrivate::textInserted, this, &KateTemplateHandler::slotTemplateInserted); KTextEditor::Document::EditingTransaction t(doc()); // insert the raw template string if (!doc()->insertText(position, templateString)) { deleteLater(); return; } // now there must be a range, caught by the textInserted slot Q_ASSERT(m_wholeTemplateRange); doc()->align(m_view, *m_wholeTemplateRange); } // before initialization, restore selection (if any) so user scripts can retrieve it m_view->setSelection(selection->toRange()); initializeTemplate(); // then delete the selected text (if any); it was replaced by the template doc()->removeText(selection->toRange()); bool have_editable_field = false; Q_FOREACH ( const auto& field, m_fields ) { if ( field.kind == TemplateField::Editable ) { have_editable_field = true; break; } } // only do complex stuff when required if ( have_editable_field ) { foreach (View *view, doc()->views()) { setupEventHandler(view); } // place the cursor at the first field and select stuff jump(1, true); connect(doc(), &KTextEditor::Document::viewCreated, this, &KateTemplateHandler::slotViewCreated); connect(doc(), &KTextEditor::DocumentPrivate::textInserted, this, &KateTemplateHandler::updateDependentFields); connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateTemplateHandler::updateDependentFields); connect(doc(), &KTextEditor::Document::aboutToReload, this, &KateTemplateHandler::deleteLater); } else { // when no interesting ranges got added, we can terminate directly jumpToFinalCursorPosition(); deleteLater(); } } KateTemplateHandler::~KateTemplateHandler() { m_undoManager->setAllowComplexMerge(false); } void KateTemplateHandler::sortFields() { std::sort(m_fields.begin(), m_fields.end(), [](const TemplateField& l, const TemplateField& r) { // always sort the final cursor pos last if ( l.kind == TemplateField::FinalCursorPosition ) { return false; } if ( r.kind == TemplateField::FinalCursorPosition ) { return true; } // sort by range return l.range->toRange() < r.range->toRange(); }); } void KateTemplateHandler::jumpToNextRange() { jump(+1); } void KateTemplateHandler::jumpToPreviousRange() { jump(-1); } void KateTemplateHandler::jump(int by, bool initial) { Q_ASSERT(by == 1 || by == -1); sortFields(); // find (editable) field index of current cursor position int pos = -1; auto cursor = view()->cursorPosition(); // if initial is not set, should start from the beginning (field -1) if ( ! initial ) { pos = m_fields.indexOf(fieldForRange(KTextEditor::Range(cursor, cursor))); } // modulo field count and make positive auto wrap = [this](int x) -> unsigned int { x %= m_fields.size(); return x + (x < 0 ? m_fields.size() : 0); }; pos = wrap(pos); // choose field to jump to, including wrap-around auto choose_next_field = [this, by, wrap](unsigned int from_field_index) { for ( int i = from_field_index + by; ; i += by ) { auto wrapped_i = wrap(i); auto kind = m_fields.at(wrapped_i).kind; if ( kind == TemplateField::Editable || kind == TemplateField::FinalCursorPosition ) { // found an editable field by walking into the desired direction return wrapped_i; } if ( wrapped_i == from_field_index ) { // nothing found, do nothing (i.e. keep cursor in current field) break; } } return from_field_index; }; // jump auto jump_to_field = m_fields.at(choose_next_field(pos)); view()->setCursorPosition(jump_to_field.range->toRange().start()); if ( ! jump_to_field.touched ) { // field was never edited by the user, so select its contents view()->setSelection(jump_to_field.range->toRange()); } } void KateTemplateHandler::jumpToFinalCursorPosition() { Q_FOREACH ( const auto& field, m_fields ) { if ( field.kind == TemplateField::FinalCursorPosition ) { view()->setCursorPosition(field.range->toRange().start()); return; } } view()->setCursorPosition(m_wholeTemplateRange->end()); } void KateTemplateHandler::slotTemplateInserted(Document* /*document*/, const Range&range) { m_wholeTemplateRange.reset(doc()->newMovingRange(range, MovingRange::ExpandLeft | MovingRange::ExpandRight)); disconnect(doc(), &KTextEditor::DocumentPrivate::textInserted, this, &KateTemplateHandler::slotTemplateInserted); } KTextEditor::DocumentPrivate* KateTemplateHandler::doc() const { return m_view->doc(); } void KateTemplateHandler::slotViewCreated(Document *document, View *view) { Q_ASSERT(document == doc()); Q_UNUSED(document) setupEventHandler(view); } void KateTemplateHandler::setupEventHandler(View *view) { view->focusProxy()->installEventFilter(this); } bool KateTemplateHandler::eventFilter(QObject *object, QEvent *event) { // prevent indenting by eating the keypress event for TAB if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab) { if (!m_view->isCompletionActive()) { return true; } } } // actually offer shortcuts for navigation if (event->type() == QEvent::ShortcutOverride) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape || (keyEvent->key() == Qt::Key_Return && keyEvent->modifiers() & Qt::AltModifier)) { // terminate jumpToFinalCursorPosition(); view()->clearSelection(); deleteLater(); keyEvent->accept(); return true; } else if (keyEvent->key() == Qt::Key_Tab && !m_view->isCompletionActive()) { if (keyEvent->modifiers() & Qt::ShiftModifier) { jumpToPreviousRange(); } else { jumpToNextRange(); } keyEvent->accept(); return true; } else if (keyEvent->key() == Qt::Key_Backtab && !m_view->isCompletionActive()) { jumpToPreviousRange(); keyEvent->accept(); return true; } } return QObject::eventFilter(object, event); } /** * Returns an attribute with \p color as background with @p alpha alpha value. */ Attribute::Ptr getAttribute(QColor color, int alpha = 230) { Attribute::Ptr attribute(new Attribute()); color.setAlpha(alpha); attribute->setBackground(QBrush(color)); return attribute; } void KateTemplateHandler::parseFields(const QString& templateText) { // matches any field, i.e. the three forms ${foo}, ${foo=expr}, ${func()} // this also captures escaped fields, i.e. \\${foo} etc. QRegularExpression field(QStringLiteral("\\\\?\\${([^}]+)}")); // matches the "foo=expr" form within a match of the above expression QRegularExpression defaultField(QStringLiteral("\\w+=([^\\}]*)")); // compute start cursor of a match auto startOfMatch = [this, &templateText](const QRegularExpressionMatch& match) { const auto offset = match.capturedStart(0); const auto left = templateText.left(offset); const auto nl = QLatin1Char('\n'); const auto rel_lineno = left.count(nl); const auto start = m_wholeTemplateRange->start().toCursor(); return Cursor(start.line(), rel_lineno == 0 ? start.column() : 0) + Cursor(rel_lineno, offset - left.lastIndexOf(nl) - 1); }; // create a moving range spanning the given field auto createMovingRangeForMatch = [this, startOfMatch](const QRegularExpressionMatch& match) { auto matchStart = startOfMatch(match); return doc()->newMovingRange({matchStart, matchStart + Cursor(0, match.capturedLength(0))}, MovingRange::ExpandLeft | MovingRange::ExpandRight); }; // list of escape backslashes to remove after parsing QVector stripBackslashes; auto fieldMatch = field.globalMatch(templateText); while ( fieldMatch.hasNext() ) { auto match = fieldMatch.next(); if ( match.captured(0).startsWith(QLatin1Char('\\')) ) { // $ is escaped, not a field; mark the backslash for removal // prepend it to the list so the characters are removed starting from the // back and ranges do not move around stripBackslashes.prepend(startOfMatch(match)); continue; } // a template field was found, instantiate a field object and populate it auto defaultMatch = defaultField.match(match.captured(0)); auto contents = match.captured(1); TemplateField f; f.range.reset(createMovingRangeForMatch(match)); f.identifier = contents; f.kind = TemplateField::Editable; if ( defaultMatch.hasMatch() ) { // the field has a default value, i.e. ${foo=3} f.defaultValue = defaultMatch.captured(1); f.identifier = contents.split(QLatin1Char('=')).at(0).trimmed(); } else if ( f.identifier.contains(QLatin1Char('(')) ) { // field is a function call when it contains an opening parenthesis f.kind = TemplateField::FunctionCall; } else if ( f.identifier == QStringLiteral("cursor") ) { // field marks the final cursor position f.kind = TemplateField::FinalCursorPosition; } Q_FOREACH ( const auto& other, m_fields ) { if ( other.kind == TemplateField::Editable && ! (f == other) && other.identifier == f.identifier ) { // field is a mirror field f.kind = TemplateField::Mirror; break; } } m_fields.append(f); } // remove escape characters Q_FOREACH ( const auto& backslash, stripBackslashes ) { doc()->removeText(KTextEditor::Range(backslash, backslash + Cursor(0, 1))); } } void KateTemplateHandler::setupFieldRanges() { auto config = m_view->renderer()->config(); auto editableAttribute = getAttribute(config->templateEditablePlaceholderColor(), 255); editableAttribute->setDynamicAttribute( Attribute::ActivateCaretIn, getAttribute(config->templateFocusedEditablePlaceholderColor(), 255) ); auto notEditableAttribute = getAttribute(config->templateNotEditablePlaceholderColor(), 255); // color the whole template m_wholeTemplateRange->setAttribute(getAttribute(config->templateBackgroundColor(), 200)); // color all the template fields Q_FOREACH ( const auto& field, m_fields ) { field.range->setAttribute(field.kind == TemplateField::Editable ? editableAttribute : notEditableAttribute); } } void KateTemplateHandler::setupDefaultValues() { Q_FOREACH ( const auto& field, m_fields ) { if ( field.kind != TemplateField::Editable ) { continue; } QString value; if ( field.defaultValue.isEmpty() ) { // field has no default value specified; use its identifier value = field.identifier; } else { // field has a default value; evaluate it with the JS engine value = m_templateScript.evaluate(field.defaultValue).toString(); } doc()->replaceText(field.range->toRange(), value); } } void KateTemplateHandler::initializeTemplate() { auto templateString = doc()->text(*m_wholeTemplateRange); parseFields(templateString); setupFieldRanges(); setupDefaultValues(); // call update for each field to set up the initial stuff for ( int i = 0; i < m_fields.size(); i++ ) { auto& field = m_fields[i]; ifDebug(qCDebug(LOG_KTE) << "update field:" << field.range->toRange();) updateDependentFields(doc(), field.range->toRange()); // remove "user edited field" mark set by the above call since it's not a real edit field.touched = false; } } const KateTemplateHandler::TemplateField KateTemplateHandler::fieldForRange(const KTextEditor::Range& range) const { Q_FOREACH ( const auto& field, m_fields ) { if ( field.range->contains(range.start()) || field.range->end() == range.start() ) { return field; } if ( field.kind == TemplateField::FinalCursorPosition && range.end() == field.range->end().toCursor() ) { return field; } } return {}; } void KateTemplateHandler::updateDependentFields(Document *document, const Range &range) { Q_ASSERT(document == doc()); Q_UNUSED(document); if ( ! m_undoManager->isActive() ) { // currently undoing stuff; don't update fields return; } bool in_range = m_wholeTemplateRange->toRange().contains(range.start()); bool at_end = m_wholeTemplateRange->toRange().end() == range.end() || m_wholeTemplateRange->toRange().end() == range.start(); if ( m_wholeTemplateRange->toRange().isEmpty() || (!in_range && !at_end) ) { // edit outside template range, abort ifDebug(qCDebug(LOG_KTE) << "edit outside template range, exiting";) deleteLater(); return; } if ( m_internalEdit || range.isEmpty() ) { // internal or null edit; for internal edits, don't do anything // to prevent unwanted recursion return; } ifDebug(qCDebug(LOG_KTE) << "text changed" << document << range;) // group all the changes into one undo transaction KTextEditor::Document::EditingTransaction t(doc()); // find the field which was modified, if any sortFields(); const auto changedField = fieldForRange(range); if ( changedField.kind == TemplateField::Invalid ) { // edit not within a field, nothing to do ifDebug(qCDebug(LOG_KTE) << "edit not within a field:" << range;) return; } if ( changedField.kind == TemplateField::FinalCursorPosition && doc()->text(changedField.range->toRange()).isEmpty() ) { // text changed at final cursor position: the user is done, so exit // this is not executed when the field's range is not empty: in that case this call // is for initial setup and we have to continue below ifDebug(qCDebug(LOG_KTE) << "final cursor changed:" << range;) deleteLater(); return; } // turn off expanding left/right for all ranges except @p current; // this prevents ranges from overlapping each other when they are adjacent auto dontExpandOthers = [this](const TemplateField& current) { for ( int i = 0; i < m_fields.size(); i++ ) { if ( current.range != m_fields.at(i).range ) { m_fields.at(i).range->setInsertBehaviors(MovingRange::DoNotExpand); } else { m_fields.at(i).range->setInsertBehaviors(MovingRange::ExpandLeft | MovingRange::ExpandRight); } } }; // new contents of the changed template field const auto& newText = doc()->text(changedField.range->toRange()); m_internalEdit = true; // go through all fields and update the contents of the dependent ones for ( auto field = m_fields.begin(); field != m_fields.end(); field++ ) { if ( field->kind == TemplateField::FinalCursorPosition ) { // only relevant on first run doc()->replaceText(field->range->toRange(), QString()); } if ( *field == changedField ) { // mark that the user changed this field field->touched = true; } // If this is mirrored field with the same identifier as the // changed one and the changed one is editable, mirror changes // edits to non-editable mirror fields are ignored if ( field->kind == TemplateField::Mirror && changedField.kind == TemplateField::Editable && field->identifier == changedField.identifier ) { // editable field changed, mirror changes dontExpandOthers(*field); doc()->replaceText(field->range->toRange(), newText); } else if ( field->kind == TemplateField::FunctionCall ) { // replace field by result of function call dontExpandOthers(*field); // build map of objects in the scope to pass to the function auto map = fieldMap(); const auto& callResult = m_templateScript.evaluate(field->identifier, map); doc()->replaceText(field->range->toRange(), callResult.toString()); } } m_internalEdit = false; updateRangeBehaviours(); } void KateTemplateHandler::updateRangeBehaviours() { KTextEditor::Cursor last = {-1, -1}; for ( int i = 0; i < m_fields.size(); i++ ) { auto field = m_fields.at(i); auto end = field.range->end().toCursor(); auto start = field.range->start().toCursor(); if ( field.kind == TemplateField::FinalCursorPosition ) { // final cursor position never grows field.range->setInsertBehaviors(MovingRange::DoNotExpand); } else if ( start <= last ) { // ranges are adjacent, only expand to the right to prevent overlap field.range->setInsertBehaviors(MovingRange::ExpandRight); } else { // ranges are not adjacent, can grow in both directions field.range->setInsertBehaviors(MovingRange::ExpandLeft | MovingRange::ExpandRight); } last = end; } } KateScript::FieldMap KateTemplateHandler::fieldMap() const { KateScript::FieldMap map; Q_FOREACH ( const auto& field, m_fields ) { if ( field.kind != TemplateField::Editable ) { // only editable fields are of interest to the scripts continue; } map.insert(field.identifier, QScriptValue(doc()->text(field.range->toRange()))); } return map; } KTextEditor::ViewPrivate *KateTemplateHandler::view() const { return m_view; } diff --git a/src/utils/katetemplatehandler.h b/src/utils/katetemplatehandler.h index dd3db3e4..887c506b 100644 --- a/src/utils/katetemplatehandler.h +++ b/src/utils/katetemplatehandler.h @@ -1,250 +1,252 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2004,2010 Joseph Wenninger * Copyright (C) 2009 Milian Wolff * 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 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_TEMPLATE_HANDLER_H_ #define _KATE_TEMPLATE_HANDLER_H_ #include #include #include #include #include #include class KateUndoManager; namespace KTextEditor { class DocumentPrivate; class ViewPrivate; class MovingCursor; class MovingRange; } /** * \brief Inserts a template and offers advanced snippet features, like navigation and mirroring. * * For each template inserted a new KateTemplateHandler will be created. * * The handler has the following features: * * \li It inserts the template string into the document at the requested position. * \li When the template contains at least one variable, the cursor will be placed * at the start of the first variable and its range gets selected. * \li When more than one variable exists,TAB and SHIFT TAB can be used to navigate * to the next/previous variable. * \li When a variable occurs more than once in the template, edits to any of the * occurrences will be mirroed to the other ones. * \li When ESC is pressed, the template handler closes. * \li When ALT + RETURN is pressed and a \c ${cursor} variable * exists in the template,the cursor will be placed there. Else the cursor will * be placed at the end of the template. * * \author Milian Wolff */ class KateTemplateHandler: public QObject { Q_OBJECT public: /** * Setup the template handler, insert the template string. * * NOTE: The handler deletes itself when required, you do not need to * keep track of it. */ KateTemplateHandler(KTextEditor::ViewPrivate *view, KTextEditor::Cursor position, const QString &templateString, const QString& script, KateUndoManager *undoManager); virtual ~KateTemplateHandler(); protected: /** * \brief Provide keyboard interaction for the template handler. * * The event filter handles the following shortcuts: * * TAB: jump to next editable (i.e. not mirrored) range. * NOTE: this prevents indenting via TAB. * SHIFT + TAB: jump to previous editable (i.e. not mirrored) range. * NOTE: this prevents un-indenting via SHIFT + TAB. * ESC: terminate template handler (only when no completion is active). * ALT + RETURN: accept template and jump to the end-cursor. * if %{cursor} was given in the template, that will be the * end-cursor. * else just jump to the end of the inserted text. */ bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; private: /** * Inserts the @p text template at @p position and performs * all necessary initializations, such as populating default values * and placing the cursor. */ void initializeTemplate(); /** * Parse @p templateText and populate m_fields. */ void parseFields(const QString& templateText); /** * Set necessary attributes (esp. background colour) on all moving * ranges for the fields in m_fields. */ void setupFieldRanges(); /** * Evaluate default values for all fields in m_fields and * store them in the fields. This updates the @property defaultValue property - * of the @class TemplateField instances in m_fields from the raw, user-entered + * of the TemplateField instances in m_fields from the raw, user-entered * default value to its evaluated equivalent (e.g. "func()" -> result of function call) + * + * @sa TemplateField */ void setupDefaultValues(); /** * Install an event filter on the filter proxy of \p view for * navigation between the ranges and terminating the KateTemplateHandler. * * \see eventFilter() */ void setupEventHandler(KTextEditor::View *view); /** * Jumps to the previous editable range. If there is none, wrap and jump to the first range. * * \see jumpToNextRange() */ void jumpToPreviousRange(); /** * Jumps to the next editable range. If there is none, wrap and jump to the last range. * * \see jumpToPreviousRange() */ void jumpToNextRange(); /** * Helper function for jumpTo{Next,Previous} * if initial is set to true, assumes the cursor is before the snippet * and selects the first field */ void jump(int by, bool initial = false); /** * Jumps to the final cursor position. This is either \p m_finalCursorPosition, or * if that is not set, the end of \p m_templateRange. */ void jumpToFinalCursorPosition(); /** * Go through all template fields and decide if their moving ranges expand * when edited at the corners. Expansion is turned off if two fields are * directly adjacent to avoid overlaps when characters are inserted between * them. */ void updateRangeBehaviours(); /** * Sort all template fields in m_fields by tab order, which means, * by range; except for ${cursor} which is always sorted last. */ void sortFields(); private Q_SLOTS: /** * Saves the range of the inserted template. This is required since * tabs could get expanded on insert. While we are at it, we can * use it to auto-indent the code after insert. */ void slotTemplateInserted(KTextEditor::Document *document, const KTextEditor::Range &range); /** * Install event filter on new views. */ void slotViewCreated(KTextEditor::Document *document, KTextEditor::View *view); /** * Update content of all dependent fields, i.e. mirror or script fields. */ void updateDependentFields(KTextEditor::Document *document, const KTextEditor::Range &oldRange); public: KTextEditor::ViewPrivate* view() const; KTextEditor::DocumentPrivate* doc() const; private: /// The view we operate on KTextEditor::ViewPrivate* m_view; /// The undo manager associated with our document KateUndoManager* const m_undoManager; // Describes a single template field, e.g. ${foo}. struct TemplateField { // up-to-date range for the field QSharedPointer range; // contents of the field, i.e. identifier or function to call QString identifier; // default value, if applicable; else empty QString defaultValue; enum Kind { Invalid, // not an actual field Editable, // normal, user-editable field (green by default) [non-dependent field] Mirror, // field mirroring contents of another field [dependent field] FunctionCall, // field containing the up-to-date result of a function call [dependent field] FinalCursorPosition // field marking the final cursor position }; Kind kind = Invalid; // true if this field was edited by the user before bool touched = false; bool operator==(const TemplateField& other) { return range == other.range; } }; // List of all template fields in the inserted snippet. @see sortFields() QVector m_fields; // Get the template field which contains @p range. const TemplateField fieldForRange(const KTextEditor::Range& range) const; /// Construct a map of master fields and their current value, for use in scripts. KateScript::FieldMap fieldMap() const; /// A range that occupies the whole range of the inserted template. /// When the an edit happens outside it, the template handler gets closed. QSharedPointer m_wholeTemplateRange; /// Set to true when currently updating dependent fields, to prevent recursion. bool m_internalEdit; /// template script (i.e. javascript stuff), which can be used by the current template KateScript m_templateScript; }; #endif diff --git a/src/utils/ktexteditor.cpp b/src/utils/ktexteditor.cpp index 2a2bf1c9..65c047bd 100644 --- a/src/utils/ktexteditor.cpp +++ b/src/utils/ktexteditor.cpp @@ -1,251 +1,281 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann (cullmann@kde.org) 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 "kateview.h" #include "cursor.h" #include "configpage.h" #include "editor.h" #include "document.h" #include "view.h" #include "plugin.h" #include "command.h" #include "markinterface.h" #include "modificationinterface.h" #include "sessionconfiginterface.h" #include "texthintinterface.h" #include "annotationinterface.h" #include "kateglobal.h" #include "kateconfig.h" #include "katecmd.h" using namespace KTextEditor; Cursor Cursor::fromString(const QStringRef& str) Q_DECL_NOEXCEPT { // parse format "(line, column)" const int startIndex = str.indexOf(QLatin1Char('(')); const int endIndex = str.indexOf(QLatin1Char(')')); const int commaIndex = str.indexOf(QLatin1Char(',')); if (startIndex < 0 || endIndex < 0 || commaIndex < 0 || commaIndex < startIndex || endIndex < commaIndex || endIndex < startIndex) { return invalid(); } bool ok1 = false; bool ok2 = false; const int line = str.mid(startIndex + 1, commaIndex - startIndex - 1).toInt(&ok1); const int column = str.mid(commaIndex + 1, endIndex - commaIndex - 1).toInt(&ok2); if (!ok1 || !ok2) { return invalid(); } return {line, column}; } Editor::Editor(EditorPrivate *impl) : QObject () , d (impl) { } Editor::~Editor() { } Editor *KTextEditor::Editor::instance() { /** * Just use internal KTextEditor::EditorPrivate::self() */ return KTextEditor::EditorPrivate::self(); } QString Editor::defaultEncoding () const { /** * return default encoding in global config object */ return d->documentConfig()->encoding (); } bool View::insertText(const QString &text) { KTextEditor::Document *doc = document(); if (!doc) { return false; } return doc->insertText(cursorPosition(), text, blockSelection()); } bool View::isStatusBarEnabled() const { /** * is the status bar around? */ return !!d->statusBar(); } void View::setStatusBarEnabled(bool enable) { /** * no state change, do nothing */ if (enable == !!d->statusBar()) return; /** * else toggle it */ d->toggleStatusBar (); } bool View::insertTemplate(const KTextEditor::Cursor& insertPosition, const QString& templateString, const QString& script) { return d->insertTemplateInternal(insertPosition, templateString, script); } ConfigPage::ConfigPage(QWidget *parent) : QWidget(parent) - , d(Q_NULLPTR) + , d(nullptr) {} ConfigPage::~ConfigPage() {} QString ConfigPage::fullName() const { return name(); } QIcon ConfigPage::icon() const { return QIcon::fromTheme(QStringLiteral("document-properties")); } View::View (ViewPrivate *impl, QWidget *parent) : QWidget (parent), KXMLGUIClient() , d(impl) {} View::~View() {} Plugin::Plugin(QObject *parent) : QObject(parent) - , d(Q_NULLPTR) + , d(nullptr) {} Plugin::~Plugin() {} int Plugin::configPages() const { return 0; } ConfigPage *Plugin::configPage(int, QWidget *) { - return Q_NULLPTR; + return nullptr; } MarkInterface::MarkInterface() - : d(Q_NULLPTR) + : d(nullptr) {} MarkInterface::~MarkInterface() {} ModificationInterface::ModificationInterface() - : d(Q_NULLPTR) + : d(nullptr) {} ModificationInterface::~ModificationInterface() {} SessionConfigInterface::SessionConfigInterface() - : d(Q_NULLPTR) + : d(nullptr) {} SessionConfigInterface::~SessionConfigInterface() {} TextHintInterface::TextHintInterface() - : d(Q_NULLPTR) + : d(nullptr) {} TextHintInterface::~TextHintInterface() {} TextHintProvider::TextHintProvider() - : d(Q_NULLPTR) + : d(nullptr) {} TextHintProvider::~TextHintProvider() {} Command::Command(const QStringList &cmds, QObject *parent) : QObject(parent) , m_cmds (cmds) - , d(Q_NULLPTR) + , d(nullptr) { // register this command static_cast (KTextEditor::Editor::instance())->cmdManager()->registerCommand (this); } Command::~Command() { // unregister this command, if instance is still there! if (KTextEditor::Editor::instance()) static_cast (KTextEditor::Editor::instance())->cmdManager()->unregisterCommand (this); } bool Command::supportsRange(const QString &) { return false; } KCompletion *Command::completionObject(KTextEditor::View *, const QString &) { - return Q_NULLPTR; + return nullptr; } bool Command::wantsToProcessText(const QString &) { return false; } void Command::processText(KTextEditor::View *, const QString &) { } + +void View::setScrollPosition(KTextEditor::Cursor &cursor) +{ + d->setScrollPositionInternal(cursor); +} + +void View::setHorizontalScrollPosition(int x) +{ + d->setHorizontalScrollPositionInternal(x); +} + +KTextEditor::Cursor View::maxScrollPosition() const +{ + return d->maxScrollPositionInternal(); +} + +int View::firstDisplayedLine(LineType lineType) const +{ + return d->firstDisplayedLineInternal(lineType); +} + +int View::lastDisplayedLine(LineType lineType) const +{ + return d->lastDisplayedLineInternal(lineType); +} + +QRect View::textAreaRect() const +{ + return d->textAreaRectInternal(); +} diff --git a/src/utils/mainwindow.cpp b/src/utils/mainwindow.cpp index 89068a5f..f389311a 100644 --- a/src/utils/mainwindow.cpp +++ b/src/utils/mainwindow.cpp @@ -1,310 +1,310 @@ /* * This file is part of the KDE project. * * Copyright (C) 2013 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 #include #include namespace KTextEditor { MainWindow::MainWindow(QObject *parent) : QObject(parent) - , d(Q_NULLPTR) + , d(nullptr) { } MainWindow::~MainWindow() { } QWidget *MainWindow::window() { /** * dispatch to parent */ - QWidget *window = Q_NULLPTR; + QWidget *window = nullptr; QMetaObject::invokeMethod(parent() , "window" , Qt::DirectConnection , Q_RETURN_ARG(QWidget *, window)); return window; } KXMLGUIFactory *MainWindow::guiFactory() { /** * dispatch to parent */ - KXMLGUIFactory *guiFactory = Q_NULLPTR; + KXMLGUIFactory *guiFactory = nullptr; QMetaObject::invokeMethod(parent() , "guiFactory" , Qt::DirectConnection , Q_RETURN_ARG(KXMLGUIFactory *, guiFactory)); return guiFactory; } QList MainWindow::views() { /** * dispatch to parent */ QList views; QMetaObject::invokeMethod(parent() , "views" , Qt::DirectConnection , Q_RETURN_ARG(QList, views)); return views; } KTextEditor::View *MainWindow::activeView() { /** * dispatch to parent */ - KTextEditor::View *view = Q_NULLPTR; + KTextEditor::View *view = nullptr; QMetaObject::invokeMethod(parent() , "activeView" , Qt::DirectConnection , Q_RETURN_ARG(KTextEditor::View *, view)); return view; } KTextEditor::View *MainWindow::activateView(KTextEditor::Document *document) { /** * dispatch to parent */ - KTextEditor::View *view = Q_NULLPTR; + KTextEditor::View *view = nullptr; QMetaObject::invokeMethod(parent() , "activateView" , Qt::DirectConnection , Q_RETURN_ARG(KTextEditor::View *, view) , Q_ARG(KTextEditor::Document *, document)); return view; } KTextEditor::View *MainWindow::openUrl(const QUrl &url, const QString &encoding) { /** * dispatch to parent */ - KTextEditor::View *view = Q_NULLPTR; + KTextEditor::View *view = nullptr; QMetaObject::invokeMethod(parent() , "openUrl" , Qt::DirectConnection , Q_RETURN_ARG(KTextEditor::View *, view) , Q_ARG(const QUrl &, url) , Q_ARG(const QString &, encoding)); return view; } bool MainWindow::closeView(KTextEditor::View *view) { /** * dispatch to parent */ bool success = false; QMetaObject::invokeMethod(parent() , "closeView" , Qt::DirectConnection , Q_RETURN_ARG(bool, success) , Q_ARG(KTextEditor::View *, view)); return success; } void MainWindow::splitView(Qt::Orientation orientation) { /** * dispatch to parent */ QMetaObject::invokeMethod(parent() , "splitView" , Qt::DirectConnection , Q_ARG(Qt::Orientation, orientation)); } bool MainWindow::closeSplitView(KTextEditor::View *view) { /** * dispatch to parent */ bool success = false; QMetaObject::invokeMethod(parent() , "closeSplitView" , Qt::DirectConnection , Q_RETURN_ARG(bool, success) , Q_ARG(KTextEditor::View *, view)); return success; } bool MainWindow::viewsInSameSplitView(KTextEditor::View *view1, KTextEditor::View *view2) { /** * dispatch to parent */ bool success = false; QMetaObject::invokeMethod(parent() , "viewsInSameSplitView" , Qt::DirectConnection , Q_RETURN_ARG(bool, success) , Q_ARG(KTextEditor::View *, view1) , Q_ARG(KTextEditor::View *, view2)); return success; } QWidget *MainWindow::createViewBar(KTextEditor::View *view) { /** * dispatch to parent */ - QWidget *viewBar = Q_NULLPTR; + QWidget *viewBar = nullptr; QMetaObject::invokeMethod(parent() , "createViewBar" , Qt::DirectConnection , Q_RETURN_ARG(QWidget *, viewBar) , Q_ARG(KTextEditor::View *, view)); return viewBar; } void MainWindow::deleteViewBar(KTextEditor::View *view) { /** * dispatch to parent */ QMetaObject::invokeMethod(parent() , "deleteViewBar" , Qt::DirectConnection , Q_ARG(KTextEditor::View *, view)); } void MainWindow::addWidgetToViewBar(KTextEditor::View *view, QWidget *bar) { /** * dispatch to parent */ QMetaObject::invokeMethod(parent() , "addWidgetToViewBar" , Qt::DirectConnection , Q_ARG(KTextEditor::View *, view) , Q_ARG(QWidget *, bar)); } void MainWindow::showViewBar(KTextEditor::View *view) { /** * dispatch to parent */ QMetaObject::invokeMethod(parent() , "showViewBar" , Qt::DirectConnection , Q_ARG(KTextEditor::View *, view)); } void MainWindow::hideViewBar(KTextEditor::View *view) { /** * dispatch to parent */ QMetaObject::invokeMethod(parent() , "hideViewBar" , Qt::DirectConnection , Q_ARG(KTextEditor::View *, view)); } QWidget *MainWindow::createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text) { /** * dispatch to parent */ - QWidget *toolView = Q_NULLPTR; + QWidget *toolView = nullptr; QMetaObject::invokeMethod(parent() , "createToolView" , Qt::DirectConnection , Q_RETURN_ARG(QWidget *, toolView) , Q_ARG(KTextEditor::Plugin *, plugin) , Q_ARG(const QString &, identifier) , Q_ARG(KTextEditor::MainWindow::ToolViewPosition, pos) , Q_ARG(const QIcon &, icon) , Q_ARG(const QString &, text)); return toolView; } bool MainWindow::moveToolView(QWidget *widget, KTextEditor::MainWindow::ToolViewPosition pos) { /** * dispatch to parent */ bool success = false; QMetaObject::invokeMethod(parent() , "moveToolView" , Qt::DirectConnection , Q_RETURN_ARG(bool, success) , Q_ARG(QWidget *, widget) , Q_ARG(KTextEditor::MainWindow::ToolViewPosition, pos)); return success; } bool MainWindow::showToolView(QWidget *widget) { /** * dispatch to parent */ bool success = false; QMetaObject::invokeMethod(parent() , "showToolView" , Qt::DirectConnection , Q_RETURN_ARG(bool, success) , Q_ARG(QWidget *, widget)); return success; } bool MainWindow::hideToolView(QWidget *widget) { /** * dispatch to parent */ bool success = false; QMetaObject::invokeMethod(parent() , "hideToolView" , Qt::DirectConnection , Q_RETURN_ARG(bool, success) , Q_ARG(QWidget *, widget)); return success; } QObject *MainWindow::pluginView(const QString &name) { /** * dispatch to parent */ - QObject *pluginView = Q_NULLPTR; + QObject *pluginView = nullptr; QMetaObject::invokeMethod(parent() , "pluginView" , Qt::DirectConnection , Q_RETURN_ARG(QObject *, pluginView) , Q_ARG(const QString &, name)); return pluginView; } } // namespace KTextEditor diff --git a/src/utils/messageinterface.cpp b/src/utils/messageinterface.cpp index 45d0657c..7ef5a463 100644 --- a/src/utils/messageinterface.cpp +++ b/src/utils/messageinterface.cpp @@ -1,180 +1,180 @@ /* This file is part of the KDE project * * Copyright (C) 2012-2013 Dominik Haumann * * 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 "ktexteditor/message.h" namespace KTextEditor { class MessagePrivate { public: QList actions; Message::MessageType messageType; Message::MessagePosition position; QString text; QIcon icon; bool wordWrap; int autoHide; KTextEditor::Message::AutoHideMode autoHideMode; int priority; KTextEditor::View *view; KTextEditor::Document *document; }; Message::Message(const QString &richtext, MessageType type) : d(new MessagePrivate()) { d->messageType = type; d->position = Message::AboveView; d->text = richtext; d->wordWrap = false; d->autoHide = -1; d->autoHideMode = KTextEditor::Message::AfterUserInteraction; d->priority = 0; - d->view = 0; - d->document = 0; + d->view = nullptr; + d->document = nullptr; } Message::~Message() { emit closed(this); delete d; } QString Message::text() const { return d->text; } void Message::setText(const QString &text) { if (d->text != text) { d->text = text; emit textChanged(text); } } void Message::setIcon(const QIcon &newIcon) { d->icon = newIcon; emit iconChanged(d->icon); } QIcon Message::icon() const { return d->icon; } Message::MessageType Message::messageType() const { return d->messageType; } void Message::addAction(QAction *action, bool closeOnTrigger) { // make sure this is the parent, so all actions are deleted in the destructor action->setParent(this); d->actions.append(action); // call close if wanted if (closeOnTrigger) { connect(action, SIGNAL(triggered()), SLOT(deleteLater())); } } QList Message::actions() const { return d->actions; } void Message::setAutoHide(int autoHideTimer) { d->autoHide = autoHideTimer; } int Message::autoHide() const { return d->autoHide; } void Message::setAutoHideMode(KTextEditor::Message::AutoHideMode mode) { d->autoHideMode = mode; } KTextEditor::Message::AutoHideMode Message::autoHideMode() const { return d->autoHideMode; } void Message::setWordWrap(bool wordWrap) { d->wordWrap = wordWrap; } bool Message::wordWrap() const { return d->wordWrap; } void Message::setPriority(int priority) { d->priority = priority; } int Message::priority() const { return d->priority; } void Message::setView(KTextEditor::View *view) { d->view = view; } KTextEditor::View *Message::view() const { return d->view; } void Message::setDocument(KTextEditor::Document *document) { d->document = document; } KTextEditor::Document *Message::document() const { return d->document; } void Message::setPosition(Message::MessagePosition position) { d->position = position; } Message::MessagePosition Message::position() const { return d->position; } } diff --git a/src/utils/movinginterface.cpp b/src/utils/movinginterface.cpp index afd92c6c..42682909 100644 --- a/src/utils/movinginterface.cpp +++ b/src/utils/movinginterface.cpp @@ -1,36 +1,36 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 "movinginterface.h" using namespace KTextEditor; MovingInterface::MovingInterface() - : d(0) + : d(nullptr) { } MovingInterface::~MovingInterface() { } diff --git a/src/utils/movingrangefeedback.cpp b/src/utils/movingrangefeedback.cpp index 6db6f224..b0c757e9 100644 --- a/src/utils/movingrangefeedback.cpp +++ b/src/utils/movingrangefeedback.cpp @@ -1,60 +1,60 @@ /* This file is part of the KDE project * * Copyright (C) 2010 Christoph Cullmann * * Based on code of the SmartCursor/Range by: * Copyright (C) 2003-2005 Hamish Rodda * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License 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 "movingrangefeedback.h" using namespace KTextEditor; MovingRangeFeedback::MovingRangeFeedback() - : d(0) + : d(nullptr) { } MovingRangeFeedback::~MovingRangeFeedback() { } void MovingRangeFeedback::rangeEmpty(MovingRange *) { } void MovingRangeFeedback::rangeInvalid(MovingRange *) { } void MovingRangeFeedback::mouseEnteredRange(MovingRange *, View *) { } void MovingRangeFeedback::mouseExitedRange(MovingRange *, View *) { } void MovingRangeFeedback::caretEnteredRange(MovingRange *, View *) { } void MovingRangeFeedback::caretExitedRange(MovingRange *, View *) { } diff --git a/src/variableeditor/katehelpbutton.h b/src/variableeditor/katehelpbutton.h index 47ba2b80..f7202742 100644 --- a/src/variableeditor/katehelpbutton.h +++ b/src/variableeditor/katehelpbutton.h @@ -1,52 +1,52 @@ /* This file is part of the KDE project Copyright (C) 2011 Dominik Haumann 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_HELP_BUTTON_H #define KATE_HELP_BUTTON_H #include class KateHelpButton : public QToolButton { Q_OBJECT public: enum IconState { IconColored = 0, IconGrayscaled, IconHidden }; void setSection(const QString §ion); public: - KateHelpButton(QWidget *parent = 0); + KateHelpButton(QWidget *parent = nullptr); virtual ~KateHelpButton(); public Q_SLOTS: void setIconState(IconState state); void invokeHelp(); private: QString m_section; }; #endif diff --git a/src/variableeditor/variableeditor.h b/src/variableeditor/variableeditor.h index e559777e..46ba1346 100644 --- a/src/variableeditor/variableeditor.h +++ b/src/variableeditor/variableeditor.h @@ -1,187 +1,187 @@ /* This file is part of the KDE project Copyright (C) 2011 Dominik Haumann 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 VARIABLE_EDITOR_H #define VARIABLE_EDITOR_H #include class KateHelpButton; class VariableBoolItem; class VariableColorItem; class VariableFontItem; class VariableItem; class VariableStringListItem; class VariableIntItem; class VariableStringItem; class VariableSpellCheckItem; class VariableRemoveSpacesItem; class KColorCombo; class QFontComboBox; class QCheckBox; class QComboBox; class QLabel; class QLineEdit; class QSpinBox; namespace Sonnet { class DictionaryComboBox; } class VariableEditor : public QWidget { Q_OBJECT public: - VariableEditor(VariableItem *item, QWidget *parent = 0); + VariableEditor(VariableItem *item, QWidget *parent = nullptr); virtual ~VariableEditor(); VariableItem *item() const; Q_SIGNALS: void valueChanged(); protected Q_SLOTS: void itemEnabled(bool enabled); void activateItem(); protected: void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; void enterEvent(QEvent *event) Q_DECL_OVERRIDE; void leaveEvent(QEvent *event) Q_DECL_OVERRIDE; private: VariableItem *m_item; QCheckBox *m_checkBox; QLabel *m_variable; QLabel *m_helpText; KateHelpButton *m_btnHelp; }; class VariableIntEditor : public VariableEditor { Q_OBJECT public: VariableIntEditor(VariableIntItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(int newValue); private: QSpinBox *m_spinBox; }; class VariableBoolEditor : public VariableEditor { Q_OBJECT public: VariableBoolEditor(VariableBoolItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(int enabled); private: QComboBox *m_comboBox; }; class VariableStringListEditor : public VariableEditor { Q_OBJECT public: VariableStringListEditor(VariableStringListItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(const QString &newValue); private: QComboBox *m_comboBox; }; class VariableColorEditor : public VariableEditor { Q_OBJECT public: VariableColorEditor(VariableColorItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(const QColor &newValue); private: KColorCombo *m_comboBox; }; class VariableFontEditor : public VariableEditor { Q_OBJECT public: VariableFontEditor(VariableFontItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(const QFont &newValue); private: QFontComboBox *m_comboBox; }; class VariableStringEditor : public VariableEditor { Q_OBJECT public: VariableStringEditor(VariableStringItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(const QString &newValue); private: QLineEdit *m_lineEdit; }; class VariableSpellCheckEditor : public VariableEditor { Q_OBJECT public: VariableSpellCheckEditor(VariableSpellCheckItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(const QString &newValue); private: Sonnet::DictionaryComboBox *m_dictionaryCombo; }; class VariableRemoveSpacesEditor : public VariableEditor { Q_OBJECT public: VariableRemoveSpacesEditor(VariableRemoveSpacesItem *item, QWidget *parent); protected Q_SLOTS: void setItemValue(int enabled); private: QComboBox *m_comboBox; }; #endif diff --git a/src/variableeditor/variablelineedit.cpp b/src/variableeditor/variablelineedit.cpp index 52f8f9ad..c2818b29 100644 --- a/src/variableeditor/variablelineedit.cpp +++ b/src/variableeditor/variablelineedit.cpp @@ -1,377 +1,377 @@ /* This file is part of the KDE project Copyright (C) 2011 Dominik Haumann Copyright (C) 2013 Gerald Senarclens de Grancy 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 "variablelineedit.h" #include "variableitem.h" #include "variablelistview.h" #include "kateautoindent.h" #include "katesyntaxmanager.h" #include "kateschema.h" #include "kateview.h" #include "katedocument.h" #include "kateglobal.h" #include "katerenderer.h" #include #include #include #include #include #include #include #include #include #include VariableLineEdit::VariableLineEdit(QWidget *parent) : QWidget(parent) { - m_listview = 0; + m_listview = nullptr; QHBoxLayout *hl = new QHBoxLayout(); hl->setMargin(0); hl->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); setLayout(hl); m_lineedit = new QLineEdit(this); m_button = new QToolButton(this); m_button->setIcon(QIcon::fromTheme(QStringLiteral("tools-wizard"))); m_button->setToolTip(i18n("Show list of valid variables.")); hl->addWidget(m_lineedit); hl->addWidget(m_button); - m_popup = new QFrame(0, Qt::Popup); + m_popup = new QFrame(nullptr, Qt::Popup); m_popup->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); QVBoxLayout *l = new QVBoxLayout(m_popup); l->setSpacing(0); l->setMargin(0); m_popup->setLayout(l); // forward text changed signal connect(m_lineedit, SIGNAL(textChanged(QString)), this, SIGNAL(textChanged(QString))); // open popup on button click connect(m_button, SIGNAL(clicked()), this, SLOT(editVariables())); } VariableLineEdit::~VariableLineEdit() { } void VariableLineEdit::editVariables() { m_listview = new VariableListView(m_lineedit->text(), m_popup); addKateItems(m_listview); connect(m_listview, SIGNAL(aboutToHide()), this, SLOT(updateVariableLine())); m_popup->layout()->addWidget(m_listview); if (layoutDirection() == Qt::LeftToRight) { QPoint topLeft = mapToGlobal(m_lineedit->geometry().bottomLeft()); const int w = m_button->geometry().right() - m_lineedit->geometry().left(); const int h = 300; //(w * 2) / 4; m_popup->setGeometry(QRect(topLeft, QSize(w, h))); } else { QPoint topLeft = mapToGlobal(m_button->geometry().bottomLeft()); const int w = m_lineedit->geometry().right() - m_button->geometry().left(); const int h = 300; //(w * 2) / 4; m_popup->setGeometry(QRect(topLeft, QSize(w, h))); } m_popup->show(); } void VariableLineEdit::updateVariableLine() { QString variables = m_listview->variableLine(); m_lineedit->setText(variables); m_popup->layout()->removeWidget(m_listview); m_listview->deleteLater(); - m_listview = 0; + m_listview = nullptr; } void VariableLineEdit::addKateItems(VariableListView *listview) { - VariableItem *item = 0; + VariableItem *item = nullptr; // If a current active doc is available - KTextEditor::ViewPrivate *activeView = 0; - KTextEditor::DocumentPrivate *activeDoc = 0; + KTextEditor::ViewPrivate *activeView = nullptr; + KTextEditor::DocumentPrivate *activeDoc = nullptr; KateDocumentConfig *docConfig = KateDocumentConfig::global(); KateViewConfig *viewConfig = KateViewConfig::global(); KateRendererConfig *rendererConfig = KateRendererConfig::global(); if ((activeView = qobject_cast(KTextEditor::EditorPrivate::self()->application()->activeMainWindow()->activeView()))) { activeDoc = activeView->doc(); viewConfig = activeView->config(); docConfig = activeDoc->config(); rendererConfig = activeView->renderer()->config(); } // Add 'auto-brackets' to list item = new VariableBoolItem(QStringLiteral("auto-brackets"), false); item->setHelpText(i18nc("short translation please", "Enable automatic insertion of brackets.")); listview->addItem(item); // Add 'auto-center-lines' to list item = new VariableIntItem(QStringLiteral("auto-center-lines"), viewConfig->autoCenterLines()); static_cast(item)->setRange(1, 100); item->setHelpText(i18nc("short translation please", "Set the number of autocenter lines.")); listview->addItem(item); // Add 'background-color' to list item = new VariableColorItem(QStringLiteral("background-color"), rendererConfig->backgroundColor()); item->setHelpText(i18nc("short translation please", "Set the document background color.")); listview->addItem(item); // Add 'backspace-indents' to list item = new VariableBoolItem(QStringLiteral("backspace-indents"), docConfig->backspaceIndents()); item->setHelpText(i18nc("short translation please", "Pressing backspace in leading whitespace unindents.")); listview->addItem(item); // Add 'block-selection' to list item = new VariableBoolItem(QStringLiteral("block-selection"), false); if (activeView) { static_cast(item)->setValue(activeView->blockSelection()); } item->setHelpText(i18nc("short translation please", "Enable block selection mode.")); listview->addItem(item); - // Add 'byte-order-marker' (bom) to list - item = new VariableBoolItem(QStringLiteral("byte-order-marker"), docConfig->bom()); - item->setHelpText(i18nc("short translation please", "Enable the byte order marker when saving unicode files.")); + // Add 'byte-order-mark' (bom) to list + item = new VariableBoolItem(QStringLiteral("byte-order-mark"), docConfig->bom()); + item->setHelpText(i18nc("short translation please", "Enable the byte order mark (BOM) when saving Unicode files.")); listview->addItem(item); // Add 'bracket-highlight-color' to list item = new VariableColorItem(QStringLiteral("bracket-highlight-color"), rendererConfig->highlightedBracketColor()); item->setHelpText(i18nc("short translation please", "Set the color for the bracket highlight.")); listview->addItem(item); // Add 'current-line-color' to list item = new VariableColorItem(QStringLiteral("current-line-color"), rendererConfig->highlightedLineColor()); item->setHelpText(i18nc("short translation please", "Set the background color for the current line.")); listview->addItem(item); // Add 'default-dictionary' to list Sonnet::Speller speller; item = new VariableSpellCheckItem(QStringLiteral("default-dictionary"), speller.defaultLanguage()); item->setHelpText(i18nc("short translation please", "Set the default dictionary used for spell checking.")); listview->addItem(item); // Add 'dynamic-word-wrap' to list item = new VariableBoolItem(QStringLiteral("dynamic-word-wrap"), viewConfig->dynWordWrap()); item->setHelpText(i18nc("short translation please", "Enable dynamic word wrap of long lines.")); listview->addItem(item); // Add 'end-of-line' (eol) to list item = new VariableStringListItem(QStringLiteral("end-of-line"), QStringList() << QStringLiteral("unix") << QStringLiteral("mac") << QStringLiteral("dos"), docConfig->eolString()); item->setHelpText(i18nc("short translation please", "Sets the end of line mode.")); listview->addItem(item); // Add 'folding-markers' to list item = new VariableBoolItem(QStringLiteral("folding-markers"), viewConfig->foldingBar()); item->setHelpText(i18nc("short translation please", "Enable folding markers in the editor border.")); listview->addItem(item); // Add 'folding-preview' to list item = new VariableBoolItem(QStringLiteral("folding-preview"), viewConfig->foldingPreview()); item->setHelpText(i18nc("short translation please", "Enable folding preview on in the editor border.")); listview->addItem(item); // Add 'font-size' to list item = new VariableIntItem(QStringLiteral("font-size"), rendererConfig->font().pointSize()); static_cast(item)->setRange(4, 128); item->setHelpText(i18nc("short translation please", "Set the point size of the document font.")); listview->addItem(item); // Add 'font' to list item = new VariableFontItem(QStringLiteral("font"), rendererConfig->font()); item->setHelpText(i18nc("short translation please", "Set the font of the document.")); listview->addItem(item); // Add 'syntax' (hl) to list /* Prepare list of highlighting modes */ const int count = KateHlManager::self()->highlights(); QStringList hl; for (int z = 0; z < count; ++z) { hl << KateHlManager::self()->hlName(z); } item = new VariableStringListItem(QStringLiteral("syntax"), hl, hl.at(0)); if (activeDoc) { static_cast(item)->setValue(activeDoc->highlightingMode()); } item->setHelpText(i18nc("short translation please", "Set the syntax highlighting.")); listview->addItem(item); // Add 'icon-bar-color' to list item = new VariableColorItem(QStringLiteral("icon-bar-color"), rendererConfig->iconBarColor()); item->setHelpText(i18nc("short translation please", "Set the icon bar color.")); listview->addItem(item); // Add 'icon-border' to list item = new VariableBoolItem(QStringLiteral("icon-border"), viewConfig->iconBar()); item->setHelpText(i18nc("short translation please", "Enable the icon border in the editor view.")); listview->addItem(item); // Add 'indent-mode' to list item = new VariableStringListItem(QStringLiteral("indent-mode"), KateAutoIndent::listIdentifiers(), docConfig->indentationMode()); item->setHelpText(i18nc("short translation please", "Set the auto indentation style.")); listview->addItem(item); // Add 'indent-pasted-text' to list item = new VariableBoolItem(QStringLiteral("indent-pasted-text"), docConfig->indentPastedText()); item->setHelpText(i18nc("short translation please", "Adjust indentation of text pasted from the clipboard.")); listview->addItem(item); // Add 'indent-width' to list item = new VariableIntItem(QStringLiteral("indent-width"), docConfig->indentationWidth()); static_cast(item)->setRange(1, 16); item->setHelpText(i18nc("short translation please", "Set the indentation depth for each indent level.")); listview->addItem(item); // Add 'keep-extra-spaces' to list item = new VariableBoolItem(QStringLiteral("keep-extra-spaces"), docConfig->keepExtraSpaces()); item->setHelpText(i18nc("short translation please", "Allow odd indentation level (no multiple of indent width).")); listview->addItem(item); // Add 'line-numbers' to list item = new VariableBoolItem(QStringLiteral("line-numbers"), viewConfig->lineNumbers()); item->setHelpText(i18nc("short translation please", "Show line numbers.")); listview->addItem(item); // Add 'newline-at-eof' to list item = new VariableBoolItem(QStringLiteral("newline-at-eof"), docConfig->ovr()); item->setHelpText(i18nc("short translation please", "Insert newline at end of file on save.")); listview->addItem(item); // Add 'overwrite-mode' to list item = new VariableBoolItem(QStringLiteral("overwrite-mode"), docConfig->ovr()); item->setHelpText(i18nc("short translation please", "Enable overwrite mode in the document.")); listview->addItem(item); // Add 'persistent-selection' to list item = new VariableBoolItem(QStringLiteral("persistent-selection"), viewConfig->persistentSelection()); item->setHelpText(i18nc("short translation please", "Enable persistent text selection.")); listview->addItem(item); // Add 'replace-tabs-save' to list item = new VariableBoolItem(QStringLiteral("replace-tabs-save"), false); item->setHelpText(i18nc("short translation please", "Replace tabs with spaces when saving the document.")); listview->addItem(item); // Add 'replace-tabs' to list item = new VariableBoolItem(QStringLiteral("replace-tabs"), docConfig->replaceTabsDyn()); item->setHelpText(i18nc("short translation please", "Replace tabs with spaces.")); listview->addItem(item); // Add 'remove-trailing-spaces' to list item = new VariableRemoveSpacesItem(QStringLiteral("remove-trailing-spaces"), docConfig->removeSpaces()); item->setHelpText(i18nc("short translation please", "Remove trailing spaces when saving the document.")); listview->addItem(item); // Add 'scrollbar-minimap' to list item = new VariableBoolItem(QStringLiteral("scrollbar-minimap"), viewConfig->scrollBarMiniMap()); item->setHelpText(i18nc("short translation please", "Show scrollbar minimap.")); listview->addItem(item); // Add 'scrollbar-preview' to list item = new VariableBoolItem(QStringLiteral("scrollbar-preview"), viewConfig->scrollBarPreview()); item->setHelpText(i18nc("short translation please", "Show scrollbar preview.")); listview->addItem(item); // Add 'scheme' to list QStringList schemas; Q_FOREACH (const KateSchema &schema, KTextEditor::EditorPrivate::self()->schemaManager()->list()) { schemas.append(schema.rawName); } item = new VariableStringListItem(QStringLiteral("scheme"), schemas, rendererConfig->schema()); item->setHelpText(i18nc("short translation please", "Set the color scheme.")); listview->addItem(item); // Add 'selection-color' to list item = new VariableColorItem(QStringLiteral("selection-color"), rendererConfig->selectionColor()); item->setHelpText(i18nc("short translation please", "Set the text selection color.")); listview->addItem(item); // Add 'show-tabs' to list item = new VariableBoolItem(QStringLiteral("show-tabs"), docConfig->showTabs()); item->setHelpText(i18nc("short translation please", "Visualize tabs and trailing spaces.")); listview->addItem(item); // Add 'smart-home' to list item = new VariableBoolItem(QStringLiteral("smart-home"), docConfig->smartHome()); item->setHelpText(i18nc("short translation please", "Enable smart home navigation.")); listview->addItem(item); // Add 'tab-indents' to list item = new VariableBoolItem(QStringLiteral("tab-indents"), docConfig->tabIndentsEnabled()); item->setHelpText(i18nc("short translation please", "Pressing TAB key indents.")); listview->addItem(item); // Add 'tab-width' to list item = new VariableIntItem(QStringLiteral("tab-width"), docConfig->tabWidth()); static_cast(item)->setRange(1, 16); item->setHelpText(i18nc("short translation please", "Set the tab display width.")); listview->addItem(item); // Add 'undo-steps' to list item = new VariableIntItem(QStringLiteral("undo-steps"), 0); static_cast(item)->setRange(0, 100); item->setHelpText(i18nc("short translation please", "Set the number of undo steps to remember (0 equals infinity).")); listview->addItem(item); // Add 'word-wrap-column' to list item = new VariableIntItem(QStringLiteral("word-wrap-column"), docConfig->wordWrapAt()); static_cast(item)->setRange(20, 200); item->setHelpText(i18nc("short translation please", "Set the word wrap column.")); listview->addItem(item); // Add 'word-wrap-marker-color' to list item = new VariableColorItem(QStringLiteral("word-wrap-marker-color"), rendererConfig->wordWrapMarkerColor()); item->setHelpText(i18nc("short translation please", "Set the word wrap marker color.")); listview->addItem(item); // Add 'word-wrap' to list item = new VariableBoolItem(QStringLiteral("word-wrap"), docConfig->wordWrap()); item->setHelpText(i18nc("short translation please", "Enable word wrap while typing text.")); listview->addItem(item); } void VariableLineEdit::setText(const QString &text) { m_lineedit->setText(text); } void VariableLineEdit::clear() { m_lineedit->clear(); } QString VariableLineEdit::text() { return m_lineedit->text(); } diff --git a/src/variableeditor/variablelineedit.h b/src/variableeditor/variablelineedit.h index 355cf101..6afb2b21 100644 --- a/src/variableeditor/variablelineedit.h +++ b/src/variableeditor/variablelineedit.h @@ -1,59 +1,59 @@ /* This file is part of the KDE project Copyright (C) 2011 Dominik Haumann 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 VARIABLE_LINE_EDIT_H #define VARIABLE_LINE_EDIT_H #include class QFrame; class QLineEdit; class QToolButton; class VariableListView; class VariableLineEdit : public QWidget { Q_OBJECT public: - VariableLineEdit(QWidget *parent = 0); + VariableLineEdit(QWidget *parent = nullptr); virtual ~VariableLineEdit(); void addKateItems(VariableListView *listview); QString text(); public Q_SLOTS: void editVariables(); void setText(const QString &text); void clear(); void updateVariableLine(); Q_SIGNALS: void textChanged(const QString &); private: QFrame *m_popup; QLineEdit *m_lineedit; QToolButton *m_button; VariableListView *m_listview; }; #endif diff --git a/src/variableeditor/variablelistview.h b/src/variableeditor/variablelistview.h index ff195ed0..a4cd9064 100644 --- a/src/variableeditor/variablelistview.h +++ b/src/variableeditor/variablelistview.h @@ -1,60 +1,60 @@ /* This file is part of the KDE project Copyright (C) 2011 Dominik Haumann 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 VARIABLE_LIST_VIEW_H #define VARIABLE_LIST_VIEW_H #include #include class VariableItem; class VariableEditor; class VariableListView : public QScrollArea { Q_OBJECT public: - VariableListView(const QString &variableLine, QWidget *parent = 0); + VariableListView(const QString &variableLine, QWidget *parent = nullptr); virtual ~VariableListView(); void addItem(VariableItem *item); /// always returns the up-to-date variables line QString variableLine(); Q_SIGNALS: void aboutToHide(); void changed(); // unused right now protected: void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; void hideEvent(QHideEvent *event) Q_DECL_OVERRIDE; void parseVariables(const QString &line); QVector m_items; QVector m_editors; QMap m_variables; }; #endif diff --git a/src/view/kateanimation.cpp b/src/view/kateanimation.cpp index c3a1ca02..a36e51ee 100644 --- a/src/view/kateanimation.cpp +++ b/src/view/kateanimation.cpp @@ -1,100 +1,100 @@ /* This file is part of the KDE and the Kate project * * Copyright (C) 2013 Dominik Haumann * * 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 "kateanimation.h" #include "katefadeeffect.h" #include "kateglobal.h" #include #include #include KateAnimation::KateAnimation(KMessageWidget *widget, EffectType effect) : QObject(widget) , m_widget(widget) - , m_fadeEffect(0) + , m_fadeEffect(nullptr) { - Q_ASSERT(m_widget != 0); + Q_ASSERT(m_widget != nullptr); // create wanted effect if (effect == FadeEffect) { m_fadeEffect = new KateFadeEffect(widget); connect(m_fadeEffect, SIGNAL(hideAnimationFinished()), this, SIGNAL(widgetHidden())); connect(m_fadeEffect, SIGNAL(showAnimationFinished()), this, SIGNAL(widgetShown())); } else { connect(m_widget, SIGNAL(hideAnimationFinished()), this, SIGNAL(widgetHidden())); connect(m_widget, SIGNAL(showAnimationFinished()), this, SIGNAL(widgetShown())); } } bool KateAnimation::isHideAnimationRunning() const { return m_fadeEffect ? m_fadeEffect->isHideAnimationRunning() : m_widget->isHideAnimationRunning(); } bool KateAnimation::isShowAnimationRunning() const { return m_fadeEffect ? m_fadeEffect->isShowAnimationRunning() : m_widget->isShowAnimationRunning(); } void KateAnimation::show() { - Q_ASSERT(m_widget != 0); + Q_ASSERT(m_widget != nullptr); // show according to effects config - if (m_widget->style()->styleHint(QStyle::SH_Widget_Animate, 0, m_widget)) { + if (m_widget->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, m_widget)) { // launch show effect // NOTE: use a singleShot timer to avoid resizing issues when showing the message widget the first time (bug #316666) if (m_fadeEffect) { QTimer::singleShot(0, m_fadeEffect, SLOT(fadeIn())); } else { QTimer::singleShot(0, m_widget, SLOT(animatedShow())); } } else { m_widget->show(); emit widgetShown(); } } void KateAnimation::hide() { - Q_ASSERT(m_widget != 0); + Q_ASSERT(m_widget != nullptr); // hide according to effects config - if (m_widget->style()->styleHint(QStyle::SH_Widget_Animate, 0, m_widget) + if (m_widget->style()->styleHint(QStyle::SH_Widget_Animate, nullptr, m_widget) || KTextEditor::EditorPrivate::unitTestMode() // due to timing issues in the unit test ) { // hide depending on effect if (m_fadeEffect) { m_fadeEffect->fadeOut(); } else { m_widget->animatedHide(); } } else { m_widget->hide(); emit widgetHidden(); } } diff --git a/src/view/katefadeeffect.cpp b/src/view/katefadeeffect.cpp index 4557f5ab..50804f5e 100644 --- a/src/view/katefadeeffect.cpp +++ b/src/view/katefadeeffect.cpp @@ -1,105 +1,105 @@ /* This file is part of the KDE and the Kate project * * Copyright (C) 2013 Dominik Haumann * * 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 "katefadeeffect.h" #include #include #include KateFadeEffect::KateFadeEffect(QWidget *widget) : QObject(widget) , m_widget(widget) - , m_effect(0) // effect only exists during fading animation + , m_effect(nullptr) // effect only exists during fading animation { m_timeLine = new QTimeLine(500, this); m_timeLine->setUpdateInterval(40); connect(m_timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(opacityChanged(qreal))); connect(m_timeLine, SIGNAL(finished()), this, SLOT(animationFinished())); } bool KateFadeEffect::isHideAnimationRunning() const { return (m_timeLine->direction() == QTimeLine::Backward) && (m_timeLine->state() == QTimeLine::Running); } bool KateFadeEffect::isShowAnimationRunning() const { return (m_timeLine->direction() == QTimeLine::Forward) && (m_timeLine->state() == QTimeLine::Running); } void KateFadeEffect::fadeIn() { // stop time line if still running if (m_timeLine->state() == QTimeLine::Running) { m_timeLine->stop(); } // assign new graphics effect, old one is deleted in setGraphicsEffect() m_effect = new QGraphicsOpacityEffect(this); m_effect->setOpacity(0.0); m_widget->setGraphicsEffect(m_effect); // show widget and start fade in animation m_widget->show(); m_timeLine->setDirection(QTimeLine::Forward); m_timeLine->start(); } void KateFadeEffect::fadeOut() { // stop time line if still running if (m_timeLine->state() == QTimeLine::Running) { m_timeLine->stop(); } // assign new graphics effect, old one is deleted in setGraphicsEffect() m_effect = new QGraphicsOpacityEffect(this); m_effect->setOpacity(1.0); m_widget->setGraphicsEffect(m_effect); // start fade out animation m_timeLine->setDirection(QTimeLine::Backward); m_timeLine->start(); } void KateFadeEffect::opacityChanged(qreal value) { Q_ASSERT(m_effect); m_effect->setOpacity(value); } void KateFadeEffect::animationFinished() { // fading finished: remove graphics effect, deletes the effect as well - m_widget->setGraphicsEffect(0); + m_widget->setGraphicsEffect(nullptr); Q_ASSERT(!m_effect); if (m_timeLine->direction() == QTimeLine::Backward) { m_widget->hide(); emit hideAnimationFinished(); } else { emit showAnimationFinished(); } } diff --git a/src/view/katefadeeffect.h b/src/view/katefadeeffect.h index 96f535c1..4243220a 100644 --- a/src/view/katefadeeffect.h +++ b/src/view/katefadeeffect.h @@ -1,109 +1,109 @@ /* This file is part of the KDE and the Kate project * * Copyright (C) 2013 Dominik Haumann * * 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_FADE_EFFECT_H #define KATE_FADE_EFFECT_H #include #include class QWidget; class QTimeLine; class QGraphicsOpacityEffect; /** * This class provides a fade in/out effect for arbitrary QWidget%s. * Example: * \code * KateFadeEffect* fadeEffect = new KateFadeEffect(someWidget); * fadeEffect->fadeIn(); * //... * fadeEffect->fadeOut(); * \endcode */ class KateFadeEffect : public QObject { Q_OBJECT public: /** * Constructor. * By default, the widget is fully opaque (opacity = 1.0). */ - KateFadeEffect(QWidget *widget = 0); + KateFadeEffect(QWidget *widget = nullptr); /** * Check whether the hide animation started by calling fadeOut() * is still running. If animations are disabled, this function always * returns @e false. */ bool isHideAnimationRunning() const; /** * Check whether the show animation started by calling fadeIn() * is still running. If animations are disabled, this function always * returns @e false. */ bool isShowAnimationRunning() const; public Q_SLOTS: /** * Call to fade out and hide the widget. */ void fadeOut(); /** * Call to show and fade in the widget */ void fadeIn(); Q_SIGNALS: /** * This signal is emitted when the fadeOut animation is finished, started by * calling fadeOut(). If animations are disabled, this signal is * emitted immediately. */ void hideAnimationFinished(); /** * This signal is emitted when the fadeIn animation is finished, started by * calling fadeIn(). If animations are disabled, this signal is * emitted immediately. */ void showAnimationFinished(); protected Q_SLOTS: /** * Helper to update opacity value */ void opacityChanged(qreal value); /** * When the animation is finished, hide the widget if fading out. */ void animationFinished(); private: QPointer m_widget; ///< the fading widget QTimeLine *m_timeLine; ///< update time line QPointer m_effect; ///< graphics opacity effect }; #endif diff --git a/src/view/katemessagewidget.cpp b/src/view/katemessagewidget.cpp index c1bc54fb..841ccff3 100644 --- a/src/view/katemessagewidget.cpp +++ b/src/view/katemessagewidget.cpp @@ -1,292 +1,292 @@ /* This file is part of the KDE and the Kate project * * Copyright (C) 2012 Dominik Haumann * * 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 "katemessagewidget.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include static const int s_defaultAutoHideTime = 6 * 1000; KateMessageWidget::KateMessageWidget(QWidget *parent, bool applyFadeEffect) : QWidget(parent) - , m_animation(0) + , m_animation(nullptr) , m_autoHideTimer(new QTimer(this)) , m_autoHideTime(-1) { QVBoxLayout *l = new QVBoxLayout(); l->setMargin(0); m_messageWidget = new KMessageWidget(this); m_messageWidget->setCloseButtonVisible(false); l->addWidget(m_messageWidget); setLayout(l); // tell the widget to always use the minimum size. setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); // install event filter so we catch the end of the hide animation m_messageWidget->installEventFilter(this); // by default, hide widgets m_messageWidget->hide(); hide(); // create animation controller, and connect widgetHidden() to showNextMessage() m_animation = new KateAnimation(m_messageWidget, applyFadeEffect ? KateAnimation::FadeEffect : KateAnimation::GrowEffect); connect(m_animation, SIGNAL(widgetHidden()), this, SLOT(showNextMessage())); // setup autoHide timer details m_autoHideTimer->setSingleShot(true); connect(m_messageWidget, SIGNAL(linkHovered(QString)), SLOT(linkHovered(QString))); } void KateMessageWidget::showNextMessage() { // at this point, we should not have a currently shown message - Q_ASSERT(m_currentMessage == 0); + Q_ASSERT(m_currentMessage == nullptr); // if not message to show, just stop if (m_messageQueue.size() == 0) { hide(); return; } // track current message m_currentMessage = m_messageQueue[0]; // set text etc. m_messageWidget->setText(m_currentMessage->text()); m_messageWidget->setIcon(m_currentMessage->icon()); // connect textChanged() and iconChanged(), so it's possible to change this on the fly connect(m_currentMessage, SIGNAL(textChanged(QString)), m_messageWidget, SLOT(setText(QString)), Qt::UniqueConnection); connect(m_currentMessage, SIGNAL(iconChanged(QIcon)), m_messageWidget, SLOT(setIcon(QIcon)), Qt::UniqueConnection); // the enums values do not necessarily match, hence translate with switch switch (m_currentMessage->messageType()) { case KTextEditor::Message::Positive: m_messageWidget->setMessageType(KMessageWidget::Positive); break; case KTextEditor::Message::Information: m_messageWidget->setMessageType(KMessageWidget::Information); break; case KTextEditor::Message::Warning: m_messageWidget->setMessageType(KMessageWidget::Warning); break; case KTextEditor::Message::Error: m_messageWidget->setMessageType(KMessageWidget::Error); break; default: m_messageWidget->setMessageType(KMessageWidget::Information); break; } // remove all actions from the message widget foreach (QAction *a, m_messageWidget->actions()) { m_messageWidget->removeAction(a); } // add new actions to the message widget foreach (QAction *a, m_currentMessage->actions()) { m_messageWidget->addAction(a); } // set word wrap of the message setWordWrap(m_currentMessage); // setup auto-hide timer, and start if requested m_autoHideTime = m_currentMessage->autoHide(); m_autoHideTimer->stop(); if (m_autoHideTime >= 0) { connect(m_autoHideTimer, SIGNAL(timeout()), m_currentMessage, SLOT(deleteLater()), Qt::UniqueConnection); if (m_currentMessage->autoHideMode() == KTextEditor::Message::Immediate) { m_autoHideTimer->start(m_autoHideTime == 0 ? s_defaultAutoHideTime : m_autoHideTime); } } // finally show show(); m_animation->show(); } void KateMessageWidget::setWordWrap(KTextEditor::Message *message) { // want word wrap anyway? -> ok if (message->wordWrap()) { m_messageWidget->setWordWrap(message->wordWrap()); return; } // word wrap not wanted, that's ok if a parent widget does not exist if (!parentWidget()) { m_messageWidget->setWordWrap(false); return; } // word wrap not wanted -> enable word wrap if it breaks the layout otherwise int margin = 0; if (parentWidget()->layout()) { // get left/right margin of the layout, since we need to subtract these int leftMargin = 0, rightMargin = 0; - parentWidget()->layout()->getContentsMargins(&leftMargin, 0, &rightMargin, 0); + parentWidget()->layout()->getContentsMargins(&leftMargin, nullptr, &rightMargin, nullptr); margin = leftMargin + rightMargin; } // if word wrap enabled, first disable it if (m_messageWidget->wordWrap()) { m_messageWidget->setWordWrap(false); } // make sure the widget's size is up-to-date in its hidden state m_messageWidget->ensurePolished(); m_messageWidget->adjustSize(); // finally enable word wrap, if there is not enough free horizontal space const int freeSpace = (parentWidget()->width() - margin) - m_messageWidget->width(); if (freeSpace < 0) { // qCDebug(LOG_KTE) << "force word wrap to avoid breaking the layout" << freeSpace; m_messageWidget->setWordWrap(true); } } void KateMessageWidget::postMessage(KTextEditor::Message *message, QList > actions) { Q_ASSERT(!m_messageHash.contains(message)); m_messageHash[message] = actions; // insert message sorted after priority int i = 0; for (; i < m_messageQueue.count(); ++i) { if (message->priority() > m_messageQueue[i]->priority()) { break; } } // queue message m_messageQueue.insert(i, message); // catch if the message gets deleted connect(message, SIGNAL(closed(KTextEditor::Message*)), SLOT(messageDestroyed(KTextEditor::Message*))); if (i == 0 && !m_animation->isHideAnimationRunning()) { // if message has higher priority than the one currently shown, // then hide the current one and then show the new one. if (m_currentMessage) { // autoHide timer may be running for currently shown message, therefore // simply disconnect autoHide timer to all timeout() receivers - disconnect(m_autoHideTimer, SIGNAL(timeout()), 0, 0); + disconnect(m_autoHideTimer, SIGNAL(timeout()), nullptr, nullptr); m_autoHideTimer->stop(); // if there is a current message, the message queue must contain 2 messages Q_ASSERT(m_messageQueue.size() > 1); Q_ASSERT(m_currentMessage == m_messageQueue[1]); // a bit unnice: disconnect textChanged() and iconChanged() signals of previously visible message disconnect(m_currentMessage, SIGNAL(textChanged(QString)), m_messageWidget, SLOT(setText(QString))); disconnect(m_currentMessage, SIGNAL(iconChanged(QIcon)), m_messageWidget, SLOT(setIcon(QIcon))); - m_currentMessage = 0; + m_currentMessage = nullptr; m_animation->hide(); } else { showNextMessage(); } } } void KateMessageWidget::messageDestroyed(KTextEditor::Message *message) { // last moment when message is valid, since KTE::Message is already in // destructor we have to do the following: // 1. remove message from m_messageQueue, so we don't care about it anymore // 2. activate hide animation or show a new message() // remove widget from m_messageQueue int i = 0; for (; i < m_messageQueue.count(); ++i) { if (m_messageQueue[i] == message) { break; } } // the message must be in the list Q_ASSERT(i < m_messageQueue.count()); // remove message m_messageQueue.removeAt(i); // remove message from hash -> release QActions Q_ASSERT(m_messageHash.contains(message)); m_messageHash.remove(message); // if deleted message is the current message, launch hide animation if (message == m_currentMessage) { - m_currentMessage = 0; + m_currentMessage = nullptr; m_animation->hide(); } } void KateMessageWidget::startAutoHideTimer() { // message does not want autohide, or timer already running if (!m_currentMessage // no message, nothing to do || m_autoHideTime < 0 // message does not want auto-hide || m_autoHideTimer->isActive() // auto-hide timer is already active || m_animation->isHideAnimationRunning() // widget is in hide animation phase || m_animation->isShowAnimationRunning() // widget is in show animation phase ) { return; } // safety checks: the message must still still be valid Q_ASSERT(m_messageQueue.size()); Q_ASSERT(m_currentMessage->autoHide() == m_autoHideTime); // start autoHide timer as requrested m_autoHideTimer->start(m_autoHideTime == 0 ? s_defaultAutoHideTime : m_autoHideTime); } void KateMessageWidget::linkHovered(const QString &link) { QToolTip::showText(QCursor::pos(), link, m_messageWidget); } QString KateMessageWidget::text() const { return m_messageWidget->text(); } diff --git a/src/view/katestatusbar.cpp b/src/view/katestatusbar.cpp index 99fa63cc..87577ccf 100644 --- a/src/view/katestatusbar.cpp +++ b/src/view/katestatusbar.cpp @@ -1,451 +1,451 @@ /* This file is part of the KDE and the Kate project * * Copyright (C) 2013 Dominik Haumann * * 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 "katestatusbar.h" #include "katemodemenu.h" #include "kateglobal.h" #include "katemodemanager.h" #include "katedocument.h" #include "kateconfig.h" #include "kateabstractinputmode.h" #include "wordcounter.h" #include #include #include #include #include //BEGIN menu KateStatusBarOpenUpMenu::KateStatusBarOpenUpMenu(QWidget *parent) : QMenu(parent) {} KateStatusBarOpenUpMenu::~KateStatusBarOpenUpMenu(){} void KateStatusBarOpenUpMenu::setVisible(bool visibility) { if (visibility) { QRect geo=geometry(); QPoint pos=((QPushButton*)parent())->mapToGlobal(QPoint(0,0)); geo.moveTopLeft(QPoint(pos.x(),pos.y()-geo.height())); if (geo.top()<0) geo.moveTop(0); setGeometry(geo); } QMenu::setVisible(visibility); } //END menu KateStatusBar::KateStatusBar(KTextEditor::ViewPrivate *view) : KateViewBarWidget(false) , m_view(view) - , m_insertModeLabel(Q_NULLPTR) + , m_insertModeLabel(nullptr) , m_modifiedStatus (-1) , m_selectionMode (-1) - , m_wordCounter(Q_NULLPTR) + , m_wordCounter(nullptr) { KAcceleratorManager::setNoAccel(this); setFocusProxy(m_view); /** * just add our status bar to central widget, full sized */ QHBoxLayout *topLayout = new QHBoxLayout(centralWidget()); topLayout->setMargin(0); topLayout->setSpacing(4); /** * add a bit space */ topLayout->addSpacing (4); /** * show Line XXX, Column XXX */ m_lineColLabel = new QLabel( this ); m_lineColLabel->installEventFilter(this); // register for doubleclick topLayout->addWidget( m_lineColLabel, 0 ); m_lineColLabel->setAlignment( Qt::AlignVCenter | Qt::AlignLeft ); m_lineColLabel->setFocusProxy(m_view); m_lineColLabel->setWhatsThis(i18n("Current cursor position. Doubleclick to go to specific line.")); /** * show word count */ m_wordCountLabel = new QLabel(this); topLayout->addWidget(m_wordCountLabel, 0); m_wordCountLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); m_wordCountLabel->setFocusProxy(m_view); m_wordCountLabel->setWhatsThis(i18n("Words and Chars count in document/selection.")); m_wordCountLabel->hide(); /** * show the current mode, like INSERT, OVERWRITE, VI + modifiers like [BLOCK] */ m_insertModeLabel = new QLabel( this ); m_insertModeLabel->installEventFilter(this); // register for doubleclick topLayout->addWidget( m_insertModeLabel, 1000 /* this one should strech */ ); m_insertModeLabel->setAlignment( Qt::AlignVCenter | Qt::AlignRight ); m_insertModeLabel->setFocusProxy(m_view); m_insertModeLabel->setWhatsThis(i18n("Insert mode and VI input mode indicator")); /** * allow to change indentation configuration */ m_spacesOnly = ki18n("Soft Tabs: %1"); m_spacesOnlyShowTabs = ki18n("Soft Tabs: %1 (%2)"); m_tabsOnly = ki18n("Tab Size: %1"); m_tabSpacesMixed = ki18n("Indent/Tab: %1/%2"); QAction *action; m_tabGroup = new QActionGroup(this); m_indentGroup = new QActionGroup(this); m_tabsIndent = new QPushButton(QString(), this); m_tabsIndent->setFlat(true); topLayout->addWidget(m_tabsIndent, 0); m_tabsIndent->setFocusProxy(m_view); m_indentSettingsMenu = new KateStatusBarOpenUpMenu(m_tabsIndent); m_indentSettingsMenu->addSection(i18n("Tab Width")); addNumberAction(m_tabGroup, m_indentSettingsMenu, -1); addNumberAction(m_tabGroup, m_indentSettingsMenu, 8); addNumberAction(m_tabGroup, m_indentSettingsMenu, 4); addNumberAction(m_tabGroup, m_indentSettingsMenu, 2); m_indentSettingsMenu->addSection(i18n("Indentation Width")); addNumberAction(m_indentGroup, m_indentSettingsMenu, -1); addNumberAction(m_indentGroup, m_indentSettingsMenu, 8); addNumberAction(m_indentGroup, m_indentSettingsMenu, 4); addNumberAction(m_indentGroup, m_indentSettingsMenu, 2); m_indentSettingsMenu->addSection(i18n("Indentation Mode")); QActionGroup *radioGroup = new QActionGroup(m_indentSettingsMenu); action = m_indentSettingsMenu->addAction(i18n("Tabulators && Spaces")); action->setCheckable(true); action->setActionGroup(radioGroup); m_mixedAction = action; action = m_indentSettingsMenu->addAction(i18n("Tabulators")); action->setCheckable(true); action->setActionGroup(radioGroup); m_hardAction = action; action = m_indentSettingsMenu->addAction(i18n("Spaces")); action->setCheckable(true); action->setActionGroup(radioGroup); m_softAction = action; m_tabsIndent->setMenu(m_indentSettingsMenu); /** * add encoding button which allows user to switch encoding of document * this will reuse the encoding action menu of the view */ m_encoding = new QPushButton( QString(), this ); m_encoding->setFlat(true); topLayout->addWidget( m_encoding, 0 ); m_encoding->setMenu(m_view->encodingAction()->menu()); m_encoding->setFocusProxy(m_view); m_encoding->setWhatsThis(i18n("Encoding")); /** * add mode button which allows user to switch mode of document * this will reuse the mode action menu of the view */ m_mode = new QPushButton( QString(), this ); m_mode->setFlat(true); topLayout->addWidget( m_mode, 0 ); m_mode->setMenu(m_view->modeAction()->menu()); m_mode->setFocusProxy(m_view); m_mode->setWhatsThis(i18n("Syntax highlighting")); /** * show modification state of the document */ m_modifiedLabel = new QToolButton( this ); m_modifiedLabel->setAutoRaise(true); m_modifiedLabel->setEnabled(false); topLayout->addWidget( m_modifiedLabel, 0 ); m_modifiedLabel->setFocusProxy(m_view); /** * add a bit space */ topLayout->addSpacing (4); // signals for the statusbar connect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(cursorPositionChanged())); connect(m_view, SIGNAL(viewModeChanged(KTextEditor::View*,KTextEditor::View::ViewMode)), this, SLOT(viewModeChanged())); connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(selectionChanged())); connect(m_view->document(), SIGNAL(modifiedChanged(KTextEditor::Document*)), this, SLOT(modifiedChanged())); connect(m_view->document(), SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(modifiedChanged()) ); connect(m_view->document(), SIGNAL(configChanged()), this, SLOT(documentConfigChanged())); connect(m_view->document(), SIGNAL(modeChanged(KTextEditor::Document*)), this, SLOT(modeChanged())); connect(m_view, &KTextEditor::ViewPrivate::configChanged, this, &KateStatusBar::configChanged); connect(m_tabGroup,SIGNAL(triggered(QAction*)),this,SLOT(slotTabGroup(QAction*))); connect(m_indentGroup,SIGNAL(triggered(QAction*)),this,SLOT(slotIndentGroup(QAction*))); connect(radioGroup,SIGNAL(triggered(QAction*)),this,SLOT(slotIndentTabMode(QAction*))); updateStatus (); wordCountChanged(0, 0, 0, 0); } bool KateStatusBar::eventFilter(QObject *obj, QEvent *event) { if (obj == m_insertModeLabel) { if (event->type() == QEvent::MouseButtonDblClick) { m_view->currentInputMode()->toggleInsert(); return true; } } else if (obj == m_lineColLabel) { if (event->type() == QEvent::MouseButtonDblClick) { m_view->gotoLine(); return true; } } return KateViewBarWidget::eventFilter(obj, event); } void KateStatusBar::updateStatus () { selectionChanged (); viewModeChanged (); cursorPositionChanged (); modifiedChanged (); documentConfigChanged(); modeChanged(); } void KateStatusBar::selectionChanged () { const unsigned int newSelectionMode = m_view->blockSelection(); if (newSelectionMode == m_selectionMode) { return; } // remember new mode and update info m_selectionMode = newSelectionMode; viewModeChanged(); } void KateStatusBar::viewModeChanged () { // prepend BLOCK for block selection mode QString text = m_view->viewModeHuman(); if (m_view->blockSelection()) text = i18n ("[BLOCK] %1", text); m_insertModeLabel->setText(text); } void KateStatusBar::cursorPositionChanged () { KTextEditor::Cursor position (m_view->cursorPositionVirtual()); m_lineColLabel->setText( i18n("Line %1, Column %2" , QLocale().toString(position.line() + 1) , QLocale().toString(position.column() + 1) ) ); } void KateStatusBar::modifiedChanged() { const bool mod = m_view->doc()->isModified(); const bool modOnHD = m_view->doc()->isModifiedOnDisc(); /** * combine to modified status, update only if changed */ unsigned int newStatus = (unsigned int)mod | ((unsigned int)modOnHD << 1); if (m_modifiedStatus == newStatus) return; m_modifiedStatus = newStatus; switch (m_modifiedStatus) { case 0x1: m_modifiedLabel->setIcon (QIcon::fromTheme(QStringLiteral("document-save"))); m_modifiedLabel->setWhatsThis(i18n("Meaning of current icon: Document was modified since it was loaded")); break; case 0x2: m_modifiedLabel->setIcon (QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_modifiedLabel->setWhatsThis(i18n("Meaning of current icon: Document was modified or deleted by another program")); break; case 0x3: m_modifiedLabel->setIcon (QIcon(KIconUtils::addOverlay(QIcon::fromTheme(QStringLiteral("document-save")), QIcon(QStringLiteral("emblem-important")), Qt::TopLeftCorner))); m_modifiedLabel->setWhatsThis(QString()); break; default: m_modifiedLabel->setIcon (QIcon::fromTheme(QStringLiteral("text-plain"))); m_modifiedLabel->setWhatsThis(i18n("Meaning of current icon: Document was not modified since it was loaded")); break; } } void KateStatusBar::documentConfigChanged () { m_encoding->setText( m_view->document()->encoding() ); KateDocumentConfig *config=((KTextEditor::DocumentPrivate*)m_view->document())->config(); int tabWidth=config->tabWidth(); int indentationWidth=config->indentationWidth(); bool replaceTabsDyn=config->replaceTabsDyn(); if (!replaceTabsDyn) { if (tabWidth==indentationWidth) { m_tabsIndent->setText(m_tabsOnly.subs(tabWidth).toString()); m_tabGroup->setEnabled(false); m_hardAction->setChecked(true); } else { m_tabsIndent->setText(m_tabSpacesMixed.subs(indentationWidth).subs(tabWidth).toString()); m_tabGroup->setEnabled(true); m_mixedAction->setChecked(true); } } else { if (tabWidth==indentationWidth) { m_tabsIndent->setText(m_spacesOnly.subs(indentationWidth).toString()); m_tabGroup->setEnabled(true); m_softAction->setChecked(true); } else { m_tabsIndent->setText(m_spacesOnlyShowTabs.subs(indentationWidth).subs(tabWidth).toString()); m_tabGroup->setEnabled(true); m_softAction->setChecked(true); } } updateGroup(m_tabGroup,tabWidth); updateGroup(m_indentGroup,indentationWidth); } void KateStatusBar::modeChanged () { m_mode->setText( KTextEditor::EditorPrivate::self()->modeManager()->fileType(m_view->document()->mode()).nameTranslated() ); } void KateStatusBar::addNumberAction(QActionGroup *group, QMenu *menu, int data) { QAction *a; if (data != -1) { a = menu->addAction(QStringLiteral("%1").arg(data)); } else { a = menu->addAction(i18n("Other...")); } a->setData(data); a->setCheckable(true); a->setActionGroup(group); } void KateStatusBar::updateGroup(QActionGroup *group, int w) { - QAction *m1=0; + QAction *m1=nullptr; bool found=false; //linear search should be fast enough here, no additional hash Q_FOREACH(QAction *action, group->actions()) { int val=action->data().toInt(); if (val==-1) m1=action; if (val==w) { found=true; action->setChecked(true); } } if (found) { m1->setText(i18n("Other...")); } else { m1->setText(i18np("Other (%1)", "Other (%1)", w)); m1->setChecked(true); } } void KateStatusBar::slotTabGroup(QAction* a) { int val=a->data().toInt(); bool ok; KateDocumentConfig *config=((KTextEditor::DocumentPrivate*)m_view->document())->config(); if (val==-1) { val=QInputDialog::getInt(this, i18n("Tab Width"), i18n("Please specify the wanted tab width:"), config->tabWidth(), 1, 16, 1, &ok); if (!ok) val=config->tabWidth(); } config->setTabWidth(val); } void KateStatusBar::slotIndentGroup(QAction* a) { int val=a->data().toInt(); bool ok; KateDocumentConfig *config=((KTextEditor::DocumentPrivate*)m_view->document())->config(); if (val==-1) { val=QInputDialog::getInt(this, i18n("Indentation Width"), i18n("Please specify the wanted indentation width:"), config->indentationWidth(), 1, 16, 1, &ok); if (!ok) val=config->indentationWidth(); } config->configStart(); config->setIndentationWidth(val); if (m_hardAction->isChecked()) config->setTabWidth(val); config->configEnd(); } void KateStatusBar::slotIndentTabMode(QAction* a) { KateDocumentConfig *config=((KTextEditor::DocumentPrivate*)m_view->document())->config(); if (a==m_softAction) { config->setReplaceTabsDyn(true); } else if (a==m_mixedAction) { if (config->replaceTabsDyn()) config->setReplaceTabsDyn(false); m_tabGroup->setEnabled(true); } else if (a==m_hardAction) { if (config->replaceTabsDyn()) { config->configStart(); config->setReplaceTabsDyn(false); config->setTabWidth(config->indentationWidth()); config->configEnd(); } else { config->setTabWidth(config->indentationWidth()); } m_tabGroup->setEnabled(false); } } void KateStatusBar::toggleWordCount(bool on) { - if ((m_wordCounter != Q_NULLPTR) == on) { + if ((m_wordCounter != nullptr) == on) { return; } if (on) { m_wordCounter = new WordCounter(m_view); connect(m_wordCounter, &WordCounter::changed, this, &KateStatusBar::wordCountChanged); } else { delete m_wordCounter; - m_wordCounter = Q_NULLPTR; + m_wordCounter = nullptr; wordCountChanged(0, 0, 0, 0); } m_wordCountLabel->setVisible(on); } void KateStatusBar::wordCountChanged(int wordsInDocument, int wordsInSelection, int charsInDocument, int charsInSelection) { m_wordCountLabel->setText(i18n("Words %1/%2, Chars %3/%4", wordsInDocument, wordsInSelection, charsInDocument, charsInSelection)); } void KateStatusBar::configChanged() { toggleWordCount(m_view->config()->showWordCount()); } diff --git a/src/view/kateview.cpp b/src/view/kateview.cpp index d9be17a1..e041c5e9 100644 --- a/src/view/kateview.cpp +++ b/src/view/kateview.cpp @@ -1,3705 +1,3757 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Michel Ludwig Copyright (C) 2007 Mirko Stocker Copyright (C) 2003 Hamish Rodda Copyright (C) 2002 John Firebaugh Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001-2010 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN includes #include "kateview.h" #include "kateviewinternal.h" #include "kateviewhelpers.h" #include "katerenderer.h" #include "katedocument.h" #include "kateundomanager.h" #include "kateglobal.h" #include "katehighlight.h" #include "katehighlightmenu.h" #include "katedialogs.h" #include "katetextline.h" #include "kateschema.h" #include "katebookmarks.h" #include "kateconfig.h" #include "katemodemenu.h" #include "kateautoindent.h" #include "katecompletionwidget.h" #include "katewordcompletion.h" #include "katekeywordcompletion.h" #include "katelayoutcache.h" #include "spellcheck/spellcheck.h" #include "spellcheck/spellcheckdialog.h" #include "spellcheck/spellingmenu.h" #include "katebuffer.h" #include "script/katescriptmanager.h" #include "script/katescriptaction.h" #include "export/exporter.h" #include "katemessagewidget.h" #include "katetemplatehandler.h" #include "katepartdebug.h" #include "printing/kateprinter.h" #include "katestatusbar.h" #include "kateabstractinputmode.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define VIEW_RANGE_DEBUG //END includes namespace { bool hasCommentInFirstLine(KTextEditor::DocumentPrivate* doc) { const Kate::TextLine& line = doc->kateTextLine(0); Q_ASSERT(line); return doc->isComment(0, line->firstChar()); } } void KTextEditor::ViewPrivate::blockFix(KTextEditor::Range &range) { if (range.start().column() > range.end().column()) { int tmp = range.start().column(); range.setStart(KTextEditor::Cursor(range.start().line(), range.end().column())); range.setEnd(KTextEditor::Cursor(range.end().line(), tmp)); } } KTextEditor::ViewPrivate::ViewPrivate(KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow) : KTextEditor::View (this, parent) - , m_completionWidget(0) - , m_annotationModel(0) + , m_completionWidget(nullptr) + , m_annotationModel(nullptr) , m_hasWrap(false) , m_doc(doc) , m_textFolding(doc->buffer()) , m_config(new KateViewConfig(this)) , m_renderer(new KateRenderer(doc, m_textFolding, this)) , m_viewInternal(new KateViewInternal(this)) , m_spell(new KateSpellCheckDialog(this)) , m_bookmarks(new KateBookmarks(this)) , m_topSpacer(new QSpacerItem(0,0)) , m_leftSpacer(new QSpacerItem(0,0)) , m_rightSpacer(new QSpacerItem(0,0)) , m_bottomSpacer(new QSpacerItem(0,0)) , m_startingUp(true) , m_updatingDocumentConfig(false) , blockSelect(false) - , m_bottomViewBar(0) - , m_gotoBar(0) - , m_dictionaryBar(NULL) + , m_bottomViewBar(nullptr) + , m_gotoBar(nullptr) + , m_dictionaryBar(nullptr) , m_spellingMenu(new KateSpellingMenu(this)) , m_userContextMenuSet(false) , m_delayedUpdateTriggered(false) , m_lineToUpdateMin(-1) , m_lineToUpdateMax(-1) - , m_floatTopMessageWidget(0) - , m_floatBottomMessageWidget(0) + , m_floatTopMessageWidget(nullptr) + , m_floatBottomMessageWidget(nullptr) , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there! - , m_statusBar(Q_NULLPTR) + , m_statusBar(nullptr) , m_temporaryAutomaticInvocationDisabled(false) , m_autoFoldedFirstLine(false) , m_clipboard(cursors()) { // queued connect to collapse view updates for range changes, INIT THIS EARLY ENOUGH! connect(this, SIGNAL(delayedUpdateOfView()), this, SLOT(slotDelayedUpdateOfView()), Qt::QueuedConnection); KXMLGUIClient::setComponentName(KTextEditor::EditorPrivate::self()->aboutData().componentName(), KTextEditor::EditorPrivate::self()->aboutData().displayName()); KTextEditor::EditorPrivate::self()->registerView(this); /** * try to let the main window, if any, create a view bar for this view */ QWidget *bottomBarParent = m_mainWindow->createViewBar(this); - m_bottomViewBar = new KateViewBar(bottomBarParent != 0, bottomBarParent ? bottomBarParent : this, this); + m_bottomViewBar = new KateViewBar(bottomBarParent != nullptr, bottomBarParent ? bottomBarParent : this, this); // ugly workaround: // Force the layout to be left-to-right even on RTL deskstop, as discussed // on the mailing list. This will cause the lines and icons panel to be on // the left, even for Arabic/Hebrew/Farsi/whatever users. setLayoutDirection(Qt::LeftToRight); m_bottomViewBar->installEventFilter(m_viewInternal); // add KateMessageWidget for KTE::MessageInterface immediately above view m_topMessageWidget = new KateMessageWidget(this); m_topMessageWidget->hide(); // add KateMessageWidget for KTE::MessageInterface immediately above view m_bottomMessageWidget = new KateMessageWidget(this); m_bottomMessageWidget->hide(); // add bottom viewbar... if (bottomBarParent) { m_mainWindow->addWidgetToViewBar(this, m_bottomViewBar); } // add layout for floating message widgets to KateViewInternal m_notificationLayout = new QVBoxLayout(m_viewInternal); m_notificationLayout->setContentsMargins(20, 20, 20, 20); m_viewInternal->setLayout(m_notificationLayout); // this really is needed :) m_viewInternal->updateView(); doc->addView(this); setFocusProxy(m_viewInternal); setFocusPolicy(Qt::StrongFocus); setXMLFile(QStringLiteral("katepart5ui.rc")); setupConnections(); setupActions(); // auto word completion new KateWordCompletionView(this, actionCollection()); // update the enabled state of the undo/redo actions... slotUpdateUndo(); /** * create the status bar of this view * do this after action creation, we use some of them! */ toggleStatusBar(); m_startingUp = false; updateConfig(); slotHlChanged(); KCursor::setAutoHideCursor(m_viewInternal, true); // user interaction (scrolling) starts notification auto-hide timer connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_topMessageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_bottomMessageWidget, SLOT(startAutoHideTimer())); // user interaction (cursor navigation) starts notification auto-hide timer connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_topMessageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_bottomMessageWidget, SLOT(startAutoHideTimer())); // folding restoration on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(saveFoldingState())); connect(m_doc, SIGNAL(reloaded(KTextEditor::Document*)), SLOT(applyFoldingState())); // update highlights on scrolling and co connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(createHighlights())); // clear highlights on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(clearHighlights())); // setup layout setupLayout(); } KTextEditor::ViewPrivate::~ViewPrivate() { // invalidate update signal m_delayedUpdateTriggered = false; // remove from xmlgui factory, to be safe if (factory()) { factory()->removeClient(this); } // delete internal view before view bar! delete m_viewInternal; /** * remove view bar again, if needed */ m_mainWindow->deleteViewBar(this); - m_bottomViewBar = 0; + m_bottomViewBar = nullptr; m_doc->removeView(this); delete m_renderer; delete m_config; KTextEditor::EditorPrivate::self()->deregisterView(this); } void KTextEditor::ViewPrivate::toggleStatusBar() { /** * if there, delete it */ if (m_statusBar) { bottomViewBar()->removePermanentBarWidget(m_statusBar); delete m_statusBar; - m_statusBar = Q_NULLPTR; + m_statusBar = nullptr; emit statusBarEnabledChanged(this, false); return; } /** * else: create it */ m_statusBar = new KateStatusBar(this); bottomViewBar()->addPermanentBarWidget(m_statusBar); emit statusBarEnabledChanged(this, true); } void KTextEditor::ViewPrivate::setupLayout() { // delete old layout if any if (layout()) { delete layout(); /** * need to recreate spacers because they are deleted with * their belonging layout */ m_topSpacer = new QSpacerItem(0,0); m_leftSpacer = new QSpacerItem(0,0); m_rightSpacer = new QSpacerItem(0,0); m_bottomSpacer = new QSpacerItem(0,0); } // set margins QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); m_topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); m_leftSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_rightSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); // define layout QGridLayout *layout=new QGridLayout(this); layout->setMargin(0); layout->setSpacing(0); const bool frameAroundContents = style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, this); if (frameAroundContents) { // top message widget layout->addWidget(m_topMessageWidget, 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 4); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 3, 0, 1, 4); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 1, 4, 3, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 4, 0, 1, 4); // dummy layout->addWidget(m_viewInternal->m_dummy, 4, 4, 1, 1); // bottom message layout->addWidget(m_bottomMessageWidget, 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_lineScroll->setAutoFillBackground(false); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_columnScroll->setAutoFillBackground(false); } else { // top message widget layout->addWidget(m_topMessageWidget, 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 5); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 2, 3, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 4, 1, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 3, 1, 1, 2); // dummy layout->addWidget(m_viewInternal->m_dummy, 3, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 4, 0, 1, 5); // bottom message layout->addWidget(m_bottomMessageWidget, 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_lineScroll->setAutoFillBackground(true); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_columnScroll->setAutoFillBackground(true); } } void KTextEditor::ViewPrivate::setupConnections() { connect(m_doc, SIGNAL(undoChanged()), this, SLOT(slotUpdateUndo())); connect(m_doc, SIGNAL(highlightingModeChanged(KTextEditor::Document*)), this, SLOT(slotHlChanged())); connect(m_doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); connect(m_viewInternal, SIGNAL(dropEventPass(QDropEvent*)), this, SIGNAL(dropEventPass(QDropEvent*))); connect(m_doc, SIGNAL(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*)), m_viewInternal->m_leftBorder, SLOT(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*))); } void KTextEditor::ViewPrivate::goToPreviousEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Previous, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::goToNextEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Next, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::setupActions() { KActionCollection *ac = actionCollection(); QAction *a; - m_toggleWriteLock = 0; + m_toggleWriteLock = nullptr; m_cut = a = ac->addAction(KStandardAction::Cut, this, SLOT(cut())); a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard")); m_paste = a = ac->addAction(KStandardAction::PasteText, this, SLOT(paste())); a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents")); m_copy = a = ac->addAction(KStandardAction::Copy, this, SLOT(copy())); a->setWhatsThis(i18n("Use this command to copy the currently selected text to the system clipboard.")); m_pasteMenu = ac->addAction(QStringLiteral("edit_paste_menu"), new KatePasteMenu(i18n("Clipboard &History"), this)); connect(KTextEditor::EditorPrivate::self(), SIGNAL(clipboardHistoryChanged()), this, SLOT(slotClipboardHistoryChanged())); if (!m_doc->readOnly()) { a = ac->addAction(KStandardAction::Save, m_doc, SLOT(documentSave())); a->setWhatsThis(i18n("Save the current document")); a = m_editUndo = ac->addAction(KStandardAction::Undo, m_doc, SLOT(undo())); a->setWhatsThis(i18n("Revert the most recent editing actions")); a = m_editRedo = ac->addAction(KStandardAction::Redo, m_doc, SLOT(redo())); a->setWhatsThis(i18n("Revert the most recent undo operation")); // Tools > Scripts KateScriptActionMenu *scriptActionMenu = new KateScriptActionMenu(this, i18n("&Scripts")); ac->addAction(QStringLiteral("tools_scripts"), scriptActionMenu); a = ac->addAction(QStringLiteral("tools_apply_wordwrap")); a->setText(i18n("Apply &Word Wrap")); a->setWhatsThis(i18n("Use this command to wrap all lines of the current document which are longer than the width of the" " current view, to fit into this view.

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

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

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

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

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

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

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

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

" "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(indent())); a = ac->addAction(QStringLiteral("tools_unindent")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-less"))); a->setText(i18n("&Unindent")); a->setWhatsThis(i18n("Use this to unindent a selected block of text.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(unIndent())); } if (hasFocus()) { slotGotFocus(); } else { slotLostFocus(); } } void KTextEditor::ViewPrivate::setSecondaryCursorsFrozen(bool freeze) { cursors()->setSecondaryFrozen(freeze); auto a = actionCollection()->action(QStringLiteral("freeze_secondary_cursors")); Q_ASSERT(a); if ( !a ) return; if ( a->isChecked() != freeze ) { a->blockSignals(true); a->setChecked(freeze); a->blockSignals(false); } } void KTextEditor::ViewPrivate::placeSecondaryCursor() { cursors()->toggleSecondaryCursorAt(cursors()->primaryCursor()); setSecondaryCursorsFrozen(true); } void KTextEditor::ViewPrivate::setupCodeFolding() { KActionCollection *ac = this->actionCollection(); QAction *a; a = ac->addAction(QStringLiteral("folding_toplevel")); a->setText(i18n("Fold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotFoldToplevelNodes())); a = ac->addAction(QLatin1String("folding_expandtoplevel")); a->setText(i18n("Unfold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Plus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandToplevelNodes())); /*a = ac->addAction(QLatin1String("folding_expandall")); a->setText(i18n("Unfold All Nodes")); connect(a, SIGNAL(triggered(bool)), m_doc->foldingTree(), SLOT(expandAll())); a = ac->addAction(QLatin1String("folding_collapse_dsComment")); a->setText(i18n("Fold Multiline Comments")); connect(a, SIGNAL(triggered(bool)), m_doc->foldingTree(), SLOT(collapseAll_dsComments())); */ a = ac->addAction(QStringLiteral("folding_collapselocal")); a->setText(i18n("Fold Current Node")); connect(a, SIGNAL(triggered(bool)), SLOT(slotCollapseLocal())); a = ac->addAction(QStringLiteral("folding_expandlocal")); a->setText(i18n("Unfold Current Node")); connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandLocal())); } void KTextEditor::ViewPrivate::slotFoldToplevelNodes() { for (int line = 0; line < doc()->lines(); ++line) { if (textFolding().isLineVisible(line)) { foldLine(line); } } } void KTextEditor::ViewPrivate::slotExpandToplevelNodes() { const auto topLevelRanges(textFolding().foldingRangesForParentRange()); for (const auto &range : topLevelRanges) { textFolding().unfoldRange(range.first); } } void KTextEditor::ViewPrivate::slotCollapseLocal() { foldLine(cursorPosition().line()); } void KTextEditor::ViewPrivate::slotExpandLocal() { unfoldLine(cursorPosition().line()); } void KTextEditor::ViewPrivate::foldLine(int startLine) { // only for valid lines if (startLine < 0 || startLine >= doc()->buffer().lines()) { return; } // try to fold all known ranges QVector > startingRanges = textFolding().foldingRangesStartingOnLine(startLine); for (int i = 0; i < startingRanges.size(); ++i) { textFolding().foldRange(startingRanges[i].first); } // try if the highlighting can help us and create a fold textFolding().newFoldingRange(doc()->buffer().computeFoldingRangeForStartLine(startLine), Kate::TextFolding::Folded); } void KTextEditor::ViewPrivate::unfoldLine(int startLine) { // only for valid lines if (startLine < 0 || startLine >= doc()->buffer().lines()) { return; } // try to unfold all known ranges QVector > startingRanges = textFolding().foldingRangesStartingOnLine(startLine); for (int i = 0; i < startingRanges.size(); ++i) { textFolding().unfoldRange(startingRanges[i].first); } } KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const { return currentInputMode()->viewMode(); } QString KTextEditor::ViewPrivate::viewModeHuman() const { QString currentMode = currentInputMode()->viewModeHuman(); /** * append read-only if needed */ if (!m_doc->isReadWrite()) { currentMode = i18n("(R/O) %1", currentMode); } /** * return full mode */ return currentMode; } KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const { return currentInputMode()->viewInputMode(); } QString KTextEditor::ViewPrivate::viewInputModeHuman() const { return currentInputMode()->viewInputModeHuman(); } void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode) { if (currentInputMode()->viewInputMode() == mode) { return; } if (!m_viewInternal->m_inputModes.contains(mode)) { return; } m_viewInternal->m_currentInputMode->deactivate(); m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode]; m_viewInternal->m_currentInputMode->activate(); config()->setInputMode(mode); // TODO: this could be called from read config procedure, so it's not a good idea to set a specific view mode here /* small duplication, but need to do this; TODO: make it more sane */ Q_FOREACH(QAction *action, m_inputModeActions) { bool checked = static_cast(action->data().toInt()) == mode; action->setChecked(checked); } /* inform the rest of the system about the change */ emit viewInputModeChanged(this, mode); emit viewModeChanged(this, viewMode()); } void KTextEditor::ViewPrivate::slotGotFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus"; currentInputMode()->gotFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusIn(this); } void KTextEditor::ViewPrivate::slotLostFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus"; currentInputMode()->lostFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusOut(this); } void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode) { config()->setDynWordWrapIndicators(mode); } bool KTextEditor::ViewPrivate::isOverwriteMode() const { return m_doc->config()->ovr(); } void KTextEditor::ViewPrivate::reloadFile() { // bookmarks and cursor positions are temporarily saved by the document m_doc->documentReload(); } void KTextEditor::ViewPrivate::slotReadWriteChanged() { if (m_toggleWriteLock) { m_toggleWriteLock->setChecked(! m_doc->isReadWrite()); } m_cut->setEnabled(m_doc->isReadWrite() && (selection() || m_config->smartCopyCut())); m_paste->setEnabled(m_doc->isReadWrite()); m_pasteMenu->setEnabled(m_doc->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); m_setEndOfLine->setEnabled(m_doc->isReadWrite()); QStringList l; l << QStringLiteral("edit_replace") << QStringLiteral("tools_spelling") << QStringLiteral("tools_indent") << QStringLiteral("tools_unindent") << QStringLiteral("tools_cleanIndent") << QStringLiteral("tools_align") << QStringLiteral("tools_comment") << QStringLiteral("tools_uncomment") << QStringLiteral("tools_toggle_comment") << QStringLiteral("tools_uppercase") << QStringLiteral("tools_lowercase") << QStringLiteral("tools_capitalize") << QStringLiteral("tools_join_lines") << QStringLiteral("tools_apply_wordwrap") << QStringLiteral("tools_spelling_from_cursor") << QStringLiteral("tools_spelling_selection"); foreach (const QString &action, l) { QAction *a = actionCollection()->action(action); if (a) { a->setEnabled(m_doc->isReadWrite()); } } slotUpdateUndo(); currentInputMode()->readWriteChanged(m_doc->isReadWrite()); // => view mode changed emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotClipboardHistoryChanged() { m_pasteMenu->setEnabled(m_doc->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); } void KTextEditor::ViewPrivate::slotUpdateUndo() { if (m_doc->readOnly()) { return; } m_editUndo->setEnabled(m_doc->isReadWrite() && m_doc->undoCount() > 0); m_editRedo->setEnabled(m_doc->isReadWrite() && m_doc->redoCount() > 0); } bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor &position, uint tabwidth, bool calledExternally) { Kate::TextLine l = m_doc->kateTextLine(position.line()); if (!l) { return false; } QString line_str = m_doc->line(position.line()); int x = 0; int z = 0; for (; z < line_str.length() && z < position.column(); z++) { if (line_str[z] == QLatin1Char('\t')) { x += tabwidth - (x % tabwidth); } else { x++; } } if (blockSelection()) { if (z < position.column()) { x += position.column() - z; } } auto cur = KTextEditor::Cursor(position.line(), x); m_viewInternal->cursors()->setPrimaryCursor(cur, false); m_viewInternal->notifyPrimaryCursorChanged(cur, false, true, calledExternally); return true; } void KTextEditor::ViewPrivate::toggleInsert() { m_doc->config()->setOvr(!m_doc->config()->ovr()); m_toggleInsert->setChecked(isOverwriteMode()); emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error) { if (!error.isEmpty()) { // happens when canceling a job KMessageBox::error(this, error); } } void KTextEditor::ViewPrivate::gotoLine() { gotoBar()->updateData(); bottomViewBar()->showBarWidget(gotoBar()); } void KTextEditor::ViewPrivate::changeDictionary() { dictionaryBar()->updateData(); bottomViewBar()->showBarWidget(dictionaryBar()); } void KTextEditor::ViewPrivate::joinLines() { int first = selectionRange().start().line(); int last = selectionRange().end().line(); //int left = m_doc->line( last ).length() - m_doc->selEndCol(); if (first == last) { first = cursorPosition().line(); last = first + 1; } m_doc->joinLines(first, last); } void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position setCursorPositionInternal(KTextEditor::Cursor(config.readEntry("CursorLine", 0), config.readEntry("CursorColumn", 0))); // restore dyn word wrap if set for this view if (config.hasKey("Dynamic Word Wrap")) { m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false)); } // restore text folding m_savedFoldingState = QJsonDocument::fromJson(config.readEntry("TextFolding", QByteArray())); applyFoldingState(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->readSessionConfig(config); } } void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position #warning TODO multicursor config.writeEntry("CursorLine", m_viewInternal->primaryCursor().line()); config.writeEntry("CursorColumn", m_viewInternal->primaryCursor().column()); // save dyn word wrap if set for this view if (m_config->dynWordWrapSet()) { config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap()); } // save text folding state saveFoldingState(); config.writeEntry("TextFolding", m_savedFoldingState.toJson(QJsonDocument::Compact)); m_savedFoldingState = QJsonDocument(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->writeSessionConfig(config); } } int KTextEditor::ViewPrivate::getEol() const { return m_doc->config()->eol(); } void KTextEditor::ViewPrivate::setEol(int eol) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } if (eol != m_doc->config()->eol()) { m_doc->setModified(true); // mark modified (bug #143120) m_doc->config()->setEol(eol); } } void KTextEditor::ViewPrivate::setAddBom(bool enabled) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } m_doc->config()->setBom(enabled); m_doc->bomSetByUser(); } void KTextEditor::ViewPrivate::setIconBorder(bool enable) { config()->setIconBar(enable); } void KTextEditor::ViewPrivate::toggleIconBorder() { config()->setIconBar(!config()->iconBar()); } void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable) { config()->setLineNumbers(enable); } void KTextEditor::ViewPrivate::toggleLineNumbersOn() { config()->setLineNumbers(!config()->lineNumbers()); } void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable) { config()->setScrollBarMarks(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMarks() { config()->setScrollBarMarks(!config()->scrollBarMarks()); } void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable) { config()->setScrollBarMiniMap(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMap() { config()->setScrollBarMiniMap(!config()->scrollBarMiniMap()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable) { config()->setScrollBarMiniMapAll(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll() { config()->setScrollBarMiniMapAll(!config()->scrollBarMiniMapAll()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width) { config()->setScrollBarMiniMapWidth(width); } void KTextEditor::ViewPrivate::toggleDynWordWrap() { config()->setDynWordWrap(!config()->dynWordWrap()); } void KTextEditor::ViewPrivate::toggleWWMarker() { m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker()); } void KTextEditor::ViewPrivate::toggleNPSpaces() { m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces()); m_viewInternal->update(); // force redraw } void KTextEditor::ViewPrivate::toggleWordCount(bool on) { config()->setShowWordCount(on); } void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable) { config()->setFoldingBar(enable); } void KTextEditor::ViewPrivate::toggleFoldingMarkers() { config()->setFoldingBar(!config()->foldingBar()); } bool KTextEditor::ViewPrivate::iconBorder() { return m_viewInternal->m_leftBorder->iconBorderOn(); } bool KTextEditor::ViewPrivate::lineNumbersOn() { return m_viewInternal->m_leftBorder->lineNumbersOn(); } bool KTextEditor::ViewPrivate::scrollBarMarks() { return m_viewInternal->m_lineScroll->showMarks(); } bool KTextEditor::ViewPrivate::scrollBarMiniMap() { return m_viewInternal->m_lineScroll->showMiniMap(); } int KTextEditor::ViewPrivate::dynWrapIndicators() { return m_viewInternal->m_leftBorder->dynWrapIndicators(); } bool KTextEditor::ViewPrivate::foldingMarkersOn() { return m_viewInternal->m_leftBorder->foldingMarkersOn(); } void KTextEditor::ViewPrivate::toggleWriteLock() { m_doc->setReadWrite(! m_doc->isReadWrite()); } void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->registerTextHintProvider(provider); } void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->unregisterTextHintProvider(provider); } void KTextEditor::ViewPrivate::setTextHintDelay(int delay) { m_viewInternal->setTextHintDelay(delay); } int KTextEditor::ViewPrivate::textHintDelay() const { return m_viewInternal->textHintDelay(); } void KTextEditor::ViewPrivate::find() { currentInputMode()->find(); } void KTextEditor::ViewPrivate::findSelectedForwards() { currentInputMode()->findSelectedForwards(); } void KTextEditor::ViewPrivate::findSelectedBackwards() { currentInputMode()->findSelectedBackwards(); } void KTextEditor::ViewPrivate::replace() { currentInputMode()->findReplace(); } void KTextEditor::ViewPrivate::findNext() { currentInputMode()->findNext(); } void KTextEditor::ViewPrivate::findPrevious() { currentInputMode()->findPrevious(); } void KTextEditor::ViewPrivate::slotSelectionChanged() { m_copy->setEnabled(selection() || m_config->smartCopyCut()); m_deSelect->setEnabled(selection()); m_copyHtmlAction->setEnabled (selection()); // update highlighting of current selected word selectionChangedForHighlights (); if (m_doc->readOnly()) { return; } m_cut->setEnabled(selection() || m_config->smartCopyCut()); m_spell->updateActions(); } void KTextEditor::ViewPrivate::switchToCmdLine() { currentInputMode()->activateCommandLine(); } KateRenderer *KTextEditor::ViewPrivate::renderer() { return m_renderer; } void KTextEditor::ViewPrivate::updateConfig() { if (m_startingUp) { return; } // dyn. word wrap & markers if (m_hasWrap != config()->dynWordWrap()) { m_viewInternal->prepareForDynWrapChange(); m_hasWrap = config()->dynWordWrap(); m_viewInternal->dynWrapChanged(); m_setDynWrapIndicators->setEnabled(config()->dynWordWrap()); m_toggleDynWrap->setChecked(config()->dynWordWrap()); } m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators()); m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators()); // line numbers m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers()); m_toggleLineNumbers->setChecked(config()->lineNumbers()); // icon bar m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar()); m_toggleIconBar->setChecked(config()->iconBar()); // scrollbar marks m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks()); m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks()); // scrollbar mini-map m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap()); m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap()); // scrollbar mini-map - (whole document) m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll()); //m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() ); // scrollbar mini-map.width m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth()); // misc edit m_toggleBlockSelection->setChecked(blockSelection()); m_toggleInsert->setChecked(isOverwriteMode()); updateFoldingConfig(); // bookmark m_bookmarks->setSorting((KateBookmarks::Sorting) config()->bookmarkSort()); m_viewInternal->setAutoCenterLines(config()->autoCenterLines()); Q_FOREACH(KateAbstractInputMode *input, m_viewInternal->m_inputModes) { input->updateConfig(); } setInputMode(config()->inputMode()); reflectOnTheFlySpellCheckStatus(m_doc->isOnTheFlySpellCheckingEnabled()); // register/unregister word completion... bool wc = config()->wordCompletion(); if (wc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->wordCompletionModel())) { if (wc) registerCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); else unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); } bool kc = config()->keywordCompletion(); if (kc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->keywordCompletionModel())) { if (kc) registerCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel()); else unregisterCompletionModel (KTextEditor::EditorPrivate::self()->keywordCompletionModel()); } m_cut->setEnabled(m_doc->isReadWrite() && (selection() || m_config->smartCopyCut())); m_copy->setEnabled(selection() || m_config->smartCopyCut()); // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); emit configChanged(); } void KTextEditor::ViewPrivate::updateDocumentConfig() { if (m_startingUp) { return; } m_updatingDocumentConfig = true; m_setEndOfLine->setCurrentItem(m_doc->config()->eol()); m_addBom->setChecked(m_doc->config()->bom()); m_updatingDocumentConfig = false; // maybe block selection or wrap-cursor mode changed ensureCursorColumnValid(); // first change this m_renderer->setTabWidth(m_doc->config()->tabWidth()); m_renderer->setIndentWidth(m_doc->config()->indentationWidth()); // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); } void KTextEditor::ViewPrivate::updateRendererConfig() { if (m_startingUp) { return; } m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker()); m_viewInternal->updateBracketMarkAttributes(); m_viewInternal->updateBracketMarks(); // now redraw... m_viewInternal->cache()->clear(); tagAll(); m_viewInternal->updateView(true); // update the left border right, for example linenumbers m_viewInternal->m_leftBorder->updateFont(); m_viewInternal->m_leftBorder->repaint(); m_viewInternal->m_lineScroll->queuePixmapUpdate(); currentInputMode()->updateRendererConfig(); // @@ showIndentLines is not cached anymore. // m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines()); emit configChanged(); } void KTextEditor::ViewPrivate::updateFoldingConfig() { // folding bar m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar()); m_toggleFoldingMarkers->setChecked(config()->foldingBar()); if (hasCommentInFirstLine(m_doc)) { if (config()->foldFirstLine() && !m_autoFoldedFirstLine) { foldLine(0); m_autoFoldedFirstLine = true; } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) { unfoldLine(0); m_autoFoldedFirstLine = false; } } else { m_autoFoldedFirstLine = false; } #if 0 // FIXME: FOLDING QStringList l; l << "folding_toplevel" << "folding_expandtoplevel" << "folding_collapselocal" << "folding_expandlocal"; QAction *a = 0; for (int z = 0; z < l.size(); z++) if ((a = actionCollection()->action(l[z].toAscii().constData()))) { a->setEnabled(m_doc->highlight() && m_doc->highlight()->allowsFolding()); } #endif } void KTextEditor::ViewPrivate::ensureCursorColumnValid() { #warning TODO multicursor // KTextEditor::Cursor c = m_viewInternal->getCursor(); // // // make sure the cursor is valid: // // - in block selection mode or if wrap cursor is off, the column is arbitrary // // - otherwise: it's bounded by the line length // if (!blockSelection() && wrapCursor() // && (!c.isValid() || c.column() > m_doc->lineLength(c.line()))) { // c.setColumn(m_doc->kateTextLine(cursorPosition().line())->length()); // setCursorPosition(c); // } } //BEGIN EDIT STUFF void KTextEditor::ViewPrivate::editStart() { m_viewInternal->editStart(); } void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom); } void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor &cursor) { m_viewInternal->editSetCursor(cursor); } //END //BEGIN TAG & CLEAR bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor &virtualCursor) { return m_viewInternal->tagLine(virtualCursor); } bool KTextEditor::ViewPrivate::tagRange(const KTextEditor::Range &range, bool realLines) { return m_viewInternal->tagRange(range, realLines); } bool KTextEditor::ViewPrivate::tagLines(int start, int end, bool realLines) { return m_viewInternal->tagLines(start, end, realLines); } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) { return m_viewInternal->tagLines(start, end, realCursors); } void KTextEditor::ViewPrivate::tagAll() { m_viewInternal->tagAll(); } void KTextEditor::ViewPrivate::clear() { m_viewInternal->clear(); } void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty) { if (paintOnlyDirty) { m_viewInternal->updateDirty(); } else { m_viewInternal->update(); } } void KTextEditor::ViewPrivate::updateView(bool changed) { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView"; m_viewInternal->updateView(changed); m_viewInternal->m_leftBorder->update(); } //END void KTextEditor::ViewPrivate::slotHlChanged() { KateHighlighting *hl = m_doc->highlight(); bool ok(!hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty()); if (actionCollection()->action(QStringLiteral("tools_comment"))) { actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_uncomment"))) { actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) { actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok); } // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry updateFoldingConfig(); } int KTextEditor::ViewPrivate::virtualCursorColumn() const { return m_doc->toVirtualColumn(m_viewInternal->primaryCursor()); } void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor &newPosition) { emit mousePositionChanged(this, newPosition); } //BEGIN KTextEditor::SelectionInterface stuff bool KTextEditor::ViewPrivate::setSelection(const KTextEditor::Range &selection) { qDebug() << "called" << selection; /** * anything to do? */ if ( !selections()->hasMultipleSelections() && selection == primarySelection()) { return false; } /** * set new range; repainting is done by the selection manager */ selections()->setSelection(selection); /** * emit holy signal */ emit selectionChanged(this); /** * be done */ return true; } bool KTextEditor::ViewPrivate::clearSelection() { return clearSelection(true); } bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection) { /** * no selection, nothing to do... */ if (!selection()) { return false; } /** * do clear */ selections()->clearSelection(); /** * emit holy signal */ if (finishedChangingSelection) { emit selectionChanged(this); } /** * be done */ return true; } bool KTextEditor::ViewPrivate::selection() const { return selections()->hasSelections(); } QString KTextEditor::ViewPrivate::selectionText() const { return m_doc->text(primarySelection(), blockSelect); } bool KTextEditor::ViewPrivate::removeSelectedText() { if (!selection()) { return false; } m_doc->editStart(); auto sels = selections()->selections(); std::sort(sels.begin(), sels.end(), [](const Range& a, const Range& b) { return a > b; }); Q_FOREACH ( const auto& range, sels ) { m_doc->removeText(range, blockSelect); } // don't redraw the cleared selection - that's done in editEnd(). clearSelection(false); m_doc->editEnd(); return true; } bool KTextEditor::ViewPrivate::selectAll() { setBlockSelection(false); top(); shiftBottom(); return true; } bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor &cursor) { return selections()->positionSelected(cursor); } bool KTextEditor::ViewPrivate::lineSelected(int line) { return selections()->lineSelected(line); } bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor &lineEndPos) { return selections()->lineEndSelected(lineEndPos); } bool KTextEditor::ViewPrivate::lineHasSelected(int line) { return selections()->lineHasSelection(line); } bool KTextEditor::ViewPrivate::lineIsSelection(int line) { #warning TODO fix this return ( line == primarySelection().start().line() && line == primarySelection().end().line()); } void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor &cursor) { setSelection(m_doc->wordRangeAt(cursor)); } void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor &cursor) { int line = cursor.line(); if (line + 1 >= m_doc->lines()) { setSelection(KTextEditor::Range(line, 0, line, m_doc->lineLength(line))); } else { setSelection(KTextEditor::Range(line, 0, line + 1, 0)); } } void KTextEditor::ViewPrivate::cut() { if (!selection() && !m_config->smartCopyCut()) { return; } copy(); #warning fixme: smart copy cut // if (!selection()) { // selectLine(m_viewInternal->primaryCursor()); // } removeSelectedText(); } void KTextEditor::ViewPrivate::copy() const { #warning fixme: smart copy cut // if (!selection()) { // if (!m_config->smartCopyCut()) { // return; // } // text = m_doc->line(m_viewInternal->primaryCursor().line()) + QLatin1Char('\n'); // m_viewInternal->cursors()->moveCursorsStartOfLine(); // } m_clipboard.copyToClipboard(); } void KTextEditor::ViewPrivate::applyWordWrap() { if (selection()) { m_doc->wrapText(selectionRange().start().line(), selectionRange().end().line()); } else { m_doc->wrapText(0, m_doc->lastLine()); } } //END //BEGIN KTextEditor::BlockSelectionInterface stuff bool KTextEditor::ViewPrivate::blockSelection() const { return blockSelect; } bool KTextEditor::ViewPrivate::setBlockSelection(bool on) { if (on != blockSelect) { blockSelect = on; auto oldSelection = primarySelection(); const bool hadSelection = clearSelection(false, false); #warning handle properly: block mode setSelection(oldSelection); m_toggleBlockSelection->setChecked(blockSelection()); // when leaving block selection mode, if cursor is at an invalid position or past the end of the // line, move the cursor to the last column of the current line unless cursor wrapping is off ensureCursorColumnValid(); if (!hadSelection) { // emit selectionChanged() according to the KTextEditor::View api // documentation also if there is no selection around. This is needed, // as e.g. the Kate App status bar uses this signal to update the state // of the selection mode (block selection, line based selection) emit selectionChanged(this); } } return true; } bool KTextEditor::ViewPrivate::toggleBlockSelection() { m_toggleBlockSelection->setChecked(!blockSelect); return setBlockSelection(!blockSelect); } bool KTextEditor::ViewPrivate::wrapCursor() const { return !blockSelection(); } //END void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text) { emit textInserted(view, position, text); } bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor& c, const QString& templateString, const QString& script) { /** * no empty templates */ if (templateString.isEmpty()) { return false; } /** * not for read-only docs */ if (!m_doc->isReadWrite()) { return false; } /** * only one handler maybe active at a time; store it in the document. * Clear it first to make sure at no time two handlers are active at once */ doc()->setActiveTemplateHandler(nullptr); doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, m_doc->undoManager())); return true; } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange) { return tagLines(range.start(), range.end(), realRange); } void KTextEditor::ViewPrivate::deactivateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(false); } } void KTextEditor::ViewPrivate::activateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(true); } } bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const { // FIXME support return true; } bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool) { // FIXME support return true; } bool KTextEditor::ViewPrivate::isCompletionActive() const { return completionWidget()->isCompletionActive(); } KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const { if (!m_completionWidget) { m_completionWidget = new KateCompletionWidget(const_cast(this)); } return m_completionWidget; } void KTextEditor::ViewPrivate::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) { completionWidget()->startCompletion(word, model); } void KTextEditor::ViewPrivate::abortCompletion() { completionWidget()->abortCompletion(); } void KTextEditor::ViewPrivate::forceCompletion() { completionWidget()->execute(); } void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->registerCompletionModel(model); } void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->unregisterCompletionModel(model); } bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const { return completionWidget()->isCompletionModelRegistered(model); } bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const { return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation(); } void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled) { config()->setAutomaticCompletionInvocation(enabled); } void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index) { emit completionExecuted(this, position, model, index); } void KTextEditor::ViewPrivate::sendCompletionAborted() { emit completionAborted(this); } void KTextEditor::ViewPrivate::paste() { m_temporaryAutomaticInvocationDisabled = true; m_clipboard.pasteFromClipboard(QClipboard::Clipboard); m_temporaryAutomaticInvocationDisabled = false; } void KTextEditor::ViewPrivate::pasteInternal(const QVector& texts) { m_clipboard.pasteVector(texts); } bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position) { return setCursorPositionInternal(position, 1, true); } KateMultiSelection* KTextEditor::ViewPrivate::selections() { return m_viewInternal->selections(); } const KateMultiSelection* KTextEditor::ViewPrivate::selections() const { return m_viewInternal->selections(); } bool KTextEditor::ViewPrivate::setSelections(const QVector& newSelections, const QVector& newCursors) { if ( !newCursors.isEmpty() && (newSelections.size() != newCursors.size()) ) { Q_ASSERT(false); qWarning() << "mismatching cursor/selection size"; return false; } if ( std::any_of(newCursors.begin(), newCursors.end(), [](const KTextEditor::Cursor& c) { return !c.isValid(); }) ) { return false; } if ( newSelections.isEmpty() ) { bool ret = selections()->hasSelections(); selections()->clearSelection(); return ret; } selections()->setSelection(newSelections, newCursors); return true; } bool KTextEditor::ViewPrivate::setCursorPositions(const QVector& positions) { if ( std::any_of(positions.begin(), positions.end(), [](const KTextEditor::Cursor& c) { return !c.isValid(); }) ) { return false; } if ( positions.isEmpty() ) { return false; } auto s = QVector(); s.resize(positions.size()); selections()->setSelection(s, positions); return true; } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const { return m_viewInternal->primaryCursor(); } QVector KTextEditor::ViewPrivate::cursorPositions() const { return cursors()->cursors(); } const KateMultiCursor* KTextEditor::ViewPrivate::cursors() const { return m_viewInternal->cursors(); } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const { return KTextEditor::Cursor(m_viewInternal->primaryCursor().line(), virtualCursorColumn()); } QPoint KTextEditor::ViewPrivate::cursorToCoordinate(const KTextEditor::Cursor &cursor) const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, true, false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const { // map from View to ViewInternal coordinates return m_viewInternal->coordinatesToCursor(m_viewInternal->mapFromParent(coords), false); } QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const { // map from ViewInternal to View coordinates - const QPoint pt = m_viewInternal->cursorCoordinates(); + const QPoint pt = m_viewInternal->cursorCoordinates(false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } +void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor &cursor) +{ + m_viewInternal->scrollPos(cursor, false, true, false); +} + +void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x) +{ + m_viewInternal->scrollColumns(x); +} + +KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const +{ + return m_viewInternal->maxStartPos(true); +} + + +int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const +{ + if (lineType == RealLine) { + return m_textFolding.visibleLineToLine(m_viewInternal->startLine()); + } else { + return m_viewInternal->startLine(); + } +} + +int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const +{ + if (lineType == RealLine) { + return m_textFolding.visibleLineToLine(m_viewInternal->endLine()); + } else { + return m_viewInternal->endLine(); + } +} + +QRect KTextEditor::ViewPrivate::textAreaRectInternal() const +{ + const auto sourceRect = m_viewInternal->rect(); + const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft()); + const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight()); + return {topLeft, bottomRight}; +} + bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor &position) { return setCursorPositionInternal(position, m_doc->config()->tabWidth(), true); } QString KTextEditor::ViewPrivate::currentTextLine() { return m_doc->line(cursorPosition().line()); } QTextLayout * KTextEditor::ViewPrivate::textLayout(int line) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(line); return thisLine->isValid() ? thisLine->layout() : nullptr; } QTextLayout * KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor &pos) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(pos); return thisLine->isValid() ? thisLine->layout() : nullptr; } void KTextEditor::ViewPrivate::indent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, 1); } void KTextEditor::ViewPrivate::unIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, -1); } void KTextEditor::ViewPrivate::cleanIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, 0); } void KTextEditor::ViewPrivate::align() { // no selection: align current line; selection: use selection range const int line = cursorPosition().line(); KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0)); if (selection()) { alignRange = selectionRange(); } m_doc->align(this, alignRange); } void KTextEditor::ViewPrivate::comment() { #warning fixme // m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), 1); // m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uncomment() { m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), -1); } void KTextEditor::ViewPrivate::toggleComment() { #warning fixme // m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), 0); // m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uppercase() { m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Uppercase); } void KTextEditor::ViewPrivate::killLine() { if (!selections()->hasSelections()) { m_doc->removeLine(cursorPosition().line()); } else { m_doc->editStart(); // cache endline, else that moves and we might delete complete document if last line is selected! Q_FOREACH ( const auto range, selections()->selections() ) { for (int line = range.end().line(), endLine = range.start().line(); line >= endLine; line--) { m_doc->removeLine(line); } } m_doc->editEnd(); } } void KTextEditor::ViewPrivate::lowercase() { m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Lowercase); } void KTextEditor::ViewPrivate::capitalize() { m_doc->editStart(); m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Lowercase); m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Capitalize); m_doc->editEnd(); } void KTextEditor::ViewPrivate::keyReturn() { m_viewInternal->doReturn(); } void KTextEditor::ViewPrivate::smartNewline() { m_viewInternal->doSmartNewline(); } void KTextEditor::ViewPrivate::backspace() { m_viewInternal->doBackspace(); } void KTextEditor::ViewPrivate::insertTab() { m_viewInternal->doTabulator(); } void KTextEditor::ViewPrivate::deleteWordLeft() { m_viewInternal->doDeletePrevWord(); } void KTextEditor::ViewPrivate::keyDelete() { m_viewInternal->doDelete(); } void KTextEditor::ViewPrivate::deleteWordRight() { m_viewInternal->doDeleteNextWord(); } void KTextEditor::ViewPrivate::transpose() { m_viewInternal->doTranspose(); } void KTextEditor::ViewPrivate::cursorLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(); } else { m_viewInternal->cursorPrevChar(); } } void KTextEditor::ViewPrivate::shiftCursorLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(true); } else { m_viewInternal->cursorPrevChar(true); } } void KTextEditor::ViewPrivate::cursorRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(); } else { m_viewInternal->cursorNextChar(); } } void KTextEditor::ViewPrivate::shiftCursorRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(true); } else { m_viewInternal->cursorNextChar(true); } } void KTextEditor::ViewPrivate::wordLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(); } else { m_viewInternal->wordPrev(); } } void KTextEditor::ViewPrivate::shiftWordLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(true); } else { m_viewInternal->wordPrev(true); } } void KTextEditor::ViewPrivate::wordRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(); } else { m_viewInternal->wordNext(); } } void KTextEditor::ViewPrivate::shiftWordRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(true); } else { m_viewInternal->wordNext(true); } } void KTextEditor::ViewPrivate::home() { m_viewInternal->home(); } void KTextEditor::ViewPrivate::shiftHome() { m_viewInternal->home(true); } void KTextEditor::ViewPrivate::end() { m_viewInternal->end(); } void KTextEditor::ViewPrivate::shiftEnd() { m_viewInternal->end(true); } void KTextEditor::ViewPrivate::up() { m_viewInternal->cursorUp(); } void KTextEditor::ViewPrivate::shiftUp() { m_viewInternal->cursorUp(true); } void KTextEditor::ViewPrivate::down() { m_viewInternal->cursorDown(); } void KTextEditor::ViewPrivate::shiftDown() { m_viewInternal->cursorDown(true); } void KTextEditor::ViewPrivate::scrollUp() { m_viewInternal->scrollUp(); } void KTextEditor::ViewPrivate::scrollDown() { m_viewInternal->scrollDown(); } void KTextEditor::ViewPrivate::topOfView() { m_viewInternal->topOfView(); } void KTextEditor::ViewPrivate::shiftTopOfView() { m_viewInternal->topOfView(true); } void KTextEditor::ViewPrivate::bottomOfView() { m_viewInternal->bottomOfView(); } void KTextEditor::ViewPrivate::shiftBottomOfView() { m_viewInternal->bottomOfView(true); } void KTextEditor::ViewPrivate::pageUp() { m_viewInternal->pageUp(); } void KTextEditor::ViewPrivate::shiftPageUp() { m_viewInternal->pageUp(true); } void KTextEditor::ViewPrivate::pageDown() { m_viewInternal->pageDown(); } void KTextEditor::ViewPrivate::shiftPageDown() { m_viewInternal->pageDown(true); } void KTextEditor::ViewPrivate::top() { m_viewInternal->top_home(); } void KTextEditor::ViewPrivate::shiftTop() { m_viewInternal->top_home(true); } void KTextEditor::ViewPrivate::bottom() { m_viewInternal->bottom_end(); } void KTextEditor::ViewPrivate::shiftBottom() { m_viewInternal->bottom_end(true); } void KTextEditor::ViewPrivate::toMatchingBracket() { cursors()->clearSecondaryCursors(); m_viewInternal->cursorToMatchingBracket(); } void KTextEditor::ViewPrivate::shiftToMatchingBracket() { m_viewInternal->cursorToMatchingBracket(true); } void KTextEditor::ViewPrivate::toPrevModifiedLine() { const int startLine = m_viewInternal->primaryCursor().line() - 1; const int line = m_doc->findTouchedLine(startLine, false); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->cursors()->setPrimaryCursor(c); } } void KTextEditor::ViewPrivate::toNextModifiedLine() { const int startLine = m_viewInternal->primaryCursor().line() + 1; const int line = m_doc->findTouchedLine(startLine, true); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->cursors()->setPrimaryCursor(c); } } KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const { return primarySelection(); } QVector KTextEditor::ViewPrivate::selectionRanges() const { return selections()->selections(); } KTextEditor::Document *KTextEditor::ViewPrivate::document() const { return m_doc; } void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu) { if (m_contextMenu) { disconnect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); disconnect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } m_contextMenu = menu; m_userContextMenuSet = true; if (m_contextMenu) { connect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } } QMenu *KTextEditor::ViewPrivate::contextMenu() const { if (m_userContextMenuSet) { return m_contextMenu; } else { KXMLGUIClient *client = const_cast(this); while (client->parentClient()) { client = client->parentClient(); } //qCDebug(LOG_KTE) << "looking up all menu containers"; if (client->factory()) { QList conts = client->factory()->containers(QStringLiteral("menu")); foreach (QWidget *w, conts) { if (w->objectName() == QLatin1String("ktexteditor_popup")) { //perhaps optimize this block QMenu *menu = (QMenu *)w; disconnect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); disconnect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); connect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); return menu; } } } } - return 0; + return nullptr; } QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const { if (!menu) { menu = new QMenu(const_cast(this)); } menu->addAction(m_editUndo); menu->addAction(m_editRedo); menu->addSeparator(); menu->addAction(m_cut); menu->addAction(m_copy); menu->addAction(m_paste); menu->addSeparator(); menu->addAction(m_selectAll); menu->addAction(m_deSelect); if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) { menu->addSeparator(); menu->addAction(spellingSuggestions); } if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) { menu->addSeparator(); menu->addAction(bookmark); } return menu; } void KTextEditor::ViewPrivate::aboutToShowContextMenu() { QMenu *menu = qobject_cast(sender()); if (menu) { emit contextMenuAboutToShow(this, menu); } } void KTextEditor::ViewPrivate::aboutToHideContextMenu() { m_spellingMenu->setUseMouseForMisspelledRange(false); } // BEGIN ConfigInterface stff QStringList KTextEditor::ViewPrivate::configKeys() const { static const QStringList keys = { QStringLiteral("icon-bar"), QStringLiteral("line-numbers"), QStringLiteral("dynamic-word-wrap"), QStringLiteral("background-color"), QStringLiteral("selection-color"), QStringLiteral("search-highlight-color"), QStringLiteral("replace-highlight-color"), QStringLiteral("default-mark-type"), QStringLiteral("allow-mark-menu"), QStringLiteral("folding-bar"), QStringLiteral("folding-preview"), QStringLiteral("icon-border-color"), QStringLiteral("folding-marker-color"), QStringLiteral("line-number-color"), QStringLiteral("current-line-number-color"), QStringLiteral("modification-markers"), QStringLiteral("keyword-completion"), QStringLiteral("word-count"), QStringLiteral("scrollbar-minimap"), - QStringLiteral("scrollbar-preview") + QStringLiteral("scrollbar-preview"), + QStringLiteral("font") }; return keys; } QVariant KTextEditor::ViewPrivate::configValue(const QString &key) { if (key == QLatin1String("icon-bar")) { return config()->iconBar(); } else if (key == QLatin1String("line-numbers")) { return config()->lineNumbers(); } else if (key == QLatin1String("dynamic-word-wrap")) { return config()->dynWordWrap(); } else if (key == QLatin1String("background-color")) { return renderer()->config()->backgroundColor(); } else if (key == QLatin1String("selection-color")) { return renderer()->config()->selectionColor(); } else if (key == QLatin1String("search-highlight-color")) { return renderer()->config()->searchHighlightColor(); } else if (key == QLatin1String("replace-highlight-color")) { return renderer()->config()->replaceHighlightColor(); } else if (key == QLatin1String("default-mark-type")) { return config()->defaultMarkType(); } else if (key == QLatin1String("allow-mark-menu")) { return config()->allowMarkMenu(); } else if (key == QLatin1String("folding-bar")) { return config()->foldingBar(); } else if (key == QLatin1String("folding-preview")) { return config()->foldingPreview(); } else if (key == QLatin1String("icon-border-color")) { return renderer()->config()->iconBarColor(); } else if (key == QLatin1String("folding-marker-color")) { return renderer()->config()->foldingColor(); } else if (key == QLatin1String("line-number-color")) { return renderer()->config()->lineNumberColor(); } else if (key == QLatin1String("current-line-number-color")) { return renderer()->config()->currentLineNumberColor(); } else if (key == QLatin1String("modification-markers")) { return config()->lineModification(); } else if (key == QLatin1String("keyword-completion")) { return config()->keywordCompletion(); + } else if (key == QLatin1String("word-count")) { + return config()->showWordCount(); } else if (key == QLatin1String("scrollbar-minimap")) { return config()->scrollBarMiniMap(); } else if (key == QLatin1String("scrollbar-preview")) { return config()->scrollBarPreview(); + } else if (key == QLatin1String("font")) { + return renderer()->config()->font(); } // return invalid variant return QVariant(); } void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value) { if (value.canConvert(QVariant::Color)) { if (key == QLatin1String("background-color")) { renderer()->config()->setBackgroundColor(value.value()); } else if (key == QLatin1String("selection-color")) { renderer()->config()->setSelectionColor(value.value()); } else if (key == QLatin1String("search-highlight-color")) { renderer()->config()->setSearchHighlightColor(value.value()); } else if (key == QLatin1String("replace-highlight-color")) { renderer()->config()->setReplaceHighlightColor(value.value()); } else if (key == QLatin1String("icon-border-color")) { renderer()->config()->setIconBarColor(value.value()); } else if (key == QLatin1String("folding-marker-color")) { renderer()->config()->setFoldingColor(value.value()); } else if (key == QLatin1String("line-number-color")) { renderer()->config()->setLineNumberColor(value.value()); } else if (key == QLatin1String("current-line-number-color")) { renderer()->config()->setCurrentLineNumberColor(value.value()); } } else if (value.type() == QVariant::Bool) { // Note explicit type check above. If we used canConvert, then // values of type UInt will be trapped here. if (key == QLatin1String("icon-bar")) { config()->setIconBar(value.toBool()); } else if (key == QLatin1String("line-numbers")) { config()->setLineNumbers(value.toBool()); } else if (key == QLatin1String("dynamic-word-wrap")) { config()->setDynWordWrap(value.toBool()); } else if (key == QLatin1String("allow-mark-menu")) { config()->setAllowMarkMenu(value.toBool()); } else if (key == QLatin1String("folding-bar")) { config()->setFoldingBar(value.toBool()); } else if (key == QLatin1String("folding-preview")) { config()->setFoldingPreview(value.toBool()); } else if (key == QLatin1String("modification-markers")) { config()->setLineModification(value.toBool()); } else if (key == QLatin1String("keyword-completion")) { config()->setKeywordCompletion(value.toBool()); } else if (key == QLatin1String("word-count")) { config()->setShowWordCount(value.toBool()); } else if (key == QLatin1String("scrollbar-minimap")) { config()->setScrollBarMiniMap(value.toBool()); } else if (key == QLatin1String("scrollbar-preview")) { config()->setScrollBarPreview(value.toBool()); } } else if (value.canConvert(QVariant::UInt)) { if (key == QLatin1String("default-mark-type")) { config()->setDefaultMarkType(value.toUInt()); } + + } else if (value.canConvert(QVariant::Font)) { + if (key == QLatin1String("font")) { + renderer()->config()->setFont(value.value()); + } } } // END ConfigInterface void KTextEditor::ViewPrivate::userInvokedCompletion() { completionWidget()->userInvokedCompletion(); } KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const { return m_bottomViewBar; } KateGotoBar *KTextEditor::ViewPrivate::gotoBar() { if (!m_gotoBar) { m_gotoBar = new KateGotoBar(this); bottomViewBar()->addBarWidget(m_gotoBar); } return m_gotoBar; } KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar() { if (!m_dictionaryBar) { m_dictionaryBar = new KateDictionaryBar(this); bottomViewBar()->addBarWidget(m_dictionaryBar); } return m_dictionaryBar; } void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) { KTextEditor::AnnotationModel *oldmodel = m_annotationModel; m_annotationModel = model; m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, m_annotationModel); } KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const { return m_annotationModel; } void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible) { m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible); if ( !visible ) { // make sure the tooltip is hidden QToolTip::hideText(); } } bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const { return m_viewInternal->m_leftBorder->annotationBorderOn(); } KTextEditor::Range KTextEditor::ViewPrivate::visibleRange() { //ensure that the view is up-to-date, otherwise 'endPos()' might fail! m_viewInternal->updateView(); return KTextEditor::Range(m_viewInternal->toRealCursor(m_viewInternal->startPos()), m_viewInternal->toRealCursor(m_viewInternal->endPos())); } bool KTextEditor::ViewPrivate::event(QEvent *e) { switch (e->type()) { case QEvent::StyleChange: setupLayout(); return true; default: return KTextEditor::View::event(e); } } void KTextEditor::ViewPrivate::paintEvent(QPaintEvent *e) { //base class KTextEditor::View::paintEvent(e); const QRect contentsRect = m_topSpacer->geometry()| m_bottomSpacer->geometry()| m_leftSpacer->geometry()| m_rightSpacer->geometry(); if (contentsRect.isValid()) { QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; // clear mouseOver and focus state // update from relevant widgets opt.state &= ~(QStyle::State_HasFocus|QStyle::State_MouseOver); const QList widgets = QList() << m_viewInternal << m_viewInternal->m_leftBorder << m_viewInternal->m_lineScroll << m_viewInternal->m_columnScroll; foreach (const QWidget *w, widgets) { if (w->hasFocus()) opt.state |= QStyle::State_HasFocus; if (w->underMouse()) opt.state |= QStyle::State_MouseOver; } // update rect opt.rect=contentsRect; // render QPainter paint(this); paint.setClipRegion(e->region()); paint.setRenderHints(QPainter::Antialiasing); style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this); } } void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b) { m_doc->onTheFlySpellCheckingEnabled(b); } void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled) { m_spellingMenu->setVisible(enabled); m_toggleOnTheFlySpellCheck->setChecked(enabled); } KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu() { return m_spellingMenu; } void KTextEditor::ViewPrivate::notifyAboutRangeChange(int startLine, int endLine, bool rangeWithAttribute) { #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "trigger attribute changed from" << startLine << "to" << endLine << "rangeWithAttribute" << rangeWithAttribute; #endif // first call: if (!m_delayedUpdateTriggered) { m_delayedUpdateTriggered = true; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; // only set initial line range, if range with attribute! if (rangeWithAttribute) { m_lineToUpdateMin = startLine; m_lineToUpdateMax = endLine; } // emit queued signal and be done emit delayedUpdateOfView(); return; } // ignore lines if no attribute if (!rangeWithAttribute) { return; } // update line range if (startLine != -1 && (m_lineToUpdateMin == -1 || startLine < m_lineToUpdateMin)) { m_lineToUpdateMin = startLine; } if (endLine != -1 && endLine > m_lineToUpdateMax) { m_lineToUpdateMax = endLine; } } void KTextEditor::ViewPrivate::slotDelayedUpdateOfView() { if (!m_delayedUpdateTriggered) { return; } #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "delayed attribute changed from" << m_lineToUpdateMin << "to" << m_lineToUpdateMax; #endif // update ranges in updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); // update view, if valid line range, else only feedback update wanted anyway if (m_lineToUpdateMin != -1 && m_lineToUpdateMax != -1) { tagLines(m_lineToUpdateMin, m_lineToUpdateMax, true); updateView(true); } // reset flags m_delayedUpdateTriggered = false; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; } void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType) { // new ranges with cursor in, default none QSet newRangesIn; // on which range set we work? QSet &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn; // which cursor position to honor? KTextEditor::Cursor currentCursor = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->getMouse() : m_viewInternal->primaryCursor(); // first: validate the remembered ranges QSet validRanges; foreach (Kate::TextRange *range, oldSet) if (m_doc->buffer().rangePointerValid(range)) { validRanges.insert(range); } // cursor valid? else no new ranges can be found if (currentCursor.isValid() && currentCursor.line() < m_doc->buffer().lines()) { // now: get current ranges for the line of cursor with an attribute QList rangesForCurrentCursor = m_doc->buffer().rangesForLine(currentCursor.line(), this, false); // match which ranges really fit the given cursor foreach (Kate::TextRange *range, rangesForCurrentCursor) { // range has no dynamic attribute of right type and no feedback object if ((!range->attribute() || !range->attribute()->dynamicAttribute(activationType)) && !range->feedback()) { continue; } // range doesn't contain cursor, not interesting if ((range->start().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->start().toCursor()) : (currentCursor <= range->start().toCursor())) { continue; } if ((range->end().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->end().toCursor() <= currentCursor) : (range->end().toCursor() < currentCursor)) { continue; } // range contains cursor, was it already in old set? if (validRanges.contains(range)) { // insert in new, remove from old, be done with it newRangesIn.insert(range); validRanges.remove(range); continue; } // oh, new range, trigger update and insert into new set newRangesIn.insert(range); if (range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseEnteredRange(range, this); } else { range->feedback()->caretEnteredRange(range, this); } } #ifdef VIEW_RANGE_DEBUG // found new range for activation qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType; #endif } } // now: notify for left ranges! foreach (Kate::TextRange *range, validRanges) { // range valid + right dynamic attribute, trigger update if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseExitedRange(range, this); } else { range->feedback()->caretExitedRange(range, this); } } } // set new ranges oldSet = newRangesIn; } void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList > actions) { // just forward to KateMessageWidget :-) if (message->position() == KTextEditor::Message::AboveView) { m_topMessageWidget->postMessage(message, actions); } else if (message->position() == KTextEditor::Message::BelowView) { m_bottomMessageWidget->postMessage(message, actions); } else if (message->position() == KTextEditor::Message::TopInView) { if (!m_floatTopMessageWidget) { m_floatTopMessageWidget = new KateMessageWidget(m_viewInternal, true); m_notificationLayout->insertWidget(0, m_floatTopMessageWidget, 0, Qt::Alignment(Qt::AlignTop | Qt::AlignRight)); connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_floatTopMessageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_floatTopMessageWidget, SLOT(startAutoHideTimer())); } m_floatTopMessageWidget->postMessage(message, actions); } else if (message->position() == KTextEditor::Message::BottomInView) { if (!m_floatBottomMessageWidget) { m_floatBottomMessageWidget = new KateMessageWidget(m_viewInternal, true); m_notificationLayout->addWidget(m_floatBottomMessageWidget, 0, Qt::Alignment(Qt::AlignBottom | Qt::AlignRight)); connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_floatBottomMessageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_floatBottomMessageWidget, SLOT(startAutoHideTimer())); } m_floatBottomMessageWidget->postMessage(message, actions); } } void KTextEditor::ViewPrivate::saveFoldingState() { m_savedFoldingState = m_textFolding.exportFoldingRanges(); } void KTextEditor::ViewPrivate::applyFoldingState() { m_textFolding.importFoldingRanges(m_savedFoldingState); m_savedFoldingState = QJsonDocument(); } void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file) { KateExporter(this).exportToFile(file); } void KTextEditor::ViewPrivate::exportHtmlToClipboard () { KateExporter(this).exportToClipboard(); } void KTextEditor::ViewPrivate::exportHtmlToFile () { const QString file = QFileDialog::getSaveFileName(this, i18n("Export File as HTML"), m_doc->documentName()); if (!file.isEmpty()) { KateExporter(this).exportToFile(file); } } void KTextEditor::ViewPrivate::clearHighlights() { qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); m_currentTextForHighlights.clear(); } void KTextEditor::ViewPrivate::selectionChangedForHighlights() { QString text; // if text of selection is still the same, abort if (selection() && selectionRange().onSingleLine()) { text = selectionText(); if (text == m_currentTextForHighlights) return; } // text changed: remove all highlights + create new ones // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); // do not highlight strings with leading and trailing spaces if (!text.isEmpty() && (text.at(0).isSpace() || text.at(text.length()-1).isSpace())) return; // trigger creation of ranges for current view range m_currentTextForHighlights = text; createHighlights(); } void KTextEditor::ViewPrivate::createHighlights() { // do nothing if no text to highlight if (m_currentTextForHighlights.isEmpty()) { return; } KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); attr->setBackground(Qt::yellow); // set correct highlight color from Kate's color schema QColor fgColor = defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); QColor bgColor = renderer()->config()->searchHighlightColor(); attr->setForeground(fgColor); attr->setBackground(bgColor); KTextEditor::Cursor start(visibleRange().start()); KTextEditor::Range searchRange; /** * only add word boundary if we can find the text then * fixes $lala hl */ QString regex = QRegExp::escape (m_currentTextForHighlights); if (QRegExp (QStringLiteral("\\b%1").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("\\b%1").arg(regex); if (QRegExp (QStringLiteral("%1\\b").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("%1\\b").arg(regex); QVector matches; do { searchRange.setRange(start, visibleRange().end()); matches = m_doc->searchText(searchRange, regex, KTextEditor::Regex); if (matches.first().isValid()) { KTextEditor::MovingRange* mr = m_doc->newMovingRange(matches.first()); mr->setAttribute(attr); mr->setView(this); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); m_rangesForHighlights.append(mr); start = matches.first().end(); } } while (matches.first().isValid()); } KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const { return m_viewInternal->m_currentInputMode; } void KTextEditor::ViewPrivate::toggleInputMode(bool on) { QAction *a = dynamic_cast(sender()); if (!a) { return; } InputMode newmode = static_cast(a->data().toInt()); if (currentInputMode()->viewInputMode() == newmode) { if (!on) { a->setChecked(true); // nasty } return; } Q_FOREACH(QAction *ac, m_inputModeActions) { if (ac != a) { ac->setChecked(false); } } setInputMode(newmode); } void KTextEditor::ViewPrivate::cycleInputMode() { InputMode current = currentInputMode()->viewInputMode(); InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode; setInputMode(to); } //BEGIN KTextEditor::PrintInterface stuff bool KTextEditor::ViewPrivate::print() { return KatePrinter::print(this); } void KTextEditor::ViewPrivate::printPreview() { KatePrinter::printPreview(this); } //END // void KTextEditor::ViewPrivate::moveSecondaryCursors(int chars) // { // Q_FOREACH ( auto c, m_secondaryCursors ) { // if ( blockSelection() ) { // c->setPosition(c->line(), qMax(0, c->column() + chars)); // } // else { // c->move(chars, KTextEditor::MovingCursor::Wrap); // } // Q_ASSERT(c->toCursor().isValid()); // } // } // // void KTextEditor::ViewPrivate::moveSecondaryCursorsVertically(int lines) // { // Q_FOREACH ( auto c, m_secondaryCursors ) { // const auto oldColumn = c->column(); // c->setLine(qMin(qMax(0, c->line() + lines), doc()->lines())); // auto newColumn = blockSelection() ? oldColumn : qMin(c->document()->lineLength(c->line()), oldColumn); // c->setColumn(newColumn); // Q_ASSERT(c->toCursor().isValid()); // } // } // // void KTextEditor::ViewPrivate::updateSecondaryCursorsPositions(std::function update) // { // Q_FOREACH ( const auto& c, m_secondaryCursors ) { // c->setPosition(update(c->toCursor())); // Q_ASSERT(c->toCursor().isValid()); // } // } KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const { KateRendererConfig * renderConfig = const_cast(this)->renderer()->config(); KTextEditor::Attribute::Ptr style = m_doc->highlight()->attributes(renderConfig->schema()).at(defaultStyle); if (!style->hasProperty(QTextFormat::BackgroundBrush)) { // make sure the returned style has the default background color set style = new KTextEditor::Attribute(*style); style->setBackground(QBrush(renderConfig->backgroundColor())); } return style; } QList KTextEditor::ViewPrivate::lineAttributes(int line) { QList attribs; if (line < 0 || line >= m_doc->lines()) return attribs; Kate::TextLine kateLine = m_doc->kateTextLine(line); if (!kateLine) { return attribs; } const QVector &intAttrs = kateLine->attributesList(); for (int i = 0; i < intAttrs.size(); ++i) { if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) { attribs << KTextEditor::AttributeBlock( intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(intAttrs.at(i).attributeValue) ); } } return attribs; } KateMultiCursor * KTextEditor::ViewPrivate::cursors() { return m_viewInternal->cursors(); } Cursors KTextEditor::ViewPrivate::allCursors() { return cursors()->cursors(); } diff --git a/src/view/kateview.h b/src/view/kateview.h index d71cb7ec..ab64c59a 100644 --- a/src/view/kateview.h +++ b/src/view/kateview.h @@ -1,1015 +1,1029 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001-2010 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef kate_view_h #define kate_view_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katetextrange.h" #include "katetextfolding.h" #include "katerenderer.h" #include "katemessagewidget.h" #include "katemulticursor.h" #include "katemulticlipboard.h" namespace KTextEditor { class AnnotationModel; class Message; } namespace KTextEditor { class DocumentPrivate; } class KateBookmarks; class KateViewConfig; class KateRenderer; class KateSpellCheckDialog; class KateCompletionWidget; class KateViewInternal; class KateViewBar; class KateTextPreview; class KateGotoBar; class KateDictionaryBar; class KateSpellingMenu; class KateMessageWidget; class KateIconBorder; class KateStatusBar; class KateViewEncodingAction; class KateModeMenu; class KateAbstractInputMode; class KToggleAction; class KSelectAction; class QAction; class QGridLayout; class QVBoxLayout; namespace KTextEditor { // // Kate KTextEditor::View class ;) // class KTEXTEDITOR_EXPORT ViewPrivate : public KTextEditor::View, public KTextEditor::TextHintInterface, public KTextEditor::CodeCompletionInterface, public KTextEditor::ConfigInterface, public KTextEditor::AnnotationViewInterface { Q_OBJECT Q_INTERFACES(KTextEditor::TextHintInterface) Q_INTERFACES(KTextEditor::ConfigInterface) Q_INTERFACES(KTextEditor::CodeCompletionInterface) Q_INTERFACES(KTextEditor::AnnotationViewInterface) friend class KTextEditor::View; friend class ::KateViewInternal; friend class ::KateIconBorder; friend class CalculatingCursor; friend class ::KateTextPreview; public: - ViewPrivate (KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow = Q_NULLPTR); + ViewPrivate (KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow = nullptr); ~ViewPrivate (); /** * Get the view's main window, if any * \return the view's main window */ KTextEditor::MainWindow *mainWindow() const Q_DECL_OVERRIDE { return m_mainWindow; } KTextEditor::Document *document() const Q_DECL_OVERRIDE; ViewMode viewMode() const Q_DECL_OVERRIDE; QString viewModeHuman() const Q_DECL_OVERRIDE; InputMode viewInputMode() const Q_DECL_OVERRIDE; QString viewInputModeHuman() const Q_DECL_OVERRIDE; void setInputMode(InputMode mode); const KateMultiCursor* cursors() const; const KateMultiSelection* selections() const; KateMultiCursor* cursors(); KateMultiSelection* selections(); Cursors allCursors(); // // KTextEditor::ClipboardInterface // public Q_SLOTS: void paste(); void pasteInternal(const QVector& texts); +// void paste(const QString *textToPaste = nullptr); void cut(); void copy() const; private Q_SLOTS: /** * internal use, apply word wrap */ void applyWordWrap(); // // KTextEditor::PopupMenuInterface // public: void setContextMenu(QMenu *menu) Q_DECL_OVERRIDE; QMenu *contextMenu() const Q_DECL_OVERRIDE; - QMenu *defaultContextMenu(QMenu *menu = 0L) const Q_DECL_OVERRIDE; + QMenu *defaultContextMenu(QMenu *menu = nullptr) const Q_DECL_OVERRIDE; private Q_SLOTS: void aboutToShowContextMenu(); void aboutToHideContextMenu(); private: QPointer m_contextMenu; // // KTextEditor::ViewCursorInterface // public: bool setCursorPosition(KTextEditor::Cursor position) Q_DECL_OVERRIDE; bool setCursorPositions(const QVector &positions) Q_DECL_OVERRIDE; KTextEditor::Cursor cursorPosition() const Q_DECL_OVERRIDE; QVector cursorPositions() const Q_DECL_OVERRIDE; KTextEditor::Cursor cursorPositionVirtual() const Q_DECL_OVERRIDE; QPoint cursorToCoordinate(const KTextEditor::Cursor &cursor) const Q_DECL_OVERRIDE; KTextEditor::Cursor coordinatesToCursor(const QPoint &coord) const Q_DECL_OVERRIDE; QPoint cursorPositionCoordinates() const Q_DECL_OVERRIDE; bool setCursorPositionVisual(const KTextEditor::Cursor &position); /** * Return the virtual cursor column, each tab is expanded into the * document's tabWidth characters. If word wrap is off, the cursor may be * behind the line's length. */ int virtualCursorColumn() const; bool mouseTrackingEnabled() const Q_DECL_OVERRIDE; bool setMouseTrackingEnabled(bool enable) Q_DECL_OVERRIDE; private: void notifyMousePositionChanged(const KTextEditor::Cursor &newPosition); // Internal public: bool setCursorPositionInternal(const KTextEditor::Cursor &position, uint tabwidth = 1, bool calledExternally = false); // // KTextEditor::ConfigInterface // public: QStringList configKeys() const Q_DECL_OVERRIDE; QVariant configValue(const QString &key) Q_DECL_OVERRIDE; void setConfigValue(const QString &key, const QVariant &value) Q_DECL_OVERRIDE; Q_SIGNALS: void configChanged(); public: /** * Try to fold starting at the given line. * This will both try to fold existing folding ranges of this line and to query the highlighting what to fold. * @param startLine start line to fold at */ void foldLine(int startLine); /** * Try to unfold all foldings starting at the given line. * @param startLine start line to unfold at */ void unfoldLine(int startLine); // // KTextEditor::CodeCompletionInterface2 // public: bool isCompletionActive() const Q_DECL_OVERRIDE; void startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) Q_DECL_OVERRIDE; void abortCompletion() Q_DECL_OVERRIDE; void forceCompletion() Q_DECL_OVERRIDE; void registerCompletionModel(KTextEditor::CodeCompletionModel *model) Q_DECL_OVERRIDE; void unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) Q_DECL_OVERRIDE; bool isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const; bool isAutomaticInvocationEnabled() const Q_DECL_OVERRIDE; void setAutomaticInvocationEnabled(bool enabled = true) Q_DECL_OVERRIDE; Q_SIGNALS: void completionExecuted(KTextEditor::View *view, const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &); void completionAborted(KTextEditor::View *view); public Q_SLOTS: void userInvokedCompletion(); public: KateCompletionWidget *completionWidget() const; mutable KateCompletionWidget *m_completionWidget; void sendCompletionExecuted(const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index); void sendCompletionAborted(); // // KTextEditor::TextHintInterface // public: void registerTextHintProvider(KTextEditor::TextHintProvider *provider) Q_DECL_OVERRIDE; void unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) Q_DECL_OVERRIDE; void setTextHintDelay(int delay) Q_DECL_OVERRIDE; int textHintDelay() const Q_DECL_OVERRIDE; public: bool dynWordWrap() const { return m_hasWrap; } // // KTextEditor::SelectionInterface stuff // public Q_SLOTS: /** * @brief Get the primary selection. * * The primary selection is the selection range containing the primary * cursor. If no such selection exists, as might be the case * in "persistent selection" mode, the persistent selection is returned instead. */ KTextEditor::Range primarySelection() const { return selections()->primarySelection(); }; /** * @brief Set the primary selection. */ bool setSelection(const KTextEditor::Range &selection) Q_DECL_OVERRIDE; bool setPrimarySelection(const KTextEditor::Range &selection) { return setSelection(selection); }; bool setSelections(const QVector &selections, const QVector &cursors) Q_DECL_OVERRIDE; bool removeSelection() Q_DECL_OVERRIDE { return clearSelection(); } bool removeSelectionText() Q_DECL_OVERRIDE { return removeSelectedText(); } bool setBlockSelection(bool on) Q_DECL_OVERRIDE; bool toggleBlockSelection(); bool clearSelection(); bool clearSelection(bool redraw, bool finishedChangingSelection = true); bool removeSelectedText(); bool selectAll(); public: bool selection() const Q_DECL_OVERRIDE; QString selectionText() const Q_DECL_OVERRIDE; bool blockSelection() const Q_DECL_OVERRIDE; KTextEditor::Range selectionRange() const Q_DECL_OVERRIDE; QVector selectionRanges() const Q_DECL_OVERRIDE; static void blockFix(KTextEditor::Range &range); // // Arbitrary Syntax HL + Action extensions // public: // Action association extension void deactivateEditActions(); void activateEditActions(); // // internal helper stuff, for katerenderer and so on // public: // should cursor be wrapped ? take config + blockselection state in account bool wrapCursor() const; // some internal functions to get selection state of a line/col bool cursorSelected(const KTextEditor::Cursor &cursor); bool lineSelected(int line); bool lineEndSelected(const KTextEditor::Cursor &lineEndPos); bool lineHasSelected(int line); bool lineIsSelection(int line); void ensureCursorColumnValid(); void selectWord(const KTextEditor::Cursor &cursor); void selectLine(const KTextEditor::Cursor &cursor); //BEGIN EDIT STUFF public: void editStart(); void editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom); void editSetCursor(const KTextEditor::Cursor &cursor); //END //BEGIN TAG & CLEAR public: bool tagLine(const KTextEditor::Cursor &virtualCursor); bool tagRange(const KTextEditor::Range &range, bool realLines = false); bool tagLines(int start, int end, bool realLines = false); bool tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors = false); bool tagLines(KTextEditor::Range range, bool realRange = false); void tagAll(); void clear(); void repaintText(bool paintOnlyDirty = false); void updateView(bool changed = false); //END // // KTextEditor::AnnotationView // public: void setAnnotationModel(KTextEditor::AnnotationModel *model) Q_DECL_OVERRIDE; KTextEditor::AnnotationModel *annotationModel() const Q_DECL_OVERRIDE; void setAnnotationBorderVisible(bool visible) Q_DECL_OVERRIDE; bool isAnnotationBorderVisible() const Q_DECL_OVERRIDE; Q_SIGNALS: void annotationContextMenuAboutToShow(KTextEditor::View *view, QMenu *menu, int line) Q_DECL_OVERRIDE; void annotationActivated(KTextEditor::View *view, int line) Q_DECL_OVERRIDE; void annotationBorderVisibilityChanged(View *view, bool visible) Q_DECL_OVERRIDE; void navigateLeft(); void navigateRight(); void navigateUp(); void navigateDown(); void navigateAccept(); void navigateBack(); private: KTextEditor::AnnotationModel *m_annotationModel; // // KTextEditor::View // public: void emitNavigateLeft() { emit navigateLeft(); } void emitNavigateRight() { emit navigateRight(); } void emitNavigateUp() { emit navigateUp(); } void emitNavigateDown() { emit navigateDown(); } void emitNavigateAccept() { emit navigateAccept(); } void emitNavigateBack() { emit navigateBack(); } /** Return values for "save" related commands. */ bool isOverwriteMode() const; QString currentTextLine(); QTextLayout * textLayout(int line) const; QTextLayout * textLayout(const KTextEditor::Cursor &pos) const; public Q_SLOTS: void indent(); void unIndent(); void cleanIndent(); void align(); void comment(); void uncomment(); void toggleComment(); void killLine(); /** * Sets the cursor to the previous editing position in this document */ void goToPreviousEditingPosition(); /** * Sets the cursor to the next editing position in this document */ void goToNextEditingPosition(); /** Uppercases selected text, or an alphabetic character next to the cursor. */ void uppercase(); /** Lowercases selected text, or an alphabetic character next to the cursor. */ void lowercase(); /** Capitalizes the selection (makes each word start with an uppercase) or the word under the cursor. */ void capitalize(); /** Joins lines touched by the selection */ void joinLines(); // Note - the following functions simply forward to KateViewInternal void keyReturn(); void smartNewline(); void backspace(); void deleteWordLeft(); void keyDelete(); void deleteWordRight(); void transpose(); void cursorLeft(); void shiftCursorLeft(); void cursorRight(); void shiftCursorRight(); void wordLeft(); void shiftWordLeft(); void wordRight(); void shiftWordRight(); void home(); void shiftHome(); void end(); void shiftEnd(); void up(); void shiftUp(); void down(); void shiftDown(); void scrollUp(); void scrollDown(); void topOfView(); void shiftTopOfView(); void bottomOfView(); void shiftBottomOfView(); void pageUp(); void shiftPageUp(); void pageDown(); void shiftPageDown(); void top(); void shiftTop(); void bottom(); void shiftBottom(); void toMatchingBracket(); void shiftToMatchingBracket(); void toPrevModifiedLine(); void toNextModifiedLine(); void insertTab(); void gotoLine(); // config file / session management functions public: /** * Read session settings from the given \p config. * * Known flags: * "SkipUrl" => don't save/restore the file * "SkipMode" => don't save/restore the mode * "SkipHighlighting" => don't save/restore the highlighting * "SkipEncoding" => don't save/restore the encoding * * \param config read the session settings from this KConfigGroup * \param flags additional flags * \see writeSessionConfig() */ void readSessionConfig(const KConfigGroup &config, const QSet &flags = QSet()) Q_DECL_OVERRIDE; /** * Write session settings to the \p config. * See readSessionConfig() for more details. * * \param config write the session settings to this KConfigGroup * \param flags additional flags * \see readSessionConfig() */ void writeSessionConfig(KConfigGroup &config, const QSet &flags = QSet()) Q_DECL_OVERRIDE; public Q_SLOTS: void setEol(int eol); void setAddBom(bool enabled); void find(); void findSelectedForwards(); void findSelectedBackwards(); void replace(); void findNext(); void findPrevious(); void setFoldingMarkersOn(bool enable); // Not in KTextEditor::View, but should be void setIconBorder(bool enable); void setLineNumbersOn(bool enable); void setScrollBarMarks(bool enable); void setScrollBarMiniMap(bool enable); void setScrollBarMiniMapAll(bool enable); void setScrollBarMiniMapWidth(int width); void toggleFoldingMarkers(); void toggleIconBorder(); void toggleLineNumbersOn(); void toggleScrollBarMarks(); void toggleScrollBarMiniMap(); void toggleScrollBarMiniMapAll(); void toggleDynWordWrap(); void setDynWrapIndicators(int mode); public Q_SLOTS: void setSecondaryCursorsFrozen(bool freeze); void placeSecondaryCursor(); public: int getEol() const; public: KateRenderer *renderer(); bool iconBorder(); bool lineNumbersOn(); bool scrollBarMarks(); bool scrollBarMiniMap(); bool scrollBarMiniMapAll(); int dynWrapIndicators(); bool foldingMarkersOn(); private Q_SLOTS: /** * used to update actions after selection changed */ void slotSelectionChanged(); void toggleInputMode(bool); void cycleInputMode(); public: /** * accessor to katedocument pointer * @return pointer to document */ KTextEditor::DocumentPrivate *doc() { return m_doc; } const KTextEditor::DocumentPrivate *doc() const { return m_doc; } public Q_SLOTS: void slotUpdateUndo(); void toggleInsert(); void reloadFile(); void toggleWWMarker(); void toggleNPSpaces(); void toggleWordCount(bool on); void toggleWriteLock(); void switchToCmdLine(); void slotReadWriteChanged(); void slotClipboardHistoryChanged(); Q_SIGNALS: void dropEventPass(QDropEvent *); public: /** * Folding handler for this view. * @return folding handler */ Kate::TextFolding &textFolding() { return m_textFolding; } public: void slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text); void exportHtmlToFile(const QString &file); private Q_SLOTS: void slotGotFocus(); void slotLostFocus(); void slotSaveCanceled(const QString &error); void slotConfigDialog(); void exportHtmlToClipboard (); void exportHtmlToFile (); public Q_SLOTS: void slotFoldToplevelNodes(); void slotExpandToplevelNodes(); void slotCollapseLocal(); void slotExpandLocal(); private: void setupLayout(); void setupConnections(); void setupActions(); void setupEditActions(); void setupCodeFolding(); QList m_editActions; QAction *m_editUndo; QAction *m_editRedo; QAction *m_pasteMenu; KToggleAction *m_toggleFoldingMarkers; KToggleAction *m_toggleIconBar; KToggleAction *m_toggleLineNumbers; KToggleAction *m_toggleScrollBarMarks; KToggleAction *m_toggleScrollBarMiniMap; KToggleAction *m_toggleScrollBarMiniMapAll; KToggleAction *m_toggleDynWrap; KSelectAction *m_setDynWrapIndicators; KToggleAction *m_toggleWWMarker; KToggleAction *m_toggleNPSpaces; KToggleAction *m_toggleWordCount; QAction *m_switchCmdLine; KToggleAction *m_viInputModeAction; KSelectAction *m_setEndOfLine; KToggleAction *m_addBom; QAction *m_cut; QAction *m_copy; QAction *m_copyHtmlAction; QAction *m_paste; QAction *m_selectAll; QAction *m_deSelect; QList m_inputModeActions; KToggleAction *m_toggleBlockSelection; KToggleAction *m_toggleInsert; KToggleAction *m_toggleWriteLock; bool m_hasWrap; KTextEditor::DocumentPrivate *const m_doc; Kate::TextFolding m_textFolding; KateViewConfig *const m_config; KateRenderer *const m_renderer; KateViewInternal *const m_viewInternal; KateSpellCheckDialog *m_spell; KateBookmarks *const m_bookmarks; //* margins QSpacerItem *m_topSpacer; QSpacerItem *m_leftSpacer; QSpacerItem *m_rightSpacer; QSpacerItem *m_bottomSpacer; private Q_SLOTS: void slotHlChanged(); /** * Configuration */ public: inline KateViewConfig *config() { return m_config; } void updateConfig(); void updateDocumentConfig(); void updateRendererConfig(); private Q_SLOTS: void updateFoldingConfig(); private: bool m_startingUp; bool m_updatingDocumentConfig; // do we select normal or blockwise ? bool blockSelect; // templates public: bool insertTemplateInternal(const KTextEditor::Cursor& insertPosition, const QString& templateString, const QString& script = QString()); /** * Accessors to the bars... */ public: KateViewBar *bottomViewBar() const; KateDictionaryBar *dictionaryBar(); private: KateGotoBar *gotoBar(); /** * viewbar + its widgets * they are created on demand... */ private: // created in constructor of the view KateViewBar *m_bottomViewBar; // created on demand..., only access them through the above accessors.... KateGotoBar *m_gotoBar; KateDictionaryBar *m_dictionaryBar; // input modes public: KateAbstractInputMode *currentInputMode() const; public: KTextEditor::Range visibleRange(); Q_SIGNALS: void displayRangeChanged(KTextEditor::ViewPrivate *view); protected: bool event(QEvent *e) Q_DECL_OVERRIDE; void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; KToggleAction *m_toggleOnTheFlySpellCheck; KateSpellingMenu *m_spellingMenu; protected Q_SLOTS: void toggleOnTheFlySpellCheck(bool b); public Q_SLOTS: void changeDictionary(); void reflectOnTheFlySpellCheckStatus(bool enabled); public: KateSpellingMenu *spellingMenu(); private: bool m_userContextMenuSet; private Q_SLOTS: /** * save folding state before document reload */ void saveFoldingState(); /** * restore folding state after document reload */ void applyFoldingState(); void clearHighlights(); void createHighlights(); private: void selectionChangedForHighlights(); /** * saved folding state */ QJsonDocument m_savedFoldingState; QString m_currentTextForHighlights; QList m_rangesForHighlights; public: /** * Attribute of a range changed or range with attribute changed in given line range. * @param startLine start line of change * @param endLine end line of change * @param rangeWithAttribute attribute changed or is active, this will perhaps lead to repaints */ void notifyAboutRangeChange(int startLine, int endLine, bool rangeWithAttribute); private Q_SLOTS: /** * Delayed update for view after text ranges changed */ void slotDelayedUpdateOfView(); Q_SIGNALS: /** * Delayed update for view after text ranges changed */ void delayedUpdateOfView(); public: /** * set of ranges which had the mouse inside last time, used for rendering * @return set of ranges which had the mouse inside last time checked */ const QSet *rangesMouseIn() const { return &m_rangesMouseIn; } /** * set of ranges which had the caret inside last time, used for rendering * @return set of ranges which had the caret inside last time checked */ const QSet *rangesCaretIn() const { return &m_rangesCaretIn; } /** * check if ranges changed for mouse in and caret in * @param activationType type of activation to check */ void updateRangesIn(KTextEditor::Attribute::ActivationType activationType); // // helpers for delayed view update after ranges changes // private: /** * update already inited? */ bool m_delayedUpdateTriggered; /** * minimal line to update */ int m_lineToUpdateMin; /** * maximal line to update */ int m_lineToUpdateMax; /** * set of ranges which had the mouse inside last time */ QSet m_rangesMouseIn; /** * set of ranges which had the caret inside last time */ QSet m_rangesCaretIn; // // forward impl for KTextEditor::MessageInterface // public: /** * Used by Document::postMessage(). */ void postMessage(KTextEditor::Message *message, QList > actions); private: /** Message widget showing KTextEditor::Messages above the View. */ KateMessageWidget *m_topMessageWidget; /** Message widget showing KTextEditor::Messages below the View. */ KateMessageWidget *m_bottomMessageWidget; /** Message widget showing KTextEditor::Messages as view overlay in top right corner. */ KateMessageWidget *m_floatTopMessageWidget; /** Message widget showing KTextEditor::Messages as view overlay in bottom left corner. */ KateMessageWidget *m_floatBottomMessageWidget; /** Layout for floating notifications */ QVBoxLayout *m_notificationLayout; // for unit test 'tests/messagetest.cpp' public: KateMessageWidget *messageWidget() { return m_floatTopMessageWidget; } private: /** * The main window responsible for this view, if any */ QPointer m_mainWindow; // // KTextEditor::PrintInterface // public Q_SLOTS: bool print() Q_DECL_OVERRIDE; void printPreview() Q_DECL_OVERRIDE; public: /** * Get the view status bar * @return status bar, in enabled */ KateStatusBar *statusBar () const { return m_statusBar; } /** * Toogle status bar, e.g. create or remove it */ void toggleStatusBar (); /** * Get the encoding menu * @return the encoding menu */ KateViewEncodingAction *encodingAction () const { return m_encodingAction; } /** * Get the mode menu * @return the mode menu */ KateModeMenu *modeAction () const { return m_modeAction; } private: /** * the status bar of this view */ KateStatusBar *m_statusBar; /** * the encoding selection menu, used by view + status bar */ KateViewEncodingAction *m_encodingAction; /** * the mode selection menu, used by view + status bar */ KateModeMenu *m_modeAction; /** * is automatic invocation of completion disabled temporarily? */ bool m_temporaryAutomaticInvocationDisabled; public: /** * Returns the attribute for the default style \p defaultStyle. */ Attribute::Ptr defaultStyleAttribute(DefaultStyle defaultStyle) const Q_DECL_OVERRIDE; /** * Get the list of AttributeBlocks for a given \p line in the document. * * \return list of AttributeBlocks for given \p line. */ QList lineAttributes(int line) Q_DECL_OVERRIDE; private: // remember folding state to prevent refolding the first line if it was manually unfolded, // e.g. when saving a file or changing other config vars bool m_autoFoldedFirstLine; private: KateMultiClipboard m_clipboard; + +public: + void setScrollPositionInternal(KTextEditor::Cursor &cursor); + + void setHorizontalScrollPositionInternal(int x); + + KTextEditor::Cursor maxScrollPositionInternal() const; + + int firstDisplayedLineInternal(LineType lineType) const; + + int lastDisplayedLineInternal(LineType lineType) const; + + QRect textAreaRectInternal() const; }; } #endif diff --git a/src/view/kateviewaccessible.h b/src/view/kateviewaccessible.h index 673bf460..c3387008 100644 --- a/src/view/kateviewaccessible.h +++ b/src/view/kateviewaccessible.h @@ -1,247 +1,247 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Sebastian Sauer Copyright (C) 2012 Frederik Gladhorn 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_VIEW_ACCESSIBLE_ #define _KATE_VIEW_ACCESSIBLE_ #ifndef QT_NO_ACCESSIBILITY #include "kateviewinternal.h" #include "katedocument.h" #include #include #include /** * This class implements a QAccessible-interface for a KateViewInternal. * * This is the root class for the kateview. The \a KateCursorAccessible class * represents the cursor in the kateview and is a child of this class. */ class KateViewAccessible : public QAccessibleWidget, public QAccessibleTextInterface // FIXME maybe:, public QAccessibleEditableTextInterface { public: explicit KateViewAccessible(KateViewInternal *view) : QAccessibleWidget(view, QAccessible::EditableText) { } void *interface_cast(QAccessible::InterfaceType t) Q_DECL_OVERRIDE { if (t == QAccessible::TextInterface) return static_cast(this); - return 0; + return nullptr; } virtual ~KateViewAccessible() { } QAccessibleInterface *childAt(int x, int y) const Q_DECL_OVERRIDE { Q_UNUSED(x); Q_UNUSED(y); - return 0; + return nullptr; } void setText(QAccessible::Text t, const QString &text) Q_DECL_OVERRIDE { if (t == QAccessible::Value && view()->view()->document()) { view()->view()->document()->setText(text); } } QAccessible::State state() const Q_DECL_OVERRIDE { QAccessible::State s = QAccessibleWidget::state(); s.focusable = view()->focusPolicy() != Qt::NoFocus; s.focused = view()->hasFocus(); s.editable = true; s.multiLine = true; s.selectableText = true; return s; } QString text(QAccessible::Text t) const Q_DECL_OVERRIDE { QString s; if (view()->view()->document()) { if (t == QAccessible::Name) { s = view()->view()->document()->documentName(); } if (t == QAccessible::Value) { s = view()->view()->document()->text(); } } return s; } int characterCount() const Q_DECL_OVERRIDE { return view()->view()->document()->text().size(); } void addSelection(int startOffset, int endOffset) Q_DECL_OVERRIDE { KTextEditor::Range range; range.setRange(cursorFromInt(startOffset), cursorFromInt(endOffset)); view()->view()->setSelection(range); view()->view()->setCursorPosition(cursorFromInt(endOffset)); } QString attributes(int offset, int *startOffset, int *endOffset) const Q_DECL_OVERRIDE { Q_UNUSED(offset); *startOffset = 0; *endOffset = characterCount(); return QString(); } QRect characterRect(int offset) const Q_DECL_OVERRIDE { KTextEditor::Cursor c = cursorFromInt(offset); if (!c.isValid()) { return QRect(); } QPoint p = view()->cursorToCoordinate(c); KTextEditor::Cursor endCursor = KTextEditor::Cursor(c.line(), c.column() + 1); QPoint size = view()->cursorToCoordinate(endCursor) - p; return QRect(view()->mapToGlobal(p), QSize(size.x(), size.y())); } int cursorPosition() const Q_DECL_OVERRIDE { KTextEditor::Cursor c = view()->primaryCursor(); return positionFromCursor(view(), c); } int offsetAtPoint(const QPoint & /*point*/) const Q_DECL_OVERRIDE { return 0; } void removeSelection(int selectionIndex) Q_DECL_OVERRIDE { if (selectionIndex != 0) { return; } view()->view()->clearSelection(); } void scrollToSubstring(int /*startIndex*/, int /*endIndex*/) Q_DECL_OVERRIDE { // FIXME } void selection(int selectionIndex, int *startOffset, int *endOffset) const Q_DECL_OVERRIDE { if (selectionIndex != 0 || !view()->view()->selection()) { *startOffset = 0; *endOffset = 0; return; } KTextEditor::Range range = view()->view()->selectionRange(); *startOffset = positionFromCursor(view(), range.start()); *endOffset = positionFromCursor(view(), range.end()); } int selectionCount() const Q_DECL_OVERRIDE { return view()->view()->selection() ? 1 : 0; } void setCursorPosition(int position) Q_DECL_OVERRIDE { view()->view()->setCursorPosition(cursorFromInt(position)); } void setSelection(int selectionIndex, int startOffset, int endOffset) Q_DECL_OVERRIDE { if (selectionIndex != 0) { return; } KTextEditor::Range range = KTextEditor::Range(cursorFromInt(startOffset), cursorFromInt(endOffset)); view()->view()->setSelection(range); } QString text(int startOffset, int endOffset) const Q_DECL_OVERRIDE { if (startOffset > endOffset) { return QString(); } return view()->view()->document()->text().mid(startOffset, endOffset - startOffset); } static int positionFromCursor(KateViewInternal *view, const KTextEditor::Cursor &cursor) { int pos = 0; for (int line = 0; line < cursor.line(); ++line) { // length of the line plus newline pos += view->view()->document()->line(line).size() + 1; } pos += cursor.column(); return pos; } private: inline KateViewInternal *view() const { return static_cast(object()); } KTextEditor::Cursor cursorFromInt(int position) const { int line = 0; for (;;) { const QString lineString = view()->view()->document()->line(line); if (position > lineString.length()) { // one is the newline position -= lineString.length() + 1; ++line; } else { break; } } return KTextEditor::Cursor(line, position); } QString textLine(int shiftLines, int offset, int *startOffset, int *endOffset) const { KTextEditor::Cursor pos = cursorFromInt(offset); pos.setColumn(0); if (shiftLines) { pos.setLine(pos.line() + shiftLines); } *startOffset = positionFromCursor(view(), pos); QString line = view()->view()->document()->line(pos.line()) + QLatin1Char('\n'); *endOffset = *startOffset + line.length(); return line; } }; /** * Factory-function used to create \a KateViewAccessible instances for KateViewInternal * to make the KateViewInternal accessible. */ QAccessibleInterface *accessibleInterfaceFactory(const QString &key, QObject *object) { Q_UNUSED(key) //if (key == QLatin1String("KateViewInternal")) if (KateViewInternal *view = qobject_cast(object)) { return new KateViewAccessible(view); } - return 0; + return nullptr; } #endif #endif diff --git a/src/view/kateviewhelpers.cpp b/src/view/kateviewhelpers.cpp index 8803304c..d2ab4864 100644 --- a/src/view/kateviewhelpers.cpp +++ b/src/view/kateviewhelpers.cpp @@ -1,2783 +1,2809 @@ /* This file is part of the KDE libraries Copyright (C) 2008, 2009 Matthew Woehlke Copyright (C) 2007 Mirko Stocker Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Anders Lund Copyright (C) 2001 Christoph Cullmann Copyright (C) 2011 Svyatoslav Kuzmich Copyright (C) 2012 Kåre Särs (Minimap) 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. */ #include "kateviewhelpers.h" #include "katecmd.h" #include #include #include #include "kateconfig.h" #include "katedocument.h" #include #include "katerenderer.h" #include "kateview.h" #include "kateviewinternal.h" #include "katelayoutcache.h" #include "katetextlayout.h" #include "kateglobal.h" #include "katepartdebug.h" #include "katecommandrangeexpressionparser.h" #include "kateabstractinputmode.h" #include "katetextpreview.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 #include #include //BEGIN KateScrollBar static const int s_lineWidth = 100; static const int s_pixelMargin = 8; static const int s_linePixelIncLimit = 6; const unsigned char KateScrollBar::characterOpacity[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 15 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, // <- 31 0, 125, 41, 221, 138, 195, 218, 21, 142, 142, 137, 137, 97, 87, 87, 140, // <- 47 223, 164, 183, 190, 191, 193, 214, 158, 227, 216, 103, 113, 146, 140, 146, 149, // <- 63 248, 204, 240, 174, 217, 197, 178, 205, 209, 176, 168, 211, 160, 246, 238, 218, // <- 79 195, 229, 227, 196, 167, 212, 188, 238, 197, 169, 189, 158, 21, 151, 115, 90, // <- 95 15, 192, 209, 153, 208, 187, 162, 221, 183, 149, 161, 191, 146, 203, 167, 182, // <- 111 208, 203, 139, 166, 158, 167, 157, 189, 164, 179, 156, 167, 145, 166, 109, 0, // <- 127 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 159 0, 125, 184, 187, 146, 201, 127, 203, 89, 194, 156, 141, 117, 87, 202, 88, // <- 175 115, 165, 118, 121, 85, 190, 236, 87, 88, 111, 151, 140, 194, 191, 203, 148, // <- 191 215, 215, 222, 224, 223, 234, 230, 192, 208, 208, 216, 217, 187, 187, 194, 195, // <- 207 228, 255, 228, 228, 235, 239, 237, 150, 255, 222, 222, 229, 232, 180, 197, 225, // <- 223 208, 208, 216, 217, 212, 230, 218, 170, 202, 202, 211, 204, 156, 156, 165, 159, // <- 239 214, 194, 197, 197, 206, 206, 201, 132, 214, 183, 183, 192, 187, 195, 227, 198 }; KateScrollBar::KateScrollBar(Qt::Orientation orientation, KateViewInternal *parent) : QScrollBar(orientation, parent->m_view) , m_middleMouseDown(false) , m_leftMouseDown(false) , m_view(parent->m_view) , m_doc(parent->doc()) , m_viewInternal(parent) , m_textPreview(nullptr) , m_showMarks(false) , m_showMiniMap(false) , m_miniMapAll(true) , m_miniMapWidth(40) , m_grooveHeight(height()) , m_linesModified(0) { connect(this, SIGNAL(valueChanged(int)), this, SLOT(sliderMaybeMoved(int))); connect(m_doc, SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(marksChanged())); m_updateTimer.setInterval(300); m_updateTimer.setSingleShot(true); QTimer::singleShot(10, this, SLOT(updatePixmap())); // track mouse for text preview widget setMouseTracking(orientation == Qt::Vertical); // setup text preview timer m_delayTextPreviewTimer.setSingleShot(true); m_delayTextPreviewTimer.setInterval(250); connect(&m_delayTextPreviewTimer, SIGNAL(timeout()), this, SLOT(showTextPreview())); } KateScrollBar::~KateScrollBar() { delete m_textPreview; } void KateScrollBar::setShowMiniMap(bool b) { if (b && !m_showMiniMap) { connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_doc, SIGNAL(textChanged(KTextEditor::Document*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_view, SIGNAL(delayedUpdateOfView()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updatePixmap()), Qt::UniqueConnection); connect(&(m_view->textFolding()), SIGNAL(foldingRangesChanged()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); } else if (!b) { disconnect(&m_updateTimer); } m_showMiniMap = b; updateGeometry(); update(); } QSize KateScrollBar::sizeHint() const { if (m_showMiniMap) { return QSize(m_miniMapWidth, QScrollBar::sizeHint().height()); } return QScrollBar::sizeHint(); } int KateScrollBar::minimapYToStdY(int y) { // Check if the minimap fills the whole scrollbar if (m_stdGroveRect.height() == m_mapGroveRect.height()) { return y; } // check if y is on the step up/down if ((y < m_stdGroveRect.top()) || (y > m_stdGroveRect.bottom())) { return y; } if (y < m_mapGroveRect.top()) { return m_stdGroveRect.top() + 1; } if (y > m_mapGroveRect.bottom()) { return m_stdGroveRect.bottom() - 1; } // check for div/0 if (m_mapGroveRect.height() == 0) { return y; } int newY = (y - m_mapGroveRect.top()) * m_stdGroveRect.height() / m_mapGroveRect.height(); newY += m_stdGroveRect.top(); return newY; } void KateScrollBar::mousePressEvent(QMouseEvent *e) { // delete text preview hideTextPreview(); + if (e->button() == Qt::MidButton) { + m_middleMouseDown = true; + } else if (e->button() == Qt::LeftButton) { + m_leftMouseDown = true; + } + if (m_showMiniMap) { + if (m_leftMouseDown) { + // if we show the minimap left-click jumps directly to the selected position + int newVal = (e->pos().y()-m_mapGroveRect.top()) / (double)m_mapGroveRect.height() * (double)(maximum()+pageStep()) - pageStep()/2; + newVal = qBound(0, newVal, maximum()); + setSliderPosition(newVal); + } QMouseEvent eMod(QEvent::MouseButtonPress, QPoint(6, minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mousePressEvent(&eMod); } else { QScrollBar::mousePressEvent(e); } - if (e->button() == Qt::MidButton) { - m_middleMouseDown = true; - } else if (e->button() == Qt::LeftButton) { - m_leftMouseDown = true; - } - m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); redrawMarks(); } void KateScrollBar::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::MidButton) { m_middleMouseDown = false; } else if (e->button() == Qt::LeftButton) { m_leftMouseDown = false; } redrawMarks(); if (m_leftMouseDown || m_middleMouseDown) { QToolTip::hideText(); } if (m_showMiniMap) { QMouseEvent eMod(QEvent::MouseButtonRelease, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mouseReleaseEvent(&eMod); } else { QScrollBar::mouseReleaseEvent(e); } } void KateScrollBar::mouseMoveEvent(QMouseEvent *e) { if (m_showMiniMap) { QMouseEvent eMod(QEvent::MouseMove, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mouseMoveEvent(&eMod); } else { QScrollBar::mouseMoveEvent(e); } if (e->buttons() & (Qt::LeftButton | Qt::MidButton)) { redrawMarks(); // current line tool tip m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); } showTextPreviewDelayed(); } void KateScrollBar::leaveEvent(QEvent *event) { hideTextPreview(); QAbstractSlider::leaveEvent(event); } bool KateScrollBar::eventFilter(QObject *object, QEvent *event) { Q_UNUSED(object) if (m_textPreview && event->type() == QEvent::WindowDeactivate) { // We need hide the scrollbar TextPreview widget hideTextPreview(); } return false; } void KateScrollBar::paintEvent(QPaintEvent *e) { if (m_doc->marks().size() != m_lines.size()) { recomputeMarksPositions(); } if (m_showMiniMap) { miniMapPaintEvent(e); } else { normalPaintEvent(e); } } void KateScrollBar::showTextPreviewDelayed() { if (!m_textPreview) { if (!m_delayTextPreviewTimer.isActive()) { m_delayTextPreviewTimer.start(); } } else { showTextPreview(); } } void KateScrollBar::showTextPreview() { if (orientation() != Qt::Vertical || isSliderDown() || (minimum() == maximum()) || !m_view->config()->scrollBarPreview()) { return; } QRect grooveRect; if (m_showMiniMap) { // If mini-map is shown, the height of the map might not be the whole height grooveRect = m_mapGroveRect; } else { QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); } if (m_view->config()->scrollPastEnd()) { // Adjust the grove size to accommodate the added pageStep at the bottom int adjust = pageStep()*grooveRect.height() / (maximum() + pageStep() - minimum()); grooveRect.adjust(0,0,0, -adjust); } const QPoint cursorPos = mapFromGlobal(QCursor::pos()); if (grooveRect.contains(cursorPos)) { if (!m_textPreview) { m_textPreview = new KateTextPreview(m_view); m_textPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_textPreview->setFrameStyle(QFrame::StyledPanel); // event filter to catch application WindowDeactivate event, to hide the preview window qApp->installEventFilter(this); } const qreal posInPercent = static_cast(cursorPos.y() - grooveRect.top()) / grooveRect.height(); const qreal startLine = posInPercent * m_view->textFolding().visibleLines(); m_textPreview->resize(m_view->width() / 2, m_view->height() / 5); const int xGlobal = mapToGlobal(QPoint(0, 0)).x(); const int yGlobal = qMin(mapToGlobal(QPoint(0, height())).y() - m_textPreview->height(), qMax(mapToGlobal(QPoint(0, 0)).y(), mapToGlobal(cursorPos).y() - m_textPreview->height() / 2)); m_textPreview->move(xGlobal - m_textPreview->width(), yGlobal); m_textPreview->setLine(startLine); m_textPreview->setCenterView(true); m_textPreview->setScaleFactor(0.8); m_textPreview->raise(); m_textPreview->show(); } else { hideTextPreview(); } } void KateScrollBar::hideTextPreview() { if (m_delayTextPreviewTimer.isActive()) { m_delayTextPreviewTimer.stop(); } qApp->removeEventFilter(this); delete m_textPreview; } // This function is optimized for bing called in sequence. const QColor KateScrollBar::charColor(const QVector &attributes, int &attributeIndex, const QList &decorations, const QColor &defaultColor, int x, QChar ch) { QColor color = defaultColor; bool styleFound = false; // Query the decorations, that is, things like search highlighting, or the // KDevelop DUChain highlighting, for a color to use foreach (const QTextLayout::FormatRange &range, decorations) { if (range.start <= x && range.start + range.length > x) { // If there's a different background color set (search markers, ...) // use that, otherwise use the foreground color. if (range.format.hasProperty(QTextFormat::BackgroundBrush)) { color = range.format.background().color(); } else { color = range.format.foreground().color(); } styleFound = true; break; } } // If there's no decoration set for the current character (this will mostly be the case for // plain Kate), query the styles, that is, the default kate syntax highlighting. if (!styleFound) { // go to the block containing x while ((attributeIndex < attributes.size()) && ((attributes[attributeIndex].offset + attributes[attributeIndex].length) < x)) { ++attributeIndex; } if ((attributeIndex < attributes.size()) && (x < attributes[attributeIndex].offset + attributes[attributeIndex].length)) { color = m_view->renderer()->attribute(attributes[attributeIndex].attributeValue)->foreground().color(); } } // Query how much "blackness" the character has. // This causes for example a dot or a dash to appear less intense // than an A or similar. // This gives the pixels created a bit of structure, which makes it look more // like real text. color.setAlpha((ch.unicode() < 256) ? characterOpacity[ch.unicode()] : 1.0); return color; } void KateScrollBar::updatePixmap() { //QTime time; //time.start(); if (!m_showMiniMap) { // make sure no time is wasted if the option is disabled return; } // For performance reason, only every n-th line will be drawn if the widget is // sufficiently small compared to the amount of lines in the document. int docLineCount = m_view->textFolding().visibleLines(); int pixmapLineCount = docLineCount; if (m_view->config()->scrollPastEnd()) { pixmapLineCount += pageStep(); } int pixmapLinesUnscaled = pixmapLineCount; if (m_grooveHeight < 5) { m_grooveHeight = 5; } int lineDivisor = pixmapLinesUnscaled / m_grooveHeight; if (lineDivisor < 1) { lineDivisor = 1; } int charIncrement = 1; int lineIncrement = 1; if ((m_grooveHeight > 10) && (pixmapLineCount >= m_grooveHeight * 2)) { charIncrement = pixmapLineCount / m_grooveHeight; while (charIncrement > s_linePixelIncLimit) { lineIncrement++; pixmapLineCount = pixmapLinesUnscaled / lineIncrement; charIncrement = pixmapLineCount / m_grooveHeight; } pixmapLineCount /= charIncrement; } int pixmapLineWidth = s_pixelMargin + s_lineWidth / charIncrement; //qCDebug(LOG_KTE) << "l" << lineIncrement << "c" << charIncrement << "d" << lineDivisor; //qCDebug(LOG_KTE) << "pixmap" << pixmapLineCount << pixmapLineWidth << "docLines" << m_view->textFolding().visibleLines() << "height" << m_grooveHeight; const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color(); const QColor defaultTextColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor selectionBgColor = m_view->renderer()->config()->selectionColor(); QColor modifiedLineColor = m_view->renderer()->config()->modifiedLineColor(); QColor savedLineColor = m_view->renderer()->config()->savedLineColor(); // move the modified line color away from the background color modifiedLineColor.setHsv(modifiedLineColor.hue(), 255, 255 - backgroundColor.value() / 3); savedLineColor.setHsv(savedLineColor.hue(), 100, 255 - backgroundColor.value() / 3); - m_pixmap = QPixmap(pixmapLineWidth, pixmapLineCount); + // increase dimensions by ratio + m_pixmap = QPixmap(pixmapLineWidth * m_view->devicePixelRatio(), pixmapLineCount * m_view->devicePixelRatio()); m_pixmap.fill(QColor("transparent")); // The text currently selected in the document, to be drawn later. const KTextEditor::Range &selection = m_view->selectionRange(); QPainter painter; if (painter.begin(&m_pixmap)) { + // init pen once, afterwards, only change it if color changes to avoid a lot of allocation for setPen + painter.setPen(selectionBgColor); + // Do not force updates of the highlighting if the document is very large bool simpleMode = m_doc->lines() > 7500; int pixelY = 0; int drawnLines = 0; // Iterate over all visible lines, drawing them. for (int virtualLine = 0; virtualLine < docLineCount; virtualLine += lineIncrement) { int realLineNumber = m_view->textFolding().visibleLineToLine(virtualLine); QString lineText = m_doc->line(realLineNumber); if (!simpleMode) { m_doc->buffer().ensureHighlighted(realLineNumber); } const Kate::TextLine &kateline = m_doc->plainKateTextLine(realLineNumber); const QVector &attributes = kateline->attributesList(); QList< QTextLayout::FormatRange > decorations = m_view->renderer()->decorationsForLine(kateline, realLineNumber); int attributeIndex = 0; // Draw selection if it is on an empty line if (selection.contains(KTextEditor::Cursor(realLineNumber, 0)) && lineText.size() == 0) { - painter.setPen(selectionBgColor); + if (selectionBgColor != painter.pen().color()) { + painter.setPen(selectionBgColor); + } painter.drawLine(s_pixelMargin, pixelY, s_pixelMargin + s_lineWidth - 1, pixelY); } // Iterate over the line to draw the background int selStartX = -1; int selEndX = -1; int pixelX = s_pixelMargin; // use this to control the offset of the text from the left for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) { if (pixelX >= s_lineWidth + s_pixelMargin) { break; } // Query the selection and draw it behind the character if (selection.contains(KTextEditor::Cursor(realLineNumber, x))) { if (selStartX == -1) selStartX = pixelX; selEndX = pixelX; if (lineText.size() - 1 == x) { selEndX = s_lineWidth + s_pixelMargin-1; } } if (lineText[x] == QLatin1Char('\t')) { pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width... } else { pixelX++; } } if (selStartX != -1) { - painter.setPen(selectionBgColor); + if (selectionBgColor != painter.pen().color()) { + painter.setPen(selectionBgColor); + } painter.drawLine(selStartX, pixelY, selEndX, pixelY); } // Iterate over all the characters in the current line pixelX = s_pixelMargin; for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) { if (pixelX >= s_lineWidth + s_pixelMargin) { break; } // draw the pixels if (lineText[x] == QLatin1Char(' ')) { pixelX++; } else if (lineText[x] == QLatin1Char('\t')) { pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width... } else { - painter.setPen(charColor(attributes, attributeIndex, decorations, defaultTextColor, x, lineText[x])); + const QColor newPenColor(charColor(attributes, attributeIndex, decorations, defaultTextColor, x, lineText[x])); + if (newPenColor != painter.pen().color()) { + painter.setPen(newPenColor); + } // Actually draw the pixel with the color queried from the renderer. painter.drawPoint(pixelX, pixelY); pixelX++; } } drawnLines++; if (((drawnLines) % charIncrement) == 0) { pixelY++; } } //qCDebug(LOG_KTE) << drawnLines; // Draw line modification marker map. // Disable this if the document is really huge, // since it requires querying every line. if (m_doc->lines() < 50000) { for (int lineno = 0; lineno < docLineCount; lineno++) { int realLineNo = m_view->textFolding().visibleLineToLine(lineno); const Kate::TextLine &line = m_doc->plainKateTextLine(realLineNo); const QColor & col = line->markedAsModified() ? modifiedLineColor : savedLineColor; if (line->markedAsModified() || line->markedAsSavedOnDisk()) { painter.fillRect(2, lineno / lineDivisor, 3, 1, col); } } } + + // end painting + painter.end(); } + + // set right ratio + m_pixmap.setDevicePixelRatio(m_view->devicePixelRatio()); + //qCDebug(LOG_KTE) << time.elapsed(); // Redraw the scrollbar widget with the updated pixmap. update(); } void KateScrollBar::miniMapPaintEvent(QPaintEvent *e) { QScrollBar::paintEvent(e); QPainter painter(this); QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); m_stdGroveRect = grooveRect; if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSubLine, this).height() == 0) { int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this); grooveRect.moveTop(alignMargin); grooveRect.setHeight(grooveRect.height() - alignMargin); } if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarAddLine, this).height() == 0) { int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this); grooveRect.setHeight(grooveRect.height() - alignMargin); } m_grooveHeight = grooveRect.height(); const int docXMargin = 1; QRect sliderRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this); sliderRect.adjust(docXMargin, 0, 0, 0); //style()->drawControl(QStyle::CE_ScrollBarAddLine, &opt, &painter, this); //style()->drawControl(QStyle::CE_ScrollBarSubLine, &opt, &painter, this); // calculate the document size and position - const int docHeight = qMin(grooveRect.height(), m_pixmap.height() * 2) - 2 * docXMargin; + const int docHeight = qMin(grooveRect.height(), int(m_pixmap.height() / m_pixmap.devicePixelRatio() * 2)) - 2 * docXMargin; const int yoffset = 1; // top-aligned in stead of center-aligned (grooveRect.height() - docHeight) / 2; const QRect docRect(QPoint(grooveRect.left() + docXMargin, yoffset + grooveRect.top()), QSize(grooveRect.width() - docXMargin, docHeight)); m_mapGroveRect = docRect; // calculate the visible area int max = qMax(maximum() + 1, 1); int visibleStart = value() * docHeight / (max + pageStep()) + docRect.top() + 0.5; int visibleEnd = (value() + pageStep()) * docHeight / (max + pageStep()) + docRect.top(); QRect visibleRect = docRect; visibleRect.moveTop(visibleStart); visibleRect.setHeight(visibleEnd - visibleStart); - m_mapSliderRect = visibleRect; // calculate colors const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color(); const QColor foregroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor highlightColor = palette().link().color(); const int backgroundLightness = backgroundColor.lightness(); const int foregroundLightness = foregroundColor.lightness(); const int lighnessDiff = (foregroundLightness - backgroundLightness); // get a color suited for the color theme QColor darkShieldColor = palette().color(QPalette::Mid); int hue, sat, light; darkShieldColor.getHsl(&hue, &sat, &light); // apply suitable lightness darkShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.35); // gradient for nicer results QLinearGradient gradient(0, 0, width(), 0); gradient.setColorAt(0, darkShieldColor); gradient.setColorAt(0.3, darkShieldColor.lighter(115)); gradient.setColorAt(1, darkShieldColor); QColor lightShieldColor; lightShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.15); QColor outlineColor; outlineColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.5); // draw the grove background in case the document is small painter.setPen(Qt::NoPen); painter.setBrush(backgroundColor); painter.drawRect(grooveRect); // adjust the rectangles if ((docHeight + 2 * docXMargin >= grooveRect.height()) && (sliderRect.height() > visibleRect.height() + 2)) { visibleRect.adjust(2, 0, -3, 0); } else { visibleRect.adjust(1, 0, -1, 2); sliderRect.setTop(visibleRect.top() - 1); sliderRect.setBottom(visibleRect.bottom() + 1); } // Smooth transform only when squeezing - if (grooveRect.height() < m_pixmap.height()) { + if (grooveRect.height() < m_pixmap.height() / m_pixmap.devicePixelRatio()) { painter.setRenderHint(QPainter::SmoothPixmapTransform); } // draw the modified lines margin - QRect pixmapMarginRect(QPoint(0, 0), QSize(s_pixelMargin, m_pixmap.height())); + QRect pixmapMarginRect(QPoint(0, 0), QSize(s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio())); QRect docPixmapMarginRect(QPoint(0, docRect.top()), QSize(s_pixelMargin, docRect.height())); painter.drawPixmap(docPixmapMarginRect, m_pixmap, pixmapMarginRect); // calculate the stretch and draw the stretched lines (scrollbar marks) - QRect pixmapRect(QPoint(s_pixelMargin, 0), QSize(m_pixmap.width() - s_pixelMargin, m_pixmap.height())); + QRect pixmapRect(QPoint(s_pixelMargin, 0), QSize(m_pixmap.width() / m_pixmap.devicePixelRatio() - s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio())); QRect docPixmapRect(QPoint(s_pixelMargin, docRect.top()), QSize(docRect.width() - s_pixelMargin, docRect.height())); painter.drawPixmap(docPixmapRect, m_pixmap, pixmapRect); // delimit the end of the document const int y = docPixmapRect.height() + grooveRect.y(); if (y+2 < grooveRect.y() + grooveRect.height()) { QColor fg(foregroundColor); fg.setAlpha(30); painter.setBrush(Qt::NoBrush); painter.setPen(QPen(fg, 1)); painter.drawLine(grooveRect.x()+1,y+2,width()-1,y+2); } // fade the invisible sections const QRect top( grooveRect.x(), grooveRect.y(), grooveRect.width(), visibleRect.y()-grooveRect.y() //Pen width ); const QRect bottom( grooveRect.x(), grooveRect.y()+visibleRect.y()+visibleRect.height()-grooveRect.y(), //Pen width grooveRect.width(), grooveRect.height() - (visibleRect.y()-grooveRect.y())-visibleRect.height() ); QColor faded(backgroundColor); faded.setAlpha(110); painter.fillRect(top, faded); painter.fillRect(bottom, faded); // add a thin line to limit the scrollbar QColor c(foregroundColor); c.setAlpha(10); painter.setPen(QPen(c,1)); painter.drawLine(0, 0, 0, height()); if (m_showMarks) { QHashIterator it = m_lines; QPen penBg; penBg.setWidth(4); lightShieldColor.setAlpha(180); penBg.setColor(lightShieldColor); painter.setPen(penBg); while (it.hasNext()) { it.next(); int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();; painter.drawLine(6, y, width() - 6, y); } it = m_lines; QPen pen; pen.setWidth(2); while (it.hasNext()) { it.next(); pen.setColor(it.value()); painter.setPen(pen); int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top(); painter.drawLine(6, y, width() - 6, y); } } // slider outline QColor sliderColor(highlightColor); sliderColor.setAlpha(50); painter.fillRect(sliderRect, sliderColor); painter.setPen(QPen(highlightColor, 0)); // rounded rect looks ugly for some reason, so we draw 4 lines. painter.drawLine(sliderRect.left(), sliderRect.top() + 1, sliderRect.left(), sliderRect.bottom() - 1); painter.drawLine(sliderRect.right(), sliderRect.top() + 1, sliderRect.right(), sliderRect.bottom() - 1); painter.drawLine(sliderRect.left() + 1, sliderRect.top(), sliderRect.right() - 1, sliderRect.top()); painter.drawLine(sliderRect.left() + 1, sliderRect.bottom(), sliderRect.right() - 1, sliderRect.bottom()); } void KateScrollBar::normalPaintEvent(QPaintEvent *e) { QScrollBar::paintEvent(e); if (!m_showMarks) { return; } QPainter painter(this); QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); QRect rect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this); int sideMargin = width() - rect.width(); if (sideMargin < 4) { sideMargin = 4; } sideMargin /= 2; QHashIterator it = m_lines; while (it.hasNext()) { it.next(); painter.setPen(it.value()); if (it.key() < rect.top() || it.key() > rect.bottom()) { painter.drawLine(0, it.key(), width(), it.key()); } else { painter.drawLine(0, it.key(), sideMargin, it.key()); painter.drawLine(width() - sideMargin, it.key(), width(), it.key()); } } } void KateScrollBar::resizeEvent(QResizeEvent *e) { QScrollBar::resizeEvent(e); m_updateTimer.start(); m_lines.clear(); update(); } void KateScrollBar::sliderChange(SliderChange change) { // call parents implementation QScrollBar::sliderChange(change); if (change == QAbstractSlider::SliderValueChange) { redrawMarks(); } else if (change == QAbstractSlider::SliderRangeChange) { marksChanged(); } if (m_leftMouseDown || m_middleMouseDown) { const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); } } void KateScrollBar::marksChanged() { m_lines.clear(); update(); } void KateScrollBar::redrawMarks() { if (!m_showMarks) { return; } update(); } void KateScrollBar::recomputeMarksPositions() { // get the style options to compute the scrollbar pixels QStyleOptionSlider opt; initStyleOption(&opt); QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); // cache top margin and groove height const int top = grooveRect.top(); const int h = grooveRect.height() - 1; // make sure we have a sane height if (h <= 0) { return; } // get total visible (=without folded) lines in the document int visibleLines = m_view->textFolding().visibleLines() - 1; if (m_view->config()->scrollPastEnd()) { visibleLines += m_viewInternal->linesDisplayed() - 1; visibleLines -= m_view->config()->autoCenterLines(); } // now repopulate the scrollbar lines list m_lines.clear(); const QHash &marks = m_doc->marks(); for (QHash::const_iterator i = marks.constBegin(); i != marks.constEnd(); ++i) { KTextEditor::Mark *mark = i.value(); const int line = m_view->textFolding().lineToVisibleLine(mark->line); const double ratio = static_cast(line) / visibleLines; m_lines.insert(top + (int)(h * ratio), KateRendererConfig::global()->lineMarkerColor((KTextEditor::MarkInterface::MarkTypes)mark->type)); } } void KateScrollBar::sliderMaybeMoved(int value) { if (m_middleMouseDown) { // we only need to emit this signal once, as for the following slider // movements the signal sliderMoved() is already emitted. // Thus, set m_middleMouseDown to false right away. m_middleMouseDown = false; emit sliderMMBMoved(value); } } //END //BEGIN KateCmdLineEditFlagCompletion /** * This class provide completion of flags. It shows a short description of * each flag, and flags are appended. */ class KateCmdLineEditFlagCompletion : public KCompletion { public: KateCmdLineEditFlagCompletion() { ; } QString makeCompletion(const QString & /*s*/) Q_DECL_OVERRIDE { return QString(); } }; //END KateCmdLineEditFlagCompletion //BEGIN KateCmdLineEdit KateCommandLineBar::KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent) : KateViewBarWidget(true, parent) { QHBoxLayout *topLayout = new QHBoxLayout(); centralWidget()->setLayout(topLayout); topLayout->setMargin(0); m_lineEdit = new KateCmdLineEdit(this, view); connect(m_lineEdit, SIGNAL(hideRequested()), SIGNAL(hideMe())); topLayout->addWidget(m_lineEdit); QToolButton *helpButton = new QToolButton(this); helpButton->setAutoRaise(true); helpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); topLayout->addWidget(helpButton); connect(helpButton, SIGNAL(clicked()), this, SLOT(showHelpPage())); setFocusProxy(m_lineEdit); } void KateCommandLineBar::showHelpPage() { KHelpClient::invokeHelp(QStringLiteral("advanced-editing-tools-commandline"), QStringLiteral("kate")); } KateCommandLineBar::~KateCommandLineBar() { } // inserts the given string in the command line edit and (if selected = true) selects it so the user // can type over it if they want to void KateCommandLineBar::setText(const QString &text, bool selected) { m_lineEdit->setText(text); if (selected) { m_lineEdit->selectAll(); } } void KateCommandLineBar::execute(const QString &text) { m_lineEdit->slotReturnPressed(text); } KateCmdLineEdit::KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view) : KLineEdit() , m_view(view) , m_bar(bar) , m_msgMode(false) , m_histpos(0) , m_cmdend(0) - , m_command(0L) + , m_command(nullptr) { connect(this, SIGNAL(returnPressed(QString)), this, SLOT(slotReturnPressed(QString))); setCompletionObject(KateCmd::self()->commandCompletionObject()); setAutoDeleteCompletionObject(false); m_hideTimer = new QTimer(this); m_hideTimer->setSingleShot(true); connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hideLineEdit())); // make sure the timer is stopped when the user switches views. if not, focus will be given to the // wrong view when KateViewBar::hideCurrentBarWidget() is called after 4 seconds. (the timer is // used for showing things like "Success" for four seconds after the user has used the kate // command line) connect(m_view, SIGNAL(focusOut(KTextEditor::View*)), m_hideTimer, SLOT(stop())); } void KateCmdLineEdit::hideEvent(QHideEvent *e) { Q_UNUSED(e); } QString KateCmdLineEdit::helptext(const QPoint &) const { QString beg = QStringLiteral("
Help: "); QString mid = QStringLiteral("
"); QString end = QStringLiteral("
"); QString t = text(); QRegExp re(QLatin1String("\\s*help\\s+(.*)")); if (re.indexIn(t) > -1) { QString s; // get help for command QString name = re.cap(1); if (name == QLatin1String("list")) { return beg + i18n("Available Commands") + mid + KateCmd::self()->commandList().join(QLatin1Char(' ')) + i18n("

For help on individual commands, do 'help <command>'

") + end; } else if (! name.isEmpty()) { KTextEditor::Command *cmd = KateCmd::self()->queryCommand(name); if (cmd) { if (cmd->help(m_view, name, s)) { return beg + name + mid + s + end; } else { return beg + name + mid + i18n("No help for '%1'", name) + end; } } else { return beg + mid + i18n("No such command %1", name) + end; } } } return beg + mid + i18n( "

This is the Katepart command line.
" "Syntax: command [ arguments ]
" "For a list of available commands, enter help list
" "For help for individual commands, enter help <command>

") + end; } bool KateCmdLineEdit::event(QEvent *e) { if (e->type() == QEvent::QueryWhatsThis) { setWhatsThis(helptext(QPoint())); e->accept(); return true; } return KLineEdit::event(e); } /** * Parse the text as a command. * * The following is a simple PEG grammar for the syntax of the command. * (A PEG grammar is like a BNF grammar, except that "/" stands for * ordered choice: only the first matching rule is used. In other words, * the parsing is short-circuited in the manner of the "or" operator in * programming languages, and so the grammar is unambiguous.) * * Text <- Range? Command * / Position * Range <- Position ("," Position)? * / "%" * Position <- Base Offset? * Base <- Line * / LastLine * / ThisLine * / Mark * Offset <- [+-] Base * Line <- [0-9]+ * LastLine <- "$" * ThisLine <- "." * Mark <- "'" [a-z] */ void KateCmdLineEdit::slotReturnPressed(const QString &text) { if (text.isEmpty()) { return; } // silently ignore leading space characters uint n = 0; const uint textlen = text.length(); while ((n < textlen) && (text[n].isSpace())) { n++; } if (n >= textlen) { return; } QString cmd = text.mid(n); // Parse any leading range expression, and strip it (and maybe do some other transforms on the command). QString leadingRangeExpression; KTextEditor::Range range = CommandRangeExpressionParser::parseRangeExpression(cmd, m_view, leadingRangeExpression, cmd); // Built in help: if the command starts with "help", [try to] show some help if (cmd.startsWith(QLatin1String("help"))) { QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), helptext(QPoint())); clear(); KateCmd::self()->appendHistory(cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); return; } if (cmd.length() > 0) { KTextEditor::Command *p = KateCmd::self()->queryCommand(cmd); m_oldText = leadingRangeExpression + cmd; m_msgMode = true; // the following commands changes the focus themselves, so bar should be hidden before execution. if (QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { emit hideRequested(); } if (!p) { setText(i18n("No such command: \"%1\"", cmd)); } else if (range.isValid() && !p->supportsRange(cmd)) { // we got a range and a valid command, but the command does not inherit the RangeCommand // extension. bail out. setText(i18n("Error: No range allowed for command \"%1\".", cmd)); } else { QString msg; if (p->exec(m_view, cmd, msg, range)) { // append command along with range (will be empty if none given) to history KateCmd::self()->appendHistory(leadingRangeExpression + cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); if (msg.length() > 0) { setText(i18n("Success: ") + msg); } else if (isVisible()) { // always hide on success without message emit hideRequested(); } } else { if (msg.length() > 0) { if (msg.contains(QLatin1Char('\n'))) { // multiline error, use widget with more space QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), msg); } else { setText(msg); } } else { setText(i18n("Command \"%1\" failed.", cmd)); } } } } // clean up if (completionObject() != KateCmd::self()->commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } - m_command = 0; + m_command = nullptr; m_cmdend = 0; // the following commands change the focus themselves if (!QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { m_view->setFocus(); } if (isVisible()) { m_hideTimer->start(4000); } } void KateCmdLineEdit::hideLineEdit() // unless i have focus ;) { if (! hasFocus()) { emit hideRequested(); } } void KateCmdLineEdit::focusInEvent(QFocusEvent *ev) { if (m_msgMode) { m_msgMode = false; setText(m_oldText); selectAll(); } KLineEdit::focusInEvent(ev); } void KateCmdLineEdit::keyPressEvent(QKeyEvent *ev) { if (ev->key() == Qt::Key_Escape || (ev->key() == Qt::Key_BracketLeft && ev->modifiers() == Qt::ControlModifier)) { m_view->setFocus(); hideLineEdit(); clear(); } else if (ev->key() == Qt::Key_Up) { fromHistory(true); } else if (ev->key() == Qt::Key_Down) { fromHistory(false); } uint cursorpos = cursorPosition(); KLineEdit::keyPressEvent(ev); // during typing, let us see if we have a valid command if (! m_cmdend || cursorpos <= m_cmdend) { QChar c; if (! ev->text().isEmpty()) { c = ev->text().at(0); } if (! m_cmdend && ! c.isNull()) { // we have no command, so lets see if we got one if (! c.isLetterOrNumber() && c != QLatin1Char('-') && c != QLatin1Char('_')) { m_command = KateCmd::self()->queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_cmdend = 0; } } // if we got a command, check if it wants to do something. if (m_command) { KCompletion *cmpl = m_command->completionObject(m_view, text().left(m_cmdend).trimmed()); if (cmpl) { // We need to prepend the current command name + flag string // when completion is done //qCDebug(LOG_KTE)<<"keypress in commandline: Setting completion object!"; setCompletionObject(cmpl); } } } else if (m_command && !ev->text().isEmpty()) { // check if we should call the commands processText() if (m_command->wantsToProcessText(text().left(m_cmdend).trimmed())) { m_command->processText(m_view, text()); } } } void KateCmdLineEdit::fromHistory(bool up) { if (! KateCmd::self()->historyLength()) { return; } QString s; if (up) { if (m_histpos > 0) { m_histpos--; s = KateCmd::self()->fromHistory(m_histpos); } } else { if (m_histpos < (KateCmd::self()->historyLength() - 1)) { m_histpos++; s = KateCmd::self()->fromHistory(m_histpos); } else { m_histpos = KateCmd::self()->historyLength(); setText(m_oldText); } } if (! s.isEmpty()) { // Select the argument part of the command, so that it is easy to overwrite setText(s); static QRegExp reCmd = QRegExp(QLatin1String(".*[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)")); if (reCmd.indexIn(text()) == 0) { setSelection(text().length() - reCmd.cap(1).length(), reCmd.cap(1).length()); } } } //END KateCmdLineEdit //BEGIN KateIconBorder using namespace KTextEditor; KateIconBorder::KateIconBorder(KateViewInternal *internalView, QWidget *parent) : QWidget(parent) , m_view(internalView->m_view) , m_doc(internalView->doc()) , m_viewInternal(internalView) , m_iconBorderOn(false) , m_lineNumbersOn(false) , m_relLineNumbersOn(false) , m_updateRelLineNumbers(false) , m_foldingMarkersOn(false) , m_dynWrapIndicatorsOn(false) , m_annotationBorderOn(false) , m_dynWrapIndicators(0) , m_lastClickedLine(-1) , m_cachedLNWidth(0) , m_maxCharWidth(0.0) , iconPaneWidth(16) , m_annotationBorderWidth(6) , m_foldingPreview(nullptr) , m_foldingRange(nullptr) , m_nextHighlightBlock(-2) , m_currentBlockLine(-1) { setAttribute(Qt::WA_StaticContents); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); setMouseTracking(true); m_doc->setMarkDescription(MarkInterface::markType01, i18n("Bookmark")); m_doc->setMarkPixmap(MarkInterface::markType01, QIcon::fromTheme(QStringLiteral("bookmarks")).pixmap(16, 16)); updateFont(); m_delayFoldingHlTimer.setSingleShot(true); m_delayFoldingHlTimer.setInterval(150); connect(&m_delayFoldingHlTimer, SIGNAL(timeout()), this, SLOT(showBlock())); // user interaction (scrolling) hides e.g. preview connect(m_view, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(displayRangeChanged())); } KateIconBorder::~KateIconBorder() { delete m_foldingPreview; delete m_foldingRange; } void KateIconBorder::setIconBorderOn(bool enable) { if (enable == m_iconBorderOn) { return; } m_iconBorderOn = enable; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setAnnotationBorderOn(bool enable) { if (enable == m_annotationBorderOn) { return; } m_annotationBorderOn = enable; emit m_view->annotationBorderVisibilityChanged(m_view, enable); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::removeAnnotationHovering() { // remove hovering if it's still there if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) { m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::setLineNumbersOn(bool enable) { if (enable == m_lineNumbersOn) { return; } m_lineNumbersOn = enable; m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setRelLineNumbersOn(bool enable) { if (enable == m_relLineNumbersOn) { return; } m_relLineNumbersOn = enable; /* * We don't have to touch the m_dynWrapIndicatorsOn because * we already got it right from the m_lineNumbersOn */ updateGeometry(); QTimer::singleShot( 0, this, SLOT(update()) ); } void KateIconBorder::updateForCursorLineChange() { if (m_relLineNumbersOn) { m_updateRelLineNumbers = true; } // always do normal update, e.g. for different current line color! update(); } void KateIconBorder::setDynWrapIndicators(int state) { if (state == m_dynWrapIndicators) { return; } m_dynWrapIndicators = state; m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setFoldingMarkersOn(bool enable) { if (enable == m_foldingMarkersOn) { return; } m_foldingMarkersOn = enable; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } QSize KateIconBorder::sizeHint() const { int w = 0; if (m_iconBorderOn) { w += iconPaneWidth + 2; } if (m_annotationBorderOn) { w += m_annotationBorderWidth + 2; } if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { w += lineNumberWidth() + 2; } if (m_foldingMarkersOn) { w += iconPaneWidth; } // space for the line modification system border if (m_view->config()->lineModification()) { w += 3; } // two pixel space w += 2; return QSize(w, 0); } // This function (re)calculates the maximum width of any of the digit characters (0 -> 9) // for graceful handling of variable-width fonts as the linenumber font. void KateIconBorder::updateFont() { const QFontMetricsF &fm = m_view->renderer()->config()->fontMetrics(); m_maxCharWidth = 0.0; // Loop to determine the widest numeric character in the current font. // 48 is ascii '0' for (int i = 48; i < 58; i++) { const qreal charWidth = ceil(fm.width(QChar(i))); m_maxCharWidth = qMax(m_maxCharWidth, charWidth); } // the icon pane scales with the font... iconPaneWidth = fm.height(); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } int KateIconBorder::lineNumberWidth() const { // width = (number of digits + 1) * char width const int digits = (int) ceil(log10((double)(m_view->doc()->lines() + 1))); int width = m_lineNumbersOn ? (int)ceil((digits + 1) * m_maxCharWidth) : 0; if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { // HACK: 16 == style().scrollBarExtent().width() width = qMax(16 + 4, width); if (m_cachedLNWidth != width || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { int w = 16;// HACK: 16 == style().scrollBarExtent().width() style().scrollBarExtent().width(); int h = m_view->renderer()->lineHeight(); QSize newSize(w * devicePixelRatio(), h * devicePixelRatio()); if ((m_arrow.size() != newSize || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) && !newSize.isEmpty()) { m_arrow = QPixmap(newSize); m_arrow.setDevicePixelRatio(devicePixelRatio()); QPainter p(&m_arrow); p.fillRect(0, 0, w, h, m_view->renderer()->config()->iconBarColor()); h = m_view->renderer()->config()->fontMetrics().ascent(); p.setPen(m_view->renderer()->config()->lineNumberColor()); QPainterPath path; path.moveTo(w / 2, h / 2); path.lineTo(w / 2, 0); path.lineTo(w / 4, h / 4); path.lineTo(0, 0); path.lineTo(0, h / 2); path.lineTo(w / 2, h - 1); path.lineTo(w * 3 / 4, h - 1); path.lineTo(w - 1, h * 3 / 4); path.lineTo(w * 3 / 4, h / 2); path.lineTo(0, h / 2); p.drawPath(path); } } } return width; } void KateIconBorder::paintEvent(QPaintEvent *e) { paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height()); } static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open) { painter.setRenderHint(QPainter::Antialiasing); qreal size = qMin(width, height); if (KColorUtils::luma(c) > 0.25) { c = KColorUtils::darken(c); } else { c = KColorUtils::shade(c, 0.1); } QPen pen; pen.setJoinStyle(Qt::RoundJoin); pen.setColor(c); pen.setWidthF(1.5); painter.setPen(pen); painter.setBrush(c); // let some border, if possible size *= 0.6; qreal halfSize = size / 2; qreal halfSizeP = halfSize * 0.6; QPointF middle(xOffset + (qreal)width / 2, yOffset + (qreal)height / 2); if (open) { QPointF points[3] = { middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP) }; painter.drawConvexPolygon(points, 3); } else { QPointF points[3] = { middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0) }; painter.drawConvexPolygon(points, 3); } painter.setRenderHint(QPainter::Antialiasing, false); } void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height) { uint h = m_view->renderer()->lineHeight(); uint startz = (y / h); uint endz = startz + 1 + (height / h); uint lineRangesSize = m_viewInternal->cache()->viewCacheLineCount(); uint currentLine = m_view->cursorPosition().line(); // center the folding boxes int m_px = (h - 11) / 2; if (m_px < 0) { m_px = 0; } int lnWidth(0); if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { // avoid calculating unless needed ;-) lnWidth = lineNumberWidth(); if (lnWidth != m_cachedLNWidth || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { // we went from n0 ->n9 lines or vice verca // this causes an extra updateGeometry() first time the line numbers // are displayed, but sizeHint() is supposed to be const so we can't set // the cached value there. m_cachedLNWidth = lnWidth; m_oldBackgroundColor = m_view->renderer()->config()->iconBarColor(); updateGeometry(); update(); return; } } int w(this->width()); // sane value/calc only once QPainter p(this); p.setRenderHints(QPainter::TextAntialiasing); p.setFont(m_view->renderer()->config()->font()); // for line numbers KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); for (uint z = startz; z <= endz; z++) { int y = h * z; int realLine = -1; if (z < lineRangesSize) { realLine = m_viewInternal->cache()->viewLine(z).line(); } int lnX = 0; p.fillRect(0, y, w - 5, h, m_view->renderer()->config()->iconBarColor()); p.fillRect(w - 5, y, 5, h, m_view->renderer()->config()->backgroundColor()); // icon pane if (m_iconBorderOn) { p.setPen(m_view->renderer()->config()->separatorColor()); p.setBrush(m_view->renderer()->config()->separatorColor()); p.drawLine(lnX + iconPaneWidth + 1, y, lnX + iconPaneWidth + 1, y + h); if ((realLine > -1) && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { uint mrk(m_doc->mark(realLine)); // call only once if (mrk) { for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (mrk & markType) { QPixmap px_mark(m_doc->markPixmap(markType)); if (!px_mark.isNull() && h > 0 && iconPaneWidth > 0) { if (iconPaneWidth < px_mark.width() || h < (uint)px_mark.height()) { px_mark = px_mark.scaled(iconPaneWidth, h, Qt::KeepAspectRatio); } // center the mark pixmap int x_px = (iconPaneWidth - px_mark.width()) / 2; if (x_px < 0) { x_px = 0; } int y_px = (h - px_mark.height()) / 2; if (y_px < 0) { y_px = 0; } p.drawPixmap(lnX + x_px, y + y_px, px_mark); } } } } } lnX += iconPaneWidth + 2; } // annotation information if (m_annotationBorderOn) { // Draw a border line between annotations and the line numbers p.setPen(m_view->renderer()->config()->lineNumberColor()); p.setBrush(m_view->renderer()->config()->lineNumberColor()); int borderWidth = m_annotationBorderWidth; p.drawLine(lnX + borderWidth + 1, y, lnX + borderWidth + 1, y + h); if ((realLine > -1) && model) { // Fetch data from the model QVariant text = model->data(realLine, Qt::DisplayRole); QVariant foreground = model->data(realLine, Qt::ForegroundRole); QVariant background = model->data(realLine, Qt::BackgroundRole); // Fill the background if (background.isValid()) { p.fillRect(lnX, y, borderWidth + 1, h, background.value()); } // Set the pen for drawing the foreground if (foreground.isValid()) { p.setPen(foreground.value()); } // Draw a border around all adjacent entries that have the same text as the currently hovered one const QVariant identifier = model->data( realLine, (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ); if( m_hoveredAnnotationGroupIdentifier == identifier.toString() ) { p.drawLine(lnX, y, lnX, y + h); p.drawLine(lnX + borderWidth, y, lnX + borderWidth, y + h); QVariant beforeText = model->data(realLine - 1, Qt::DisplayRole); QVariant afterText = model->data(realLine + 1, Qt::DisplayRole); if (((beforeText.isValid() && beforeText.canConvert() && text.isValid() && text.canConvert() && beforeText.toString() != text.toString()) || realLine == 0) && m_viewInternal->cache()->viewLine(z).viewLine() == 0) { p.drawLine(lnX + 1, y, lnX + borderWidth, y); } if (((afterText.isValid() && afterText.canConvert() && text.isValid() && text.canConvert() && afterText.toString() != text.toString()) || realLine == m_view->doc()->lines() - 1) && m_viewInternal->cache()->viewLine(z).viewLine() == m_viewInternal->cache()->viewLineCount(realLine) - 1) { p.drawLine(lnX + 1, y + h - 1, lnX + borderWidth, y + h - 1); } } if (foreground.isValid()) { QPen pen = p.pen(); pen.setWidth(1); p.setPen(pen); } // Now draw the normal text if (text.isValid() && text.canConvert() && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { p.drawText(lnX + 3, y, borderWidth - 3, h, Qt::AlignLeft | Qt::AlignVCenter, text.toString()); } } // adjust current X position and reset the pen and brush lnX += borderWidth + 2; } // line number if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { if (realLine > -1) { int distanceToCurrent = abs(realLine - static_cast(currentLine)); QColor color; if (distanceToCurrent == 0) { color = m_view->renderer()->config()->currentLineNumberColor(); } else { color = m_view->renderer()->config()->lineNumberColor(); } p.setPen(color); p.setBrush(color); if (m_viewInternal->cache()->viewLine(z).startCol() == 0) { if (m_relLineNumbersOn) { if (distanceToCurrent == 0) { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip|Qt::AlignLeft|Qt::AlignVCenter, QString::number(realLine + 1)); } else { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip|Qt::AlignRight|Qt::AlignVCenter, QString::number(distanceToCurrent)); } if (m_updateRelLineNumbers) { m_updateRelLineNumbers = false; update(); } } else if (m_lineNumbersOn) { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, QString::number(realLine + 1)); } } else if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { p.drawPixmap(lnX + lnWidth - (m_arrow.width() / m_arrow.devicePixelRatio()) - 2, y, m_arrow); } } lnX += lnWidth + 2; } // folding markers if (m_foldingMarkersOn) { // first icon border background p.fillRect(lnX, y, iconPaneWidth, h, m_view->renderer()->config()->iconBarColor()); // possible additional folding highlighting if ((realLine >= 0) && m_foldingRange && m_foldingRange->overlapsLine(realLine)) { p.save(); // use linear gradient as brush QLinearGradient g(lnX, y, lnX + iconPaneWidth, y); const QColor foldingColor(m_view->renderer()->config()->foldingColor()); g.setColorAt(0, foldingColor); g.setColorAt(0.3, foldingColor.lighter(110)); g.setColorAt(1, foldingColor); p.setBrush(g); p.setPen(foldingColor); p.setClipRect(lnX, y, iconPaneWidth, h); p.setRenderHint(QPainter::Antialiasing); const qreal r = 4.0; if (m_foldingRange->start().line() == realLine && m_viewInternal->cache()->viewLine(z).viewLine() == 0) { p.drawRect(lnX, y, iconPaneWidth, h + r); } else if (m_foldingRange->end().line() == realLine && m_viewInternal->cache()->viewLine(z).viewLine() == m_viewInternal->cache()->viewLineCount(realLine) - 1) { p.drawRect(lnX, y - r, iconPaneWidth, h + r); } else { p.drawRect(lnX, y - r, iconPaneWidth, h + 2 * r); } p.restore(); } if ((realLine >= 0) && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(realLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } Kate::TextLine tl = m_doc->kateTextLine(realLine); if (!startingRanges.isEmpty() || tl->markedAsFoldingStart()) { if (anyFolded) { paintTriangle(p, m_view->renderer()->config()->foldingColor(), lnX, y, iconPaneWidth, h, false); } else { paintTriangle(p, m_view->renderer()->config()->foldingColor(), lnX, y, iconPaneWidth, h, true); } } } lnX += iconPaneWidth; } // modified line system if (m_view->config()->lineModification() && realLine > -1 && !m_doc->url().isEmpty()) { // one pixel space ++lnX; Kate::TextLine tl = m_doc->plainKateTextLine(realLine); if (tl->markedAsModified()) { p.fillRect(lnX, y, 3, h, m_view->renderer()->config()->modifiedLineColor()); } if (tl->markedAsSavedOnDisk()) { p.fillRect(lnX, y, 3, h, m_view->renderer()->config()->savedLineColor()); } } } } KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const { // see KateIconBorder::sizeHint() for pixel spacings int x = 0; if (m_iconBorderOn) { x += iconPaneWidth; if (p.x() <= x) { return IconBorder; } x += 2; } if (this->m_annotationBorderOn) { x += m_annotationBorderWidth; if (p.x() <= x) { return AnnotationBorder; } x += 2; } if (m_lineNumbersOn || m_dynWrapIndicators) { x += lineNumberWidth(); if (p.x() <= x) { return LineNumbers; } x += 2; } if (m_foldingMarkersOn) { x += iconPaneWidth; if (p.x() <= x) { return FoldingMarkers; } } if (m_view->config()->lineModification()) { x += 3 + 2; if (p.x() <= x) { return ModificationBorder; } } return None; } void KateIconBorder::mousePressEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { m_lastClickedLine = t.line(); if (positionToArea(e->pos()) != IconBorder && positionToArea(e->pos()) != AnnotationBorder) { QMouseEvent forward(QEvent::MouseButtonPress, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mousePressEvent(&forward); } return e->accept(); } QWidget::mousePressEvent(e); } void KateIconBorder::showDelayedBlock(int line) { // save the line over which the mouse hovers // either we start the timer for delay, or we show the block immediately // if the moving range already exists m_nextHighlightBlock = line; if (!m_foldingRange) { if (!m_delayFoldingHlTimer.isActive()) { m_delayFoldingHlTimer.start(); } } else { showBlock(); } } void KateIconBorder::showBlock() { if (m_nextHighlightBlock == m_currentBlockLine) { return; } m_currentBlockLine = m_nextHighlightBlock; if (m_currentBlockLine >= m_doc->buffer().lines()) { return; } /** * compute to which folding range we belong * FIXME: optimize + perhaps have some better threshold or use timers! */ KTextEditor::Range newRange = KTextEditor::Range::invalid(); for (int line = m_currentBlockLine; line >= qMax(0, m_currentBlockLine - 1024); --line) { /** * try if we have folding range from that line, should be fast per call */ KTextEditor::Range foldingRange = m_doc->buffer().computeFoldingRangeForStartLine(line); if (!foldingRange.isValid()) { continue; } /** * does the range reach us? */ if (foldingRange.overlapsLine(m_currentBlockLine)) { newRange = foldingRange; break; } } if (newRange.isValid() && m_foldingRange && *m_foldingRange == newRange) { // new range equals the old one, nothing to do. return; } else { // the ranges differ, delete the old, if it exists delete m_foldingRange; - m_foldingRange = 0; + m_foldingRange = nullptr; } if (newRange.isValid()) { //qCDebug(LOG_KTE) << "new folding hl-range:" << newRange; m_foldingRange = m_doc->newMovingRange(newRange, KTextEditor::MovingRange::ExpandRight); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); /** * create highlighting color with alpha for the range! */ QColor result = m_view->renderer()->config()->foldingColor(); result.setAlphaF(0.5); attr->setBackground(QBrush(result)); m_foldingRange->setView(m_view); // use z depth defined in moving ranges interface m_foldingRange->setZDepth(-100.0); m_foldingRange->setAttribute(attr); } // show text preview, if a folded region starts here bool foldUnderMouse = false; if (m_foldingRange && m_view->config()->foldingPreview()) { const QPoint globalPos = QCursor::pos(); const QPoint pos = mapFromGlobal(globalPos); const KateTextLayout &t = m_view->m_viewInternal->yToKateTextLayout(pos.y()); if (t.isValid() && positionToArea(pos) == FoldingMarkers) { const int realLine = t.line(); foldUnderMouse = !m_view->textFolding().isLineVisible(realLine + 1); if (foldUnderMouse) { if (!m_foldingPreview) { m_foldingPreview = new KateTextPreview(m_view); m_foldingPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_foldingPreview->setFrameStyle(QFrame::StyledPanel); // event filter to catch application WindowDeactivate event, to hide the preview window // qApp->installEventFilter(this); } // TODO: use KateViewInternal::maxLen() somehow to compute proper width for amount of lines to display // try using the end line of the range for proper popup height const int lineCount = qMin(m_foldingRange->numberOfLines() + 1, (height() - pos.y()) / m_view->renderer()->lineHeight()); m_foldingPreview->resize(m_view->width() / 2, lineCount * m_view->renderer()->lineHeight() + 2 * m_foldingPreview->frameWidth()); const int xGlobal = mapToGlobal(QPoint(width(), 0)).x(); const int yGlobal = m_view->mapToGlobal(m_view->cursorToCoordinate(KTextEditor::Cursor(realLine, 0))).y(); m_foldingPreview->move(QPoint(xGlobal, yGlobal) - m_foldingPreview->contentsRect().topLeft()); m_foldingPreview->setLine(realLine); m_foldingPreview->setCenterView(false); m_foldingPreview->setShowFoldedLines(true); m_foldingPreview->raise(); m_foldingPreview->show(); } } } if (!foldUnderMouse) { delete m_foldingPreview; } /** * repaint */ repaint(); } void KateIconBorder::hideBlock() { if (m_delayFoldingHlTimer.isActive()) { m_delayFoldingHlTimer.stop(); } m_nextHighlightBlock = -2; m_currentBlockLine = -1; delete m_foldingRange; - m_foldingRange = 0; + m_foldingRange = nullptr; delete m_foldingPreview; } void KateIconBorder::leaveEvent(QEvent *event) { hideBlock(); removeAnnotationHovering(); QWidget::leaveEvent(event); } void KateIconBorder::mouseMoveEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { if (positionToArea(e->pos()) == FoldingMarkers) { showDelayedBlock(t.line()); } else { hideBlock(); } if (positionToArea(e->pos()) == AnnotationBorder) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { m_hoveredAnnotationGroupIdentifier = model->data( t.line(), (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ).toString(); showAnnotationTooltip(t.line(), e->globalPos()); QTimer::singleShot(0, this, SLOT(update())); } } else { if (positionToArea(e->pos()) == IconBorder) { m_doc->requestMarkTooltip(t.line(), e->globalPos()); } m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); QTimer::singleShot(0, this, SLOT(update())); } if (positionToArea(e->pos()) != IconBorder) { QPoint p = m_viewInternal->mapFromGlobal(e->globalPos()); QMouseEvent forward(QEvent::MouseMove, p, e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseMoveEvent(&forward); } } else { // remove hovering if it's still there removeAnnotationHovering(); } QWidget::mouseMoveEvent(e); } void KateIconBorder::mouseReleaseEvent(QMouseEvent *e) { const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) { BorderArea area = positionToArea(e->pos()); if (area == IconBorder) { if (e->button() == Qt::LeftButton) { if (!m_doc->handleMarkClick(cursorOnLine)) { KateViewConfig *config = m_view->config(); if (m_doc->editableMarks() & config->defaultMarkType()) { if (m_doc->mark(cursorOnLine) & config->defaultMarkType()) { m_doc->removeMark(cursorOnLine, config->defaultMarkType()); } else { m_doc->addMark(cursorOnLine, config->defaultMarkType()); } } else if (config->allowMarkMenu()) { showMarkMenu(cursorOnLine, QCursor::pos()); } } } else if (e->button() == Qt::RightButton) { showMarkMenu(cursorOnLine, QCursor::pos()); } } if (area == FoldingMarkers) { // ask the folding info for this line, if any folds are around! QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(cursorOnLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } // fold or unfold all ranges, remember if any action happened! bool actionDone = false; for (int i = 0; i < startingRanges.size(); ++i) { actionDone = (anyFolded ? m_view->textFolding().unfoldRange(startingRanges[i].first) : m_view->textFolding().foldRange(startingRanges[i].first)) || actionDone; } // if no action done, try to fold it, create non-persistent folded range, if possible! if (!actionDone) { // either use the fold for this line or the range that is highlighted ATM if any! KTextEditor::Range foldingRange = m_view->doc()->buffer().computeFoldingRangeForStartLine(cursorOnLine); if (!foldingRange.isValid() && m_foldingRange) { foldingRange = m_foldingRange->toRange(); } m_view->textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); } delete m_foldingPreview; } if (area == AnnotationBorder) { - const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this); + const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (e->button() == Qt::LeftButton && singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } else if (e->button() == Qt::RightButton) { showAnnotationMenu(cursorOnLine, e->globalPos()); } } } QMouseEvent forward(QEvent::MouseButtonRelease, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseReleaseEvent(&forward); } void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e) { int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) { BorderArea area = positionToArea(e->pos()); - const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this); + const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (area == AnnotationBorder && !singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } } QMouseEvent forward(QEvent::MouseButtonDblClick, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseDoubleClickEvent(&forward); } void KateIconBorder::wheelEvent(QWheelEvent *e) { QCoreApplication::sendEvent(m_viewInternal, e); } void KateIconBorder::showMarkMenu(uint line, const QPoint &pos) { if (m_doc->handleMarkContextMenu(line, pos)) { return; } if (!m_view->config()->allowMarkMenu()) { return; } QMenu markMenu; QMenu selectDefaultMark; + auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark); QVector vec(33); int i = 1; for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (!(m_doc->editableMarks() & markType)) { continue; } QAction *mA; QAction *dMA; if (!m_doc->markDescription(markType).isEmpty()) { mA = markMenu.addAction(m_doc->markDescription(markType)); dMA = selectDefaultMark.addAction(m_doc->markDescription(markType)); } else { mA = markMenu.addAction(i18n("Mark Type %1", bit + 1)); dMA = selectDefaultMark.addAction(i18n("Mark Type %1", bit + 1)); } + selectDefaultMarkActionGroup->addAction(dMA); mA->setData(i); mA->setCheckable(true); dMA->setData(i + 100); dMA->setCheckable(true); if (m_doc->mark(line) & markType) { mA->setChecked(true); } if (markType & KateViewConfig::global()->defaultMarkType()) { dMA->setChecked(true); } vec[i++] = markType; } if (markMenu.actions().count() == 0) { return; } if (markMenu.actions().count() > 1) { markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark); } QAction *rA = markMenu.exec(pos); if (!rA) { return; } int result = rA->data().toInt(); if (result > 100) { KateViewConfig::global()->setDefaultMarkType(vec[result - 100]); } else { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes) vec[result]; if (m_doc->mark(line) & markType) { m_doc->removeMark(line, markType); } else { m_doc->addMark(line, markType); } } } void KateIconBorder::showAnnotationTooltip(int line, const QPoint &pos) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { QVariant data = model->data(line, Qt::ToolTipRole); QString tip = data.toString(); if (!tip.isEmpty()) { QToolTip::showText(pos, data.toString(), this); } } } int KateIconBorder::annotationLineWidth(int line) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { QVariant data = model->data(line, Qt::DisplayRole); return data.toString().length() * m_maxCharWidth + 8; } return 8; } void KateIconBorder::updateAnnotationLine(int line) { if (annotationLineWidth(line) > m_annotationBorderWidth) { m_annotationBorderWidth = annotationLineWidth(line); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos) { QMenu menu; QAction a(i18n("Disable Annotation Bar"), &menu); a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); menu.addAction(&a); emit m_view->annotationContextMenuAboutToShow(m_view, &menu, line); if (menu.exec(pos) == &a) { m_view->setAnnotationBorderVisible(false); } } void KateIconBorder::hideAnnotationTooltip() { QToolTip::hideText(); } void KateIconBorder::updateAnnotationBorderWidth() { m_annotationBorderWidth = 6; KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { for (int i = 0; i < m_view->doc()->lines(); i++) { int curwidth = annotationLineWidth(i); if (curwidth > m_annotationBorderWidth) { m_annotationBorderWidth = curwidth; } } } updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel) { if (oldmodel) { oldmodel->disconnect(this); } if (newmodel) { connect(newmodel, SIGNAL(reset()), this, SLOT(updateAnnotationBorderWidth())); connect(newmodel, SIGNAL(lineChanged(int)), this, SLOT(updateAnnotationLine(int))); } updateAnnotationBorderWidth(); } void KateIconBorder::displayRangeChanged() { hideBlock(); removeAnnotationHovering(); } //END KateIconBorder //BEGIN KateViewEncodingAction // Acording to http://www.iana.org/assignments/ianacharset-mib // the default/unknown mib value is 2. #define MIB_DEFAULT 2 bool lessThanAction(KSelectAction *a, KSelectAction *b) { return a->text() < b->text(); } void KateViewEncodingAction::Private::init() { QList actions; q->setToolBarMode(MenuMode); int i; foreach (const QStringList &encodingsForScript, KCharsets::charsets()->encodingsByScript()) { KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), q); for (i = 1; i < encodingsForScript.size(); ++i) { tmp->addAction(encodingsForScript.at(i)); } q->connect(tmp, SIGNAL(triggered(QAction*)), q, SLOT(_k_subActionTriggered(QAction*))); //tmp->setCheckable(true); actions << tmp; } qSort(actions.begin(), actions.end(), lessThanAction); foreach (KSelectAction *action, actions) { q->addAction(action); } } void KateViewEncodingAction::Private::_k_subActionTriggered(QAction *action) { if (currentSubAction == action) { return; } currentSubAction = action; bool ok = false; int mib = q->mibForName(action->text(), &ok); if (ok) { emit q->KSelectAction::triggered(action->text()); emit q->triggered(q->codecForMib(mib)); } } KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode) : KSelectAction(text, parent), doc(_doc), view(_view), d(new Private(this)) , m_saveAsMode(saveAsMode) { d->init(); connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); } KateViewEncodingAction::~KateViewEncodingAction() { delete d; } void KateViewEncodingAction::slotAboutToShow() { setCurrentCodec(doc->config()->encoding()); } void KateViewEncodingAction::setEncoding(const QString &e) { /** * in save as mode => trigger saveAs */ if (m_saveAsMode) { doc->documentSaveAsWithEncoding(e); return; } /** * else switch encoding */ doc->userSetEncodingForNextReload(); doc->setEncoding(e); view->reloadFile(); } int KateViewEncodingAction::mibForName(const QString &codecName, bool *ok) const { // FIXME logic is good but code is ugly bool success = false; int mib = MIB_DEFAULT; KCharsets *charsets = KCharsets::charsets(); QTextCodec *codec = charsets->codecForName(codecName, success); if (!success) { // Maybe we got a description name instead codec = charsets->codecForName(charsets->encodingForName(codecName), success); } if (codec) { mib = codec->mibEnum(); } if (ok) { *ok = success; } if (success) { return mib; } qCWarning(LOG_KTE) << "Invalid codec name: " << codecName; return MIB_DEFAULT; } QTextCodec *KateViewEncodingAction::codecForMib(int mib) const { if (mib == MIB_DEFAULT) { // FIXME offer to change the default codec return QTextCodec::codecForLocale(); } else { return QTextCodec::codecForMib(mib); } } QTextCodec *KateViewEncodingAction::currentCodec() const { return codecForMib(currentCodecMib()); } bool KateViewEncodingAction::setCurrentCodec(QTextCodec *codec) { disconnect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); int i, j; for (i = 0; i < actions().size(); ++i) { if (actions().at(i)->menu()) { for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) { if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) { continue; } if (actions().at(i)->menu()->actions().at(j)->isSeparator()) { continue; } if (codec == KCharsets::charsets()->codecForName(actions().at(i)->menu()->actions().at(j)->text())) { d->currentSubAction = actions().at(i)->menu()->actions().at(j); d->currentSubAction->setChecked(true); } else { actions().at(i)->menu()->actions().at(j)->setChecked(false); } } } } connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); return true; } QString KateViewEncodingAction::currentCodecName() const { return d->currentSubAction->text(); } bool KateViewEncodingAction::setCurrentCodec(const QString &codecName) { return setCurrentCodec(KCharsets::charsets()->codecForName(codecName)); } int KateViewEncodingAction::currentCodecMib() const { return mibForName(currentCodecName()); } bool KateViewEncodingAction::setCurrentCodec(int mib) { return setCurrentCodec(codecForMib(mib)); } //END KateViewEncodingAction //BEGIN KateViewBar related classes KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent) : QWidget(parent) - , m_viewBar(0) + , m_viewBar(nullptr) { QHBoxLayout *layout = new QHBoxLayout(this); // NOTE: Here be cosmetics. layout->setMargin(0); // hide button if (addCloseButton) { QToolButton *hideButton = new QToolButton(this); hideButton->setAutoRaise(true); hideButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); connect(hideButton, SIGNAL(clicked()), SIGNAL(hideMe())); layout->addWidget(hideButton); layout->setAlignment(hideButton, Qt::AlignLeft | Qt::AlignTop); } // widget to be used as parent for the real content m_centralWidget = new QWidget(this); layout->addWidget(m_centralWidget); setLayout(layout); setFocusProxy(m_centralWidget); } KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view) - : QWidget(parent), m_external(external), m_view(view), m_permanentBarWidget(0) + : QWidget(parent), m_external(external), m_view(view), m_permanentBarWidget(nullptr) { m_layout = new QVBoxLayout(this); m_stack = new QStackedWidget(this); m_layout->addWidget(m_stack); m_layout->setMargin(0); m_stack->hide(); hide(); } void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget) { // just ignore additional adds for already existing widgets if (hasBarWidget(newBarWidget)) { return; } // add new widget, invisible... newBarWidget->hide(); m_stack->addWidget(newBarWidget); newBarWidget->setAssociatedViewBar(this); connect(newBarWidget, SIGNAL(hideMe()), SLOT(hideCurrentBarWidget())); } void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget) { // remove only if there if (!hasBarWidget(barWidget)) { return; } m_stack->removeWidget(barWidget); - barWidget->setAssociatedViewBar(0); + barWidget->setAssociatedViewBar(nullptr); barWidget->hide(); - disconnect(barWidget, 0, this, 0); + disconnect(barWidget, nullptr, this, nullptr); } void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget); Q_ASSERT(!m_permanentBarWidget); m_stack->addWidget(barWidget); m_stack->setCurrentWidget(barWidget); m_stack->show(); m_permanentBarWidget = barWidget; m_permanentBarWidget->show(); setViewBarVisible(true); } void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(m_permanentBarWidget == barWidget); Q_UNUSED(barWidget); const bool hideBar = m_stack->currentWidget() == m_permanentBarWidget; m_permanentBarWidget->hide(); m_stack->removeWidget(m_permanentBarWidget); - m_permanentBarWidget = 0; + m_permanentBarWidget = nullptr; if (hideBar) { m_stack->hide(); setViewBarVisible(false); } } bool KateViewBar::hasPermanentWidget(KateViewBarWidget *barWidget) const { return (m_permanentBarWidget == barWidget); } void KateViewBar::showBarWidget(KateViewBarWidget *barWidget) { - Q_ASSERT(barWidget != 0); + Q_ASSERT(barWidget != nullptr); if (barWidget != qobject_cast(m_stack->currentWidget())) { hideCurrentBarWidget(); } // raise correct widget m_stack->setCurrentWidget(barWidget); barWidget->show(); barWidget->setFocus(Qt::ShortcutFocusReason); m_stack->show(); setViewBarVisible(true); } bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const { return m_stack->indexOf(barWidget) != -1; } void KateViewBar::hideCurrentBarWidget() { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (current) { current->closed(); } // if we have any permanent widget, make it visible again if (m_permanentBarWidget) { m_stack->setCurrentWidget (m_permanentBarWidget); } else { // else: hide the bar m_stack->hide(); setViewBarVisible(false); } m_view->setFocus(); } void KateViewBar::setViewBarVisible(bool visible) { if (m_external) { if (visible) { m_view->mainWindow()->showViewBar(m_view); } else { m_view->mainWindow()->hideViewBar(m_view); } } else { setVisible(visible); } } bool KateViewBar::hiddenOrPermanent() const { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (!isVisible() || (m_permanentBarWidget && m_permanentBarWidget == current)) { return true; } return false; } void KateViewBar::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { hideCurrentBarWidget(); return; } QWidget::keyPressEvent(event); } void KateViewBar::hideEvent(QHideEvent *event) { Q_UNUSED(event); // if (!event->spontaneous()) // m_view->setFocus(); } //END KateViewBar related classes KatePasteMenu::KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view) : KActionMenu(text, view) , m_view(view) { connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); } void KatePasteMenu::slotAboutToShow() { menu()->clear(); /** * insert complete paste history */ int i = 0; Q_FOREACH (const auto &texts, KTextEditor::EditorPrivate::self()->clipboardHistory()) { /** * get text for the menu ;) */ QString text; Q_FOREACH (const auto& t, texts) { if ( !text.isEmpty() ) { text.append(QLatin1String(" ")); } text.append(t); } if ( texts.size() > 1 ) { text.prepend(QLatin1String("[") + i18nc("%1 entries", "always plural", texts.size()) + QLatin1String("] ")); } QString leftPart = (text.size() > 48) ? (text.left(48) + QLatin1String("...")) : text; QAction *a = menu()->addAction(leftPart.replace(QLatin1String("\n"), QLatin1String(" ")), this, SLOT(paste())); a->setData(i++); } } void KatePasteMenu::paste() { if (!sender()) { return; } QAction *action = qobject_cast(sender()); if (!action) { return; } // get index int i = action->data().toInt(); if (i >= KTextEditor::EditorPrivate::self()->clipboardHistory().size()) { return; } // paste m_view->pasteInternal(KTextEditor::EditorPrivate::self()->clipboardHistory().at(i)); } diff --git a/src/view/kateviewhelpers.h b/src/view/kateviewhelpers.h index 7b5e6a99..90cff889 100644 --- a/src/view/kateviewhelpers.h +++ b/src/view/kateviewhelpers.h @@ -1,596 +1,595 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Anders Lund Copyright (C) 2001 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 version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __KATE_VIEW_HELPERS_H__ #define __KATE_VIEW_HELPERS_H__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "katetextline.h" namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } class KateViewInternal; #define MAXFOLDINGCOLORS 16 class KateLineInfo; class KateTextPreview; namespace KTextEditor { class Command; class AnnotationModel; class MovingRange; } class QTimer; class QVBoxLayout; /** * This class is required because QScrollBar's sliderMoved() signal is * really supposed to be a sliderDragged() signal... so this way we can capture * MMB slider moves as well * * Also, it adds some useful indicators on the scrollbar. */ class KateScrollBar : public QScrollBar { Q_OBJECT public: KateScrollBar(Qt::Orientation orientation, class KateViewInternal *parent); virtual ~KateScrollBar(); QSize sizeHint() const Q_DECL_OVERRIDE; inline bool showMarks() const { return m_showMarks; } inline void setShowMarks(bool b) { m_showMarks = b; update(); } inline bool showMiniMap() const { return m_showMiniMap; } void setShowMiniMap(bool b); inline bool miniMapAll() const { return m_miniMapAll; } inline void setMiniMapAll(bool b) { m_miniMapAll = b; updateGeometry(); update(); } inline bool miniMapWidth() const { return m_miniMapWidth; } inline void setMiniMapWidth(int width) { m_miniMapWidth = width; updateGeometry(); update(); } inline void queuePixmapUpdate() { m_updateTimer.start(); } Q_SIGNALS: void sliderMMBMoved(int value); protected: void mousePressEvent(QMouseEvent *e) Q_DECL_OVERRIDE; void mouseReleaseEvent(QMouseEvent *e) Q_DECL_OVERRIDE; void mouseMoveEvent(QMouseEvent *e) Q_DECL_OVERRIDE; void leaveEvent(QEvent *event) Q_DECL_OVERRIDE; bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; void sliderChange(SliderChange change) Q_DECL_OVERRIDE; protected Q_SLOTS: void sliderMaybeMoved(int value); void marksChanged(); public Q_SLOTS: void updatePixmap(); private Q_SLOTS: void showTextPreview(); private: void showTextPreviewDelayed(); void hideTextPreview(); void redrawMarks(); void recomputeMarksPositions(); void miniMapPaintEvent(QPaintEvent *e); void normalPaintEvent(QPaintEvent *e); int minimapYToStdY(int y); const QColor charColor(const QVector &attributes, int &attributeIndex, const QList &decorations, const QColor &defaultColor, int x, QChar ch); bool m_middleMouseDown; bool m_leftMouseDown; KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; class KateViewInternal *m_viewInternal; QPointer m_textPreview; QTimer m_delayTextPreviewTimer; QHash m_lines; bool m_showMarks; bool m_showMiniMap; bool m_miniMapAll; int m_miniMapWidth; QPixmap m_pixmap; int m_grooveHeight; QRect m_stdGroveRect; QRect m_mapGroveRect; - QRect m_mapSliderRect; QTimer m_updateTimer; QPoint m_toolTipPos; // lists of lines added/removed recently to avoid scrollbar flickering QHash m_linesAdded; int m_linesModified; static const unsigned char characterOpacity[256]; }; class KateIconBorder : public QWidget { Q_OBJECT public: KateIconBorder(KateViewInternal *internalView, QWidget *parent); virtual ~KateIconBorder(); // VERY IMPORTANT ;) QSize sizeHint() const Q_DECL_OVERRIDE; void updateFont(); int lineNumberWidth() const; void setIconBorderOn(bool enable); void setLineNumbersOn(bool enable); void setRelLineNumbersOn(bool enable); void setAnnotationBorderOn(bool enable); void setDynWrapIndicators(int state); int dynWrapIndicators() const { return m_dynWrapIndicators; } bool dynWrapIndicatorsOn() const { return m_dynWrapIndicatorsOn; } void setFoldingMarkersOn(bool enable); void toggleIconBorder() { setIconBorderOn(!iconBorderOn()); } void toggleLineNumbers() { setLineNumbersOn(!lineNumbersOn()); } void toggleFoldingMarkers() { setFoldingMarkersOn(!foldingMarkersOn()); } inline bool iconBorderOn() const { return m_iconBorderOn; } inline bool lineNumbersOn() const { return m_lineNumbersOn; } inline bool viRelNumbersOn() const { return m_relLineNumbersOn; } inline bool foldingMarkersOn() const { return m_foldingMarkersOn; } inline bool annotationBorderOn() const { return m_annotationBorderOn; } void updateForCursorLineChange(); enum BorderArea { None, LineNumbers, IconBorder, FoldingMarkers, AnnotationBorder, ModificationBorder }; BorderArea positionToArea(const QPoint &) const; public Q_SLOTS: void updateAnnotationBorderWidth(); void updateAnnotationLine(int line); void annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel); void displayRangeChanged(); private: void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; void paintBorder(int x, int y, int width, int height); void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE; void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE; void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE; void mouseDoubleClickEvent(QMouseEvent *) Q_DECL_OVERRIDE; void leaveEvent(QEvent *event) Q_DECL_OVERRIDE; void wheelEvent(QWheelEvent *e) Q_DECL_OVERRIDE; void showMarkMenu(uint line, const QPoint &pos); void showAnnotationTooltip(int line, const QPoint &pos); void hideAnnotationTooltip(); void removeAnnotationHovering(); void showAnnotationMenu(int line, const QPoint &pos); int annotationLineWidth(int line); KTextEditor::ViewPrivate *m_view; KTextEditor::DocumentPrivate *m_doc; KateViewInternal *m_viewInternal; bool m_iconBorderOn: 1; bool m_lineNumbersOn: 1; bool m_relLineNumbersOn:1; bool m_updateRelLineNumbers:1; bool m_foldingMarkersOn: 1; bool m_dynWrapIndicatorsOn: 1; bool m_annotationBorderOn: 1; int m_dynWrapIndicators; int m_lastClickedLine; int m_cachedLNWidth; qreal m_maxCharWidth; int iconPaneWidth; int m_annotationBorderWidth; mutable QPixmap m_arrow; mutable QColor m_oldBackgroundColor; QPointer m_foldingPreview; KTextEditor::MovingRange *m_foldingRange; int m_nextHighlightBlock; int m_currentBlockLine; QTimer m_delayFoldingHlTimer; void showDelayedBlock(int line); void hideBlock(); private Q_SLOTS: void showBlock(); private: QString m_hoveredAnnotationGroupIdentifier; void initializeFoldingColors(); }; class KateViewEncodingAction: public KSelectAction { Q_OBJECT Q_PROPERTY(QString codecName READ currentCodecName WRITE setCurrentCodec) Q_PROPERTY(int codecMib READ currentCodecMib) public: KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode = false); ~KateViewEncodingAction(); - int mibForName(const QString &codecName, bool *ok = 0) const; + int mibForName(const QString &codecName, bool *ok = nullptr) const; QTextCodec *codecForMib(int mib) const; QTextCodec *currentCodec() const; bool setCurrentCodec(QTextCodec *codec); QString currentCodecName() const; bool setCurrentCodec(const QString &codecName); int currentCodecMib() const; bool setCurrentCodec(int mib); Q_SIGNALS: /** * Specific (proper) codec was selected */ void triggered(QTextCodec *codec); private: KTextEditor::DocumentPrivate *doc; KTextEditor::ViewPrivate *view; class Private { public: Private(KateViewEncodingAction *parent) : q(parent), - currentSubAction(0) + currentSubAction(nullptr) { } void init(); void _k_subActionTriggered(QAction *); KateViewEncodingAction *q; QAction *currentSubAction; }; Private *const d; Q_PRIVATE_SLOT(d, void _k_subActionTriggered(QAction *)) const bool m_saveAsMode; private Q_SLOTS: void setEncoding(const QString &e); void slotAboutToShow(); }; class KateViewBar; class KateViewBarWidget : public QWidget { Q_OBJECT friend class KateViewBar; public: - explicit KateViewBarWidget(bool addCloseButton, QWidget *parent = 0); + explicit KateViewBarWidget(bool addCloseButton, QWidget *parent = nullptr); virtual void closed() {} /// returns the currently associated KateViewBar and 0, if it is not associated KateViewBar *viewBar() { return m_viewBar; } protected: /** * @return widget that should be used to add controls to bar widget */ QWidget *centralWidget() { return m_centralWidget; } Q_SIGNALS: void hideMe(); // for friend class KateViewBar private: void setAssociatedViewBar(KateViewBar *bar) { m_viewBar = bar; } private: QWidget *m_centralWidget; KateViewBar *m_viewBar; // 0-pointer, if not added to a view bar }; class KateViewBar : public QWidget { Q_OBJECT public: KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view); /** * Adds a widget to this viewbar. * Widget is initially invisible, you should call showBarWidget, to show it. * Several widgets can be added to the bar, but only one can be visible */ void addBarWidget(KateViewBarWidget *newBarWidget); /** * Removes a widget from this viewbar. * Removing a widget makes sense if it takes a lot of space vertically, * because we use a QStackedWidget to maintain the same height for all * widgets in the viewbar. */ void removeBarWidget(KateViewBarWidget *barWidget); /** * @return if viewbar has widget @p barWidget */ bool hasBarWidget(KateViewBarWidget *barWidget) const; /** * Shows barWidget that was previously added with addBarWidget. * @see hideCurrentBarWidget */ void showBarWidget(KateViewBarWidget *barWidget); /** * Adds widget that will be always shown in the viewbar. * After adding permanent widget viewbar is immediately shown. * ViewBar with permanent widget won't hide itself * until permanent widget is removed. * OTOH showing/hiding regular barWidgets will work as usual * (they will be shown above permanent widget) * * If permanent widget already exists, asserts! */ void addPermanentBarWidget(KateViewBarWidget *barWidget); /** * Removes permanent bar widget from viewbar. * If no other viewbar widgets are shown, viewbar gets hidden. * * barWidget is not deleted, caller must do it if it wishes */ void removePermanentBarWidget(KateViewBarWidget *barWidget); /** * @return if viewbar has permanent widget @p barWidget */ bool hasPermanentWidget(KateViewBarWidget *barWidget) const; /** * @return true if the KateViewBar is hidden or displays a permanentBarWidget */ bool hiddenOrPermanent() const; public Q_SLOTS: /** * Hides currently shown bar widget */ void hideCurrentBarWidget(); protected: void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; void hideEvent(QHideEvent *event) Q_DECL_OVERRIDE; private: /** * Shows or hides whole viewbar */ void setViewBarVisible(bool visible); bool m_external; private: KTextEditor::ViewPrivate *m_view; QStackedWidget *m_stack; KateViewBarWidget *m_permanentBarWidget; QVBoxLayout *m_layout; }; class KTEXTEDITOR_EXPORT KateCommandLineBar : public KateViewBarWidget { Q_OBJECT public: - explicit KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent = 0); + explicit KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent = nullptr); ~KateCommandLineBar(); void setText(const QString &text, bool selected = true); void execute(const QString &text); public Q_SLOTS: void showHelpPage(); private: class KateCmdLineEdit *m_lineEdit; }; class KateCmdLineEdit : public KLineEdit { Q_OBJECT public: KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view); bool event(QEvent *e) Q_DECL_OVERRIDE; void hideEvent(QHideEvent *e) Q_DECL_OVERRIDE; Q_SIGNALS: void hideRequested(); public Q_SLOTS: void slotReturnPressed(const QString &cmd); private Q_SLOTS: void hideLineEdit(); protected: void focusInEvent(QFocusEvent *ev) Q_DECL_OVERRIDE; void keyPressEvent(QKeyEvent *ev) Q_DECL_OVERRIDE; private: /** * Parse an expression denoting a position in the document. * Return the position as an integer. * Examples of expressions are "10" (the 10th line), * "$" (the last line), "." (the current line), * "'a" (the mark 'a), "/foo/" (a forwards search for "foo"), * and "?bar?" (a backwards search for "bar"). * @param string the expression to parse * @return the position, an integer */ int calculatePosition(QString string); void fromHistory(bool up); QString helptext(const QPoint &) const; KTextEditor::ViewPrivate *m_view; KateCommandLineBar *m_bar; bool m_msgMode; QString m_oldText; uint m_histpos; ///< position in the history uint m_cmdend; ///< the point where a command ends in the text, if we have a valid one. KTextEditor::Command *m_command; ///< For completing flags/args and interactiveness class KateCmdLnWhatsThis *m_help; QTimer *m_hideTimer; }; class KatePasteMenu : public KActionMenu { Q_OBJECT public: KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view); private: KTextEditor::ViewPrivate *m_view; private Q_SLOTS: void slotAboutToShow(); void paste(); }; #endif diff --git a/src/view/kateviewinternal.cpp b/src/view/kateviewinternal.cpp index 5130d18d..a344247c 100644 --- a/src/view/kateviewinternal.cpp +++ b/src/view/kateviewinternal.cpp @@ -1,3293 +1,3302 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2002 Joseph Wenninger Copyright (C) 2002,2003 Christoph Cullmann Copyright (C) 2002-2007 Hamish Rodda Copyright (C) 2003 Anakim Border Copyright (C) 2007 Mirko Stocker Copyright (C) 2007 Matthew Woehlke Copyright (C) 2008 Erlend Hamberg Copyright (C) 2016 Sven Brauch Based on: KWriteView : 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. */ #include "kateviewinternal.h" #include "kateview.h" #include "kateviewhelpers.h" #include "katehighlight.h" #include "katebuffer.h" #include "katerenderer.h" #include "kateconfig.h" #include "katelayoutcache.h" #include "katecompletionwidget.h" #include "spellcheck/spellingmenu.h" #include "kateviewaccessible.h" #include "katetextanimation.h" #include "katemessagewidget.h" #include "kateglobal.h" #include "kateabstractinputmodefactory.h" #include "kateabstractinputmode.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const bool debugPainting = false; KateViewInternal::KateViewInternal(KTextEditor::ViewPrivate *view) : QWidget(view) , editSessionNumber(0) , editIsRunning(false) , m_view(view) , m_cursors(this) , m_selections(this) , m_mouse() , m_possibleTripleClick(false) , m_completionItemExpanded(false) , m_bm(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmLastFlashPos(doc()->newMovingCursor(KTextEditor::Cursor::invalid())) - , m_dummy(0) + , m_dummy(nullptr) // stay on cursor will avoid that the view scroll around on press return at beginning , m_startPos(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::StayOnInsert) , m_visibleLineCount(0) , m_madeVisible(false) , m_shiftKeyPressed(false) , m_autoCenterLines(0) , m_minLinesVisible(0) , m_selChangedByUser(false) , m_selectAnchor(-1, -1) , m_layoutCache(new KateLayoutCache(renderer(), this)) , m_preserveX(false) , m_preservedX(0) , m_cachedMaxStartPos(-1, -1) , m_dragScrollTimer(this) , m_scrollTimer(this) , m_cursorTimer(this) , m_textHintTimer(this) , m_textHintDelay(500) , m_textHintPos(-1, -1) - , m_imPreeditRange(0) + , m_imPreeditRange(nullptr) { QList factories = KTextEditor::EditorPrivate::self()->inputModeFactories(); Q_FOREACH(KateAbstractInputModeFactory *factory, factories) { KateAbstractInputMode *m = factory->createInputMode(this); m_inputModes.insert(m->viewInputMode(), m); } m_currentInputMode = m_inputModes[KTextEditor::View::NormalInputMode]; // TODO: twisted, but needed setMinimumSize(0, 0); setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_InputMethodEnabled); // bracket markers are only for this view and should not be printed m_bm->setView(m_view); m_bmStart->setView(m_view); m_bmEnd->setView(m_view); m_bm->setAttributeOnlyForViews(true); m_bmStart->setAttributeOnlyForViews(true); m_bmEnd->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface m_bm->setZDepth(-1000.0); m_bmStart->setZDepth(-1000.0); m_bmEnd->setZDepth(-1000.0); // update mark attributes updateBracketMarkAttributes(); // // scrollbar for lines // m_lineScroll = new KateScrollBar(Qt::Vertical, this); m_lineScroll->show(); m_lineScroll->setTracking(true); m_lineScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); // Hijack the line scroller's controls, so we can scroll nicely for word-wrap connect(m_lineScroll, SIGNAL(actionTriggered(int)), SLOT(scrollAction(int))); connect(m_lineScroll, SIGNAL(sliderMoved(int)), SLOT(scrollLines(int))); connect(m_lineScroll, SIGNAL(sliderMMBMoved(int)), SLOT(scrollLines(int))); connect(m_lineScroll, SIGNAL(valueChanged(int)), SLOT(scrollLines(int))); // // scrollbar for columns // m_columnScroll = new QScrollBar(Qt::Horizontal, m_view); if (m_view->dynWordWrap()) { m_columnScroll->hide(); } else { m_columnScroll->show(); } m_columnScroll->setTracking(true); m_startX = 0; connect(m_columnScroll, SIGNAL(valueChanged(int)), SLOT(scrollColumns(int))); // bottom corner box m_dummy = new QWidget(m_view); m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); m_dummy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); if (m_view->dynWordWrap()) { m_dummy->hide(); } else { m_dummy->show(); } cache()->setWrap(m_view->dynWordWrap()); // // iconborder ;) // m_leftBorder = new KateIconBorder(this, m_view); m_leftBorder->show(); // update view if folding ranges change connect(&m_view->textFolding(), SIGNAL(foldingRangesChanged()), SLOT(slotRegionVisibilityChanged())); m_displayCursor.setPosition(0, 0); setAcceptDrops(true); // event filter installEventFilter(this); // set initial cursor m_mouseCursor = Qt::IBeamCursor; setCursor(m_mouseCursor); // call mouseMoveEvent also if no mouse button is pressed setMouseTracking(true); m_dragInfo.state = diNone; // timers connect(&m_dragScrollTimer, SIGNAL(timeout()), this, SLOT(doDragScroll())); connect(&m_scrollTimer, SIGNAL(timeout()), this, SLOT(scrollTimeout())); connect(&m_cursorTimer, SIGNAL(timeout()), this, SLOT(cursorTimeout())); connect(&m_textHintTimer, SIGNAL(timeout()), this, SLOT(textHintTimeout())); // selection changed to set anchor connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(viewSelectionChanged())); #ifndef QT_NO_ACCESSIBILITY QAccessible::installFactory(accessibleInterfaceFactory); #endif connect(doc(), &KTextEditor::DocumentPrivate::textInserted, this, &KateViewInternal::documentTextInserted); connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateViewInternal::documentTextRemoved); // update is called in KTextEditor::ViewPrivate, after construction and layout is over // but before any other kateviewinternal call } KateViewInternal::~KateViewInternal() { // delete text animation object here, otherwise it updates the view in its destructor delete m_textAnimation; #ifndef QT_NO_ACCESSIBILITY QAccessible::removeFactory(accessibleInterfaceFactory); #endif // kill preedit ranges delete m_imPreeditRange; qDeleteAll(m_imPreeditRangeChildren); qDeleteAll(m_inputModes); // delete bracket markers delete m_bm; delete m_bmStart; delete m_bmEnd; } void KateViewInternal::prepareForDynWrapChange() { // Which is the current view line? m_wrapChangeViewLine = cache()->displayViewLine(m_displayCursor, true); } void KateViewInternal::dynWrapChanged() { m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); if (m_view->dynWordWrap()) { m_columnScroll->hide(); m_dummy->hide(); } else { // column scrollbar + bottom corner box m_columnScroll->show(); m_dummy->show(); } cache()->setWrap(m_view->dynWordWrap()); updateView(); if (m_view->dynWordWrap()) { scrollColumns(0); } // Determine where the cursor should be to get the cursor on the same view line if (m_wrapChangeViewLine != -1) { KTextEditor::Cursor newStart = viewLineOffset(m_displayCursor, -m_wrapChangeViewLine); makeVisible(newStart, newStart.column(), true); } else { update(); } } KTextEditor::Cursor KateViewInternal::endPos() const { // Hrm, no lines laid out at all?? if (!cache()->viewCacheLineCount()) { return KTextEditor::Cursor(); } for (int i = qMin(linesDisplayed() - 1, cache()->viewCacheLineCount() - 1); i >= 0; i--) { const KateTextLayout &thisLine = cache()->viewLine(i); if (thisLine.line() == -1) { continue; } if (thisLine.virtualLine() >= m_view->textFolding().visibleLines()) { // Cache is too out of date return KTextEditor::Cursor(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); } return KTextEditor::Cursor(thisLine.virtualLine(), thisLine.wrap() ? thisLine.endCol() - 1 : thisLine.endCol()); } // can happen, if view is still invisible return KTextEditor::Cursor(); } int KateViewInternal::endLine() const { return endPos().line(); } KateTextLayout KateViewInternal::yToKateTextLayout(int y) const { if (y < 0 || y > size().height()) { return KateTextLayout::invalid(); } int range = y / renderer()->lineHeight(); // lineRanges is always bigger than 0, after the initial updateView call if (range >= 0 && range < cache()->viewCacheLineCount()) { return cache()->viewLine(range); } return KateTextLayout::invalid(); } int KateViewInternal::lineToY(int viewLine) const { return (viewLine - startLine()) * renderer()->lineHeight(); } void KateViewInternal::slotIncFontSizes() { renderer()->increaseFontSizes(); } void KateViewInternal::slotDecFontSizes() { renderer()->decreaseFontSizes(); } /** * Line is the real line number to scroll to. */ void KateViewInternal::scrollLines(int line) { KTextEditor::Cursor newPos(line, 0); scrollPos(newPos); } // This can scroll less than one true line void KateViewInternal::scrollViewLines(int offset) { KTextEditor::Cursor c = viewLineOffset(startPos(), offset); scrollPos(c); bool blocked = m_lineScroll->blockSignals(true); m_lineScroll->setValue(startLine()); m_lineScroll->blockSignals(blocked); } void KateViewInternal::scrollAction(int action) { switch (action) { case QAbstractSlider::SliderSingleStepAdd: scrollNextLine(); break; case QAbstractSlider::SliderSingleStepSub: scrollPrevLine(); break; case QAbstractSlider::SliderPageStepAdd: scrollNextPage(); break; case QAbstractSlider::SliderPageStepSub: scrollPrevPage(); break; case QAbstractSlider::SliderToMinimum: top_home(); break; case QAbstractSlider::SliderToMaximum: bottom_end(); break; } } void KateViewInternal::scrollNextPage() { scrollViewLines(qMax(linesDisplayed() - 1, 0)); } void KateViewInternal::scrollPrevPage() { scrollViewLines(-qMax(linesDisplayed() - 1, 0)); } void KateViewInternal::scrollPrevLine() { scrollViewLines(-1); } void KateViewInternal::scrollNextLine() { scrollViewLines(1); } KTextEditor::Cursor KateViewInternal::maxStartPos(bool changed) { cache()->setAcceptDirtyLayouts(true); if (m_cachedMaxStartPos.line() == -1 || changed) { KTextEditor::Cursor end(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); if (m_view->config()->scrollPastEnd()) { m_cachedMaxStartPos = viewLineOffset(end, -m_minLinesVisible); } else { m_cachedMaxStartPos = viewLineOffset(end, -(linesDisplayed() - 1)); } } cache()->setAcceptDirtyLayouts(false); return m_cachedMaxStartPos; } // c is a virtual cursor void KateViewInternal::scrollPos(KTextEditor::Cursor &c, bool force, bool calledExternally, bool emitSignals) { if (!force && ((!m_view->dynWordWrap() && c.line() == startLine()) || c == startPos())) { return; } if (c.line() < 0) { c.setLine(0); } KTextEditor::Cursor limit = maxStartPos(); if (c > limit) { c = limit; // Re-check we're not just scrolling to the same place if (!force && ((!m_view->dynWordWrap() && c.line() == startLine()) || c == startPos())) { return; } } int viewLinesScrolled = 0; // only calculate if this is really used and useful, could be wrong here, please recheck // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on // try to get it really working ;) bool viewLinesScrolledUsable = !force && (c.line() >= startLine() - linesDisplayed() - 1) && (c.line() <= endLine() + linesDisplayed() + 1); if (viewLinesScrolledUsable) { viewLinesScrolled = cache()->displayViewLine(c); } m_startPos.setPosition(c); // set false here but reversed if we return to makeVisible m_madeVisible = false; if (viewLinesScrolledUsable) { int lines = linesDisplayed(); if (m_view->textFolding().visibleLines() < lines) { KTextEditor::Cursor end(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); lines = qMin(linesDisplayed(), cache()->displayViewLine(end) + 1); } Q_ASSERT(lines >= 0); if (!calledExternally && qAbs(viewLinesScrolled) < lines && // NOTE: on some machines we must update if the floating widget is visible // otherwise strange painting bugs may occur during scrolling... !((m_view->m_floatTopMessageWidget && m_view->m_floatTopMessageWidget->isVisible()) || (m_view->m_floatBottomMessageWidget && m_view->m_floatBottomMessageWidget->isVisible())) ) { updateView(false, viewLinesScrolled); int scrollHeight = -(viewLinesScrolled * (int)renderer()->lineHeight()); // scroll excluding child widgets (floating notifications) scroll(0, scrollHeight, rect()); m_leftBorder->scroll(0, scrollHeight); if (emitSignals) { emit m_view->verticalScrollPositionChanged(m_view, c); emit m_view->displayRangeChanged(m_view); } return; } } updateView(); update(); m_leftBorder->update(); if (emitSignals) { emit m_view->verticalScrollPositionChanged(m_view, c); emit m_view->displayRangeChanged(m_view); } } void KateViewInternal::scrollColumns(int x) { if (x < 0) { x = 0; } if (x > m_columnScroll->maximum()) { x = m_columnScroll->maximum(); } if (x == m_startX) { return; } int dx = m_startX - x; m_startX = x; if (qAbs(dx) < width()) { // scroll excluding child widgets (floating notifications) scroll(dx, 0, rect()); } else { update(); } emit m_view->horizontalScrollPositionChanged(m_view); emit m_view->displayRangeChanged(m_view); bool blocked = m_columnScroll->blockSignals(true); m_columnScroll->setValue(m_startX); m_columnScroll->blockSignals(blocked); } // If changed is true, the lines that have been set dirty have been updated. void KateViewInternal::updateView(bool changed, int viewLinesScrolled) { doUpdateView(changed, viewLinesScrolled); if (changed) { updateDirty(); } } void KateViewInternal::doUpdateView(bool changed, int viewLinesScrolled) { if (!isVisible() && !viewLinesScrolled && !changed) { return; //When this view is not visible, don't do anything } bool blocked = m_lineScroll->blockSignals(true); if (width() != cache()->viewWidth()) { cache()->setViewWidth(width()); changed = true; } /* It was observed that height() could be negative here -- when the main Kate view has 0 as size (during creation), and there frame arount KateViewInternal. In which case we'd set the view cache to 0 (or less!) lines, and start allocating huge chunks of data, later. */ int newSize = (qMax(0, height()) / renderer()->lineHeight()) + 1; cache()->updateViewCache(startPos(), newSize, viewLinesScrolled); m_visibleLineCount = newSize; KTextEditor::Cursor maxStart = maxStartPos(changed); int maxLineScrollRange = maxStart.line(); if (m_view->dynWordWrap() && maxStart.column() != 0) { maxLineScrollRange++; } m_lineScroll->setRange(0, maxLineScrollRange); m_lineScroll->setValue(startPos().line()); m_lineScroll->setSingleStep(1); m_lineScroll->setPageStep(qMax(0, height()) / renderer()->lineHeight()); m_lineScroll->blockSignals(blocked); KateViewConfig::ScrollbarMode show_scrollbars = static_cast(view()->config()->showScrollbars()); bool visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (maxLineScrollRange != 0))); bool visible_dummy = visible; m_lineScroll->setVisible(visible); if (!m_view->dynWordWrap()) { int max = maxLen(startLine()) - width(); if (max < 0) { max = 0; } // if we lose the ability to scroll horizontally, move view to the far-left if (max == 0) { scrollColumns(0); } blocked = m_columnScroll->blockSignals(true); // disable scrollbar m_columnScroll->setDisabled(max == 0); visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (max != 0))); visible_dummy &= visible; m_columnScroll->setVisible(visible); m_columnScroll->setRange(0, max + (renderer()->spaceWidth() / 2)); // Add some space for the caret at EOL m_columnScroll->setValue(m_startX); // Approximate linescroll m_columnScroll->setSingleStep(renderer()->config()->fontMetrics().width(QLatin1Char('a'))); m_columnScroll->setPageStep(width()); m_columnScroll->blockSignals(blocked); } else { visible_dummy = false; } m_dummy->setVisible(visible_dummy); } /** * this function ensures a certain location is visible on the screen. * if endCol is -1, ignore making the columns visible. */ void KateViewInternal::makeVisible(const KTextEditor::Cursor &c, int endCol, bool force, bool center, bool calledExternally) { //qCDebug(LOG_KTE) << "MakeVisible start " << startPos() << " end " << endPos() << " -> request: " << c;// , new start [" << scroll.line << "," << scroll.col << "] lines " << (linesDisplayed() - 1) << " height " << height(); // if the line is in a folded region, unfold all the way up //if ( doc()->foldingTree()->findNodeForLine( c.line )->visible ) // qCDebug(LOG_KTE)<<"line ("< endPos())) { KTextEditor::Cursor scroll = viewLineOffset(c, -int(linesDisplayed()) / 2); scrollPos(scroll, false, calledExternally); } else if (c > viewLineOffset(startPos(), linesDisplayed() - m_minLinesVisible - 1)) { KTextEditor::Cursor scroll = viewLineOffset(c, -(linesDisplayed() - m_minLinesVisible - 1)); scrollPos(scroll, false, calledExternally); } else if (c < viewLineOffset(startPos(), m_minLinesVisible)) { KTextEditor::Cursor scroll = viewLineOffset(c, -m_minLinesVisible); scrollPos(scroll, false, calledExternally); } else { // Check to see that we're not showing blank lines KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max, max.column(), calledExternally); } } if (!m_view->dynWordWrap() && (endCol != -1 || m_view->wrapCursor())) { KTextEditor::Cursor rc = toRealCursor(c); int sX = renderer()->cursorToX(cache()->textLayout(rc), rc, !m_view->wrapCursor()); int sXborder = sX - 8; if (sXborder < 0) { sXborder = 0; } if (sX < m_startX) { scrollColumns(sXborder); } else if (sX > m_startX + width()) { scrollColumns(sX - width() + 8); } } m_madeVisible = !force; #ifndef QT_NO_ACCESSIBILITY // FIXME -- is this needed? // QAccessible::updateAccessibility(this, KateCursorAccessible::ChildId, QAccessible::Focus); #endif } void KateViewInternal::slotRegionVisibilityChanged() { qCDebug(LOG_KTE); cache()->clear(); m_cachedMaxStartPos.setLine(-1); KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max, false, false, false /* don't emit signals! */); } // if text was folded: make sure the cursor is on a visible line qint64 foldedRangeId = -1; if (!m_view->textFolding().isLineVisible(primaryCursor().line(), &foldedRangeId)) { KTextEditor::Range foldingRange = m_view->textFolding().foldingRange(foldedRangeId); Q_ASSERT(foldingRange.start().isValid()); // set cursor to start of folding region cursors()->setPrimaryCursor(foldingRange.start(), true); } else { // force an update of the cursor, since otherwise the m_displayCursor // line may be below the total amount of visible lines. cursors()->setPrimaryCursor(primaryCursor(), true); } updateView(); update(); m_leftBorder->update(); // emit signals here, scrollPos has this disabled, to ensure we do this after all stuff is updated! emit m_view->verticalScrollPositionChanged(m_view, max); emit m_view->displayRangeChanged(m_view); } void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int) { qCDebug(LOG_KTE); // FIXME: performance problem m_leftBorder->update(); } void KateViewInternal::showEvent(QShowEvent *e) { updateView(); QWidget::showEvent(e); } int KateViewInternal::linesDisplayed() const { int h = height(); // catch zero heights, even if should not happen int fh = qMax(1, renderer()->lineHeight()); // default to 1, there is always one line around.... // too many places calc with linesDisplayed() - 1 return qMax(1, (h - (h % fh)) / fh); } QPoint KateViewInternal::cursorToCoordinate(const KTextEditor::Cursor &cursor, bool realCursor, bool includeBorder) const { if (cursor.line() >= doc()->lines()) { return QPoint(-1, -1); } int viewLine = cache()->displayViewLine(realCursor ? toVirtualCursor(cursor) : cursor, true); if (viewLine < 0 || viewLine >= cache()->viewCacheLineCount()) { return QPoint(-1, -1); } const int y = (int)viewLine * renderer()->lineHeight(); KateTextLayout layout = cache()->viewLine(viewLine); + + if (cursor.column() > doc()->lineLength(cursor.line())) { + return QPoint(-1, -1); + } + int x = 0; // only set x value if we have a valid layout (bug #171027) if (layout.isValid()) { x = (int)layout.lineLayout().cursorToX(cursor.column()); } // else // qCDebug(LOG_KTE) << "Invalid Layout"; if (includeBorder) { x += m_leftBorder->width(); } x -= startX(); return QPoint(x, y); } QPoint KateViewInternal::cursorCoordinates(bool includeBorder) const { return cursorToCoordinate(m_displayCursor, false, includeBorder); } KTextEditor::Cursor KateViewInternal::findMatchingBracket() { KTextEditor::Cursor c; if (!m_bm->toRange().isValid()) { return KTextEditor::Cursor::invalid(); } Q_ASSERT(m_bmEnd->toRange().isValid()); Q_ASSERT(m_bmStart->toRange().isValid()); auto cursor = primaryCursor(); if (m_bmStart->toRange().contains(cursor) || m_bmStart->end() == cursor) { c = m_bmEnd->end(); } else if (m_bmEnd->toRange().contains(cursor) || m_bmEnd->end() == cursor) { c = m_bmStart->start(); } else { // should never happen: a range exists, but the cursor position is // neither at the start nor at the end... return KTextEditor::Cursor::invalid(); } return c; } void KateViewInternal::doReturn() { doc()->newLine(m_view); m_leftBorder->updateForCursorLineChange(); updateView(); } void KateViewInternal::doSmartNewline() { int ln = primaryCursor().line(); Kate::TextLine line = doc()->kateTextLine(ln); int col = qMin(primaryCursor().column(), line->firstChar()); if (col != -1) { while (line->length() > col && !(line->at(col).isLetterOrNumber() || line->at(col) == QLatin1Char('_')) && col < primaryCursor().column()) { ++col; } } else { col = line->length(); // stay indented } doc()->editStart(); doc()->editWrapLine(ln, primaryCursor().column()); doc()->insertText(KTextEditor::Cursor(ln + 1, 0), line->string(0, col)); doc()->editEnd(); updateView(); } void KateViewInternal::doDelete() { auto cursors = view()->allCursors(); KTextEditor::Document::EditingTransaction t(doc()); Q_FOREACH ( const auto& cursor, cursors ) { doc()->del(m_view, cursor); } } void KateViewInternal::doBackspace() { auto cursors = view()->allCursors(); KTextEditor::Document::EditingTransaction t(doc()); Q_FOREACH ( const auto& cursor, cursors ) { doc()->backspace(m_view, cursor); } } void KateViewInternal::doTabulator() { doc()->insertTab(m_view, primaryCursor()); } void KateViewInternal::doTranspose() { doc()->transpose(primaryCursor()); } void KateViewInternal::doDeletePrevWord() { doc()->editStart(); wordPrev(true); KTextEditor::Range selection = m_view->selectionRange(); m_view->removeSelectedText(); doc()->editEnd(); tagRange(selection, true); updateDirty(); } void KateViewInternal::doDeleteNextWord() { doc()->editStart(); wordNext(true); KTextEditor::Range selection = m_view->selectionRange(); m_view->removeSelectedText(); doc()->editEnd(); tagRange(selection, true); updateDirty(); } void KateViewInternal::clearSelectionUnless(bool sel) { if ( ! sel ) { selections()->clearSelectionIfNotPersistent(); } } void KateViewInternal::cursorPrevChar(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsLeft(sel); } void KateViewInternal::cursorNextChar(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsRight(sel); } void KateViewInternal::wordPrev(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsWordPrevious(sel); } void KateViewInternal::wordNext(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsWordNext(sel); } void KateViewInternal::home(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsStartOfLine(sel); } void KateViewInternal::end(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsEndOfLine(sel); } KateTextLayout KateViewInternal::currentLayout(const KTextEditor::Cursor& cursor) const { return cache()->textLayout(cursor); } KateTextLayout KateViewInternal::previousLayout(const KTextEditor::Cursor& cursor) const { int currentViewLine = cache()->viewLine(cursor); if (currentViewLine) { return cache()->textLayout(cursor.line(), currentViewLine - 1); } else { return cache()->textLayout(m_view->textFolding().visibleLineToLine(toVirtualCursor(cursor).line() - 1), -1); } } KateTextLayout KateViewInternal::nextLayout(const KTextEditor::Cursor& cursor) const { int currentViewLine = cache()->viewLine(cursor) + 1; if (currentViewLine >= cache()->line(cursor.line())->viewLineCount()) { currentViewLine = 0; return cache()->textLayout(m_view->textFolding().visibleLineToLine(toVirtualCursor(cursor).line() + 1), currentViewLine); } else { return cache()->textLayout(cursor.line(), currentViewLine); } } /* * This returns the cursor which is offset by (offset) view lines. * This is the main function which is called by code not specifically dealing with word-wrap. * The opposite conversion (cursor to offset) can be done with cache()->displayViewLine(). * * The cursors involved are virtual cursors (ie. equivalent to m_displayCursor) */ KTextEditor::Cursor KateViewInternal::viewLineOffset(const KTextEditor::Cursor &virtualCursor, int offset, bool keepX) { if (!m_view->dynWordWrap()) { KTextEditor::Cursor ret(qMin((int)m_view->textFolding().visibleLines() - 1, virtualCursor.line() + offset), 0); if (ret.line() < 0) { ret.setLine(0); } if (keepX) { int realLine = m_view->textFolding().visibleLineToLine(ret.line()); KateTextLayout t = cache()->textLayout(realLine, 0); Q_ASSERT(t.isValid()); ret.setColumn(renderer()->xToCursor(t, m_preservedX, !m_view->wrapCursor()).column()); } return ret; } KTextEditor::Cursor realCursor = virtualCursor; realCursor.setLine(m_view->textFolding().visibleLineToLine(m_view->textFolding().lineToVisibleLine(virtualCursor.line()))); int cursorViewLine = cache()->viewLine(realCursor); int currentOffset = 0; int virtualLine = 0; bool forwards = (offset > 0) ? true : false; if (forwards) { currentOffset = cache()->lastViewLine(realCursor.line()) - cursorViewLine; if (offset <= currentOffset) { // the answer is on the same line KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine + offset); - Q_ASSERT(thisLine.virtualLine() == virtualCursor.line()); + Q_ASSERT(thisLine.virtualLine() == (int) m_view->textFolding().lineToVisibleLine(virtualCursor.line())); return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol()); } virtualLine = virtualCursor.line() + 1; } else { offset = -offset; currentOffset = cursorViewLine; if (offset <= currentOffset) { // the answer is on the same line KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine - offset); Q_ASSERT(thisLine.virtualLine() == (int) m_view->textFolding().lineToVisibleLine(virtualCursor.line())); return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol()); } virtualLine = virtualCursor.line() - 1; } currentOffset++; while (virtualLine >= 0 && virtualLine < (int)m_view->textFolding().visibleLines()) { int realLine = m_view->textFolding().visibleLineToLine(virtualLine); KateLineLayoutPtr thisLine = cache()->line(realLine, virtualLine); if (!thisLine) { break; } for (int i = 0; i < thisLine->viewLineCount(); ++i) { if (offset == currentOffset) { KateTextLayout thisViewLine = thisLine->viewLine(i); if (!forwards) { // We actually want it the other way around int requiredViewLine = cache()->lastViewLine(realLine) - thisViewLine.viewLine(); if (requiredViewLine != thisViewLine.viewLine()) { thisViewLine = thisLine->viewLine(requiredViewLine); } } KTextEditor::Cursor ret(virtualLine, thisViewLine.startCol()); // keep column position if (keepX) { KTextEditor::Cursor realCursor = toRealCursor(virtualCursor); KateTextLayout t = cache()->textLayout(realCursor); // renderer()->cursorToX(t, realCursor, !m_view->wrapCursor()); realCursor = renderer()->xToCursor(thisViewLine, m_preservedX, !m_view->wrapCursor()); ret.setColumn(realCursor.column()); } return ret; } currentOffset++; } if (forwards) { virtualLine++; } else { virtualLine--; } } // Looks like we were asked for something a bit exotic. // Return the max/min valid position. if (forwards) { return KTextEditor::Cursor(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); } else { return KTextEditor::Cursor(0, 0); } } int KateViewInternal::lineMaxCursorX(const KateTextLayout &range) { if (!m_view->wrapCursor() && !range.wrap()) { return INT_MAX; } int maxX = range.endX(); if (maxX && range.wrap()) { QChar lastCharInLine = doc()->kateTextLine(range.line())->at(range.endCol() - 1); maxX -= renderer()->config()->fontMetrics().width(lastCharInLine); } return maxX; } int KateViewInternal::lineMaxCol(const KateTextLayout &range) { int maxCol = range.endCol(); if (maxCol && range.wrap()) { maxCol--; } return maxCol; } void KateViewInternal::cursorUp(bool sel) { if (!sel && m_view->completionWidget()->isCompletionActive()) { m_view->completionWidget()->cursorUp(); return; } cursors()->moveCursorsUp(sel); } void KateViewInternal::cursorDown(bool sel) { if (!sel && m_view->completionWidget()->isCompletionActive()) { m_view->completionWidget()->cursorDown(); return; } cursors()->moveCursorsDown(sel); } void KateViewInternal::cursorToMatchingBracket(bool sel) { KTextEditor::Cursor c = findMatchingBracket(); if (c.isValid()) { updateSelection(c, sel); cursors()->setPrimaryCursor(c); } } void KateViewInternal::topOfView(bool sel) { KTextEditor::Cursor c = viewLineOffset(startPos(), m_minLinesVisible); updateSelection(toRealCursor(c), sel); cursors()->setPrimaryCursor(toRealCursor(c)); } void KateViewInternal::bottomOfView(bool sel) { KTextEditor::Cursor c = viewLineOffset(endPos(), -m_minLinesVisible); updateSelection(toRealCursor(c), sel); cursors()->setPrimaryCursor(toRealCursor(c)); } // lines is the offset to scroll by void KateViewInternal::scrollLines(int lines, bool sel) { KTextEditor::Cursor c = viewLineOffset(m_displayCursor, lines, true); // Fix the virtual cursor -> real cursor c.setLine(m_view->textFolding().visibleLineToLine(c.line())); updateSelection(c, sel); cursors()->setPrimaryCursor(c); } // This is a bit misleading... it's asking for the view to be scrolled, not the cursor void KateViewInternal::scrollUp() { KTextEditor::Cursor newPos = viewLineOffset(startPos(), -1); scrollPos(newPos); } void KateViewInternal::scrollDown() { KTextEditor::Cursor newPos = viewLineOffset(startPos(), 1); scrollPos(newPos); } void KateViewInternal::setAutoCenterLines(int viewLines, bool updateView) { m_autoCenterLines = viewLines; m_minLinesVisible = qMin(int((linesDisplayed() - 1) / 2), m_autoCenterLines); if (updateView) { KateViewInternal::updateView(); } } void KateViewInternal::pageUp(bool sel, bool half) { if (m_view->isCompletionActive()) { m_view->completionWidget()->pageUp(); return; } bool atTop = startPos().atStartOfDocument(); // Adjust for an auto-centering cursor int lineadj = m_minLinesVisible; int linesToScroll; if (! half) { linesToScroll = -qMax((linesDisplayed() - 1) - lineadj, 0); } else { linesToScroll = -qMax((linesDisplayed() / 2 - 1) - lineadj, 0); } if (!doc()->pageUpDownMovesCursor() && !atTop) { KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll - 1); scrollPos(newStartPos); cursors()->moveCursorsUp(sel, linesToScroll - 1); } else { scrollLines(linesToScroll, sel); } } void KateViewInternal::pageDown(bool sel, bool half) { if (m_view->isCompletionActive()) { m_view->completionWidget()->pageDown(); return; } bool atEnd = startPos() >= m_cachedMaxStartPos; // Adjust for an auto-centering cursor int lineadj = m_minLinesVisible; int linesToScroll; if (! half) { linesToScroll = qMax((linesDisplayed() - 1) - lineadj, 0); } else { linesToScroll = qMax((linesDisplayed() / 2 - 1) - lineadj, 0); } if (!doc()->pageUpDownMovesCursor() && !atEnd) { KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll + 1); scrollPos(newStartPos); cursors()->moveCursorsDown(sel, linesToScroll + 1); } else { scrollLines(linesToScroll, sel); } } int KateViewInternal::maxLen(int startLine) { Q_ASSERT(!m_view->dynWordWrap()); int displayLines = (m_view->height() / renderer()->lineHeight()) + 1; int maxLen = 0; for (int z = 0; z < displayLines; z++) { int virtualLine = startLine + z; if (virtualLine < 0 || virtualLine >= (int)m_view->textFolding().visibleLines()) { break; } maxLen = qMax(maxLen, cache()->line(m_view->textFolding().visibleLineToLine(virtualLine))->width()); } return maxLen; } bool KateViewInternal::columnScrollingPossible() { return !m_view->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maximum() > 0); } bool KateViewInternal::lineScrollingPossible() { return m_lineScroll->minimum() != m_lineScroll->maximum(); } void KateViewInternal::top(bool sel) { KTextEditor::Cursor newCursor(0, 0); newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !m_view->wrapCursor()); updateSelection(newCursor, sel); cursors()->setPrimaryCursor(newCursor); } void KateViewInternal::bottom(bool sel) { KTextEditor::Cursor newCursor(doc()->lastLine(), 0); newCursor = renderer()->xToCursor(cache()->textLayout(newCursor), m_preservedX, !m_view->wrapCursor()); updateSelection(newCursor, sel); cursors()->setPrimaryCursor(newCursor); } void KateViewInternal::top_home(bool sel) { if (m_view->isCompletionActive()) { m_view->completionWidget()->top(); return; } KTextEditor::Cursor c(0, 0); updateSelection(c, sel); cursors()->setPrimaryCursor(c); } void KateViewInternal::bottom_end(bool sel) { if (m_view->isCompletionActive()) { m_view->completionWidget()->bottom(); return; } KTextEditor::Cursor c(doc()->lastLine(), doc()->lineLength(doc()->lastLine())); updateSelection(c, sel); cursors()->setPrimaryCursor(c); } void KateViewInternal::updateSelection(const KTextEditor::Cursor &_newCursor, bool keepSel) { /** KTextEditor::Cursor newCursor = _newCursor; if (keepSel) { if (!m_view->selection() || (m_selectAnchor.line() == -1) //don't kill the selection if we have a persistent selection and //the cursor is inside or at the boundaries of the selected area || (m_view->config()->persistentSelection() && !(m_view->selectionRange().contains(primaryCursor()) || m_view->selectionRange().boundaryAtCursor(primaryCursor())))) { m_selectAnchor = primaryCursor(); setSelection(KTextEditor::Range(primaryCursor(), newCursor)); } else { bool doSelect = true; switch (m_selectionMode) { case Word: { // Restore selStartCached if needed. It gets nuked by // viewSelectionChanged if we drag the selection into non-existence, // which can legitimately happen if a shift+DC selection is unable to // set a "proper" (i.e. non-empty) cached selection, e.g. because the // start was on something that isn't a word. Word select mode relies // on the cached selection being set properly, even if it is empty // (i.e. selStartCached == selEndCached). if (!m_selectionCached.isValid()) { m_selectionCached.setStart(m_selectionCached.end()); } int c; if (newCursor > m_selectionCached.start()) { m_selectAnchor = m_selectionCached.start(); Kate::TextLine l = doc()->kateTextLine(newCursor.line()); c = newCursor.column(); if (c > 0 && doc()->highlight()->isInWord(l->at(c - 1))) { for (; c < l->length(); c++) if (!doc()->highlight()->isInWord(l->at(c))) { break; } } newCursor.setColumn(c); } else if (newCursor < m_selectionCached.start()) { m_selectAnchor = m_selectionCached.end(); Kate::TextLine l = doc()->kateTextLine(newCursor.line()); c = newCursor.column(); if (c > 0 && c < doc()->lineLength(newCursor.line()) && doc()->highlight()->isInWord(l->at(c)) && doc()->highlight()->isInWord(l->at(c - 1))) { for (c -= 2; c >= 0; c--) if (!doc()->highlight()->isInWord(l->at(c))) { break; } newCursor.setColumn(c + 1); } } else { doSelect = false; } } break; case Line: if (!m_selectionCached.isValid()) { m_selectionCached = KTextEditor::Range(endLine(), 0, endLine(), 0); } if (newCursor.line() > m_selectionCached.start().line()) { if (newCursor.line() + 1 >= doc()->lines()) { newCursor.setColumn(doc()->line(newCursor.line()).length()); } else { newCursor.setPosition(newCursor.line() + 1, 0); } // Grow to include the entire line m_selectAnchor = m_selectionCached.start(); m_selectAnchor.setColumn(0); } else if (newCursor.line() < m_selectionCached.start().line()) { newCursor.setColumn(0); // Grow to include entire line m_selectAnchor = m_selectionCached.end(); if (m_selectAnchor.column() > 0) { if (m_selectAnchor.line() + 1 >= doc()->lines()) { m_selectAnchor.setColumn(doc()->line(newCursor.line()).length()); } else { m_selectAnchor.setPosition(m_selectAnchor.line() + 1, 0); } } } else { // same line, ignore doSelect = false; } break; case Mouse: { if (!m_selectionCached.isValid()) { break; } if (newCursor > m_selectionCached.end()) { m_selectAnchor = m_selectionCached.start(); } else if (newCursor < m_selectionCached.start()) { m_selectAnchor = m_selectionCached.end(); } else { doSelect = false; } } break; default:; } if (doSelect) { setSelection(KTextEditor::Range(m_selectAnchor, newCursor)); } else if (m_selectionCached.isValid()) { // we have a cached selection, so we restore that setSelection(m_selectionCached); } } m_selChangedByUser = true; } else if (!m_view->config()->persistentSelection()) { m_view->clearSelection(); m_selectionCached = KTextEditor::Range::invalid(); m_selectAnchor = KTextEditor::Cursor::invalid(); } **/ #ifndef QT_NO_ACCESSIBILITY // FIXME KF5 // QAccessibleTextSelectionEvent ev(this, /* selection start, selection end*/); // QAccessible::updateAccessibility(&ev); #endif } void KateViewInternal::setSelection(const KTextEditor::Range &range) { disconnect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(viewSelectionChanged())); m_view->setSelection(range); connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(viewSelectionChanged())); } void KateViewInternal::moveCursorToSelectionEdge() { if (!m_view->selection()) { return; } int tmp = m_minLinesVisible; m_minLinesVisible = 0; if (m_view->selectionRange().start() < m_selectAnchor) { cursors()->setPrimaryCursor(m_view->selectionRange().start()); } else { cursors()->setPrimaryCursor(m_view->selectionRange().end()); } m_minLinesVisible = tmp; } void KateViewInternal::updateCursorFlashTimer() { if (m_cursorTimer.isActive()) { if (QApplication::cursorFlashTime() > 0) { m_cursorTimer.start(QApplication::cursorFlashTime() / 2); } renderer()->setDrawCaret(true); } } void KateViewInternal::notifyPrimaryCursorChanged(const KTextEditor::Cursor &newCursor, bool force, bool center, bool calledExternally) { if (!force && (m_lastUpdatedPrimary == newCursor)) { m_displayCursor = toVirtualCursor(newCursor); if (!m_madeVisible && m_view == doc()->activeView()) { // unfold if required m_view->textFolding().ensureLineIsVisible(newCursor.line()); makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally); } return; } if (m_lastUpdatedPrimary.line() != newCursor.line()) { m_leftBorder->updateForCursorLineChange(); } // unfold if required m_view->textFolding().ensureLineIsVisible(newCursor.line()); m_displayCursor = toVirtualCursor(newCursor); m_lastUpdatedPrimary = newCursor; if (m_view == doc()->activeView()) { makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally); } updateBracketMarks(); updateMicroFocus(); updateCursorFlashTimer(); cursorMoved(); emit m_view->cursorPositionChanged(m_view, primaryCursor()); } void KateViewInternal::updateBracketMarkAttributes() { KTextEditor::Attribute::Ptr bracketFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); bracketFill->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); bracketFill->setBackgroundFillWhitespace(false); if (QFontInfo(renderer()->currentFont()).fixedPitch()) { // make font bold only for fixed fonts, otherwise text jumps around bracketFill->setFontBold(); } m_bmStart->setAttribute(bracketFill); m_bmEnd->setAttribute(bracketFill); if (m_view->m_renderer->config()->showWholeBracketExpression()) { KTextEditor::Attribute::Ptr expressionFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); expressionFill->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); expressionFill->setBackgroundFillWhitespace(false); m_bm->setAttribute(expressionFill); } else { m_bm->setAttribute(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute())); } } void KateViewInternal::updateBracketMarks() { // add some limit to this, this is really endless on big files without limit const int maxLines = 5000; const KTextEditor::Range newRange = doc()->findMatchingBracket(primaryCursor(), maxLines); // new range valid, then set ranges to it if (newRange.isValid()) { if (m_bm->toRange() == newRange) { return; } // modify full range m_bm->setRange(newRange); // modify start and end ranges m_bmStart->setRange(KTextEditor::Range(m_bm->start(), KTextEditor::Cursor(m_bm->start().line(), m_bm->start().column() + 1))); m_bmEnd->setRange(KTextEditor::Range(m_bm->end(), KTextEditor::Cursor(m_bm->end().line(), m_bm->end().column() + 1))); // flash matching bracket if (!renderer()->config()->animateBracketMatching()) { return; } const KTextEditor::Cursor flashPos = (primaryCursor() == m_bmStart->start() || primaryCursor() == m_bmStart->end()) ? m_bmEnd->start() : m_bm->start(); if (flashPos != m_bmLastFlashPos->toCursor()) { m_bmLastFlashPos->setPosition(flashPos); KTextEditor::Attribute::Ptr attribute = doc()->attributeAt(flashPos); attribute->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); attribute->setFontBold(m_bmStart->attribute()->fontBold()); flashChar(flashPos, attribute); } return; } // new range was invalid m_bm->setRange(KTextEditor::Range::invalid()); m_bmStart->setRange(KTextEditor::Range::invalid()); m_bmEnd->setRange(KTextEditor::Range::invalid()); m_bmLastFlashPos->setPosition(KTextEditor::Cursor::invalid()); } bool KateViewInternal::tagLine(const KTextEditor::Cursor &virtualCursor) { // FIXME may be a more efficient way for this if ((int)m_view->textFolding().visibleLineToLine(virtualCursor.line()) > doc()->lastLine()) { return false; } // End FIXME int viewLine = cache()->displayViewLine(virtualCursor, true); if (viewLine >= 0 && viewLine < cache()->viewCacheLineCount()) { cache()->viewLine(viewLine).setDirty(); // tag one line more because of overlapping things like _, bug 335079 if (viewLine+1 < cache()->viewCacheLineCount()) { cache()->viewLine(viewLine+1).setDirty(); } m_leftBorder->update(0, lineToY(viewLine), m_leftBorder->width(), renderer()->lineHeight()); return true; } return false; } bool KateViewInternal::tagLines(int start, int end, bool realLines) { return tagLines(KTextEditor::Cursor(start, 0), KTextEditor::Cursor(end, -1), realLines); } bool KateViewInternal::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) { if (realCursors) { cache()->relayoutLines(start.line(), end.line()); //qCDebug(LOG_KTE)<<"realLines is true"; start = toVirtualCursor(start); end = toVirtualCursor(end); } else { cache()->relayoutLines(toRealCursor(start).line(), toRealCursor(end).line()); } if (end.line() < startLine()) { //qCDebug(LOG_KTE)<<"end endLine(), but cache may not be valid when checking, so use a // less optimal but still adequate approximation (potential overestimation but minimal performance difference) if (start.line() > startLine() + cache()->viewCacheLineCount()) { //qCDebug(LOG_KTE)<<"start> endLine"<updateViewCache(startPos()); //qCDebug(LOG_KTE) << "tagLines( [" << start << "], [" << end << "] )"; bool ret = false; for (int z = 0; z < cache()->viewCacheLineCount(); z++) { KateTextLayout &line = cache()->viewLine(z); if ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1)) && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1)))) { ret = true; break; //qCDebug(LOG_KTE) << "Tagged line " << line.line(); } } if (!m_view->dynWordWrap()) { int y = lineToY(start.line()); // FIXME is this enough for when multiple lines are deleted int h = (end.line() - start.line() + 2) * renderer()->lineHeight(); if (end.line() >= m_view->textFolding().visibleLines() - 1) { h = height(); } m_leftBorder->update(0, y, m_leftBorder->width(), h); } else { // FIXME Do we get enough good info in editRemoveText to optimize this more? //bool justTagged = false; for (int z = 0; z < cache()->viewCacheLineCount(); z++) { KateTextLayout &line = cache()->viewLine(z); if (!line.isValid() || ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1)) && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1))))) { //justTagged = true; m_leftBorder->update(0, z * renderer()->lineHeight(), m_leftBorder->width(), m_leftBorder->height()); break; } /*else if (justTagged) { justTagged = false; leftBorder->update (0, z * doc()->viewFont.fontHeight, leftBorder->width(), doc()->viewFont.fontHeight); break; }*/ } } return ret; } bool KateViewInternal::tagRange(const KTextEditor::Range &range, bool realCursors) { return tagLines(range.start(), range.end(), realCursors); } void KateViewInternal::tagAll() { // clear the cache... cache()->clear(); m_leftBorder->updateFont(); m_leftBorder->update(); } void KateViewInternal::paintCursor() { Q_FOREACH ( const auto& secondary, view()->cursors()->cursors() ) { if (tagLine(secondary)) { updateDirty(); } } } KTextEditor::Cursor KateViewInternal::pointToCursor(const QPoint& p) const { KateTextLayout thisLine = yToKateTextLayout(p.y()); KTextEditor::Cursor c; if (!thisLine.isValid()) { // probably user clicked below the last line -> use the last line thisLine = cache()->textLayout(doc()->lines() - 1, -1); } c = renderer()->xToCursor(thisLine, startX() + p.x(), !m_view->wrapCursor()); if (c.line() < 0 || c.line() >= doc()->lines()) { return {}; } return c; } // Point in content coordinates void KateViewInternal::placeCursor(const QPoint &p, bool keepSelection, bool updateSelection) { auto c = pointToCursor(p); if ( ! c.isValid() ) { return; } if (updateSelection) { KateViewInternal::updateSelection(c, keepSelection); } int tmp = m_minLinesVisible; m_minLinesVisible = 0; cursors()->setPrimaryCursor(c); m_minLinesVisible = tmp; if (updateSelection && keepSelection) { moveCursorToSelectionEdge(); } } // Point in content coordinates bool KateViewInternal::isTargetSelected(const QPoint &p) { const KateTextLayout &thisLine = yToKateTextLayout(p.y()); if (!thisLine.isValid()) { return false; } return m_view->cursorSelected(renderer()->xToCursor(thisLine, startX() + p.x(), !m_view->wrapCursor())); } //BEGIN EVENT HANDLING STUFF bool KateViewInternal::eventFilter(QObject *obj, QEvent *e) { switch (e->type()) { case QEvent::ChildAdded: case QEvent::ChildRemoved: { QChildEvent *c = static_cast(e); if (c->added()) { c->child()->installEventFilter(this); /*foreach (QWidget* child, c->child()->findChildren()) child->installEventFilter(this);*/ } else if (c->removed()) { c->child()->removeEventFilter(this); /*foreach (QWidget* child, c->child()->findChildren()) child->removeEventFilter(this);*/ } } break; case QEvent::ShortcutOverride: { QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_view->isCompletionActive()) { m_view->abortCompletion(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "aborting completion"; return true; } else if (!m_view->bottomViewBar()->hiddenOrPermanent()) { m_view->bottomViewBar()->hideCurrentBarWidget(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "closing view bar"; return true; } else if (!m_view->config()->persistentSelection() && m_view->selection()) { m_currentInputMode->clearSelection(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "clearing selection"; return true; } else if (m_view->cursors()->hasSecondaryCursors()) { m_view->cursors()->clearSecondaryCursors(); k->accept(); return true; } } if (m_currentInputMode->stealKey(k)) { k->accept(); return true; } } break; case QEvent::KeyPress: { QKeyEvent *k = static_cast(e); // Override all other single key shortcuts which do not use a modifier other than Shift if (obj == this && (!k->modifiers() || k->modifiers() == Qt::ShiftModifier)) { keyPressEvent(k); if (k->isAccepted()) { //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "using keystroke"; return true; } } //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "ignoring"; } break; case QEvent::DragMove: { QPoint currentPoint = ((QDragMoveEvent *) e)->pos(); QRect doNotScrollRegion(s_scrollMargin, s_scrollMargin, width() - s_scrollMargin * 2, height() - s_scrollMargin * 2); if (!doNotScrollRegion.contains(currentPoint)) { startDragScroll(); // Keep sending move events ((QDragMoveEvent *)e)->accept(QRect(0, 0, 0, 0)); } dragMoveEvent((QDragMoveEvent *)e); } break; case QEvent::DragLeave: // happens only when pressing ESC while dragging stopDragScroll(); break; default: break; } return QWidget::eventFilter(obj, e); } void KateViewInternal::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Left && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateLeft(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Right && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateRight(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Up && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateUp(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Down && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateDown(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Return && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateAccept(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateBack(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Alt && m_view->completionWidget()->isCompletionActive()) { m_completionItemExpanded = m_view->completionWidget()->toggleExpanded(true); m_view->completionWidget()->resetHadNavigation(); m_altDownTime.start(); } // Note: AND'ing with is a quick hack to fix Key_Enter const int key = e->key() | (e->modifiers() & Qt::ShiftModifier); if (m_currentInputMode->keyPress(e)) { return; } if (!doc()->isReadWrite()) { e->ignore(); return; } if ((key == Qt::Key_Return) || (key == Qt::Key_Enter) || (key == Qt::SHIFT + Qt::Key_Return) || (key == Qt::SHIFT + Qt::Key_Enter)) { doReturn(); e->accept(); return; } if (key == Qt::Key_Backspace || key == Qt::SHIFT + Qt::Key_Backspace) { //m_view->backspace(); e->accept(); return; } if (key == Qt::Key_Tab || key == Qt::SHIFT + Qt::Key_Backtab || key == Qt::Key_Backtab) { if (m_view->completionWidget()->isCompletionActive()) { e->accept(); m_view->completionWidget()->tab(key != Qt::Key_Tab); return; } if (key == Qt::Key_Tab) { uint tabHandling = doc()->config()->tabHandling(); // convert tabSmart into tabInsertsTab or tabIndents: if (tabHandling == KateDocumentConfig::tabSmart) { // multiple lines selected if (m_view->selection() && !m_view->selectionRange().onSingleLine()) { tabHandling = KateDocumentConfig::tabIndents; } // otherwise: take look at cursor position else { // if the cursor is at or before the first non-space character // or on an empty line, // Tab indents, otherwise it inserts a tab character. Kate::TextLine line = doc()->kateTextLine(primaryCursor().line()); int first = line->firstChar(); if (first < 0 || primaryCursor().column() <= first) { tabHandling = KateDocumentConfig::tabIndents; } else { tabHandling = KateDocumentConfig::tabInsertsTab; } } } if (tabHandling == KateDocumentConfig::tabInsertsTab) { doc()->typeChars(m_view, QStringLiteral("\t")); } else { Q_FOREACH ( const auto& cursor, m_view->allCursors() ) { doc()->indent(m_view->selection() ? m_view->selectionRange() : KTextEditor::Range(cursor.line(), 0, cursor.line(), 0), 1); } } e->accept(); return; } else if (doc()->config()->tabHandling() != KateDocumentConfig::tabInsertsTab) { // key == Qt::SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab Q_FOREACH ( const auto& cursor, m_view->allCursors() ) { doc()->indent(m_view->selection() ? m_view->selectionRange() : KTextEditor::Range(cursor.line(), 0, cursor.line(), 0), -1); } e->accept(); return; } } if (!(e->modifiers() & Qt::ControlModifier) && !e->text().isEmpty() && doc()->typeChars(m_view, e->text())) { e->accept(); return; } // allow composition of AltGr + (q|2|3) on windows static const int altGR = Qt::ControlModifier | Qt::AltModifier; if ((e->modifiers() & altGR) == altGR && !e->text().isEmpty() && doc()->typeChars(m_view, e->text())) { e->accept(); return; } e->ignore(); } void KateViewInternal::keyReleaseEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Alt && m_view->completionWidget()->isCompletionActive() && ((m_completionItemExpanded && (m_view->completionWidget()->hadNavigation() || m_altDownTime.elapsed() > 300)) || (!m_completionItemExpanded && !m_view->completionWidget()->hadNavigation()))) { m_view->completionWidget()->toggleExpanded(false, true); } if ((e->modifiers() & Qt::SHIFT) == Qt::SHIFT) { m_shiftKeyPressed = true; } else { if (m_shiftKeyPressed) { m_shiftKeyPressed = false; if (m_selChangedByUser) { if (m_view->selection()) { QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); } m_selChangedByUser = false; } } } e->ignore(); return; } void KateViewInternal::contextMenuEvent(QContextMenuEvent *e) { // try to show popup menu QPoint p = e->pos(); if (e->reason() == QContextMenuEvent::Keyboard) { makeVisible(m_displayCursor, 0); p = cursorCoordinates(false); p.rx() -= startX(); } else if (! m_view->selection() || m_view->config()->persistentSelection()) { placeCursor(e->pos()); } // popup is a qguardedptr now if (m_view->contextMenu()) { m_view->spellingMenu()->setUseMouseForMisspelledRange((e->reason() == QContextMenuEvent::Mouse)); m_view->contextMenu()->popup(mapToGlobal(p)); e->accept(); } } void KateViewInternal::mousePressEvent(QMouseEvent *e) { qDebug() << "called"; if ( e->button() == Qt::LeftButton ) { auto newCursor = pointToCursor(e->pos()); if (e->modifiers() & Qt::ShiftModifier) { auto flags = (KateMultiSelection::SelectionFlags) (KateMultiSelection::UsePrimaryCursor | KateMultiSelection::KeepSelectionRange); selections()->beginNewSelection(primaryCursor(), KateMultiSelection::Mouse, flags); selections()->updateNewSelection(newCursor); } else { KateMultiSelection::SelectionMode selectionMode = KateMultiSelection::Mouse; KateMultiSelection::SelectionFlags flags = KateMultiSelection::UsePrimaryCursor; if ( m_possibleTripleClick ) { m_possibleTripleClick = false; selectionMode = KateMultiSelection::Line; } if ( e->modifiers() == (Qt::ControlModifier | Qt::MetaModifier) ) { flags = KateMultiSelection::AddNewCursor; } else { view()->cursors()->clearSecondaryCursors(); } selections()->beginNewSelection(newCursor, selectionMode, flags); } updateCursorFlashTimer(); e->accept(); } else { e->ignore(); } #warning fixme: copy selection to selection clipboard #warning fixme: software keyboard (?!) #warning fixme: drag and drop /* // request the software keyboard, if any if (e->button() == Qt::LeftButton && qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) { QEvent event(QEvent::RequestSoftwareInputPanel); QApplication::sendEvent(this, &event); } } if (e->modifiers() & Qt::ShiftModifier) { if (!m_selectAnchor.isValid()) { m_selectAnchor = primaryCursor(); } } else { m_selectionCached = KTextEditor::Range::invalid(); } if (!(e->modifiers() & Qt::ShiftModifier) && isTargetSelected(e->pos())) { m_dragInfo.state = diPending; m_dragInfo.start = e->pos(); } else { m_dragInfo.state = diNone; if (e->modifiers() & Qt::ShiftModifier) { placeCursor(e->pos(), true, false); if (m_selectionCached.start().isValid()) { if (primaryCursor() < m_selectionCached.start()) { m_selectAnchor = m_selectionCached.end(); } else { m_selectAnchor = m_selectionCached.start(); } } setSelection(KTextEditor::Range(m_selectAnchor, primaryCursor())); } else { placeCursor(e->pos()); } m_scrollX = 0; m_scrollY = 0; m_scrollTimer.start(50); } e->accept(); break; default: e->ignore(); break; }*/ } void KateViewInternal::mouseDoubleClickEvent(QMouseEvent *e) { auto secondary = (e->modifiers() == (Qt::MetaModifier | Qt::ControlModifier)); auto newCursor = pointToCursor(e->pos()); switch (e->button()) { case Qt::LeftButton: selections()->beginNewSelection(newCursor, KateMultiSelection::Word, secondary ? KateMultiSelection::AddNewCursor : KateMultiSelection::UsePrimaryCursor); #warning fixme: this weird "shift double click" feature #warning fixme: select to matching bracket on dclick #if 0 if (e->modifiers() & Qt::ShiftModifier) { // Now select the word under the select anchor int cs, ce; Kate::TextLine l = doc()->kateTextLine(m_selectAnchor.line()); ce = m_selectAnchor.column(); if (ce > 0 && doc()->highlight()->isInWord(l->at(ce))) { for (; ce < l->length(); ce++) if (!doc()->highlight()->isInWord(l->at(ce))) { break; } } cs = m_selectAnchor.column() - 1; if (cs < doc()->lineLength(m_selectAnchor.line()) && doc()->highlight()->isInWord(l->at(cs))) { for (cs--; cs >= 0; cs--) if (!doc()->highlight()->isInWord(l->at(cs))) { break; } } // ...and keep it selected if (cs + 1 < ce) { m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), cs + 1)); m_selectionCached.setEnd(KTextEditor::Cursor(m_selectAnchor.line(), ce)); } else { m_selectionCached.setStart(m_selectAnchor); m_selectionCached.setEnd(m_selectAnchor); } // Now word select to the mouse cursor placeCursor(e->pos(), true); } else { // first clear the selection, otherwise we run into bug #106402 // ...and set the cursor position, for the same reason (otherwise there // are *other* idiosyncrasies we can't fix without reintroducing said // bug) // Parameters: don't redraw, and don't emit selectionChanged signal yet m_view->clearSelection(false, false); placeCursor(e->pos()); m_view->selectWord(primaryCursor()); cursorToMatchingBracket(true); if (m_view->selection()) { m_selectAnchor = m_view->selectionRange().start(); m_selectionCached = m_view->selectionRange(); } else { m_selectAnchor = primaryCursor(); m_selectionCached = KTextEditor::Range(primaryCursor(), primaryCursor()); } } #endif if (m_view->selection()) { #if !defined(Q_OS_OSX) QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); #endif } m_possibleTripleClick = true; QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout())); m_scrollX = 0; m_scrollY = 0; m_scrollTimer.start(50); e->accept(); break; default: e->ignore(); break; } } void KateViewInternal::tripleClickTimeout() { m_possibleTripleClick = false; } void KateViewInternal::mouseReleaseEvent(QMouseEvent *e) { switch (e->button()) { case Qt::LeftButton: if ( selections()->currentlySelecting() ) { selections()->finishNewSelection(); updateCursorFlashTimer(); } m_dragInfo.state = diNone; e->accept(); break; #warning fixme drag and drop // m_selectionMode = Default; // m_selectionCached.start().setLine( -1 ); // if (m_selChangedByUser) { // if (m_view->selection()) { // QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); // } // moveCursorToSelectionEdge(); // // m_selChangedByUser = false; // } // // if (m_dragInfo.state == diPending) { // placeCursor(e->pos(), e->modifiers() & Qt::ShiftModifier); // } else if (m_dragInfo.state == diNone) { // m_scrollTimer.stop(); // } case Qt::MidButton: placeCursor(e->pos()); if (doc()->isReadWrite()) { view()->m_clipboard.pasteFromClipboard(QClipboard::Selection); } e->accept(); break; default: e->ignore(); break; } } void KateViewInternal::leaveEvent(QEvent *) { m_textHintTimer.stop(); // fix bug 194452, scrolling keeps going if you scroll via mouse drag and press and other mouse // button outside the view area if (m_dragInfo.state == diNone) { m_scrollTimer.stop(); } } KTextEditor::Cursor KateViewInternal::coordinatesToCursor(const QPoint &_coord, bool includeBorder) const { QPoint coord(_coord); KTextEditor::Cursor ret = KTextEditor::Cursor::invalid(); if (includeBorder) { coord.rx() -= m_leftBorder->width(); } coord.rx() += startX(); const KateTextLayout &thisLine = yToKateTextLayout(coord.y()); if (thisLine.isValid()) { ret = renderer()->xToCursor(thisLine, coord.x(), !m_view->wrapCursor()); } - if (ret.column() == view()->document()->lineLength(ret.line())) { + if (ret.column() > view()->document()->lineLength(ret.line())) { // The cursor is beyond the end of the line; in that case the renderer // gives the index of the character behind the last one. return KTextEditor::Cursor::invalid(); } return ret; } void KateViewInternal::mouseMoveEvent(QMouseEvent *e) { KTextEditor::Cursor newPosition = coordinatesToCursor(e->pos(), false); if (newPosition != m_mouse) { m_mouse = newPosition; mouseMoved(); } if (e->buttons() & Qt::LeftButton) { if (m_dragInfo.state == diPending) { // we had a mouse down, but haven't confirmed a drag yet // if the mouse has moved sufficiently, we will confirm QPoint p(e->pos() - m_dragInfo.start); // we've left the drag square, we can start a real drag operation now if (p.manhattanLength() > QApplication::startDragDistance()) { doDrag(); } return; } else if (m_dragInfo.state == diDragging) { // Don't do anything after a canceled drag until the user lets go of // the mouse button! return; } m_mouseX = e->x(); m_mouseY = e->y(); m_scrollX = 0; m_scrollY = 0; int d = renderer()->lineHeight(); if (m_mouseX < 0) { m_scrollX = -d; } if (m_mouseX > width()) { m_scrollX = d; } if (m_mouseY < 0) { m_mouseY = 0; m_scrollY = -d; } if (m_mouseY > height()) { m_mouseY = height(); m_scrollY = d; } auto c = pointToCursor(QPoint(m_mouseX, m_mouseY)); selections()->updateNewSelection(c); updateCursorFlashTimer(); } else { if (isTargetSelected(e->pos())) { // mouse is over selected text. indicate that the text is draggable by setting // the arrow cursor as other Qt text editing widgets do if (m_mouseCursor != Qt::ArrowCursor) { m_mouseCursor = Qt::ArrowCursor; setCursor(m_mouseCursor); } } else { // normal text cursor if (m_mouseCursor != Qt::IBeamCursor) { m_mouseCursor = Qt::IBeamCursor; setCursor(m_mouseCursor); } } //We need to check whether the mouse position is actually within the widget, //because other widgets like the icon border forward their events to this, //and we will create invalid text hint requests if we don't check if (textHintsEnabled() && geometry().contains(parentWidget()->mapFromGlobal(e->globalPos()))) { if (QToolTip::isVisible()) { QToolTip::hideText(); } m_textHintTimer.start(m_textHintDelay); m_textHintPos = e->pos(); } } } void KateViewInternal::updateDirty() { const int h = renderer()->lineHeight(); int currentRectStart = -1; int currentRectEnd = -1; QRegion updateRegion; { for (int i = 0; i < cache()->viewCacheLineCount(); ++i) { if (cache()->viewLine(i).isDirty()) { if (currentRectStart == -1) { currentRectStart = h * i; currentRectEnd = h; } else { currentRectEnd += h; } } else if (currentRectStart != -1) { updateRegion += QRect(0, currentRectStart, width(), currentRectEnd); currentRectStart = -1; currentRectEnd = -1; } } } if (currentRectStart != -1) { updateRegion += QRect(0, currentRectStart, width(), currentRectEnd); } if (!updateRegion.isEmpty()) { if (debugPainting) { qCDebug(LOG_KTE) << "Update dirty region " << updateRegion; } update(updateRegion); } } void KateViewInternal::hideEvent(QHideEvent *e) { Q_UNUSED(e); if (m_view->isCompletionActive()) { m_view->completionWidget()->abortCompletion(); } } void KateViewInternal::paintEvent(QPaintEvent *e) { if (debugPainting) { qCDebug(LOG_KTE) << "GOT PAINT EVENT: Region" << e->region(); } const QRect &unionRect = e->rect(); int xStart = startX() + unionRect.x(); int xEnd = xStart + unionRect.width(); uint h = renderer()->lineHeight(); uint startz = (unionRect.y() / h); uint endz = startz + 1 + (unionRect.height() / h); uint lineRangesSize = cache()->viewCacheLineCount(); QPainter paint(this); paint.setRenderHints(QPainter::Antialiasing); paint.save(); renderer()->setCaretStyle(m_currentInputMode->caretStyle()); renderer()->setShowTabs(doc()->config()->showTabs()); renderer()->setShowTrailingSpaces(doc()->config()->showSpaces()); int sy = startz * h; paint.translate(unionRect.x(), startz * h); for (uint z = startz; z <= endz; z++) { paint.save(); if ((z >= lineRangesSize) || (cache()->viewLine(z).line() == -1)) { if (!(z >= lineRangesSize)) { cache()->viewLine(z).setDirty(false); } paint.fillRect(0, 0, unionRect.width(), h, renderer()->config()->backgroundColor()); } else { //qCDebug(LOG_KTE)<<"KateViewInternal::paintEvent(QPaintEvent *e):cache()->viewLine"<viewLine(z); /* If viewLine() returns non-zero, then a document line was split in several visual lines, and we're trying to paint visual line that is not the first. In that case, this line was already painted previously, since KateRenderer::paintTextLine paints all visual lines. Except if we're at the start of the region that needs to be painted -- when no previous calls to paintTextLine were made. */ if (!thisLine.viewLine() || z == startz) { //qDebug() << "paint text: line: " << thisLine.line() << " viewLine " << thisLine.viewLine() << " x: " << unionRect.x() << " y: " << unionRect.y() << " width: " << xEnd-xStart << " height: " << h << endl; KTextEditor::Cursor pos = primaryCursor(); // first: paint our line paint.translate(QPoint(0, h * - thisLine.viewLine())); paint.setClipRect(QRect(0, 0, unionRect.width(), h * thisLine.kateLineLayout()->viewLineCount())); renderer()->paintTextLine(paint, thisLine.kateLineLayout(), xStart, xEnd, &pos); paint.translate(0, h * thisLine.viewLine()); // second: paint previous line elements, that span into our line like _, bug 335079 if (z > 0) { KateTextLayout &previousLine = cache()->viewLine(z-1); paint.translate(QPoint(0, h * - (previousLine.viewLine() + 1))); renderer()->paintTextLine(paint, previousLine.kateLineLayout(), xStart, xEnd, &pos); paint.translate(0, h * (previousLine.viewLine() + 1)); } /** * line painted, reset and state + mark line as non-dirty */ thisLine.setDirty(false); } } paint.restore(); paint.translate(0, h); sy += h; } paint.restore(); if (m_textAnimation) { m_textAnimation->draw(paint); } } void KateViewInternal::resizeEvent(QResizeEvent *e) { bool expandedHorizontally = width() > e->oldSize().width(); bool expandedVertically = height() > e->oldSize().height(); bool heightChanged = height() != e->oldSize().height(); m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); m_madeVisible = false; if (heightChanged) { setAutoCenterLines(m_autoCenterLines, false); m_cachedMaxStartPos.setPosition(-1, -1); } if (m_view->dynWordWrap()) { bool dirtied = false; for (int i = 0; i < cache()->viewCacheLineCount(); i++) { // find the first dirty line // the word wrap updateView algorithm is forced to check all lines after a dirty one KateTextLayout viewLine = cache()->viewLine(i); if (viewLine.wrap() || viewLine.isRightToLeft() || viewLine.width() > width()) { dirtied = true; viewLine.setDirty(); break; } } if (dirtied || heightChanged) { updateView(true); m_leftBorder->update(); } } else { updateView(); if (expandedHorizontally && startX() > 0) { scrollColumns(startX() - (width() - e->oldSize().width())); } } if (width() < e->oldSize().width() && !m_view->wrapCursor()) { // May have to restrain cursor to new smaller width... if (primaryCursor().column() > doc()->lineLength(primaryCursor().line())) { KateTextLayout thisLine = m_layoutCache->viewLine(primaryCursor().line()); KTextEditor::Cursor newCursor(primaryCursor().line(), thisLine.endCol() + ((width() - thisLine.xOffset() - (thisLine.width() - m_startX)) / renderer()->spaceWidth()) - 1); if (newCursor.column() < primaryCursor().column()) { cursors()->setPrimaryCursor(newCursor); } } } if (expandedVertically) { KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max); return; // already fired displayRangeChanged } } emit m_view->displayRangeChanged(m_view); } void KateViewInternal::scrollTimeout() { if (m_scrollX || m_scrollY) { scrollLines(startPos().line() + (m_scrollY / (int) renderer()->lineHeight())); placeCursor(QPoint(m_mouseX, m_mouseY), true); } } void KateViewInternal::cursorTimeout() { if (!debugPainting && m_currentInputMode->blinkCaret()) { renderer()->setDrawCaret(!renderer()->drawCaret()); paintCursor(); } } void KateViewInternal::textHintTimeout() { m_textHintTimer.stop(); KTextEditor::Cursor c = coordinatesToCursor(m_textHintPos, false); if (!c.isValid()) { return; } QStringList textHints; foreach(KTextEditor::TextHintProvider * const p, m_textHintProviders) { const QString hint = p->textHint(m_view, c); if (!hint.isEmpty()) { textHints.append(hint); } } if (!textHints.isEmpty()) { qCDebug(LOG_KTE) << "Hint text: " << textHints; QString hint; foreach(const QString & str, textHints) { hint += QStringLiteral("

%1

").arg(str); } QPoint pos(startX() + m_textHintPos.x(), m_textHintPos.y()); QToolTip::showText(mapToGlobal(pos), hint); } } void KateViewInternal::focusInEvent(QFocusEvent *) { if (QApplication::cursorFlashTime() > 0) { m_cursorTimer.start(QApplication::cursorFlashTime() / 2); } paintCursor(); doc()->setActiveView(m_view); // this will handle focus stuff in kateview m_view->slotGotFocus(); } void KateViewInternal::focusOutEvent(QFocusEvent *) { //if (m_view->isCompletionActive()) //m_view->abortCompletion(); m_cursorTimer.stop(); m_view->renderer()->setDrawCaret(true); paintCursor(); m_textHintTimer.stop(); m_view->slotLostFocus(); } void KateViewInternal::doDrag() { m_dragInfo.state = diDragging; m_dragInfo.dragObject = new QDrag(this); QMimeData *mimeData = new QMimeData(); mimeData->setText(m_view->selectionText()); m_dragInfo.dragObject->setMimeData(mimeData); m_dragInfo.dragObject->start(Qt::MoveAction); } void KateViewInternal::dragEnterEvent(QDragEnterEvent *event) { if (event->source() == this) { event->setDropAction(Qt::MoveAction); } event->setAccepted((event->mimeData()->hasText() && doc()->isReadWrite()) || event->mimeData()->hasUrls()); } void KateViewInternal::fixDropEvent(QDropEvent *event) { if (event->source() != this) { event->setDropAction(Qt::CopyAction); } else { Qt::DropAction action = Qt::MoveAction; #ifdef Q_WS_MAC if (event->keyboardModifiers() & Qt::AltModifier) { action = Qt::CopyAction; } #else if (event->keyboardModifiers() & Qt::ControlModifier) { action = Qt::CopyAction; } #endif event->setDropAction(action); } } void KateViewInternal::dragMoveEvent(QDragMoveEvent *event) { // track the cursor to the current drop location placeCursor(event->pos(), true, false); // important: accept action to switch between copy and move mode // without this, the text will always be copied. fixDropEvent(event); } void KateViewInternal::dropEvent(QDropEvent *event) { /** * if we have urls, pass this event off to the hosting application */ if (event->mimeData()->hasUrls()) { emit dropEventPass(event); return; } if (event->mimeData()->hasText() && doc()->isReadWrite()) { const QString text = event->mimeData()->text(); // is the source our own document? bool priv = false; if (KateViewInternal *vi = qobject_cast(event->source())) { priv = doc()->ownedView(vi->m_view); } // dropped on a text selection area? bool selected = m_view->cursorSelected(primaryCursor()); fixDropEvent(event); if (priv && selected && event->dropAction() != Qt::CopyAction) { // this is a drag that we started and dropped on our selection // ignore this case return; } // fix the cursor position before editStart(), so that it is correctly // stored for the undo action KTextEditor::Cursor targetCursor(primaryCursor()); // backup current cursor int selectionWidth = m_view->selectionRange().columnWidth(); // for block selection int selectionHeight = m_view->selectionRange().numberOfLines(); // for block selection if (event->dropAction() != Qt::CopyAction) { editSetCursor(m_view->selectionRange().end()); } else { m_view->clearSelection(); } // use one transaction doc()->editStart(); // on move: remove selected text; on copy: duplicate text doc()->insertText(targetCursor, text, m_view->blockSelection()); KTextEditor::DocumentCursor startCursor(doc(), targetCursor); + KTextEditor::DocumentCursor endCursor1(doc(), targetCursor); + const int textLength = text.length(); if (event->dropAction() != Qt::CopyAction) { m_view->removeSelectedText(); + if (m_cursors.primaryCursor() < startCursor.toCursor()) { + startCursor.move(-textLength); + endCursor1.move(-textLength); + } } - KTextEditor::DocumentCursor endCursor1(doc(), startCursor); - if (!m_view->blockSelection()) { - endCursor1.move(text.length()); + endCursor1.move(textLength); } else { endCursor1.setColumn(startCursor.column() + selectionWidth); endCursor1.setLine(startCursor.line() + selectionHeight); } KTextEditor::Cursor endCursor(endCursor1); - qCDebug(LOG_KTE) << startCursor << "---(" << text.length() << ")---" << endCursor; + qCDebug(LOG_KTE) << startCursor << "---(" << textLength << ")---" << endCursor; setSelection(KTextEditor::Range(startCursor, endCursor)); editSetCursor(endCursor); doc()->editEnd(); event->acceptProposedAction(); updateView(); } // finally finish drag and drop mode m_dragInfo.state = diNone; // important, because the eventFilter`s DragLeave does not occur stopDragScroll(); } //END EVENT HANDLING STUFF void KateViewInternal::clear() { m_startPos.setPosition(0, 0); m_displayCursor = KTextEditor::Cursor(0, 0); primaryCursor().setPosition(0, 0); cache()->clear(); updateView(true); } void KateViewInternal::wheelEvent(QWheelEvent *e) { // ctrl pressed -> change font size (only if angle is reported) if (e->modifiers() == Qt::ControlModifier) { if (e->angleDelta().y() > 0) { slotIncFontSizes(); } else if (e->angleDelta().y() < 0) { slotDecFontSizes(); } // accept always and be done for zooming e->accept(); return; } // handle vertical scrolling via the scrollbar if (e->orientation() == Qt::Vertical) { QWheelEvent copy = *e; QApplication::sendEvent(m_lineScroll, ©); if (copy.isAccepted()) { e->accept(); } } // handle horizontal scrolling via the scrollbar if (e->orientation() == Qt::Horizontal) { // if we have dyn word wrap, we should ignore the scroll events if (m_view->dynWordWrap()) { e->accept(); return; } QWheelEvent copy = *e; QApplication::sendEvent(m_columnScroll, ©); if (copy.isAccepted()) { e->accept(); } } } void KateViewInternal::startDragScroll() { if (!m_dragScrollTimer.isActive()) { m_dragScrollTimer.start(s_scrollTime); } } void KateViewInternal::stopDragScroll() { m_dragScrollTimer.stop(); updateView(); } void KateViewInternal::doDragScroll() { QPoint p = this->mapFromGlobal(QCursor::pos()); int dx = 0, dy = 0; if (p.y() < s_scrollMargin) { dy = p.y() - s_scrollMargin; } else if (p.y() > height() - s_scrollMargin) { dy = s_scrollMargin - (height() - p.y()); } if (p.x() < s_scrollMargin) { dx = p.x() - s_scrollMargin; } else if (p.x() > width() - s_scrollMargin) { dx = s_scrollMargin - (width() - p.x()); } dy /= 4; if (dy) { scrollLines(startPos().line() + dy); } if (columnScrollingPossible() && dx) { scrollColumns(qMin(m_startX + dx, m_columnScroll->maximum())); } if (!dy && !dx) { stopDragScroll(); } } void KateViewInternal::registerTextHintProvider(KTextEditor::TextHintProvider *provider) { if (! m_textHintProviders.contains(provider)) { m_textHintProviders.append(provider); } // we have a client, so start timeout m_textHintTimer.start(m_textHintDelay); } void KateViewInternal::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) { const int index = m_textHintProviders.indexOf(provider); if (index >= 0) { m_textHintProviders.removeAt(index); } if (m_textHintProviders.isEmpty()) { m_textHintTimer.stop(); } } void KateViewInternal::setTextHintDelay(int delay) { if (delay <= 0) { m_textHintDelay = 200; // ms } else { m_textHintDelay = delay; // ms } } int KateViewInternal::textHintDelay() const { return m_textHintDelay; } bool KateViewInternal::textHintsEnabled() { return ! m_textHintProviders.isEmpty(); } //BEGIN EDIT STUFF void KateViewInternal::editStart() { editSessionNumber++; if (editSessionNumber > 1) { return; } editIsRunning = true; editOldCursor = primaryCursor(); editOldSelection = m_view->selectionRange(); } void KateViewInternal::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { if (editSessionNumber == 0) { return; } editSessionNumber--; if (editSessionNumber > 0) { return; } // fix start position, might have moved from column 0 // try to clever calculate the right start column for the tricky dyn word wrap case int col = 0; if (m_view->dynWordWrap()) { if (KateLineLayoutPtr layout = cache()->line(m_startPos.line())) { int index = layout->viewLineForColumn(m_startPos.column()); if (index >= 0 && index < layout->viewLineCount()) { col = layout->viewLine(index).startCol(); } } } m_startPos.setPosition(m_startPos.line(), col); if (tagFrom && (editTagLineStart <= int(m_view->textFolding().visibleLineToLine(startLine())))) { tagAll(); } else { tagLines(editTagLineStart, tagFrom ? qMax(doc()->lastLine() + 1, editTagLineEnd) : editTagLineEnd, true); } if (editOldCursor == primaryCursor()) { updateBracketMarks(); } updateView(true); if (editOldCursor != primaryCursor() || m_view == doc()->activeView()) { // Only scroll the view to the cursor if the insertion happens at the cursor. // This might not be the case for e.g. collaborative editing, when a remote user // inserts text at a position not at the caret. if (primaryCursor().line() >= editTagLineStart && primaryCursor().line() <= editTagLineEnd) { m_madeVisible = false; notifyPrimaryCursorChanged(primaryCursor(), true); } } /** * selection changed? * fixes bug 316226 */ if (editOldSelection != m_view->selectionRange() || (editOldSelection.isValid() && !editOldSelection.isEmpty() && !(editTagLineStart > editOldSelection.end().line() && editTagLineEnd < editOldSelection.start().line()))) { emit m_view->selectionChanged(m_view); } editIsRunning = false; } void KateViewInternal::editSetCursor(const KTextEditor::Cursor &_cursor) { if (primaryCursor() != _cursor) { cursors()->setPrimaryCursor(_cursor, false); } } //END void KateViewInternal::viewSelectionChanged() { #warning TODO this is not what we want to do selections()->finishNewSelection(); /* if (!m_view->selection()) { m_selectAnchor = KTextEditor::Cursor::invalid(); } else { m_selectAnchor = m_view->selectionRange().start(); } // Do NOT nuke the entire range! The reason is that a shift+DC selection // might (correctly) set the range to be empty (i.e. start() == end()), and // subsequent dragging might shrink the selection into non-existence. When // this happens, we use the cached end to restore the cached start so that // updateSelection is not confused. See also comments in updateSelection. m_selectionCached.setStart(KTextEditor::Cursor::invalid());*/ } KateLayoutCache *KateViewInternal::cache() const { return m_layoutCache; } void KateViewInternal::notifyLinesUpdated(const QVector& changed) { Q_FOREACH ( const auto& cursor, changed ) { tagLine(toVirtualCursor(cursor)); } updateCursorFlashTimer(); updateDirty(); } KTextEditor::Cursor KateViewInternal::toRealCursor(const KTextEditor::Cursor &virtualCursor) const { return KTextEditor::Cursor(m_view->textFolding().visibleLineToLine(virtualCursor.line()), virtualCursor.column()); } KTextEditor::Cursor KateViewInternal::toVirtualCursor(const KTextEditor::Cursor &realCursor) const { /** * only convert valid lines, folding doesn't like invalid input! * don't validate whole cursor, column might be -1 */ if (realCursor.line() < 0) { return KTextEditor::Cursor::invalid(); } return KTextEditor::Cursor(m_view->textFolding().lineToVisibleLine(realCursor.line()), realCursor.column()); } KateRenderer *KateViewInternal::renderer() const { return m_view->renderer(); } void KateViewInternal::mouseMoved() { m_view->notifyMousePositionChanged(m_mouse); m_view->updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); } void KateViewInternal::cursorMoved() { m_view->updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); #ifndef QT_NO_ACCESSIBILITY QAccessibleTextCursorEvent ev(this, KateViewAccessible::positionFromCursor(this, primaryCursor())); QAccessible::updateAccessibility(&ev); #endif } bool KateViewInternal::rangeAffectsView(const KTextEditor::Range &range, bool realCursors) const { int startLine = m_startPos.line(); int endLine = startLine + (int)m_visibleLineCount; if (realCursors) { startLine = (int)m_view->textFolding().visibleLineToLine(startLine); endLine = (int)m_view->textFolding().visibleLineToLine(endLine); } return (range.end().line() >= startLine) || (range.start().line() <= endLine); } //BEGIN IM INPUT STUFF QVariant KateViewInternal::inputMethodQuery(Qt::InputMethodQuery query) const { switch (query) { case Qt::ImCursorRectangle: { // Cursor placement code is changed for Asian input method that // shows candidate window. This behavior is same as Qt/E 2.3.7 // which supports Asian input methods. Asian input methods need // start point of IM selection text to place candidate window as // adjacent to the selection text. // // in Qt5, cursor rectangle is used as QRectF internally, and it // will be checked by QRectF::isValid(), which will mark rectangle // with width == 0 or height == 0 as invalid. auto lineHeight = renderer()->lineHeight(); return QRect(cursorToCoordinate(primaryCursor(), true, false), QSize(1, lineHeight ? lineHeight : 1)); } case Qt::ImFont: return renderer()->currentFont(); case Qt::ImCursorPosition: return primaryCursor().column(); case Qt::ImAnchorPosition: // If selectAnchor is at the same line, return the real anchor position // Otherwise return the same position of cursor if (m_view->selection() && m_selectAnchor.line() == primaryCursor().line()) { return m_selectAnchor.column(); } else { return primaryCursor().column(); } case Qt::ImSurroundingText: if (Kate::TextLine l = doc()->kateTextLine(primaryCursor().line())) { return l->string(); } else { return QString(); } case Qt::ImCurrentSelection: if (m_view->selection()) { return m_view->selectionText(); } else { return QString(); } default: /* values: ImMaximumTextLength */ break; } return QWidget::inputMethodQuery(query); } void KateViewInternal::inputMethodEvent(QInputMethodEvent *e) { if (doc()->readOnly()) { e->ignore(); return; } //qCDebug(LOG_KTE) << "Event: cursor" << primaryCursor() << "commit" << e->commitString() << "preedit" << e->preeditString() << "replacement start" << e->replacementStart() << "length" << e->replacementLength(); if (!m_imPreeditRange) { m_imPreeditRange = doc()->newMovingRange(KTextEditor::Range(primaryCursor(), primaryCursor()), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); } if (!m_imPreeditRange->toRange().isEmpty()) { doc()->inputMethodStart(); doc()->removeText(*m_imPreeditRange); doc()->inputMethodEnd(); } if (!e->commitString().isEmpty() || e->replacementLength()) { m_view->removeSelectedText(); KTextEditor::Range preeditRange = *m_imPreeditRange; KTextEditor::Cursor start(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + e->replacementStart()); KTextEditor::Cursor removeEnd = start + KTextEditor::Cursor(0, e->replacementLength()); doc()->editStart(); if (start != removeEnd) { doc()->removeText(KTextEditor::Range(start, removeEnd)); } if (!e->commitString().isEmpty()) { // if the input method event is text that should be inserted, call KTextEditor::DocumentPrivate::typeChars() // with the text. that method will handle the input and take care of overwrite mode, etc. doc()->typeChars(m_view, e->commitString()); } doc()->editEnd(); // Revert to the same range as above m_imPreeditRange->setRange(preeditRange); } if (!e->preeditString().isEmpty()) { doc()->inputMethodStart(); doc()->insertText(m_imPreeditRange->start(), e->preeditString()); doc()->inputMethodEnd(); // The preedit range gets automatically repositioned } // Finished this input method context? if (m_imPreeditRange && e->preeditString().isEmpty()) { // delete the range and reset the pointer delete m_imPreeditRange; - m_imPreeditRange = 0L; + m_imPreeditRange = nullptr; qDeleteAll(m_imPreeditRangeChildren); m_imPreeditRangeChildren.clear(); if (QApplication::cursorFlashTime() > 0) { renderer()->setDrawCaret(false); } renderer()->setCaretOverrideColor(QColor()); e->accept(); return; } KTextEditor::Cursor newCursor = primaryCursor(); bool hideCursor = false; QColor caretColor; if (m_imPreeditRange) { qDeleteAll(m_imPreeditRangeChildren); m_imPreeditRangeChildren.clear(); int decorationColumn = 0; foreach (const QInputMethodEvent::Attribute &a, e->attributes()) { if (a.type == QInputMethodEvent::Cursor) { newCursor = m_imPreeditRange->start() + KTextEditor::Cursor(0, a.start); hideCursor = !a.length; QColor c = qvariant_cast(a.value); if (c.isValid()) { caretColor = c; } } else if (a.type == QInputMethodEvent::TextFormat) { QTextCharFormat f = qvariant_cast(a.value).toCharFormat(); if (f.isValid() && decorationColumn <= a.start) { KTextEditor::Range fr(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + a.start, m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + a.start + a.length); KTextEditor::MovingRange *formatRange = doc()->newMovingRange(fr); KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); attribute->merge(f); formatRange->setAttribute(attribute); decorationColumn = a.start + a.length; m_imPreeditRangeChildren.push_back(formatRange); } } } } renderer()->setDrawCaret(hideCursor); renderer()->setCaretOverrideColor(caretColor); if (newCursor != primaryCursor()) { cursors()->setPrimaryCursor(newCursor); } e->accept(); } //END IM INPUT STUFF void KateViewInternal::flashChar(const KTextEditor::Cursor &pos, KTextEditor::Attribute::Ptr attribute) { Q_ASSERT(pos.isValid()); Q_ASSERT(attribute.constData()); // if line is folded away, do nothing if (!m_view->textFolding().isLineVisible(pos.line())) { return; } KTextEditor::Range range(pos, KTextEditor::Cursor(pos.line(), pos.column() + 1)); if (m_textAnimation) { m_textAnimation->deleteLater(); } m_textAnimation = new KateTextAnimation(range, attribute, this); } void KateViewInternal::documentTextInserted(KTextEditor::Document *document, const KTextEditor::Range &range) { if (QAccessible::isActive()) { QAccessibleTextInsertEvent ev(this, KateViewAccessible::positionFromCursor(this, range.start()), document->text(range)); QAccessible::updateAccessibility(&ev); } } void KateViewInternal::documentTextRemoved(KTextEditor::Document * /*document*/, const KTextEditor::Range &range, const QString &oldText) { if (QAccessible::isActive()) { QAccessibleTextRemoveEvent ev(this, KateViewAccessible::positionFromCursor(this, range.start()), oldText); QAccessible::updateAccessibility(&ev); } } diff --git a/src/vimode/appcommands.cpp b/src/vimode/appcommands.cpp index 571a64a8..44aac3f9 100644 --- a/src/vimode/appcommands.cpp +++ b/src/vimode/appcommands.cpp @@ -1,503 +1,505 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Erlend Hamberg Copyright (C) 2011 Svyatoslav Kuzmich 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 #include #include #include #include #include #include #include #include using namespace KateVi; //BEGIN AppCommands -AppCommands *AppCommands::m_instance = 0; +AppCommands *AppCommands::m_instance = nullptr; AppCommands::AppCommands() : KTextEditor::Command(QStringList() << QStringLiteral("q") << QStringLiteral("qa") << QStringLiteral("qall") << QStringLiteral("q!") << QStringLiteral("qa!") << QStringLiteral("qall!") << QStringLiteral("w") << QStringLiteral("wq") << QStringLiteral("wa") << QStringLiteral("wqa") << QStringLiteral("x") << QStringLiteral("xa") << QStringLiteral("new") << QStringLiteral("vnew") << QStringLiteral("e") << QStringLiteral("edit") << QStringLiteral("enew") << QStringLiteral("sp") << QStringLiteral("split") << QStringLiteral("vs") << QStringLiteral("vsplit") << QStringLiteral("only") << QStringLiteral("tabe") << QStringLiteral("tabedit") << QStringLiteral("tabnew") << QStringLiteral("bd") << QStringLiteral("bdelete") << QStringLiteral("tabc") << QStringLiteral("tabclose") << QStringLiteral("clo") << QStringLiteral("close")) { re_write.setPattern(QStringLiteral("w(a)?")); re_close.setPattern(QStringLiteral("bd(elete)?|tabc(lose)?")); re_quit.setPattern(QStringLiteral("(w)?q(a|all)?(!)?")); re_exit.setPattern(QStringLiteral("x(a)?")); re_edit.setPattern(QStringLiteral("e(dit)?|tabe(dit)?|tabnew")); re_new.setPattern(QStringLiteral("(v)?new")); re_split.setPattern(QStringLiteral("sp(lit)?")); re_vsplit.setPattern(QStringLiteral("vs(plit)?")); re_vclose.setPattern(QStringLiteral("clo(se)?")); re_only.setPattern(QStringLiteral("on(ly)?")); } AppCommands::~AppCommands() { - m_instance = 0; + m_instance = nullptr; } bool AppCommands::exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &) { QStringList args(cmd.split(QRegExp(QLatin1String("\\s+")), QString::SkipEmptyParts)) ; QString command(args.takeFirst()); KTextEditor::MainWindow *mainWin = view->mainWindow(); KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); if (re_write.exactMatch(command)) { //TODO: handle writing to specific file if (!re_write.cap(1).isEmpty()) { // [a]ll Q_FOREACH(KTextEditor::Document *doc, app->documents()) { doc->save(); } msg = i18n("All documents written to disk"); } else { view->document()->documentSave(); msg = i18n("Document written to disk"); } } // Other buffer commands are implemented by the KateFileTree plugin else if (re_close.exactMatch(command)) { app->closeDocument(view->document()); } else if (re_quit.exactMatch(command)) { const bool save = !re_quit.cap(1).isEmpty(); // :[w]q const bool allDocuments = !re_quit.cap(2).isEmpty(); // :q[all] const bool doNotPromptForSave = !re_quit.cap(3).isEmpty(); // :q[!] if (allDocuments) { if (save) { Q_FOREACH(KTextEditor::Document *doc, app->documents()) { doc->save(); } } if (doNotPromptForSave) { Q_FOREACH(KTextEditor::Document *doc, app->documents()) { if (doc->isModified()) { doc->setModified(false); } } } QTimer::singleShot(0, this, SLOT(quit())); } else { if (save && view->document()->isModified()) { view->document()->documentSave(); } if (doNotPromptForSave) { view->document()->setModified(false); } if (mainWin->views().size() > 1) { QTimer::singleShot(0, this, SLOT(closeCurrentView())); } else { if (app->documents().size() > 1) { QTimer::singleShot(0, this, SLOT(closeCurrentDocument())); } else { QTimer::singleShot(0, this, SLOT(quit())); } } } } else if (re_exit.exactMatch(command)) { if (!re_exit.cap(1).isEmpty()) { // a[ll] Q_FOREACH(KTextEditor::Document *doc, app->documents()) { doc->save(); } QTimer::singleShot(0, this, SLOT(quit())); } else { if (view->document()->isModified()) { view->document()->documentSave(); } if (app->documents().size() > 1) { QTimer::singleShot(0, this, SLOT(closeCurrentDocument())); } else { QTimer::singleShot(0, this, SLOT(quit())); } } } else if (re_edit.exactMatch(command)) { QString argument = args.join(QLatin1Char(' ')); if (argument.isEmpty() || argument == QLatin1String("!")) { view->document()->documentReload(); } else { QUrl base = view->document()->url(); QUrl url; QUrl arg2path(argument); if (base.isValid()) { // first try to use the same path as the current open document has url = QUrl(base.resolved(arg2path)); //resolved handles the case where the args is a relative path, and is the same as using QUrl(args) elsewise } else { // else use the cwd url = QUrl(QUrl(QDir::currentPath() + QLatin1String("/")).resolved(arg2path)); // + "/" is needed because of http://lists.qt.nokia.com/public/qt-interest/2011-May/033913.html } QFileInfo file(url.toLocalFile()); KTextEditor::Document *doc = app->findUrl(url); if (doc) { mainWin->activateView(doc); } else if (file.exists()) { app->openUrl(url); } else { app->openUrl(QUrl())->saveAs(url); } } + // splitView() orientations are reversed from the usual editor convention. + // 'vsplit' and 'vnew' use Qt::Horizontal to match vi and the Kate UI actions. } else if (re_new.exactMatch(command)) { if (re_new.cap(1) == QLatin1String("v")) { // vertical split mainWin->splitView(Qt::Horizontal); } else { // horizontal split mainWin->splitView(Qt::Vertical); } mainWin->openUrl(QUrl()); } else if (command == QLatin1String("enew")) { mainWin->openUrl(QUrl()); } else if (re_split.exactMatch(command)) { - mainWin->splitView(Qt::Horizontal); + mainWin->splitView(Qt::Vertical); // see above } else if (re_vsplit.exactMatch(command)) { - mainWin->splitView(Qt::Vertical); + mainWin->splitView(Qt::Horizontal); } else if (re_vclose.exactMatch(command)) { QTimer::singleShot(0, this, SLOT(closeCurrentSplitView())); } else if (re_only.exactMatch(command)) { QTimer::singleShot(0, this, SLOT(closeOtherSplitViews())); } return true; } bool AppCommands::help(KTextEditor::View *view, const QString &cmd, QString &msg) { Q_UNUSED(view); if (re_write.exactMatch(cmd)) { msg = i18n("

w/wa — write document(s) to disk

" "

Usage: w[a]

" "

Writes the current document(s) to disk. " "It can be called in two ways:
" " w — writes the current document to disk
" " wa — writes all documents to disk.

" "

If no file name is associated with the document, " "a file dialog will be shown.

"); return true; } else if (re_quit.exactMatch(cmd)) { msg = i18n("

q/qa/wq/wqa — [write and] quit

" "

Usage: [w]q[a]

" "

Quits the application. If w is prepended, it also writes" " the document(s) to disk. This command " "can be called in several ways:
" " q — closes the current view.
" " qa — closes all views, effectively quitting the application.
" " wq — writes the current document to disk and closes its view.
" " wqa — writes all documents to disk and quits.

" "

In all cases, if the view being closed is the last view, the application quits. " "If no file name is associated with the document and it should be written to disk, " "a file dialog will be shown.

"); return true; } else if (re_exit.exactMatch(cmd)) { msg = i18n("

x/xa — write and quit

" "

Usage: x[a]

" "

Saves document(s) and quits (exits). This command " "can be called in two ways:
" " x — closes the current view.
" " xa — closes all views, effectively quitting the application.

" "

In all cases, if the view being closed is the last view, the application quits. " "If no file name is associated with the document and it should be written to disk, " "a file dialog will be shown.

" "

Unlike the 'w' commands, this command only writes the document if it is modified." "

"); return true; } else if (re_split.exactMatch(cmd)) { msg = i18n("

sp,split— Split horizontally the current view into two

" "

Usage: sp[lit]

" "

The result is two views on the same document.

"); return true; } else if (re_vsplit.exactMatch(cmd)) { msg = i18n("

vs,vsplit— Split vertically the current view into two

" "

Usage: vs[plit]

" "

The result is two views on the same document.

"); return true; } else if (re_vclose.exactMatch(cmd)) { msg = i18n("

clo[se]— Close the current view

" "

Usage: clo[se]

" "

After executing it, the current view will be closed.

"); return true; } else if (re_new.exactMatch(cmd)) { msg = i18n("

[v]new — split view and create new document

" "

Usage: [v]new

" "

Splits the current view and opens a new document in the new view." " This command can be called in two ways:
" " new — splits the view horizontally and opens a new document.
" " vnew — splits the view vertically and opens a new document.
" "

"); return true; } else if (re_edit.exactMatch(cmd)) { msg = i18n("

e[dit] — reload current document

" "

Usage: e[dit]

" "

Starts editing the current document again. This is useful to re-edit" " the current file, when it has been changed by another program.

"); return true; } return false; } KTextEditor::View * AppCommands::findViewInDifferentSplitView(KTextEditor::MainWindow *window, KTextEditor::View *view) { Q_FOREACH (KTextEditor::View *it, window->views()) { if (!window->viewsInSameSplitView(it, view)) { return it; } } - return Q_NULLPTR; + return nullptr; } void AppCommands::closeCurrentDocument() { KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); KTextEditor::Document *doc = app->activeMainWindow()->activeView()->document(); app->closeDocument(doc); } void AppCommands::closeCurrentView() { KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); KTextEditor::MainWindow *mw = app->activeMainWindow(); mw->closeView(mw->activeView()); } void AppCommands::closeCurrentSplitView() { KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); KTextEditor::MainWindow *mw = app->activeMainWindow(); mw->closeSplitView(mw->activeView()); } void AppCommands::closeOtherSplitViews() { KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); KTextEditor::MainWindow *mw = app->activeMainWindow(); KTextEditor::View *view = mw->activeView(); - KTextEditor::View *viewToRemove = Q_NULLPTR; + KTextEditor::View *viewToRemove = nullptr; while ((viewToRemove = findViewInDifferentSplitView(mw, view))) { mw->closeSplitView(viewToRemove); } } void AppCommands::quit() { KTextEditor::Editor::instance()->application()->quit(); } //END AppCommands //BEGIN KateViBufferCommand -BufferCommands *BufferCommands::m_instance = 0; +BufferCommands *BufferCommands::m_instance = nullptr; BufferCommands::BufferCommands() : KTextEditor::Command(QStringList() << QStringLiteral("ls") << QStringLiteral("b") << QStringLiteral("buffer") << QStringLiteral("bn") << QStringLiteral("bnext") << QStringLiteral("bp") << QStringLiteral("bprevious") << QStringLiteral("tabn") << QStringLiteral("tabnext") << QStringLiteral("tabp") << QStringLiteral("tabprevious") << QStringLiteral("bf") << QStringLiteral("bfirst") << QStringLiteral("bl") << QStringLiteral("blast") << QStringLiteral("tabf") << QStringLiteral("tabfirst") << QStringLiteral("tabl") << QStringLiteral("tablast")) { } BufferCommands::~BufferCommands() { - m_instance = 0; + m_instance = nullptr; } bool BufferCommands::exec(KTextEditor::View *view, const QString &cmd, QString &, const KTextEditor::Range &) { // create list of args QStringList args(cmd.split(QLatin1Char(' '), QString::KeepEmptyParts)); QString command = args.takeFirst(); // same as cmd if split failed QString argument = args.join(QLatin1Char(' ')); if (command == QLatin1String("ls")) { // TODO: open quickview } else if (command == QLatin1String("b") || command == QLatin1String("buffer")) { switchDocument(view, argument); } else if (command == QLatin1String("bp") || command == QLatin1String("bprevious")) { prevBuffer(view); } else if (command == QLatin1String("bn") || command == QLatin1String("bnext")) { nextBuffer(view); } else if (command == QLatin1String("bf") || command == QLatin1String("bfirst")) { firstBuffer(view); } else if (command == QLatin1String("bl") || command == QLatin1String("blast")) { lastBuffer(view); } else if (command == QLatin1String("tabn") || command == QLatin1String("tabnext")) { nextTab(view); } else if (command == QLatin1String("tabp") || command == QLatin1String("tabprevious")) { prevTab(view); } else if (command == QLatin1String("tabf") || command == QLatin1String("tabfirst")) { firstTab(view); } else if (command == QLatin1String("tabl") || command == QLatin1String("tablast")) { lastTab(view); } return true; } void BufferCommands::switchDocument(KTextEditor::View *view, const QString &address) { if (address.isEmpty()) { // no argument: switch to the previous document prevBuffer(view); return; } const int idx = address.toInt(); QList docs = documents(); if (idx > 0 && idx <= docs.size()) { // numerical argument: switch to the nth document activateDocument(view, docs.at(idx - 1)); } else { // string argument: switch to the given file - KTextEditor::Document *doc = 0; + KTextEditor::Document *doc = nullptr; Q_FOREACH(KTextEditor::Document *it, docs) { if (it->documentName() == address) { doc = it; break; } } if (doc) { activateDocument(view, docs.at(idx - 1)); } } } void BufferCommands::prevBuffer(KTextEditor::View *view) { QList docs = documents(); const int idx = docs.indexOf(view->document()); if (idx > 0) { activateDocument(view, docs.at(idx - 1)); } else { // wrap activateDocument(view, docs.last()); } } void BufferCommands::nextBuffer(KTextEditor::View *view) { QList docs = documents(); const int idx = docs.indexOf(view->document()); if (idx + 1 < docs.size()) { activateDocument(view, docs.at(idx + 1)); } else { // wrap activateDocument(view, docs.first()); } } void BufferCommands::firstBuffer(KTextEditor::View *view) { activateDocument(view, documents().at(0)); } void BufferCommands::lastBuffer(KTextEditor::View *view) { activateDocument(view, documents().last()); } void BufferCommands::prevTab(KTextEditor::View *view) { prevBuffer(view); // TODO: implement properly, when interface is added } void BufferCommands::nextTab(KTextEditor::View *view) { nextBuffer(view); // TODO: implement properly, when interface is added } void BufferCommands::firstTab(KTextEditor::View *view) { firstBuffer(view); // TODO: implement properly, when interface is added } void BufferCommands::lastTab(KTextEditor::View *view) { lastBuffer(view); // TODO: implement properly, when interface is added } void BufferCommands::activateDocument(KTextEditor::View *view, KTextEditor::Document *doc) { KTextEditor::MainWindow *mainWindow = view->mainWindow(); mainWindow->activateView(doc); } QList< KTextEditor::Document * > BufferCommands::documents() { KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); return app->documents(); } bool BufferCommands::help(KTextEditor::View * /*view*/, const QString &cmd, QString &msg) { if (cmd == QLatin1String("b") || cmd == QLatin1String("buffer")) { msg = i18n("

b,buffer — Edit document N from the document list

" "

Usage: b[uffer] [N]

"); return true; } else if (cmd == QLatin1String("bp") || cmd == QLatin1String("bprevious") || cmd == QLatin1String("tabp") || cmd == QLatin1String("tabprevious")) { msg = i18n("

bp,bprev — previous buffer

" "

Usage: bp[revious] [N]

" "

Goes to [N]th previous document (\"buffer\") in document list.

" "

[N] defaults to one.

" "

Wraps around the start of the document list.

"); return true; } else if (cmd == QLatin1String("bn") || cmd == QLatin1String("bnext") || cmd == QLatin1String("tabn") || cmd == QLatin1String("tabnext")) { msg = i18n("

bn,bnext — switch to next document

" "

Usage: bn[ext] [N]

" "

Goes to [N]th next document (\"buffer\") in document list." "[N] defaults to one.

" "

Wraps around the end of the document list.

"); return true; } else if (cmd == QLatin1String("bf") || cmd == QLatin1String("bfirst") || cmd == QLatin1String("tabf") || cmd == QLatin1String("tabfirst")) { msg = i18n("

bf,bfirst — first document

" "

Usage: bf[irst]

" "

Goes to the first document (\"buffer\") in document list.

"); return true; } else if (cmd == QLatin1String("bl") || cmd == QLatin1String("blast") || cmd == QLatin1String("tabl") || cmd == QLatin1String("tablast")) { msg = i18n("

bl,blast — last document

" "

Usage: bl[ast]

" "

Goes to the last document (\"buffer\") in document list.

"); return true; } else if (cmd == QLatin1String("ls")) { msg = i18n("

ls

" "

list current buffers

"); } return false; } //END KateViBufferCommand diff --git a/src/vimode/appcommands.h b/src/vimode/appcommands.h index bc30f842..4ef0d303 100644 --- a/src/vimode/appcommands.h +++ b/src/vimode/appcommands.h @@ -1,118 +1,118 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Erlend Hamberg Copyright (C) 2011 Svyatoslav Kuzmich 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 KATEVI_APP_COMMANDS_H #define KATEVI_APP_COMMANDS_H #include #include namespace KTextEditor { class MainWindow; } namespace KateVi { class AppCommands : public KTextEditor::Command { Q_OBJECT AppCommands(); static AppCommands* m_instance; public: virtual ~AppCommands(); bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) Q_DECL_OVERRIDE; bool help(KTextEditor::View *view, const QString &cmd, QString &msg) Q_DECL_OVERRIDE; static AppCommands* self() { - if (m_instance == 0) { + if (m_instance == nullptr) { m_instance = new AppCommands(); } return m_instance; } private: /** * @returns a view in the given \p window that does not share a split * view with the given \p view. If such view could not be found, then * nullptr is returned. */ KTextEditor::View * findViewInDifferentSplitView(KTextEditor::MainWindow *window, KTextEditor::View *view); private Q_SLOTS: void closeCurrentDocument(); void closeCurrentView(); void closeCurrentSplitView(); void closeOtherSplitViews(); void quit(); private: QRegExp re_write; QRegExp re_close; QRegExp re_quit; QRegExp re_exit; QRegExp re_edit; QRegExp re_new; QRegExp re_split; QRegExp re_vsplit; QRegExp re_vclose; QRegExp re_only; }; class BufferCommands : public KTextEditor::Command { Q_OBJECT BufferCommands(); static BufferCommands* m_instance; public: virtual ~BufferCommands(); bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) Q_DECL_OVERRIDE; bool help(KTextEditor::View *view, const QString &cmd, QString &msg) Q_DECL_OVERRIDE; static BufferCommands* self() { - if (m_instance == 0) { + if (m_instance == nullptr) { m_instance = new BufferCommands(); } return m_instance; } private: void switchDocument(KTextEditor::View *, const QString &doc); void prevBuffer(KTextEditor::View *); void nextBuffer(KTextEditor::View *); void firstBuffer(KTextEditor::View *); void lastBuffer(KTextEditor::View *); void prevTab(KTextEditor::View *); void nextTab(KTextEditor::View *); void firstTab(KTextEditor::View *); void lastTab(KTextEditor::View *); void activateDocument(KTextEditor::View *, KTextEditor::Document *); QList documents(); }; } #endif /* KATEVI_APP_COMMANDS_H */ diff --git a/src/vimode/cmds.cpp b/src/vimode/cmds.cpp index 92b989aa..c5adb7d2 100644 --- a/src/vimode/cmds.cpp +++ b/src/vimode/cmds.cpp @@ -1,279 +1,279 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * 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 #include "katedocument.h" #include "kateview.h" #include "kateglobal.h" #include #include #include "katecmd.h" #include "katepartdebug.h" #include "kateviinputmode.h" #include #include "globalstate.h" #include "marks.h" #include #include #include #include using namespace KateVi; //BEGIN ViCommands -Commands *Commands::m_instance = 0; +Commands *Commands::m_instance = nullptr; bool Commands::exec(KTextEditor::View *view, const QString &_cmd, QString &msg, const KTextEditor::Range &range) { Q_UNUSED(range) // cast it hardcore, we know that it is really a kateview :) KTextEditor::ViewPrivate *v = static_cast(view); if (!v) { msg = i18n("Could not access view"); return false; } //create a list of args QStringList args(_cmd.split(QRegExp(QLatin1String("\\s+")), QString::SkipEmptyParts)); QString cmd(args.takeFirst()); // ALL commands that takes no arguments. if (mappingCommands().contains(cmd)) { if (cmd.endsWith(QLatin1String("unmap"))) { if (args.count() == 1) { m_viGlobal->mappings()->remove(modeForMapCommand(cmd), args.at(0)); return true; } else { msg = i18n("Missing argument. Usage: %1 ", cmd); return false; } } if (args.count() == 1) { msg = m_viGlobal->mappings()->get(modeForMapCommand(cmd), args.at(0), true); if (msg.isEmpty()) { msg = i18n("No mapping found for \"%1\"", args.at(0)); return false; } else { msg = i18n("\"%1\" is mapped to \"%2\"", args.at(0), msg); } } else if (args.count() == 2) { Mappings::MappingRecursion mappingRecursion = (isMapCommandRecursive(cmd)) ? Mappings::Recursive : Mappings::NonRecursive; m_viGlobal->mappings()->add(modeForMapCommand(cmd), args.at(0), args.at(1), mappingRecursion); } else { msg = i18n("Missing argument(s). Usage: %1 []", cmd); return false; } return true; } NormalViMode *nm = m_viInputModeManager->getViNormalMode(); if (cmd == QLatin1String("d") || cmd == QLatin1String("delete") || cmd == QLatin1String("j") || cmd == QLatin1String("c") || cmd == QLatin1String("change") || cmd == QLatin1String("<") || cmd == QLatin1String(">") || cmd == QLatin1String("y") || cmd == QLatin1String("yank")) { KTextEditor::Cursor start_cursor_position = v->cursorPosition(); int count = 1; if (range.isValid()) { count = qAbs(range.end().line() - range.start().line()) + 1; v->setCursorPosition(KTextEditor::Cursor(qMin(range.start().line(), range.end().line()), 0)); } QRegExp number(QLatin1String("^(\\d+)$")); for (int i = 0; i < args.count(); i++) { if (number.indexIn(args.at(i)) != -1) { count += number.cap().toInt() - 1; } QChar r = args.at(i).at(0); if (args.at(i).size() == 1 && ((r >= QLatin1Char('a') && r <= QLatin1Char('z')) || r == QLatin1Char('_') || r == QLatin1Char('+') || r == QLatin1Char('*'))) { nm->setRegister(r); } } nm->setCount(count); if (cmd == QLatin1String("d") || cmd == QLatin1String("delete")) { nm->commandDeleteLine(); } if (cmd == QLatin1String("j")) { nm->commandJoinLines(); } if (cmd == QLatin1String("c") || cmd == QLatin1String("change")) { nm->commandChangeLine(); } if (cmd == QLatin1String("<")) { nm->commandUnindentLine(); } if (cmd == QLatin1String(">")) { nm->commandIndentLine(); } if (cmd == QLatin1String("y") || cmd == QLatin1String("yank")) { nm->commandYankLine(); v->setCursorPosition(start_cursor_position); } // TODO - should we resetParser, here? We'd have to make it public, if so. // Or maybe synthesise a KateViCommand to execute instead ... ? nm->setCount(0); return true; } if (cmd == QLatin1String("mark") || cmd == QLatin1String("ma") || cmd == QLatin1String("k")) { if (args.count() == 0) { if (cmd == QLatin1String("mark")) { // TODO: show up mark list; } else { msg = i18n("Wrong arguments"); return false; } } else if (args.count() == 1) { QChar r = args.at(0).at(0); int line; if ((r >= QLatin1Char('a') && r <= QLatin1Char('z')) || r == QLatin1Char('_') || r == QLatin1Char('+') || r == QLatin1Char('*')) { if (range.isValid()) { line = qMax(range.end().line(), range.start().line()); } else { line = v->cursorPosition().line(); } m_viInputModeManager->marks()->setUserMark(r, KTextEditor::Cursor(line, 0)); } } else { msg = i18n("Wrong arguments"); return false; } return true; } // should not happen :) msg = i18n("Unknown command '%1'", cmd); return false; } bool Commands::supportsRange(const QString &range) { static QStringList l; if (l.isEmpty()) l << QStringLiteral("d") << QStringLiteral("delete") << QStringLiteral("j") << QStringLiteral("c") << QStringLiteral("change") << QStringLiteral("<") << QStringLiteral(">") << QStringLiteral("y") << QStringLiteral("yank") << QStringLiteral("ma") << QStringLiteral("mark") << QStringLiteral("k"); return l.contains(range.split(QLatin1String(" ")).at(0)); } KCompletion *Commands::completionObject(KTextEditor::View *view, const QString &cmd) { Q_UNUSED(view) KTextEditor::ViewPrivate *v = static_cast(view); if (v && (cmd == QLatin1String("nn") || cmd == QLatin1String("nnoremap"))) { QStringList l = m_viGlobal->mappings()->getAll(Mappings::NormalModeMapping); KateCmdShellCompletion *co = new KateCmdShellCompletion(); co->setItems(l); co->setIgnoreCase(false); return co; } - return 0L; + return nullptr; } const QStringList &Commands::mappingCommands() { static QStringList mappingsCommands; if (mappingsCommands.isEmpty()) { mappingsCommands << QStringLiteral("nmap") << QStringLiteral("nm") << QStringLiteral("noremap") << QStringLiteral("nnoremap") << QStringLiteral("nn") << QStringLiteral("no") << QStringLiteral("vmap") << QStringLiteral("vm") << QStringLiteral("vnoremap") << QStringLiteral("vn") << QStringLiteral("imap") << QStringLiteral("im") << QStringLiteral("inoremap") << QStringLiteral("ino") << QStringLiteral("cmap") << QStringLiteral("cm") << QStringLiteral("cnoremap") << QStringLiteral("cno"); mappingsCommands << QStringLiteral("nunmap") << QStringLiteral("vunmap") << QStringLiteral("iunmap") << QStringLiteral("cunmap"); } return mappingsCommands; } Mappings::MappingMode Commands::modeForMapCommand(const QString &mapCommand) { static QMap modeForMapCommand; if (modeForMapCommand.isEmpty()) { // Normal is the default. modeForMapCommand.insert(QStringLiteral("vmap"), Mappings::VisualModeMapping); modeForMapCommand.insert(QStringLiteral("vm"), Mappings::VisualModeMapping); modeForMapCommand.insert(QStringLiteral("vnoremap"), Mappings::VisualModeMapping); modeForMapCommand.insert(QStringLiteral("vn"), Mappings::VisualModeMapping); modeForMapCommand.insert(QStringLiteral("imap"), Mappings::InsertModeMapping); modeForMapCommand.insert(QStringLiteral("im"), Mappings::InsertModeMapping); modeForMapCommand.insert(QStringLiteral("inoremap"), Mappings::InsertModeMapping); modeForMapCommand.insert(QStringLiteral("ino"), Mappings::InsertModeMapping); modeForMapCommand.insert(QStringLiteral("cmap"), Mappings::CommandModeMapping); modeForMapCommand.insert(QStringLiteral("cm"), Mappings::CommandModeMapping); modeForMapCommand.insert(QStringLiteral("cnoremap"), Mappings::CommandModeMapping); modeForMapCommand.insert(QStringLiteral("cno"), Mappings::CommandModeMapping); modeForMapCommand.insert(QStringLiteral("nunmap"), Mappings::NormalModeMapping); modeForMapCommand.insert(QStringLiteral("vunmap"), Mappings::VisualModeMapping); modeForMapCommand.insert(QStringLiteral("iunmap"), Mappings::InsertModeMapping); modeForMapCommand.insert(QStringLiteral("cunmap"), Mappings::CommandModeMapping); } return modeForMapCommand.value(mapCommand); } bool Commands::isMapCommandRecursive(const QString &mapCommand) { static QMap isMapCommandRecursive; if (isMapCommandRecursive.isEmpty()) { isMapCommandRecursive.insert(QStringLiteral("nmap"), true); isMapCommandRecursive.insert(QStringLiteral("nm"), true); isMapCommandRecursive.insert(QStringLiteral("vmap"), true); isMapCommandRecursive.insert(QStringLiteral("vm"), true); isMapCommandRecursive.insert(QStringLiteral("imap"), true); isMapCommandRecursive.insert(QStringLiteral("im"), true); isMapCommandRecursive.insert(QStringLiteral("cmap"), true); isMapCommandRecursive.insert(QStringLiteral("cm"), true); } return isMapCommandRecursive.value(mapCommand); } //END ViCommands //BEGIN SedReplace -SedReplace *SedReplace::m_instance = 0; +SedReplace *SedReplace::m_instance = nullptr; bool SedReplace::interactiveSedReplace(KTextEditor::ViewPrivate *, QSharedPointer interactiveSedReplace) { EmulatedCommandBar *emulatedCommandBar = m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar(); emulatedCommandBar->startInteractiveSearchAndReplace(interactiveSedReplace); return true; } //END SedReplace diff --git a/src/vimode/cmds.h b/src/vimode/cmds.h index 745a9bcc..c9d4e07c 100644 --- a/src/vimode/cmds.h +++ b/src/vimode/cmds.h @@ -1,126 +1,125 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * 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 KATEVI_COMMANDS_H #define KATEVI_COMMANDS_H #include #include "kateregexpsearch.h" #include #include #include "mappings.h" #include namespace KTextEditor { class DocumentPrivate; } class KCompletion; namespace KateVi { /** * This KTextEditor::Command provides vi 'ex' commands */ class Commands : public KTextEditor::Command, public KateViCommandInterface { Commands() : KTextEditor::Command (QStringList() << mappingCommands() << QStringLiteral("d") << QStringLiteral("delete") << QStringLiteral("j") << QStringLiteral("c") << QStringLiteral("change") << QStringLiteral("<") << QStringLiteral(">") << QStringLiteral("y") << QStringLiteral("yank") << QStringLiteral("ma") << QStringLiteral("mark") << QStringLiteral("k")) { } static Commands *m_instance; public: ~Commands() { - m_instance = 0; + m_instance = nullptr; } /** * execute command on given range * @param view view to use for execution * @param cmd cmd string * @param msg message returned from running the command - * @param rangeStart first line in range - * @param rangeEnd last line in range + * @param range range to execute command on * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; bool supportsRange(const QString &range) Q_DECL_OVERRIDE; /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE { return false; } /** * Reimplement from KTextEditor::Command */ KCompletion *completionObject(KTextEditor::View *, const QString &) Q_DECL_OVERRIDE; static Commands *self() { - if (m_instance == 0) { + if (m_instance == nullptr) { m_instance = new Commands(); } return m_instance; } private: const QStringList &mappingCommands(); Mappings::MappingMode modeForMapCommand(const QString &mapCommand); bool isMapCommandRecursive(const QString &mapCommand); }; /** * Support vim/sed style search and replace * @author Charles Samuels **/ class SedReplace : public KateCommands::SedReplace, public KateViCommandInterface { SedReplace() { } static SedReplace *m_instance; public: ~SedReplace() { - m_instance = 0; + m_instance = nullptr; } static SedReplace *self() { - if (m_instance == 0) { + if (m_instance == nullptr) { m_instance = new SedReplace(); } return m_instance; } protected: bool interactiveSedReplace(KTextEditor::ViewPrivate *kateView, QSharedPointer interactiveSedReplace) Q_DECL_OVERRIDE; }; } #endif /* KATEVI_COMMANDS_H */ diff --git a/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp b/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp index 063a7eee..b11e9259 100644 --- a/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp +++ b/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp @@ -1,445 +1,443 @@ /* This file is part of the KDE libraries and the Kate part. * * 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 #include "katedocument.h" #include "kateglobal.h" #include "../commandrangeexpressionparser.h" #include "kateview.h" #include "../globalstate.h" #include #include #include #include #include "matchhighlighter.h" #include "interactivesedreplacemode.h" #include "searchmode.h" #include "commandmode.h" #include "../history.h" #include "../registers.h" #include "../searcher.h" #include #include #include #include #include using namespace KateVi; namespace { /** * @return \a originalRegex but escaped in such a way that a Qt regex search for * the resulting string will match the string \a originalRegex. */ QString escapedForSearchingAsLiteral(const QString &originalQtRegex) { QString escapedForSearchingAsLiteral = originalQtRegex; escapedForSearchingAsLiteral.replace(QLatin1Char('\\'), QLatin1String("\\\\")); escapedForSearchingAsLiteral.replace(QLatin1Char('$'), QLatin1String("\\$")); escapedForSearchingAsLiteral.replace(QLatin1Char('^'), QLatin1String("\\^")); escapedForSearchingAsLiteral.replace(QLatin1Char('.'), QLatin1String("\\.")); escapedForSearchingAsLiteral.replace(QLatin1Char('*'), QLatin1String("\\*")); escapedForSearchingAsLiteral.replace(QLatin1Char('/'), QLatin1String("\\/")); escapedForSearchingAsLiteral.replace(QLatin1Char('['), QLatin1String("\\[")); escapedForSearchingAsLiteral.replace(QLatin1Char(']'), QLatin1String("\\]")); escapedForSearchingAsLiteral.replace(QLatin1Char('\n'), QLatin1String("\\n")); return escapedForSearchingAsLiteral; } } EmulatedCommandBar::EmulatedCommandBar(KateViInputMode* viInputMode, InputModeManager *viInputModeManager, QWidget *parent) : KateViewBarWidget(false, parent), m_viInputMode(viInputMode), m_viInputModeManager(viInputModeManager), m_view(viInputModeManager->view()) { QHBoxLayout *layout = new QHBoxLayout(); layout->setMargin(0); centralWidget()->setLayout(layout); createAndAddBarTypeIndicator(layout); createAndAddEditWidget(layout); createAndAddExitStatusMessageDisplay(layout); createAndInitExitStatusMessageDisplayTimer(); createAndAddWaitingForRegisterIndicator(layout); m_matchHighligher.reset(new MatchHighlighter(m_view)); m_completer.reset(new Completer(this, m_view, m_edit)); m_interactiveSedReplaceMode.reset(new InteractiveSedReplaceMode(this, m_matchHighligher.data(), m_viInputModeManager, m_view)); layout->addWidget(m_interactiveSedReplaceMode->label()); m_searchMode.reset(new SearchMode(this, m_matchHighligher.data(), m_viInputModeManager, m_view, m_edit)); m_commandMode.reset(new CommandMode(this, m_matchHighligher.data(), m_viInputModeManager, m_view, m_edit, m_interactiveSedReplaceMode.data(), m_completer.data())); m_edit->installEventFilter(this); connect(m_edit, SIGNAL(textChanged(QString)), this, SLOT(editTextChanged(QString))); } EmulatedCommandBar::~EmulatedCommandBar() { } void EmulatedCommandBar::init(EmulatedCommandBar::Mode mode, const QString &initialText) { m_mode = mode; m_isActive = true; m_wasAborted = true; showBarTypeIndicator(mode); if (mode == KateVi::EmulatedCommandBar::SearchBackward || mode == SearchForward) { switchToMode(m_searchMode.data()); m_searchMode->init(mode == SearchBackward ? SearchMode::SearchDirection::Backward : SearchMode::SearchDirection::Forward); } else { switchToMode(m_commandMode.data()); } m_edit->setFocus(); m_edit->setText(initialText); m_edit->show(); m_exitStatusMessageDisplay->hide(); m_exitStatusMessageDisplayHideTimer->stop(); // A change in focus will have occurred: make sure we process it now, instead of having it // occur later and stop() m_commandResponseMessageDisplayHide. // This is generally only a problem when feeding a sequence of keys without human intervention, // as when we execute a mapping, macro, or test case. - while (QApplication::hasPendingEvents()) { - QApplication::processEvents(); - } + QApplication::processEvents(); } bool EmulatedCommandBar::isActive() { return m_isActive; } void EmulatedCommandBar::setCommandResponseMessageTimeout(long int commandResponseMessageTimeOutMS) { m_exitStatusMessageHideTimeOutMS = commandResponseMessageTimeOutMS; } void EmulatedCommandBar::closed() { m_matchHighligher->updateMatchHighlight(KTextEditor::Range::invalid()); m_completer->deactivateCompletion(); m_isActive = false; if (m_currentMode) { m_currentMode->deactivate(m_wasAborted); m_currentMode = nullptr; } } void EmulatedCommandBar::switchToMode ( ActiveMode* newMode ) { if (m_currentMode) m_currentMode->deactivate(false); m_currentMode = newMode; m_completer->setCurrentMode(newMode); } bool EmulatedCommandBar::barHandledKeypress ( const QKeyEvent* keyEvent ) { if ((keyEvent->modifiers() == Qt::ControlModifier && keyEvent->key() == Qt::Key_H) || keyEvent->key() == Qt::Key_Backspace) { if (m_edit->text().isEmpty()) { emit hideMe(); } m_edit->backspace(); return true; } if (keyEvent->modifiers() != Qt::ControlModifier) return false; if (keyEvent->key() == Qt::Key_B) { m_edit->setCursorPosition(0); return true; } else if (keyEvent->key() == Qt::Key_E) { m_edit->setCursorPosition(m_edit->text().length()); return true; } else if (keyEvent->key() == Qt::Key_W) { deleteSpacesToLeftOfCursor(); if (!deleteNonWordCharsToLeftOfCursor()) { deleteWordCharsToLeftOfCursor(); } return true; } else if (keyEvent->key() == Qt::Key_R || keyEvent->key() == Qt::Key_G) { m_waitingForRegister = true; m_waitingForRegisterIndicator->setVisible(true); if (keyEvent->key() == Qt::Key_G) { m_insertedTextShouldBeEscapedForSearchingAsLiteral = true; } return true; } return false; } void EmulatedCommandBar::insertRegisterContents(const QKeyEvent *keyEvent) { if (keyEvent->key() != Qt::Key_Shift && keyEvent->key() != Qt::Key_Control) { const QChar key = KeyParser::self()->KeyEventToQChar(*keyEvent).toLower(); const int oldCursorPosition = m_edit->cursorPosition(); QString textToInsert; if (keyEvent->modifiers() == Qt::ControlModifier && keyEvent->key() == Qt::Key_W) { textToInsert = m_view->doc()->wordAt(m_view->cursorPosition()); } else { textToInsert = m_viInputModeManager->globalState()->registers()->getContent(key); } if (m_insertedTextShouldBeEscapedForSearchingAsLiteral) { textToInsert = escapedForSearchingAsLiteral(textToInsert); m_insertedTextShouldBeEscapedForSearchingAsLiteral = false; } m_edit->setText(m_edit->text().insert(m_edit->cursorPosition(), textToInsert)); m_edit->setCursorPosition(oldCursorPosition + textToInsert.length()); m_waitingForRegister = false; m_waitingForRegisterIndicator->setVisible(false); } } bool EmulatedCommandBar::eventFilter(QObject *object, QEvent *event) { // The "object" will be either m_edit or m_completer's popup. if (m_suspendEditEventFiltering) { return false; } Q_UNUSED(object); if (event->type() == QEvent::KeyPress) { // Re-route this keypress through Vim's central keypress handling area, so that we can use the keypress in e.g. // mappings and macros. return m_viInputMode->keyPress(static_cast(event)); } return false; } void EmulatedCommandBar::deleteSpacesToLeftOfCursor() { while (m_edit->cursorPosition() != 0 && m_edit->text().at(m_edit->cursorPosition() - 1) == QLatin1Char(' ')) { m_edit->backspace(); } } void EmulatedCommandBar::deleteWordCharsToLeftOfCursor() { while (m_edit->cursorPosition() != 0) { const QChar charToTheLeftOfCursor = m_edit->text().at(m_edit->cursorPosition() - 1); if (!charToTheLeftOfCursor.isLetterOrNumber() && charToTheLeftOfCursor != QLatin1Char('_')) { break; } m_edit->backspace(); } } bool EmulatedCommandBar::deleteNonWordCharsToLeftOfCursor() { bool deletionsMade = false; while (m_edit->cursorPosition() != 0) { const QChar charToTheLeftOfCursor = m_edit->text().at(m_edit->cursorPosition() - 1); if (charToTheLeftOfCursor.isLetterOrNumber() || charToTheLeftOfCursor == QLatin1Char('_') || charToTheLeftOfCursor == QLatin1Char(' ')) { break; } m_edit->backspace(); deletionsMade = true; } return deletionsMade; } bool EmulatedCommandBar::handleKeyPress(const QKeyEvent *keyEvent) { if (m_waitingForRegister) { insertRegisterContents(keyEvent); return true; } const bool completerHandled = m_completer->completerHandledKeypress(keyEvent); if (completerHandled) return true; if (keyEvent->modifiers() == Qt::ControlModifier && (keyEvent->key() == Qt::Key_C || keyEvent->key() == Qt::Key_BracketLeft)) { emit hideMe(); return true; } // Is this a built-in Emulated Command Bar keypress e.g. insert from register, ctrl-h, etc? const bool barHandled = barHandledKeypress(keyEvent); if (barHandled) return true; // Can the current mode handle it? const bool currentModeHandled = m_currentMode->handleKeyPress(keyEvent); if (currentModeHandled) return true; // Couldn't handle this key event. // Send the keypress back to the QLineEdit. Ideally, instead of doing this, we would simply return "false" // and let Qt re-dispatch the event itself; however, there is a corner case in that if the selection // changes (as a result of e.g. incremental searches during Visual Mode), and the keypress that causes it // is not dispatched from within KateViInputModeHandler::handleKeypress(...) // (so KateViInputModeManager::isHandlingKeypress() returns false), we lose information about whether we are // in Visual Mode, Visual Line Mode, etc. See VisualViMode::updateSelection( ). if (m_edit->isVisible()) { if (m_suspendEditEventFiltering) return false; m_suspendEditEventFiltering = true; QKeyEvent keyEventCopy(keyEvent->type(), keyEvent->key(), keyEvent->modifiers(), keyEvent->text(), keyEvent->isAutoRepeat(), keyEvent->count()); qApp->notify(m_edit, &keyEventCopy); m_suspendEditEventFiltering = false; } return true; } bool EmulatedCommandBar::isSendingSyntheticSearchCompletedKeypress() { return m_searchMode->isSendingSyntheticSearchCompletedKeypress(); } void EmulatedCommandBar::startInteractiveSearchAndReplace(QSharedPointer interactiveSedReplace) { Q_ASSERT_X(interactiveSedReplace->currentMatch().isValid(), "startInteractiveSearchAndReplace", "KateCommands shouldn't initiate an interactive sed replace with no initial match"); switchToMode(m_interactiveSedReplaceMode.data()); m_interactiveSedReplaceMode->activate(interactiveSedReplace); } void EmulatedCommandBar::showBarTypeIndicator(EmulatedCommandBar::Mode mode) { QChar barTypeIndicator = QChar::Null; switch (mode) { case SearchForward: barTypeIndicator = QLatin1Char('/'); break; case SearchBackward: barTypeIndicator = QLatin1Char('?'); break; case Command: barTypeIndicator = QLatin1Char(':'); break; default: Q_ASSERT(false && "Unknown mode!"); } m_barTypeIndicator->setText(barTypeIndicator); m_barTypeIndicator->show(); } QString EmulatedCommandBar::executeCommand(const QString &commandToExecute) { return m_commandMode->executeCommand(commandToExecute); } void EmulatedCommandBar::closeWithStatusMessage(const QString &exitStatusMessage) { // Display the message for a while. Become inactive, so we don't steal keys in the meantime. m_isActive = false; m_exitStatusMessageDisplay->show(); m_exitStatusMessageDisplay->setText(exitStatusMessage); hideAllWidgetsExcept(m_exitStatusMessageDisplay); m_exitStatusMessageDisplayHideTimer->start(m_exitStatusMessageHideTimeOutMS); } void EmulatedCommandBar::editTextChanged(const QString &newText) { Q_ASSERT(!m_interactiveSedReplaceMode->isActive()); m_currentMode->editTextChanged(newText); m_completer->editTextChanged(newText); } void EmulatedCommandBar::startHideExitStatusMessageTimer() { if (m_exitStatusMessageDisplay->isVisible() && !m_exitStatusMessageDisplayHideTimer->isActive()) { m_exitStatusMessageDisplayHideTimer->start(m_exitStatusMessageHideTimeOutMS); } } void EmulatedCommandBar::setViInputModeManager(InputModeManager *viInputModeManager) { m_viInputModeManager = viInputModeManager; m_searchMode->setViInputModeManager(viInputModeManager); m_commandMode->setViInputModeManager(viInputModeManager); m_interactiveSedReplaceMode->setViInputModeManager(viInputModeManager); } void EmulatedCommandBar::hideAllWidgetsExcept(QWidget* widgetToKeepVisible) { QList widgets = centralWidget()->findChildren(); foreach(QWidget* widget, widgets) { if (widget != widgetToKeepVisible) widget->hide(); } } void EmulatedCommandBar::createAndAddBarTypeIndicator(QLayout* layout) { m_barTypeIndicator = new QLabel(this); m_barTypeIndicator->setObjectName(QStringLiteral("bartypeindicator")); layout->addWidget(m_barTypeIndicator); } void EmulatedCommandBar::createAndAddEditWidget(QLayout* layout) { m_edit = new QLineEdit(this); m_edit->setObjectName(QStringLiteral("commandtext")); layout->addWidget(m_edit); } void EmulatedCommandBar::createAndAddExitStatusMessageDisplay(QLayout* layout) { m_exitStatusMessageDisplay = new QLabel(this); m_exitStatusMessageDisplay->setObjectName(QStringLiteral("commandresponsemessage")); m_exitStatusMessageDisplay->setAlignment(Qt::AlignLeft); layout->addWidget(m_exitStatusMessageDisplay); } void EmulatedCommandBar::createAndInitExitStatusMessageDisplayTimer() { m_exitStatusMessageDisplayHideTimer = new QTimer(this); m_exitStatusMessageDisplayHideTimer->setSingleShot(true); connect(m_exitStatusMessageDisplayHideTimer, SIGNAL(timeout()), this, SIGNAL(hideMe())); // Make sure the timer is stopped when the user switches views. If not, focus will be given to the // wrong view when KateViewBar::hideCurrentBarWidget() is called as a result of m_commandResponseMessageDisplayHide // timing out. connect(m_view, SIGNAL(focusOut(KTextEditor::View*)), m_exitStatusMessageDisplayHideTimer, SLOT(stop())); // We can restart the timer once the view has focus again, though. connect(m_view, SIGNAL(focusIn(KTextEditor::View*)), this, SLOT(startHideExitStatusMessageTimer())); } void EmulatedCommandBar::createAndAddWaitingForRegisterIndicator(QLayout* layout) { m_waitingForRegisterIndicator = new QLabel(this); m_waitingForRegisterIndicator->setObjectName(QStringLiteral("waitingforregisterindicator")); m_waitingForRegisterIndicator->setVisible(false); m_waitingForRegisterIndicator->setText(QStringLiteral("\"")); layout->addWidget(m_waitingForRegisterIndicator); } diff --git a/src/vimode/emulatedcommandbar/emulatedcommandbar.h b/src/vimode/emulatedcommandbar/emulatedcommandbar.h index 37046620..6326b2b7 100644 --- a/src/vimode/emulatedcommandbar/emulatedcommandbar.h +++ b/src/vimode/emulatedcommandbar/emulatedcommandbar.h @@ -1,131 +1,131 @@ /* This file is part of the KDE libraries and the Kate part. * * 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. */ #ifndef KATEVI_EMULATED_COMMAND_BAR_H #define KATEVI_EMULATED_COMMAND_BAR_H #include "kateviewhelpers.h" #include #include #include #include "../searcher.h" #include "activemode.h" namespace KTextEditor { class ViewPrivate; class Command; } class QLabel; class QLayout; namespace KateVi { class MatchHighlighter; class InteractiveSedReplaceMode; class SearchMode; class CommandMode; /** * A KateViewBarWidget that attempts to emulate some of the features of Vim's own command bar, * including insertion of register contents via ctr-r; dismissal via * ctrl-c and ctrl-[; bi-directional incremental searching, with SmartCase; interactive sed-replace; * plus a few extensions such as completion from document and navigable sed search and sed replace history. */ class KTEXTEDITOR_EXPORT EmulatedCommandBar : public KateViewBarWidget { Q_OBJECT public: enum Mode { NoMode, SearchForward, SearchBackward, Command }; - explicit EmulatedCommandBar(KateViInputMode* viInputMode, InputModeManager *viInputModeManager, QWidget *parent = 0); + explicit EmulatedCommandBar(KateViInputMode* viInputMode, InputModeManager *viInputModeManager, QWidget *parent = nullptr); virtual ~EmulatedCommandBar(); void init(Mode mode, const QString &initialText = QString()); bool isActive(); void setCommandResponseMessageTimeout(long commandResponseMessageTimeOutMS); bool handleKeyPress(const QKeyEvent *keyEvent); bool isSendingSyntheticSearchCompletedKeypress(); void startInteractiveSearchAndReplace(QSharedPointer interactiveSedReplace); QString executeCommand(const QString &commandToExecute); void setViInputModeManager(InputModeManager *viInputModeManager); private: KateViInputMode *m_viInputMode; InputModeManager *m_viInputModeManager; bool m_isActive = false; bool m_wasAborted = true; Mode m_mode = NoMode; KTextEditor::ViewPrivate *m_view = nullptr; QLineEdit *m_edit = nullptr; QLabel *m_barTypeIndicator = nullptr; void showBarTypeIndicator(Mode mode); bool m_suspendEditEventFiltering = false; bool m_waitingForRegister = false ; QLabel *m_waitingForRegisterIndicator; bool m_insertedTextShouldBeEscapedForSearchingAsLiteral = false; void hideAllWidgetsExcept(QWidget* widgetToKeepVisible); friend class ActiveMode; QScopedPointer m_matchHighligher; QScopedPointer m_completer; QScopedPointer m_interactiveSedReplaceMode; QScopedPointer m_searchMode; QScopedPointer m_commandMode; void switchToMode(ActiveMode *newMode); ActiveMode *m_currentMode = nullptr; bool barHandledKeypress(const QKeyEvent* keyEvent); void insertRegisterContents(const QKeyEvent *keyEvent); bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE; void deleteSpacesToLeftOfCursor(); void deleteWordCharsToLeftOfCursor(); bool deleteNonWordCharsToLeftOfCursor(); void closed() Q_DECL_OVERRIDE; void closeWithStatusMessage(const QString& exitStatusMessage); QTimer *m_exitStatusMessageDisplayHideTimer; QLabel *m_exitStatusMessageDisplay; long m_exitStatusMessageHideTimeOutMS = 4000; void createAndAddBarTypeIndicator(QLayout* layout); void createAndAddEditWidget(QLayout* layout); void createAndAddExitStatusMessageDisplay(QLayout* layout); void createAndInitExitStatusMessageDisplayTimer(); void createAndAddWaitingForRegisterIndicator(QLayout* layout); private Q_SLOTS: void editTextChanged(const QString &newText); void startHideExitStatusMessageTimer(); }; } #endif /* KATEVI_EMULATED_COMMAND_BAR_H */ diff --git a/src/vimode/inputmodemanager.cpp b/src/vimode/inputmodemanager.cpp index 4ecc29bb..3aac1266 100644 --- a/src/vimode/inputmodemanager.cpp +++ b/src/vimode/inputmodemanager.cpp @@ -1,482 +1,482 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008 - 2009 Erlend Hamberg * Copyright (C) 2009 Paul Gideon Dann * Copyright (C) 2011 Svyatoslav Kuzmich * Copyright (C) 2012 - 2013 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 #include #include #include #include #include #include #include "kateconfig.h" #include "kateglobal.h" #include "globalstate.h" #include "kateviewinternal.h" #include #include #include #include #include #include #include #include "kateviinputmode.h" #include "marks.h" #include "jumps.h" #include "macros.h" #include "registers.h" #include "searcher.h" #include "completionrecorder.h" #include "completionreplayer.h" #include "macrorecorder.h" #include "lastchangerecorder.h" using namespace KateVi; InputModeManager::InputModeManager(KateViInputMode *inputAdapter, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal) : m_inputAdapter(inputAdapter) { m_currentViMode = ViMode::NormalMode; m_previousViMode = ViMode::NormalMode; m_viNormalMode = new NormalViMode(this, view, viewInternal); m_viInsertMode = new InsertViMode(this, view, viewInternal); m_viVisualMode = new VisualViMode(this, view, viewInternal); m_viReplaceMode = new ReplaceViMode(this, view, viewInternal); m_view = view; m_viewInternal = viewInternal; m_insideHandlingKeyPressCount = 0; m_keyMapperStack.push(QSharedPointer(new KeyMapper(this, m_view->doc(), m_view))); m_temporaryNormalMode = false; m_jumps = new Jumps(); m_marks = new Marks(this); m_searcher = new Searcher(this); m_completionRecorder = new CompletionRecorder(this); m_completionReplayer = new CompletionReplayer(this); m_macroRecorder = new MacroRecorder(this); m_lastChangeRecorder = new LastChangeRecorder(this); // We have to do this outside of NormalMode, as we don't want // VisualMode (which inherits from NormalMode) to respond // to changes in the document as well. m_viNormalMode->beginMonitoringDocumentChanges(); } InputModeManager::~InputModeManager() { delete m_viNormalMode; delete m_viInsertMode; delete m_viVisualMode; delete m_viReplaceMode; delete m_jumps; delete m_marks; delete m_searcher; delete m_macroRecorder; delete m_completionRecorder; delete m_completionReplayer; delete m_lastChangeRecorder; } bool InputModeManager::handleKeypress(const QKeyEvent *e) { m_insideHandlingKeyPressCount++; bool res = false; bool keyIsPartOfMapping = false; const bool isSyntheticSearchCompletedKeyPress = m_inputAdapter->viModeEmulatedCommandBar()->isSendingSyntheticSearchCompletedKeypress(); // With macros, we want to record the keypresses *before* they are mapped, but if they end up *not* being part // of a mapping, we don't want to record them when they are played back by m_keyMapper, hence // the "!isPlayingBackRejectedKeys()". And obviously, since we're recording keys before they are mapped, we don't // want to also record the executed mapping, as when we replayed the macro, we'd get duplication! if (m_macroRecorder->isRecording() && !m_macroRecorder->isReplaying() && !isSyntheticSearchCompletedKeyPress && !keyMapper()->isExecutingMapping() && !keyMapper()->isPlayingBackRejectedKeys() && !lastChangeRecorder()->isReplaying()) { m_macroRecorder->record(*e); } if (!m_lastChangeRecorder->isReplaying() && !isSyntheticSearchCompletedKeyPress) { if (e->key() == Qt::Key_AltGr) { return true; // do nothing } // Hand off to the key mapper, and decide if this key is part of a mapping. if (e->key() != Qt::Key_Control && e->key() != Qt::Key_Shift && e->key() != Qt::Key_Alt && e->key() != Qt::Key_Meta) { const QChar key = KeyParser::self()->KeyEventToQChar(*e); if (keyMapper()->handleKeypress(key)) { keyIsPartOfMapping = true; res = true; } } } if (!keyIsPartOfMapping) { if (!m_lastChangeRecorder->isReplaying() && !isSyntheticSearchCompletedKeyPress) { // record key press so that it can be repeated via "." m_lastChangeRecorder->record(*e); } if (m_inputAdapter->viModeEmulatedCommandBar()->isActive()) { res = m_inputAdapter->viModeEmulatedCommandBar()->handleKeyPress(e); } else { res = getCurrentViModeHandler()->handleKeypress(e); } } m_insideHandlingKeyPressCount--; Q_ASSERT(m_insideHandlingKeyPressCount >= 0); return res; } void InputModeManager::feedKeyPresses(const QString &keyPresses) const { int key; Qt::KeyboardModifiers mods; QString text; foreach (QChar c, keyPresses) { QString decoded = KeyParser::self()->decodeKeySequence(QString(c)); key = -1; mods = Qt::NoModifier; text.clear(); if (decoded.length() > 1) { // special key // remove the angle brackets decoded.remove(0, 1); decoded.remove(decoded.indexOf(QLatin1String(">")), 1); // check if one or more modifier keys where used if (decoded.indexOf(QLatin1String("s-")) != -1 || decoded.indexOf(QLatin1String("c-")) != -1 || decoded.indexOf(QLatin1String("m-")) != -1 || decoded.indexOf(QLatin1String("a-")) != -1) { int s = decoded.indexOf(QLatin1String("s-")); if (s != -1) { mods |= Qt::ShiftModifier; decoded.remove(s, 2); } int c = decoded.indexOf(QLatin1String("c-")); if (c != -1) { mods |= Qt::ControlModifier; decoded.remove(c, 2); } int a = decoded.indexOf(QLatin1String("a-")); if (a != -1) { mods |= Qt::AltModifier; decoded.remove(a, 2); } int m = decoded.indexOf(QLatin1String("m-")); if (m != -1) { mods |= Qt::MetaModifier; decoded.remove(m, 2); } if (decoded.length() > 1) { key = KeyParser::self()->vi2qt(decoded); } else if (decoded.length() == 1) { key = int(decoded.at(0).toUpper().toLatin1()); text = decoded.at(0); } } else { // no modifiers key = KeyParser::self()->vi2qt(decoded); } } else { key = decoded.at(0).unicode(); text = decoded.at(0); } if (key == -1) continue; // We have to be clever about which widget we dispatch to, as we can trigger // shortcuts if we're not careful (even if Vim mode is configured to steal shortcuts). QKeyEvent k(QEvent::KeyPress, key, mods, text); - QWidget *destWidget = NULL; + QWidget *destWidget = nullptr; if (QApplication::activePopupWidget()) { // According to the docs, the activePopupWidget, if present, takes all events. destWidget = QApplication::activePopupWidget(); } else if (QApplication::focusWidget()) { if (QApplication::focusWidget()->focusProxy()) { destWidget = QApplication::focusWidget()->focusProxy(); } else { destWidget = QApplication::focusWidget(); } } else { destWidget = m_view->focusProxy(); } QApplication::sendEvent(destWidget, &k); } } bool InputModeManager::isHandlingKeypress() const { return m_insideHandlingKeyPressCount > 0; } void InputModeManager::storeLastChangeCommand() { m_lastChange = m_lastChangeRecorder->encodedChanges(); m_lastChangeCompletionsLog = m_completionRecorder->currentChangeCompletionsLog(); } void InputModeManager::repeatLastChange() { m_lastChangeRecorder->replay(m_lastChange, m_lastChangeCompletionsLog); } void InputModeManager::clearCurrentChangeLog() { m_lastChangeRecorder->clear(); m_completionRecorder->clearCurrentChangeCompletionsLog(); } void InputModeManager::doNotLogCurrentKeypress() { m_macroRecorder->dropLast(); m_lastChangeRecorder->dropLast(); } void InputModeManager::changeViMode(ViMode newMode) { m_previousViMode = m_currentViMode; m_currentViMode = newMode; } ViMode InputModeManager::getCurrentViMode() const { return m_currentViMode; } KTextEditor::View::ViewMode InputModeManager::getCurrentViewMode() const { switch (m_currentViMode) { case ViMode::InsertMode: return KTextEditor::View::ViModeInsert; case ViMode::VisualMode: return KTextEditor::View::ViModeVisual; case ViMode::VisualLineMode: return KTextEditor::View::ViModeVisualLine; case ViMode::VisualBlockMode: return KTextEditor::View::ViModeVisualBlock; case ViMode::ReplaceMode: return KTextEditor::View::ViModeReplace; case ViMode::NormalMode: default: return KTextEditor::View::ViModeNormal; } } ViMode InputModeManager::getPreviousViMode() const { return m_previousViMode; } bool InputModeManager::isAnyVisualMode() const { return ((m_currentViMode == ViMode::VisualMode) || (m_currentViMode == ViMode::VisualLineMode) || (m_currentViMode == ViMode::VisualBlockMode)); } ::ModeBase *InputModeManager::getCurrentViModeHandler() const { switch (m_currentViMode) { case ViMode::NormalMode: return m_viNormalMode; case ViMode::InsertMode: return m_viInsertMode; case ViMode::VisualMode: case ViMode::VisualLineMode: case ViMode::VisualBlockMode: return m_viVisualMode; case ViMode::ReplaceMode: return m_viReplaceMode; } - return NULL; + return nullptr; } void InputModeManager::viEnterNormalMode() { bool moveCursorLeft = (m_currentViMode == ViMode::InsertMode || m_currentViMode == ViMode::ReplaceMode) && m_viewInternal->primaryCursor().column() > 0; if (!m_lastChangeRecorder->isReplaying() && (m_currentViMode == ViMode::InsertMode || m_currentViMode == ViMode::ReplaceMode)) { // '^ is the insert mark and "^ is the insert register, // which holds the last inserted text KTextEditor::Range r(m_view->cursorPosition(), m_marks->getInsertStopped()); if (r.isValid()) { QString insertedText = m_view->doc()->text(r); m_inputAdapter->globalState()->registers()->setInsertStopped(insertedText); } m_marks->setInsertStopped(KTextEditor::Cursor(m_view->cursorPosition())); } changeViMode(ViMode::NormalMode); if (moveCursorLeft) { m_viewInternal->cursorPrevChar(); } m_inputAdapter->setCaretStyle(KateRenderer::Block); m_viewInternal->update(); } void InputModeManager::viEnterInsertMode() { changeViMode(ViMode::InsertMode); m_marks->setInsertStopped(KTextEditor::Cursor(m_view->cursorPosition())); if (getTemporaryNormalMode()) { // Ensure the key log contains a request to re-enter Insert mode, else the keystrokes made // after returning from temporary normal mode will be treated as commands! m_lastChangeRecorder->record(QKeyEvent(QEvent::KeyPress, Qt::Key_I, Qt::NoModifier, QStringLiteral("i"))); } m_inputAdapter->setCaretStyle(KateRenderer::Line); setTemporaryNormalMode(false); m_viewInternal->update(); } void InputModeManager::viEnterVisualMode(ViMode mode) { changeViMode(mode); // If the selection is inclusive, the caret should be a block. // If the selection is exclusive, the caret should be a line. m_inputAdapter->setCaretStyle(KateRenderer::Block); m_viewInternal->update(); getViVisualMode()->setVisualModeType(mode); getViVisualMode()->init(); } void InputModeManager::viEnterReplaceMode() { changeViMode(ViMode::ReplaceMode); m_marks->setStartEditYanked(KTextEditor::Cursor(m_view->cursorPosition())); m_inputAdapter->setCaretStyle(KateRenderer::Underline); m_viewInternal->update(); } NormalViMode *InputModeManager::getViNormalMode() { return m_viNormalMode; } InsertViMode *InputModeManager::getViInsertMode() { return m_viInsertMode; } VisualViMode *InputModeManager::getViVisualMode() { return m_viVisualMode; } ReplaceViMode *InputModeManager::getViReplaceMode() { return m_viReplaceMode; } const QString InputModeManager::getVerbatimKeys() const { QString cmd; switch (getCurrentViMode()) { case ViMode::NormalMode: cmd = m_viNormalMode->getVerbatimKeys(); break; case ViMode::InsertMode: case ViMode::ReplaceMode: // ... break; case ViMode::VisualMode: case ViMode::VisualLineMode: case ViMode::VisualBlockMode: cmd = m_viVisualMode->getVerbatimKeys(); break; } return cmd; } void InputModeManager::readSessionConfig(const KConfigGroup &config) { m_jumps->readSessionConfig(config); m_marks->readSessionConfig(config); } void InputModeManager::writeSessionConfig(KConfigGroup &config) { m_jumps->writeSessionConfig(config); m_marks->writeSessionConfig(config); } void InputModeManager::reset() { if (m_viVisualMode) { m_viVisualMode->reset(); } } KeyMapper *InputModeManager::keyMapper() { return m_keyMapperStack.top().data(); } void InputModeManager::updateCursor(const KTextEditor::Cursor &c) { m_inputAdapter->updateCursor(c); } GlobalState *InputModeManager::globalState() const { return m_inputAdapter->globalState(); } KTextEditor::ViewPrivate *InputModeManager::view() const { return m_view; } void InputModeManager::pushKeyMapper(QSharedPointer mapper) { m_keyMapperStack.push(mapper); } void InputModeManager::popKeyMapper() { m_keyMapperStack.pop(); } diff --git a/src/vimode/keyparser.cpp b/src/vimode/keyparser.cpp index f84114b1..936fd75d 100644 --- a/src/vimode/keyparser.cpp +++ b/src/vimode/keyparser.cpp @@ -1,694 +1,694 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008 Erlend Hamberg * Copyright (C) 2008 Evgeniy Ivanov * * 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 #include #include using namespace KateVi; -KeyParser *KeyParser::m_instance = NULL; +KeyParser *KeyParser::m_instance = nullptr; KeyParser::KeyParser() { initKeyTables(); } KeyParser *KeyParser::self() { - if (m_instance == NULL) { + if (m_instance == nullptr) { m_instance = new KeyParser(); } return m_instance; } void KeyParser::initKeyTables() { m_qt2katevi.insert(Qt::Key_Escape, QStringLiteral("esc")); m_qt2katevi.insert(Qt::Key_Tab, QStringLiteral("tab")); m_qt2katevi.insert(Qt::Key_Backtab, QStringLiteral("backtab")); m_qt2katevi.insert(Qt::Key_Backspace, QStringLiteral("backspace")); m_qt2katevi.insert(Qt::Key_Return, QStringLiteral("return")); m_qt2katevi.insert(Qt::Key_Enter, QStringLiteral("enter")); m_qt2katevi.insert(Qt::Key_Insert, QStringLiteral("insert")); m_qt2katevi.insert(Qt::Key_Delete, QStringLiteral("delete")); m_qt2katevi.insert(Qt::Key_Pause, QStringLiteral("pause")); m_qt2katevi.insert(Qt::Key_Print, QStringLiteral("print")); m_qt2katevi.insert(Qt::Key_SysReq, QStringLiteral("sysreq")); m_qt2katevi.insert(Qt::Key_Clear, QStringLiteral("clear")); m_qt2katevi.insert(Qt::Key_Home, QStringLiteral("home")); m_qt2katevi.insert(Qt::Key_End, QStringLiteral("end")); m_qt2katevi.insert(Qt::Key_Left, QStringLiteral("left")); m_qt2katevi.insert(Qt::Key_Up, QStringLiteral("up")); m_qt2katevi.insert(Qt::Key_Right, QStringLiteral("right")); m_qt2katevi.insert(Qt::Key_Down, QStringLiteral("down")); m_qt2katevi.insert(Qt::Key_PageUp, QStringLiteral("pageup")); m_qt2katevi.insert(Qt::Key_PageDown, QStringLiteral("pagedown")); m_qt2katevi.insert(Qt::Key_Shift, QStringLiteral("shift")); m_qt2katevi.insert(Qt::Key_Control, QStringLiteral("control")); m_qt2katevi.insert(Qt::Key_Meta, QStringLiteral("meta")); m_qt2katevi.insert(Qt::Key_Alt, QStringLiteral("alt")); m_qt2katevi.insert(Qt::Key_AltGr, QStringLiteral("altgr")); m_qt2katevi.insert(Qt::Key_CapsLock, QStringLiteral("capslock")); m_qt2katevi.insert(Qt::Key_NumLock, QStringLiteral("numlock")); m_qt2katevi.insert(Qt::Key_ScrollLock, QStringLiteral("scrolllock")); m_qt2katevi.insert(Qt::Key_F1, QStringLiteral("f1")); m_qt2katevi.insert(Qt::Key_F2, QStringLiteral("f2")); m_qt2katevi.insert(Qt::Key_F3, QStringLiteral("f3")); m_qt2katevi.insert(Qt::Key_F4, QStringLiteral("f4")); m_qt2katevi.insert(Qt::Key_F5, QStringLiteral("f5")); m_qt2katevi.insert(Qt::Key_F6, QStringLiteral("f6")); m_qt2katevi.insert(Qt::Key_F7, QStringLiteral("f7")); m_qt2katevi.insert(Qt::Key_F8, QStringLiteral("f8")); m_qt2katevi.insert(Qt::Key_F9, QStringLiteral("f9")); m_qt2katevi.insert(Qt::Key_F10, QStringLiteral("f10")); m_qt2katevi.insert(Qt::Key_F11, QStringLiteral("f11")); m_qt2katevi.insert(Qt::Key_F12, QStringLiteral("f12")); m_qt2katevi.insert(Qt::Key_F13, QStringLiteral("f13")); m_qt2katevi.insert(Qt::Key_F14, QStringLiteral("f14")); m_qt2katevi.insert(Qt::Key_F15, QStringLiteral("f15")); m_qt2katevi.insert(Qt::Key_F16, QStringLiteral("f16")); m_qt2katevi.insert(Qt::Key_F17, QStringLiteral("f17")); m_qt2katevi.insert(Qt::Key_F18, QStringLiteral("f18")); m_qt2katevi.insert(Qt::Key_F19, QStringLiteral("f19")); m_qt2katevi.insert(Qt::Key_F20, QStringLiteral("f20")); m_qt2katevi.insert(Qt::Key_F21, QStringLiteral("f21")); m_qt2katevi.insert(Qt::Key_F22, QStringLiteral("f22")); m_qt2katevi.insert(Qt::Key_F23, QStringLiteral("f23")); m_qt2katevi.insert(Qt::Key_F24, QStringLiteral("f24")); m_qt2katevi.insert(Qt::Key_F25, QStringLiteral("f25")); m_qt2katevi.insert(Qt::Key_F26, QStringLiteral("f26")); m_qt2katevi.insert(Qt::Key_F27, QStringLiteral("f27")); m_qt2katevi.insert(Qt::Key_F28, QStringLiteral("f28")); m_qt2katevi.insert(Qt::Key_F29, QStringLiteral("f29")); m_qt2katevi.insert(Qt::Key_F30, QStringLiteral("f30")); m_qt2katevi.insert(Qt::Key_F31, QStringLiteral("f31")); m_qt2katevi.insert(Qt::Key_F32, QStringLiteral("f32")); m_qt2katevi.insert(Qt::Key_F33, QStringLiteral("f33")); m_qt2katevi.insert(Qt::Key_F34, QStringLiteral("f34")); m_qt2katevi.insert(Qt::Key_F35, QStringLiteral("f35")); m_qt2katevi.insert(Qt::Key_Super_L, QStringLiteral("super_l")); m_qt2katevi.insert(Qt::Key_Super_R, QStringLiteral("super_r")); m_qt2katevi.insert(Qt::Key_Menu, QStringLiteral("menu")); m_qt2katevi.insert(Qt::Key_Hyper_L, QStringLiteral("hyper_l")); m_qt2katevi.insert(Qt::Key_Hyper_R, QStringLiteral("hyper_r")); m_qt2katevi.insert(Qt::Key_Help, QStringLiteral("help")); m_qt2katevi.insert(Qt::Key_Direction_L, QStringLiteral("direction_l")); m_qt2katevi.insert(Qt::Key_Direction_R, QStringLiteral("direction_r")); m_qt2katevi.insert(Qt::Key_Multi_key, QStringLiteral("multi_key")); m_qt2katevi.insert(Qt::Key_Codeinput, QStringLiteral("codeinput")); m_qt2katevi.insert(Qt::Key_SingleCandidate, QStringLiteral("singlecandidate")); m_qt2katevi.insert(Qt::Key_MultipleCandidate, QStringLiteral("multiplecandidate")); m_qt2katevi.insert(Qt::Key_PreviousCandidate, QStringLiteral("previouscandidate")); m_qt2katevi.insert(Qt::Key_Mode_switch, QStringLiteral("mode_switch")); m_qt2katevi.insert(Qt::Key_Kanji, QStringLiteral("kanji")); m_qt2katevi.insert(Qt::Key_Muhenkan, QStringLiteral("muhenkan")); m_qt2katevi.insert(Qt::Key_Henkan, QStringLiteral("henkan")); m_qt2katevi.insert(Qt::Key_Romaji, QStringLiteral("romaji")); m_qt2katevi.insert(Qt::Key_Hiragana, QStringLiteral("hiragana")); m_qt2katevi.insert(Qt::Key_Katakana, QStringLiteral("katakana")); m_qt2katevi.insert(Qt::Key_Hiragana_Katakana, QStringLiteral("hiragana_katakana")); m_qt2katevi.insert(Qt::Key_Zenkaku, QStringLiteral("zenkaku")); m_qt2katevi.insert(Qt::Key_Hankaku, QStringLiteral("hankaku")); m_qt2katevi.insert(Qt::Key_Zenkaku_Hankaku, QStringLiteral("zenkaku_hankaku")); m_qt2katevi.insert(Qt::Key_Touroku, QStringLiteral("touroku")); m_qt2katevi.insert(Qt::Key_Massyo, QStringLiteral("massyo")); m_qt2katevi.insert(Qt::Key_Kana_Lock, QStringLiteral("kana_lock")); m_qt2katevi.insert(Qt::Key_Kana_Shift, QStringLiteral("kana_shift")); m_qt2katevi.insert(Qt::Key_Eisu_Shift, QStringLiteral("eisu_shift")); m_qt2katevi.insert(Qt::Key_Eisu_toggle, QStringLiteral("eisu_toggle")); m_qt2katevi.insert(Qt::Key_Hangul, QStringLiteral("hangul")); m_qt2katevi.insert(Qt::Key_Hangul_Start, QStringLiteral("hangul_start")); m_qt2katevi.insert(Qt::Key_Hangul_End, QStringLiteral("hangul_end")); m_qt2katevi.insert(Qt::Key_Hangul_Hanja, QStringLiteral("hangul_hanja")); m_qt2katevi.insert(Qt::Key_Hangul_Jamo, QStringLiteral("hangul_jamo")); m_qt2katevi.insert(Qt::Key_Hangul_Romaja, QStringLiteral("hangul_romaja")); m_qt2katevi.insert(Qt::Key_Hangul_Jeonja, QStringLiteral("hangul_jeonja")); m_qt2katevi.insert(Qt::Key_Hangul_Banja, QStringLiteral("hangul_banja")); m_qt2katevi.insert(Qt::Key_Hangul_PreHanja, QStringLiteral("hangul_prehanja")); m_qt2katevi.insert(Qt::Key_Hangul_PostHanja, QStringLiteral("hangul_posthanja")); m_qt2katevi.insert(Qt::Key_Hangul_Special, QStringLiteral("hangul_special")); m_qt2katevi.insert(Qt::Key_Dead_Grave, QStringLiteral("dead_grave")); m_qt2katevi.insert(Qt::Key_Dead_Acute, QStringLiteral("dead_acute")); m_qt2katevi.insert(Qt::Key_Dead_Circumflex, QStringLiteral("dead_circumflex")); m_qt2katevi.insert(Qt::Key_Dead_Tilde, QStringLiteral("dead_tilde")); m_qt2katevi.insert(Qt::Key_Dead_Macron, QStringLiteral("dead_macron")); m_qt2katevi.insert(Qt::Key_Dead_Breve, QStringLiteral("dead_breve")); m_qt2katevi.insert(Qt::Key_Dead_Abovedot, QStringLiteral("dead_abovedot")); m_qt2katevi.insert(Qt::Key_Dead_Diaeresis, QStringLiteral("dead_diaeresis")); m_qt2katevi.insert(Qt::Key_Dead_Abovering, QStringLiteral("dead_abovering")); m_qt2katevi.insert(Qt::Key_Dead_Doubleacute, QStringLiteral("dead_doubleacute")); m_qt2katevi.insert(Qt::Key_Dead_Caron, QStringLiteral("dead_caron")); m_qt2katevi.insert(Qt::Key_Dead_Cedilla, QStringLiteral("dead_cedilla")); m_qt2katevi.insert(Qt::Key_Dead_Ogonek, QStringLiteral("dead_ogonek")); m_qt2katevi.insert(Qt::Key_Dead_Iota, QStringLiteral("dead_iota")); m_qt2katevi.insert(Qt::Key_Dead_Voiced_Sound, QStringLiteral("dead_voiced_sound")); m_qt2katevi.insert(Qt::Key_Dead_Semivoiced_Sound, QStringLiteral("dead_semivoiced_sound")); m_qt2katevi.insert(Qt::Key_Dead_Belowdot, QStringLiteral("dead_belowdot")); m_qt2katevi.insert(Qt::Key_Dead_Hook, QStringLiteral("dead_hook")); m_qt2katevi.insert(Qt::Key_Dead_Horn, QStringLiteral("dead_horn")); m_qt2katevi.insert(Qt::Key_Back, QStringLiteral("back")); m_qt2katevi.insert(Qt::Key_Forward, QStringLiteral("forward")); m_qt2katevi.insert(Qt::Key_Stop, QStringLiteral("stop")); m_qt2katevi.insert(Qt::Key_Refresh, QStringLiteral("refresh")); m_qt2katevi.insert(Qt::Key_VolumeDown, QStringLiteral("volumedown")); m_qt2katevi.insert(Qt::Key_VolumeMute, QStringLiteral("volumemute")); m_qt2katevi.insert(Qt::Key_VolumeUp, QStringLiteral("volumeup")); m_qt2katevi.insert(Qt::Key_BassBoost, QStringLiteral("bassboost")); m_qt2katevi.insert(Qt::Key_BassUp, QStringLiteral("bassup")); m_qt2katevi.insert(Qt::Key_BassDown, QStringLiteral("bassdown")); m_qt2katevi.insert(Qt::Key_TrebleUp, QStringLiteral("trebleup")); m_qt2katevi.insert(Qt::Key_TrebleDown, QStringLiteral("trebledown")); m_qt2katevi.insert(Qt::Key_MediaPlay, QStringLiteral("mediaplay")); m_qt2katevi.insert(Qt::Key_MediaStop, QStringLiteral("mediastop")); m_qt2katevi.insert(Qt::Key_MediaPrevious, QStringLiteral("mediaprevious")); m_qt2katevi.insert(Qt::Key_MediaNext, QStringLiteral("medianext")); m_qt2katevi.insert(Qt::Key_MediaRecord, QStringLiteral("mediarecord")); m_qt2katevi.insert(Qt::Key_HomePage, QStringLiteral("homepage")); m_qt2katevi.insert(Qt::Key_Favorites, QStringLiteral("favorites")); m_qt2katevi.insert(Qt::Key_Search, QStringLiteral("search")); m_qt2katevi.insert(Qt::Key_Standby, QStringLiteral("standby")); m_qt2katevi.insert(Qt::Key_OpenUrl, QStringLiteral("openurl")); m_qt2katevi.insert(Qt::Key_LaunchMail, QStringLiteral("launchmail")); m_qt2katevi.insert(Qt::Key_LaunchMedia, QStringLiteral("launchmedia")); m_qt2katevi.insert(Qt::Key_Launch0, QStringLiteral("launch0")); m_qt2katevi.insert(Qt::Key_Launch1, QStringLiteral("launch1")); m_qt2katevi.insert(Qt::Key_Launch2, QStringLiteral("launch2")); m_qt2katevi.insert(Qt::Key_Launch3, QStringLiteral("launch3")); m_qt2katevi.insert(Qt::Key_Launch4, QStringLiteral("launch4")); m_qt2katevi.insert(Qt::Key_Launch5, QStringLiteral("launch5")); m_qt2katevi.insert(Qt::Key_Launch6, QStringLiteral("launch6")); m_qt2katevi.insert(Qt::Key_Launch7, QStringLiteral("launch7")); m_qt2katevi.insert(Qt::Key_Launch8, QStringLiteral("launch8")); m_qt2katevi.insert(Qt::Key_Launch9, QStringLiteral("launch9")); m_qt2katevi.insert(Qt::Key_LaunchA, QStringLiteral("launcha")); m_qt2katevi.insert(Qt::Key_LaunchB, QStringLiteral("launchb")); m_qt2katevi.insert(Qt::Key_LaunchC, QStringLiteral("launchc")); m_qt2katevi.insert(Qt::Key_LaunchD, QStringLiteral("launchd")); m_qt2katevi.insert(Qt::Key_LaunchE, QStringLiteral("launche")); m_qt2katevi.insert(Qt::Key_LaunchF, QStringLiteral("launchf")); m_qt2katevi.insert(Qt::Key_MediaLast, QStringLiteral("medialast")); m_qt2katevi.insert(Qt::Key_unknown, QStringLiteral("unknown")); m_qt2katevi.insert(Qt::Key_Call, QStringLiteral("call")); m_qt2katevi.insert(Qt::Key_Context1, QStringLiteral("context1")); m_qt2katevi.insert(Qt::Key_Context2, QStringLiteral("context2")); m_qt2katevi.insert(Qt::Key_Context3, QStringLiteral("context3")); m_qt2katevi.insert(Qt::Key_Context4, QStringLiteral("context4")); m_qt2katevi.insert(Qt::Key_Flip, QStringLiteral("flip")); m_qt2katevi.insert(Qt::Key_Hangup, QStringLiteral("hangup")); m_qt2katevi.insert(Qt::Key_No, QStringLiteral("no")); m_qt2katevi.insert(Qt::Key_Select, QStringLiteral("select")); m_qt2katevi.insert(Qt::Key_Yes, QStringLiteral("yes")); m_qt2katevi.insert(Qt::Key_Execute, QStringLiteral("execute")); m_qt2katevi.insert(Qt::Key_Printer, QStringLiteral("printer")); m_qt2katevi.insert(Qt::Key_Play, QStringLiteral("play")); m_qt2katevi.insert(Qt::Key_Sleep, QStringLiteral("sleep")); m_qt2katevi.insert(Qt::Key_Zoom, QStringLiteral("zoom")); m_qt2katevi.insert(Qt::Key_Cancel, QStringLiteral("cancel")); for (QHash::const_iterator i = m_qt2katevi.constBegin(); i != m_qt2katevi.constEnd(); ++i) { m_katevi2qt.insert(i.value(), i.key()); } m_katevi2qt.insert(QStringLiteral("cr"), Qt::Key_Enter); m_nameToKeyCode.insert(QStringLiteral("invalid"), -1); m_nameToKeyCode.insert(QStringLiteral("esc"), 1); m_nameToKeyCode.insert(QStringLiteral("tab"), 2); m_nameToKeyCode.insert(QStringLiteral("backtab"), 3); m_nameToKeyCode.insert(QStringLiteral("backspace"), 4); m_nameToKeyCode.insert(QStringLiteral("return"), 5); m_nameToKeyCode.insert(QStringLiteral("enter"), 6); m_nameToKeyCode.insert(QStringLiteral("insert"), 7); m_nameToKeyCode.insert(QStringLiteral("delete"), 8); m_nameToKeyCode.insert(QStringLiteral("pause"), 9); m_nameToKeyCode.insert(QStringLiteral("print"), 10); m_nameToKeyCode.insert(QStringLiteral("sysreq"), 11); m_nameToKeyCode.insert(QStringLiteral("clear"), 12); m_nameToKeyCode.insert(QStringLiteral("home"), 13); m_nameToKeyCode.insert(QStringLiteral("end"), 14); m_nameToKeyCode.insert(QStringLiteral("left"), 15); m_nameToKeyCode.insert(QStringLiteral("up"), 16); m_nameToKeyCode.insert(QStringLiteral("right"), 17); m_nameToKeyCode.insert(QStringLiteral("down"), 18); m_nameToKeyCode.insert(QStringLiteral("pageup"), 19); m_nameToKeyCode.insert(QStringLiteral("pagedown"), 20); m_nameToKeyCode.insert(QStringLiteral("shift"), 21); m_nameToKeyCode.insert(QStringLiteral("control"), 22); m_nameToKeyCode.insert(QStringLiteral("meta"), 23); m_nameToKeyCode.insert(QStringLiteral("alt"), 24); m_nameToKeyCode.insert(QStringLiteral("altgr"), 25); m_nameToKeyCode.insert(QStringLiteral("capslock"), 26); m_nameToKeyCode.insert(QStringLiteral("numlock"), 27); m_nameToKeyCode.insert(QStringLiteral("scrolllock"), 28); m_nameToKeyCode.insert(QStringLiteral("f1"), 29); m_nameToKeyCode.insert(QStringLiteral("f2"), 30); m_nameToKeyCode.insert(QStringLiteral("f3"), 31); m_nameToKeyCode.insert(QStringLiteral("f4"), 32); m_nameToKeyCode.insert(QStringLiteral("f5"), 33); m_nameToKeyCode.insert(QStringLiteral("f6"), 34); m_nameToKeyCode.insert(QStringLiteral("f7"), 35); m_nameToKeyCode.insert(QStringLiteral("f8"), 36); m_nameToKeyCode.insert(QStringLiteral("f9"), 37); m_nameToKeyCode.insert(QStringLiteral("f10"), 38); m_nameToKeyCode.insert(QStringLiteral("f11"), 39); m_nameToKeyCode.insert(QStringLiteral("f12"), 40); m_nameToKeyCode.insert(QStringLiteral("f13"), 41); m_nameToKeyCode.insert(QStringLiteral("f14"), 42); m_nameToKeyCode.insert(QStringLiteral("f15"), 43); m_nameToKeyCode.insert(QStringLiteral("f16"), 44); m_nameToKeyCode.insert(QStringLiteral("f17"), 45); m_nameToKeyCode.insert(QStringLiteral("f18"), 46); m_nameToKeyCode.insert(QStringLiteral("f19"), 47); m_nameToKeyCode.insert(QStringLiteral("f20"), 48); m_nameToKeyCode.insert(QStringLiteral("f21"), 49); m_nameToKeyCode.insert(QStringLiteral("f22"), 50); m_nameToKeyCode.insert(QStringLiteral("f23"), 51); m_nameToKeyCode.insert(QStringLiteral("f24"), 52); m_nameToKeyCode.insert(QStringLiteral("f25"), 53); m_nameToKeyCode.insert(QStringLiteral("f26"), 54); m_nameToKeyCode.insert(QStringLiteral("f27"), 55); m_nameToKeyCode.insert(QStringLiteral("f28"), 56); m_nameToKeyCode.insert(QStringLiteral("f29"), 57); m_nameToKeyCode.insert(QStringLiteral("f30"), 58); m_nameToKeyCode.insert(QStringLiteral("f31"), 59); m_nameToKeyCode.insert(QStringLiteral("f32"), 60); m_nameToKeyCode.insert(QStringLiteral("f33"), 61); m_nameToKeyCode.insert(QStringLiteral("f34"), 62); m_nameToKeyCode.insert(QStringLiteral("f35"), 63); m_nameToKeyCode.insert(QStringLiteral("super_l"), 64); m_nameToKeyCode.insert(QStringLiteral("super_r"), 65); m_nameToKeyCode.insert(QStringLiteral("menu"), 66); m_nameToKeyCode.insert(QStringLiteral("hyper_l"), 67); m_nameToKeyCode.insert(QStringLiteral("hyper_r"), 68); m_nameToKeyCode.insert(QStringLiteral("help"), 69); m_nameToKeyCode.insert(QStringLiteral("direction_l"), 70); m_nameToKeyCode.insert(QStringLiteral("direction_r"), 71); m_nameToKeyCode.insert(QStringLiteral("multi_key"), 172); m_nameToKeyCode.insert(QStringLiteral("codeinput"), 173); m_nameToKeyCode.insert(QStringLiteral("singlecandidate"), 174); m_nameToKeyCode.insert(QStringLiteral("multiplecandidate"), 175); m_nameToKeyCode.insert(QStringLiteral("previouscandidate"), 176); m_nameToKeyCode.insert(QStringLiteral("mode_switch"), 177); m_nameToKeyCode.insert(QStringLiteral("kanji"), 178); m_nameToKeyCode.insert(QStringLiteral("muhenkan"), 179); m_nameToKeyCode.insert(QStringLiteral("henkan"), 180); m_nameToKeyCode.insert(QStringLiteral("romaji"), 181); m_nameToKeyCode.insert(QStringLiteral("hiragana"), 182); m_nameToKeyCode.insert(QStringLiteral("katakana"), 183); m_nameToKeyCode.insert(QStringLiteral("hiragana_katakana"), 184); m_nameToKeyCode.insert(QStringLiteral("zenkaku"), 185); m_nameToKeyCode.insert(QStringLiteral("hankaku"), 186); m_nameToKeyCode.insert(QStringLiteral("zenkaku_hankaku"), 187); m_nameToKeyCode.insert(QStringLiteral("touroku"), 188); m_nameToKeyCode.insert(QStringLiteral("massyo"), 189); m_nameToKeyCode.insert(QStringLiteral("kana_lock"), 190); m_nameToKeyCode.insert(QStringLiteral("kana_shift"), 191); m_nameToKeyCode.insert(QStringLiteral("eisu_shift"), 192); m_nameToKeyCode.insert(QStringLiteral("eisu_toggle"), 193); m_nameToKeyCode.insert(QStringLiteral("hangul"), 194); m_nameToKeyCode.insert(QStringLiteral("hangul_start"), 195); m_nameToKeyCode.insert(QStringLiteral("hangul_end"), 196); m_nameToKeyCode.insert(QStringLiteral("hangul_hanja"), 197); m_nameToKeyCode.insert(QStringLiteral("hangul_jamo"), 198); m_nameToKeyCode.insert(QStringLiteral("hangul_romaja"), 199); m_nameToKeyCode.insert(QStringLiteral("hangul_jeonja"), 200); m_nameToKeyCode.insert(QStringLiteral("hangul_banja"), 201); m_nameToKeyCode.insert(QStringLiteral("hangul_prehanja"), 202); m_nameToKeyCode.insert(QStringLiteral("hangul_posthanja"), 203); m_nameToKeyCode.insert(QStringLiteral("hangul_special"), 204); m_nameToKeyCode.insert(QStringLiteral("dead_grave"), 205); m_nameToKeyCode.insert(QStringLiteral("dead_acute"), 206); m_nameToKeyCode.insert(QStringLiteral("dead_circumflex"), 207); m_nameToKeyCode.insert(QStringLiteral("dead_tilde"), 208); m_nameToKeyCode.insert(QStringLiteral("dead_macron"), 209); m_nameToKeyCode.insert(QStringLiteral("dead_breve"), 210); m_nameToKeyCode.insert(QStringLiteral("dead_abovedot"), 211); m_nameToKeyCode.insert(QStringLiteral("dead_diaeresis"), 212); m_nameToKeyCode.insert(QStringLiteral("dead_abovering"), 213); m_nameToKeyCode.insert(QStringLiteral("dead_doubleacute"), 214); m_nameToKeyCode.insert(QStringLiteral("dead_caron"), 215); m_nameToKeyCode.insert(QStringLiteral("dead_cedilla"), 216); m_nameToKeyCode.insert(QStringLiteral("dead_ogonek"), 217); m_nameToKeyCode.insert(QStringLiteral("dead_iota"), 218); m_nameToKeyCode.insert(QStringLiteral("dead_voiced_sound"), 219); m_nameToKeyCode.insert(QStringLiteral("dead_semivoiced_sound"), 220); m_nameToKeyCode.insert(QStringLiteral("dead_belowdot"), 221); m_nameToKeyCode.insert(QStringLiteral("dead_hook"), 222); m_nameToKeyCode.insert(QStringLiteral("dead_horn"), 223); m_nameToKeyCode.insert(QStringLiteral("back"), 224); m_nameToKeyCode.insert(QStringLiteral("forward"), 225); m_nameToKeyCode.insert(QStringLiteral("stop"), 226); m_nameToKeyCode.insert(QStringLiteral("refresh"), 227); m_nameToKeyCode.insert(QStringLiteral("volumedown"), 228); m_nameToKeyCode.insert(QStringLiteral("volumemute"), 229); m_nameToKeyCode.insert(QStringLiteral("volumeup"), 230); m_nameToKeyCode.insert(QStringLiteral("bassboost"), 231); m_nameToKeyCode.insert(QStringLiteral("bassup"), 232); m_nameToKeyCode.insert(QStringLiteral("bassdown"), 233); m_nameToKeyCode.insert(QStringLiteral("trebleup"), 234); m_nameToKeyCode.insert(QStringLiteral("trebledown"), 235); m_nameToKeyCode.insert(QStringLiteral("mediaplay"), 236); m_nameToKeyCode.insert(QStringLiteral("mediastop"), 237); m_nameToKeyCode.insert(QStringLiteral("mediaprevious"), 238); m_nameToKeyCode.insert(QStringLiteral("medianext"), 239); m_nameToKeyCode.insert(QStringLiteral("mediarecord"), 240); m_nameToKeyCode.insert(QStringLiteral("homepage"), 241); m_nameToKeyCode.insert(QStringLiteral("favorites"), 242); m_nameToKeyCode.insert(QStringLiteral("search"), 243); m_nameToKeyCode.insert(QStringLiteral("standby"), 244); m_nameToKeyCode.insert(QStringLiteral("openurl"), 245); m_nameToKeyCode.insert(QStringLiteral("launchmail"), 246); m_nameToKeyCode.insert(QStringLiteral("launchmedia"), 247); m_nameToKeyCode.insert(QStringLiteral("launch0"), 248); m_nameToKeyCode.insert(QStringLiteral("launch1"), 249); m_nameToKeyCode.insert(QStringLiteral("launch2"), 250); m_nameToKeyCode.insert(QStringLiteral("launch3"), 251); m_nameToKeyCode.insert(QStringLiteral("launch4"), 252); m_nameToKeyCode.insert(QStringLiteral("launch5"), 253); m_nameToKeyCode.insert(QStringLiteral("launch6"), 254); m_nameToKeyCode.insert(QStringLiteral("launch7"), 255); m_nameToKeyCode.insert(QStringLiteral("launch8"), 256); m_nameToKeyCode.insert(QStringLiteral("launch9"), 257); m_nameToKeyCode.insert(QStringLiteral("launcha"), 258); m_nameToKeyCode.insert(QStringLiteral("launchb"), 259); m_nameToKeyCode.insert(QStringLiteral("launchc"), 260); m_nameToKeyCode.insert(QStringLiteral("launchd"), 261); m_nameToKeyCode.insert(QStringLiteral("launche"), 262); m_nameToKeyCode.insert(QStringLiteral("launchf"), 263); m_nameToKeyCode.insert(QStringLiteral("medialast"), 264); m_nameToKeyCode.insert(QStringLiteral("unknown"), 265); m_nameToKeyCode.insert(QStringLiteral("call"), 266); m_nameToKeyCode.insert(QStringLiteral("context1"), 267); m_nameToKeyCode.insert(QStringLiteral("context2"), 268); m_nameToKeyCode.insert(QStringLiteral("context3"), 269); m_nameToKeyCode.insert(QStringLiteral("context4"), 270); m_nameToKeyCode.insert(QStringLiteral("flip"), 271); m_nameToKeyCode.insert(QStringLiteral("hangup"), 272); m_nameToKeyCode.insert(QStringLiteral("no"), 273); m_nameToKeyCode.insert(QStringLiteral("select"), 274); m_nameToKeyCode.insert(QStringLiteral("yes"), 275); m_nameToKeyCode.insert(QStringLiteral("execute"), 276); m_nameToKeyCode.insert(QStringLiteral("printer"), 277); m_nameToKeyCode.insert(QStringLiteral("play"), 278); m_nameToKeyCode.insert(QStringLiteral("sleep"), 279); m_nameToKeyCode.insert(QStringLiteral("zoom"), 280); m_nameToKeyCode.insert(QStringLiteral("cancel"), 281); m_nameToKeyCode.insert(QStringLiteral("a"), 282); m_nameToKeyCode.insert(QStringLiteral("b"), 283); m_nameToKeyCode.insert(QStringLiteral("c"), 284); m_nameToKeyCode.insert(QStringLiteral("d"), 285); m_nameToKeyCode.insert(QStringLiteral("e"), 286); m_nameToKeyCode.insert(QStringLiteral("f"), 287); m_nameToKeyCode.insert(QStringLiteral("g"), 288); m_nameToKeyCode.insert(QStringLiteral("h"), 289); m_nameToKeyCode.insert(QStringLiteral("i"), 290); m_nameToKeyCode.insert(QStringLiteral("j"), 291); m_nameToKeyCode.insert(QStringLiteral("k"), 292); m_nameToKeyCode.insert(QStringLiteral("l"), 293); m_nameToKeyCode.insert(QStringLiteral("m"), 294); m_nameToKeyCode.insert(QStringLiteral("n"), 295); m_nameToKeyCode.insert(QStringLiteral("o"), 296); m_nameToKeyCode.insert(QStringLiteral("p"), 297); m_nameToKeyCode.insert(QStringLiteral("q"), 298); m_nameToKeyCode.insert(QStringLiteral("r"), 299); m_nameToKeyCode.insert(QStringLiteral("s"), 300); m_nameToKeyCode.insert(QStringLiteral("t"), 301); m_nameToKeyCode.insert(QStringLiteral("u"), 302); m_nameToKeyCode.insert(QStringLiteral("v"), 303); m_nameToKeyCode.insert(QStringLiteral("w"), 304); m_nameToKeyCode.insert(QStringLiteral("x"), 305); m_nameToKeyCode.insert(QStringLiteral("y"), 306); m_nameToKeyCode.insert(QStringLiteral("z"), 307); m_nameToKeyCode.insert(QStringLiteral("`"), 308); m_nameToKeyCode.insert(QStringLiteral("!"), 309); m_nameToKeyCode.insert(QStringLiteral("\""), 310); m_nameToKeyCode.insert(QStringLiteral("$"), 311); m_nameToKeyCode.insert(QStringLiteral("%"), 312); m_nameToKeyCode.insert(QStringLiteral("^"), 313); m_nameToKeyCode.insert(QStringLiteral("&"), 314); m_nameToKeyCode.insert(QStringLiteral("*"), 315); m_nameToKeyCode.insert(QStringLiteral("("), 316); m_nameToKeyCode.insert(QStringLiteral(")"), 317); m_nameToKeyCode.insert(QStringLiteral("-"), 318); m_nameToKeyCode.insert(QStringLiteral("_"), 319); m_nameToKeyCode.insert(QStringLiteral("="), 320); m_nameToKeyCode.insert(QStringLiteral("+"), 321); m_nameToKeyCode.insert(QStringLiteral("["), 322); m_nameToKeyCode.insert(QStringLiteral("]"), 323); m_nameToKeyCode.insert(QStringLiteral("{"), 324); m_nameToKeyCode.insert(QStringLiteral("}"), 325); m_nameToKeyCode.insert(QStringLiteral(":"), 326); m_nameToKeyCode.insert(QStringLiteral(";"), 327); m_nameToKeyCode.insert(QStringLiteral("@"), 328); m_nameToKeyCode.insert(QStringLiteral("'"), 329); m_nameToKeyCode.insert(QStringLiteral("#"), 330); m_nameToKeyCode.insert(QStringLiteral("~"), 331); m_nameToKeyCode.insert(QStringLiteral("\\"), 332); m_nameToKeyCode.insert(QStringLiteral("|"), 333); m_nameToKeyCode.insert(QStringLiteral(","), 334); m_nameToKeyCode.insert(QStringLiteral("."), 335); //m_nameToKeyCode.insert( QLatin1String( ">" ), 336 ); m_nameToKeyCode.insert(QStringLiteral("/"), 337); m_nameToKeyCode.insert(QStringLiteral("?"), 338); m_nameToKeyCode.insert(QStringLiteral(" "), 339); //m_nameToKeyCode.insert( QLatin1String( "<" ), 341 ); m_nameToKeyCode.insert(QStringLiteral("0"), 340); m_nameToKeyCode.insert(QStringLiteral("1"), 341); m_nameToKeyCode.insert(QStringLiteral("2"), 342); m_nameToKeyCode.insert(QStringLiteral("3"), 343); m_nameToKeyCode.insert(QStringLiteral("4"), 344); m_nameToKeyCode.insert(QStringLiteral("5"), 345); m_nameToKeyCode.insert(QStringLiteral("6"), 346); m_nameToKeyCode.insert(QStringLiteral("7"), 347); m_nameToKeyCode.insert(QStringLiteral("8"), 348); m_nameToKeyCode.insert(QStringLiteral("9"), 349); m_nameToKeyCode.insert(QStringLiteral("cr"), 350); m_nameToKeyCode.insert(QStringLiteral("leader"), 351); m_nameToKeyCode.insert(QStringLiteral("nop"), 352); for (QHash::const_iterator i = m_nameToKeyCode.constBegin(); i != m_nameToKeyCode.constEnd(); ++i) { m_keyCodeToName.insert(i.value(), i.key()); } } QString KeyParser::qt2vi(int key) const { return (m_qt2katevi.contains(key) ? m_qt2katevi.value(key) : QStringLiteral("invalid")); } int KeyParser::vi2qt(const QString &keypress) const { return (m_katevi2qt.contains(keypress) ? m_katevi2qt.value(keypress) : -1); } int KeyParser::encoded2qt(const QString &keypress) const { QString key = KeyParser::self()->decodeKeySequence(keypress); if (key.length() > 2 && key[0] == QLatin1Char('<') && key[key.length() - 1] == QLatin1Char('>')) { key = key.mid(1, key.length() - 2); } return (m_katevi2qt.contains(key) ? m_katevi2qt.value(key) : -1); } const QString KeyParser::encodeKeySequence(const QString &keys) const { QString encodedSequence; unsigned int keyCodeTemp = 0; bool insideTag = false; QChar c; for (int i = 0; i < keys.length(); i++) { c = keys.at(i); if (insideTag) { if (c == QLatin1Char('>')) { QString temp; temp.setNum(0xE000 + keyCodeTemp, 16); QChar code(0xE000 + keyCodeTemp); encodedSequence.append(code); keyCodeTemp = 0; insideTag = false; continue; } else { // contains modifiers if (keys.midRef(i).indexOf(QLatin1Char('-')) != -1 && keys.midRef(i).indexOf(QLatin1Char('-')) < keys.midRef(i).indexOf(QLatin1Char('>'))) { // Perform something similar to a split on '-', except that we want to keep the occurrences of '-' // e.g. will equate to the list of tokens "c-", "s-", "a". // A straight split on '-' would give us "c", "s", "a", in which case we lose the piece of information that // 'a' is just the 'a' key, not the 'alt' modifier. const QString untilClosing = keys.mid(i, keys.midRef(i).indexOf(QLatin1Char('>'))).toLower(); QStringList tokens; int currentPos = 0; int nextHypen = -1; while ((nextHypen = untilClosing.indexOf(QLatin1Char('-'), currentPos)) != -1) { tokens << untilClosing.mid(currentPos, nextHypen - currentPos + 1); currentPos = nextHypen + 1; } tokens << untilClosing.mid(currentPos); foreach (const QString &str, tokens) { if (str == QLatin1String("s-") && (keyCodeTemp & 0x01) != 0x1) { keyCodeTemp += 0x1; } else if (str == QLatin1String("c-") && (keyCodeTemp & 0x02) != 0x2) { keyCodeTemp += 0x2; } else if (str == QLatin1String("a-") && (keyCodeTemp & 0x04) != 0x4) { keyCodeTemp += 0x4; } else if (str == QLatin1String("m-") && (keyCodeTemp & 0x08) != 0x8) { keyCodeTemp += 0x8; } else { if (m_nameToKeyCode.contains(str) || (str.length() == 1 && str.at(0).isLetterOrNumber())) { if (m_nameToKeyCode.contains(str)) { keyCodeTemp += m_nameToKeyCode.value(str) * 0x10; } else { keyCodeTemp += str.at(0).unicode() * 0x10; } } else { int endOfBlock = keys.indexOf(QLatin1Char('>')); if (endOfBlock -= -1) { endOfBlock = keys.length() - 1; } encodedSequence.clear(); encodedSequence.append(m_nameToKeyCode.value(QStringLiteral("invalid"))); break; } } } } else { QString str = keys.mid(i, keys.indexOf(QLatin1Char('>'), i) - i).toLower(); if (keys.indexOf(QLatin1Char('>'), i) != -1 && (m_nameToKeyCode.contains(str) || (str.length() == 1 && str.at(0).isLetterOrNumber()))) { if (m_nameToKeyCode.contains(str)) { keyCodeTemp += m_nameToKeyCode.value(str) * 0x10; } else { keyCodeTemp += str.at(0).unicode() * 0x10; } } else { int endOfBlock = keys.indexOf(QLatin1Char('>')); if (endOfBlock -= -1) { endOfBlock = keys.length() - 1; } encodedSequence.clear(); keyCodeTemp = m_nameToKeyCode.value(QStringLiteral("invalid")) * 0x10; } } i += keys.midRef(i, keys.midRef(i).indexOf(QLatin1Char('>'))).length() - 1; } } else { if (c == QLatin1Char('<')) { // If there's no closing '>', or if there is an opening '<' before the next '>', interpret as a literal '<' // If we are , encode as a literal " ". QString rest = keys.mid(i); if (rest.indexOf(QLatin1Char('>'), 1) != -1 && rest.mid(1, rest.indexOf(QLatin1Char('>'), 1) - 1) == QLatin1String("space")) { encodedSequence.append(QLatin1String(" ")); i += rest.indexOf(QLatin1Char('>'), 1); continue; } else if (rest.indexOf(QLatin1Char('>'), 1) == -1 || (rest.indexOf(QLatin1Char('<'), 1) < rest.indexOf(QLatin1Char('>'), 1) && rest.indexOf(QLatin1Char('<'), 1) != -1)) { encodedSequence.append(c); continue; } insideTag = true; continue; } else { encodedSequence.append(c); } } } return encodedSequence; } const QString KeyParser::decodeKeySequence(const QString &keys) const { QString ret; for (int i = 0; i < keys.length(); i++) { QChar c = keys.at(i); int keycode = c.unicode(); if ((keycode & 0xE000) != 0xE000) { ret.append(c); } else { ret.append(QLatin1Char('<')); if ((keycode & 0x1) == 0x1) { ret.append(QLatin1String("s-")); //keycode -= 0x1; } if ((keycode & 0x2) == 0x2) { ret.append(QLatin1String("c-")); //keycode -= 0x2; } if ((keycode & 0x4) == 0x4) { ret.append(QLatin1String("a-")); //keycode -= 0x4; } if ((keycode & 0x8) == 0x8) { ret.append(QLatin1String("m-")); //keycode -= 0x8; } if ((keycode & 0xE000) == 0xE000) { ret.append(m_keyCodeToName.value((keycode - 0xE000) / 0x10)); } else { ret.append(QChar(keycode)); } ret.append(QLatin1Char('>')); } } return ret; } const QChar KeyParser::KeyEventToQChar(const QKeyEvent &keyEvent) { const int keyCode = keyEvent.key(); const QString &text = keyEvent.text(); const Qt::KeyboardModifiers mods = keyEvent.modifiers(); // If previous key press was AltGr, return key value right away and don't go // down the "handle modifiers" code path. AltGr is really confusing... if (mods & Qt::GroupSwitchModifier) { return (!text.isEmpty()) ? text.at(0) : QChar(); } if (text.isEmpty() || (text.length() == 1 && text.at(0) < 0x20) || keyCode == Qt::Key_Delete || (mods != Qt::NoModifier && mods != Qt::ShiftModifier && mods != Qt::KeypadModifier)) { QString keyPress; keyPress.append(QLatin1Char('<')); keyPress.append((mods & Qt::ShiftModifier) ? QStringLiteral("s-") : QString()); keyPress.append((mods & Qt::ControlModifier) ? QStringLiteral("c-") : QString()); keyPress.append((mods & Qt::AltModifier) ? QStringLiteral("a-") : QString()); keyPress.append((mods & Qt::MetaModifier) ? QStringLiteral("m-") : QString()); keyPress.append(keyCode <= 0xFF ? QChar(keyCode) : qt2vi(keyCode)); keyPress.append(QLatin1Char('>')); return encodeKeySequence(keyPress).at(0); } else { return text.at(0); } } diff --git a/src/vimode/keyparser.h b/src/vimode/keyparser.h index d6fe2055..9e765952 100644 --- a/src/vimode/keyparser.h +++ b/src/vimode/keyparser.h @@ -1,71 +1,71 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008 Erlend Hamberg * Copyright (C) 2008 Evgeniy Ivanov * * 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 KATEVI_KEY_PARSER_H #define KATEVI_KEY_PARSER_H #include #include #include #include class QKeyEvent; namespace KateVi { /** * for encoding keypresses w/ modifiers into an internal QChar representation and back again to a * descriptive text string */ class KTEXTEDITOR_EXPORT KeyParser { private: KeyParser(); public: static KeyParser *self(); ~KeyParser() { - m_instance = NULL; + m_instance = nullptr; } const QString encodeKeySequence(const QString &keys) const; const QString decodeKeySequence(const QString &keys) const; QString qt2vi(int key) const; int vi2qt(const QString &keypress) const; int encoded2qt(const QString &keypress) const; const QChar KeyEventToQChar(const QKeyEvent &keyEvent); private: void initKeyTables(); QHash m_qt2katevi; QHash m_katevi2qt; QHash m_nameToKeyCode; QHash m_keyCodeToName; static KeyParser *m_instance; }; } #endif /* KATEVI_KEY_PARSER_H */ diff --git a/src/vimode/modes/insertvimode.cpp b/src/vimode/modes/insertvimode.cpp index 1693fae4..08de89f8 100644 --- a/src/vimode/modes/insertvimode.cpp +++ b/src/vimode/modes/insertvimode.cpp @@ -1,592 +1,595 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008-2011 Erlend Hamberg * Copyright (C) 2011 Svyatoslav Kuzmich * Copyright (C) 2012 - 2013 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 #include #include "kateview.h" #include "kateviewinternal.h" #include "kateconfig.h" #include "katecompletionwidget.h" #include "katecompletiontree.h" #include "kateglobal.h" #include #include "katepartdebug.h" #include "kateviinputmode.h" #include #include #include #include #include #include using namespace KateVi; InsertViMode::InsertViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal) : ModeBase() { m_view = view; m_viewInternal = viewInternal; m_viInputModeManager = viInputModeManager; m_waitingRegister = false; m_blockInsert = None; m_eolPos = 0; m_count = 1; m_countedRepeatsBeginOnNewLine = false; m_isExecutingCompletion = false; connect(doc(), SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), this, SLOT(textInserted(KTextEditor::Document*,KTextEditor::Range))); } InsertViMode::~InsertViMode() { } bool InsertViMode::commandInsertFromAbove() { KTextEditor::Cursor c(m_view->cursorPosition()); if (c.line() <= 0) { return false; } QString line = doc()->line(c.line() - 1); int tabWidth = doc()->config()->tabWidth(); QChar ch = getCharAtVirtualColumn(line, m_view->virtualCursorColumn(), tabWidth); if (ch == QChar::Null) { return false; } return doc()->insertText(c, ch); } bool InsertViMode::commandInsertFromBelow() { KTextEditor::Cursor c(m_view->cursorPosition()); if (c.line() >= doc()->lines() - 1) { return false; } QString line = doc()->line(c.line() + 1); int tabWidth = doc()->config()->tabWidth(); QChar ch = getCharAtVirtualColumn(line, m_view->virtualCursorColumn(), tabWidth); if (ch == QChar::Null) { return false; } return doc()->insertText(c, ch); } bool InsertViMode::commandDeleteWord() { KTextEditor::Cursor c1(m_view->cursorPosition()); KTextEditor::Cursor c2; c2 = findPrevWordStart(c1.line(), c1.column()); if (c2.line() != c1.line()) { if (c1.column() == 0) { c2.setColumn(doc()->line(c2.line()).length()); } else { c2.setColumn(0); c2.setLine(c2.line() + 1); } } Range r(c2, c1, ExclusiveMotion); return deleteRange(r, CharWise, false); } bool InsertViMode::commandDeleteLine() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c.line(), 0, c.line(), c.column(), ExclusiveMotion); if (c.column() == 0) { // Try to move the current line to the end of the previous line. if (c.line() == 0) { return true; } else { r.startColumn = doc()->line(c.line() - 1).length(); r.startLine--; } } else { /* * Remove backwards until the first non-space character. If no * non-space was found, remove backwards to the first column. */ QRegExp nonSpace(QLatin1String("\\S")); r.startColumn = getLine().indexOf(nonSpace); if (r.startColumn == -1 || r.startColumn >= c.column()) { r.startColumn = 0; } } return deleteRange(r, CharWise, false); } bool InsertViMode::commandDeleteCharBackward() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c.line(), c.column() - getCount(), c.line(), c.column(), ExclusiveMotion); if (c.column() == 0) { if (c.line() == 0) { return true; } else { r.startColumn = doc()->line(c.line() - 1).length(); r.startLine--; } } return deleteRange(r, CharWise); } bool InsertViMode::commandNewLine() { doc()->newLine(m_view); return true; } bool InsertViMode::commandIndent() { KTextEditor::Cursor c(m_view->cursorPosition()); doc()->indent(KTextEditor::Range(c.line(), 0, c.line(), 0), 1); return true; } bool InsertViMode::commandUnindent() { KTextEditor::Cursor c(m_view->cursorPosition()); doc()->indent(KTextEditor::Range(c.line(), 0, c.line(), 0), -1); return true; } bool InsertViMode::commandToFirstCharacterInFile() { KTextEditor::Cursor c(0,0); updateCursor(c); return true; } bool InsertViMode::commandToLastCharacterInFile() { int lines = doc()->lines() - 1; KTextEditor::Cursor c(lines, doc()->line(lines).length()); updateCursor(c); return true; } bool InsertViMode::commandMoveOneWordLeft() { KTextEditor::Cursor c(m_view->cursorPosition()); c = findPrevWordStart(c.line(), c.column()); if (!c.isValid()) { c = KTextEditor::Cursor(0, 0); } updateCursor(c); return true; } bool InsertViMode::commandMoveOneWordRight() { KTextEditor::Cursor c(m_view->cursorPosition()); c = findNextWordStart(c.line(), c.column()); if (!c.isValid()) { c = doc()->documentEnd(); } updateCursor(c); return true; } bool InsertViMode::commandCompleteNext() { if (m_view->completionWidget()->isCompletionActive()) { const QModelIndex oldCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex(); m_view->completionWidget()->cursorDown(); const QModelIndex newCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex(); if (newCompletionItem == oldCompletionItem) { // Wrap to top. m_view->completionWidget()->top(); } } else { m_view->userInvokedCompletion(); } return true; } bool InsertViMode::commandCompletePrevious() { if (m_view->completionWidget()->isCompletionActive()) { const QModelIndex oldCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex(); m_view->completionWidget()->cursorUp(); const QModelIndex newCompletionItem = m_view->completionWidget()->treeView()->selectionModel()->currentIndex(); if (newCompletionItem == oldCompletionItem) { // Wrap to bottom. m_view->completionWidget()->bottom(); } } else { m_view->userInvokedCompletion(); m_view->completionWidget()->bottom(); } return true; } bool InsertViMode::commandInsertContentOfRegister() { KTextEditor::Cursor c(m_view->cursorPosition()); KTextEditor::Cursor cAfter = c; QChar reg = getChosenRegister(m_register); OperationMode m = getRegisterFlag(reg); QString textToInsert = getRegisterContent(reg); if (textToInsert.isNull()) { error(i18n("Nothing in register %1", reg)); return false; } if (m == LineWise) { textToInsert.chop(1); // remove the last \n c.setColumn(doc()->lineLength(c.line())); // paste after the current line and ... textToInsert.prepend(QLatin1Char('\n')); // ... prepend a \n, so the text starts on a new line cAfter.setLine(cAfter.line() + 1); cAfter.setColumn(0); } else { cAfter.setColumn(cAfter.column() + textToInsert.length()); } doc()->insertText(c, textToInsert, m == Block); updateCursor(cAfter); return true; } // Start Normal mode just for one command and return to Insert mode bool InsertViMode::commandSwitchToNormalModeForJustOneCommand() { m_viInputModeManager->setTemporaryNormalMode(true); m_viInputModeManager->changeViMode(ViMode::NormalMode); const KTextEditor::Cursor cursorPos = m_view->cursorPosition(); // If we're at end of the line, move the cursor back one step, as in Vim. if (doc()->line(cursorPos.line()).length() == cursorPos.column()) { m_view->setCursorPosition(KTextEditor::Cursor(cursorPos.line(), cursorPos.column() - 1)); } m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block); emit m_view->viewModeChanged(m_view, m_view->viewMode()); m_viewInternal->repaint(); return true; } /** * checks if the key is a valid command * @return true if a command was completed and executed, false otherwise */ bool InsertViMode::handleKeypress(const QKeyEvent *e) { // backspace should work even if the shift key is down if (e->modifiers() != Qt::ControlModifier && e->key() == Qt::Key_Backspace) { m_view->backspace(); return true; } if (m_keys.isEmpty() && !m_waitingRegister) { if (e->modifiers() == Qt::NoModifier) { switch (e->key()) { case Qt::Key_Escape: leaveInsertMode(); return true; case Qt::Key_Left: m_view->cursorLeft(); return true; case Qt::Key_Right: m_view->cursorRight(); return true; case Qt::Key_Up: m_view->up(); return true; case Qt::Key_Down: m_view->down(); return true; case Qt::Key_Insert: startReplaceMode(); return true; case Qt::Key_Delete: m_view->keyDelete(); return true; case Qt::Key_Home: m_view->home(); return true; case Qt::Key_End: m_view->end(); return true; case Qt::Key_PageUp: m_view->pageUp(); return true; case Qt::Key_PageDown: m_view->pageDown(); return true; case Qt::Key_Enter: case Qt::Key_Return: if (m_view->completionWidget()->isCompletionActive() && !m_viInputModeManager->macroRecorder()->isReplaying() && !m_viInputModeManager->lastChangeRecorder()->isReplaying()) { // Filter out Enter/ Return's that trigger a completion when recording macros/ last change stuff; they // will be replaced with the special code "ctrl-space". // (This is why there is a "!m_viInputModeManager->isReplayingMacro()" above.) m_viInputModeManager->doNotLogCurrentKeypress(); m_isExecutingCompletion = true; m_textInsertedByCompletion.clear(); m_view->completionWidget()->execute(); completionFinished(); m_isExecutingCompletion = false; return true; } +#if QT_VERSION >= QT_VERSION_CHECK(5,8,0) + Q_FALLTHROUGH(); +#endif default: return false; } } else if (e->modifiers() == Qt::ControlModifier) { switch (e->key()) { case Qt::Key_BracketLeft: case Qt::Key_3: leaveInsertMode(); return true; case Qt::Key_Space: // We use Ctrl-space as a special code in macros/ last change, which means: if replaying // a macro/ last change, fetch and execute the next completion for this macro/ last change ... if (!m_viInputModeManager->macroRecorder()->isReplaying() && !m_viInputModeManager->lastChangeRecorder()->isReplaying()) { commandCompleteNext(); // ... therefore, we should not record ctrl-space indiscriminately. m_viInputModeManager->doNotLogCurrentKeypress(); } else { m_viInputModeManager->completionReplayer()->replay(); } return true; case Qt::Key_C: leaveInsertMode(true); return true; case Qt::Key_D: commandUnindent(); return true; case Qt::Key_E: commandInsertFromBelow(); return true; case Qt::Key_N: if (!m_viInputModeManager->macroRecorder()->isReplaying()) { commandCompleteNext(); } return true; case Qt::Key_P: if (!m_viInputModeManager->macroRecorder()->isReplaying()) { commandCompletePrevious(); } return true; case Qt::Key_T: commandIndent(); return true; case Qt::Key_W: commandDeleteWord(); return true; case Qt::Key_U: return commandDeleteLine(); case Qt::Key_J: commandNewLine(); return true; case Qt::Key_H: commandDeleteCharBackward(); return true; case Qt::Key_Y: commandInsertFromAbove(); return true; case Qt::Key_O: commandSwitchToNormalModeForJustOneCommand(); return true; case Qt::Key_Home: commandToFirstCharacterInFile(); return true; case Qt::Key_R: m_waitingRegister = true; return true; case Qt::Key_End: commandToLastCharacterInFile(); return true; case Qt::Key_Left: commandMoveOneWordLeft(); return true; case Qt::Key_Right: commandMoveOneWordRight(); return true; default: return false; } } return false; } else if (m_waitingRegister) { // ignore modifier keys alone if (e->key() == Qt::Key_Shift || e->key() == Qt::Key_Control || e->key() == Qt::Key_Alt || e->key() == Qt::Key_Meta) { return false; } QChar key = KeyParser::self()->KeyEventToQChar(*e); key = key.toLower(); m_waitingRegister = false; // is it register ? // TODO: add registers such as '/'. See :h if ((key >= QLatin1Char('0') && key <= QLatin1Char('9')) || (key >= QLatin1Char('a') && key <= QLatin1Char('z')) || key == QLatin1Char('_') || key == QLatin1Char('+') || key == QLatin1Char('*') || key == QLatin1Char('"')) { m_register = key; } else { return false; } commandInsertContentOfRegister(); return true; } return false; } // leave insert mode when esc, etc, is pressed. if leaving block // prepend/append, the inserted text will be added to all block lines. if // ctrl-c is used to exit insert mode this is not done. void InsertViMode::leaveInsertMode(bool force) { m_view->abortCompletion(); if (!force) { if (m_blockInsert != None) { // block append/prepend // make sure cursor haven't been moved if (m_blockRange.startLine == m_view->cursorPosition().line()) { int start, len; QString added; KTextEditor::Cursor c; switch (m_blockInsert) { case Append: case Prepend: if (m_blockInsert == Append) { start = m_blockRange.endColumn + 1; } else { start = m_blockRange.startColumn; } len = m_view->cursorPosition().column() - start; added = getLine().mid(start, len); c = KTextEditor::Cursor(m_blockRange.startLine, start); for (int i = m_blockRange.startLine + 1; i <= m_blockRange.endLine; i++) { c.setLine(i); doc()->insertText(c, added); } break; case AppendEOL: start = m_eolPos; len = m_view->cursorPosition().column() - start; added = getLine().mid(start, len); c = KTextEditor::Cursor(m_blockRange.startLine, start); for (int i = m_blockRange.startLine + 1; i <= m_blockRange.endLine; i++) { c.setLine(i); c.setColumn(doc()->lineLength(i)); doc()->insertText(c, added); } break; default: error(QStringLiteral("not supported")); } } m_blockInsert = None; } else { const QString added = doc()->text(KTextEditor::Range(m_viInputModeManager->marks()->getStartEditYanked(), m_view->cursorPosition())); if (m_count > 1) { for (unsigned int i = 0; i < m_count - 1; i++) { if (m_countedRepeatsBeginOnNewLine) { doc()->newLine(m_view); } doc()->insertText(m_view->cursorPosition(), added); } } } } m_countedRepeatsBeginOnNewLine = false; startNormalMode(); } void InsertViMode::setBlockPrependMode(Range blockRange) { // ignore if not more than one line is selected if (blockRange.startLine != blockRange.endLine) { m_blockInsert = Prepend; m_blockRange = blockRange; } } void InsertViMode::setBlockAppendMode(Range blockRange, BlockInsert b) { Q_ASSERT(b == Append || b == AppendEOL); // ignore if not more than one line is selected if (blockRange.startLine != blockRange.endLine) { m_blockRange = blockRange; m_blockInsert = b; if (b == AppendEOL) { m_eolPos = doc()->lineLength(m_blockRange.startLine); } } else { qCDebug(LOG_KTE) << "cursor moved. ignoring block append/prepend"; } } void InsertViMode::completionFinished() { Completion::CompletionType completionType = Completion::PlainText; if (m_view->cursorPosition() != m_textInsertedByCompletionEndPos) { completionType = Completion::FunctionWithArgs; } else if (m_textInsertedByCompletion.endsWith(QLatin1String("()")) || m_textInsertedByCompletion.endsWith(QLatin1String("();"))) { completionType = Completion::FunctionWithoutArgs; } m_viInputModeManager->completionRecorder()->logCompletionEvent(Completion(m_textInsertedByCompletion, KateViewConfig::global()->wordCompletionRemoveTail(), completionType)); } void InsertViMode::textInserted(KTextEditor::Document *document, KTextEditor::Range range) { if (m_isExecutingCompletion) { m_textInsertedByCompletion += document->text(range); m_textInsertedByCompletionEndPos = range.end(); } } diff --git a/src/vimode/modes/modebase.cpp b/src/vimode/modes/modebase.cpp index 7252d4fd..4bffacbf 100644 --- a/src/vimode/modes/modebase.cpp +++ b/src/vimode/modes/modebase.cpp @@ -1,1372 +1,1373 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008 - 2012 Erlend Hamberg * Copyright (C) 2009 Paul Gideon Dann * Copyright (C) 2011 Svyatoslav Kuzmich * Copyright (C) 2012 - 2013 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 #include #include "kateglobal.h" #include "kateviinputmode.h" #include #include #include #include #include #include "katelayoutcache.h" #include "kateconfig.h" #include "katedocument.h" #include "kateviewinternal.h" #include "katerenderer.h" #include #include #include #include #include #include #include #include using namespace KateVi; // TODO: the "previous word/WORD [end]" methods should be optimized. now they're being called in a // loop and all calculations done up to finding a match are trown away when called with a count > 1 // because they will simply be called again from the last found position. // They should take the count as a parameter and collect the positions in a QList, then return // element (count - 1) //////////////////////////////////////////////////////////////////////////////// // HELPER METHODS //////////////////////////////////////////////////////////////////////////////// void ModeBase::yankToClipBoard(QChar chosen_register, QString text) { //only yank to the clipboard if no register was specified, // textlength > 1 and there is something else then whitespace if ((chosen_register == QLatin1Char('0') || chosen_register == QLatin1Char('-')) && text.length() > 1 && !text.trimmed().isEmpty()) { #warning fixme: vimode copy to clipboard // KTextEditor::EditorPrivate::self()->copyToClipboard(text); } } bool ModeBase::deleteRange(Range &r, OperationMode mode, bool addToRegister) { r.normalize(); bool res = false; QString removedText = getRange(r, mode); if (mode == LineWise) { doc()->editStart(); for (int i = 0; i < r.endLine - r.startLine + 1; i++) { res = doc()->removeLine(r.startLine); } doc()->editEnd(); } else { res = doc()->removeText(r.toEditorRange(), mode == Block); } QChar chosenRegister = getChosenRegister(ZeroRegister); if (addToRegister) { if (r.startLine == r.endLine) { chosenRegister = getChosenRegister(SmallDeleteRegister); fillRegister(chosenRegister, removedText, mode); } else { fillRegister(chosenRegister, removedText, mode); } } yankToClipBoard(chosenRegister, removedText); return res; } const QString ModeBase::getRange(Range &r, OperationMode mode) const { r.normalize(); QString s; if (mode == LineWise) { r.startColumn = 0; r.endColumn = getLine(r.endLine).length(); } if (r.motionType == InclusiveMotion) { r.endColumn++; } KTextEditor::Range range = r.toEditorRange(); if (mode == LineWise) { s = doc()->textLines(range).join(QLatin1Char('\n')); s.append(QLatin1Char('\n')); } else if (mode == Block) { s = doc()->text(range, true); } else { s = doc()->text(range); } return s; } const QString ModeBase::getLine(int line) const { return (line < 0) ? m_view->currentTextLine() : doc()->line(line); } const QChar ModeBase::getCharUnderCursor() const { KTextEditor::Cursor c(m_view->cursorPosition()); QString line = getLine(c.line()); if (line.length() == 0 && c.column() >= line.length()) { return QChar::Null; } return line.at(c.column()); } const QString ModeBase::getWordUnderCursor() const { return doc()->text(getWordRangeUnderCursor()); } const KTextEditor::Range ModeBase::getWordRangeUnderCursor() const { KTextEditor::Cursor c(m_view->cursorPosition()); // find first character that is a “word letter” and start the search there QChar ch = doc()->characterAt(c); int i = 0; while (!ch.isLetterOrNumber() && ! ch.isMark() && ch != QLatin1Char('_') && m_extraWordCharacters.indexOf(ch) == -1) { // advance cursor one position c.setColumn(c.column() + 1); if (c.column() > doc()->lineLength(c.line())) { c.setColumn(0); c.setLine(c.line() + 1); if (c.line() == doc()->lines()) { return KTextEditor::Range::invalid(); } } ch = doc()->characterAt(c); i++; // count characters that were advanced so we know where to start the search } // move cursor the word (if cursor was placed on e.g. a paren, this will move // it to the right updateCursor(c); KTextEditor::Cursor c1 = findPrevWordStart(c.line(), c.column() + 1 + i, true); KTextEditor::Cursor c2 = findWordEnd(c1.line(), c1.column() + i - 1, true); c2.setColumn(c2.column() + 1); return KTextEditor::Range(c1, c2); } KTextEditor::Cursor ModeBase::findNextWordStart(int fromLine, int fromColumn, bool onlyCurrentLine) const { QString line = getLine(fromLine); // the start of word pattern need to take m_extraWordCharacters into account if defined QString startOfWordPattern = QStringLiteral("\\b(\\w"); if (m_extraWordCharacters.length() > 0) { startOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1Char(']')); } startOfWordPattern.append(QLatin1Char(')')); QRegExp startOfWord(startOfWordPattern); // start of a word QRegExp nonSpaceAfterSpace(QLatin1String("\\s\\S")); // non-space right after space QRegExp nonWordAfterWord(QLatin1String("\\b(?!\\s)\\W")); // word-boundary followed by a non-word which is not a space int l = fromLine; int c = fromColumn; bool found = false; while (!found) { int c1 = startOfWord.indexIn(line, c + 1); int c2 = nonSpaceAfterSpace.indexIn(line, c); int c3 = nonWordAfterWord.indexIn(line, c + 1); if (c1 == -1 && c2 == -1 && c3 == -1) { if (onlyCurrentLine) { return KTextEditor::Cursor::invalid(); } else if (l >= doc()->lines() - 1) { c = qMax(line.length() - 1, 0); return KTextEditor::Cursor::invalid(); } else { c = 0; l++; line = getLine(l); if (line.length() == 0 || !line.at(c).isSpace()) { found = true; } continue; } } c2++; // the second regexp will match one character *before* the character we want to go to if (c1 <= 0) { c1 = line.length() - 1; } if (c2 <= 0) { c2 = line.length() - 1; } if (c3 <= 0) { c3 = line.length() - 1; } c = qMin(c1, qMin(c2, c3)); found = true; } return KTextEditor::Cursor(l, c); } KTextEditor::Cursor ModeBase::findNextWORDStart(int fromLine, int fromColumn, bool onlyCurrentLine) const { QString line = getLine(); int l = fromLine; int c = fromColumn; bool found = false; QRegExp startOfWORD(QLatin1String("\\s\\S")); while (!found) { c = startOfWORD.indexIn(line, c); if (c == -1) { if (onlyCurrentLine) { return KTextEditor::Cursor(l, c); } else if (l >= doc()->lines() - 1) { c = line.length() - 1; break; } else { c = 0; l++; line = getLine(l); if (line.length() == 0 || !line.at(c).isSpace()) { found = true; } continue; } } else { c++; found = true; } } return KTextEditor::Cursor(l, c); } KTextEditor::Cursor ModeBase::findPrevWordEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const { QString line = getLine(fromLine); QString endOfWordPattern = QStringLiteral("\\S\\s|\\S$|\\w\\W|\\S\\b|^$"); if (m_extraWordCharacters.length() > 0) { endOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1String("][^") + m_extraWordCharacters + QLatin1Char(']')); } QRegExp endOfWord(endOfWordPattern); int l = fromLine; int c = fromColumn; bool found = false; while (!found) { int c1 = endOfWord.lastIndexIn(line, c - 1); if (c1 != -1 && c - 1 != -1) { found = true; c = c1; } else { if (onlyCurrentLine) { return KTextEditor::Cursor::invalid(); } else if (l > 0) { line = getLine(--l); c = line.length(); continue; } else { return KTextEditor::Cursor::invalid(); } } } return KTextEditor::Cursor(l, c); } KTextEditor::Cursor ModeBase::findPrevWORDEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const { QString line = getLine(fromLine); QRegExp endOfWORDPattern(QLatin1String("\\S\\s|\\S$|^$")); QRegExp endOfWORD(endOfWORDPattern); int l = fromLine; int c = fromColumn; bool found = false; while (!found) { int c1 = endOfWORD.lastIndexIn(line, c - 1); if (c1 != -1 && c - 1 != -1) { found = true; c = c1; } else { if (onlyCurrentLine) { return KTextEditor::Cursor::invalid(); } else if (l > 0) { line = getLine(--l); c = line.length(); continue; } else { c = 0; return KTextEditor::Cursor::invalid(); } } } return KTextEditor::Cursor(l, c); } KTextEditor::Cursor ModeBase::findPrevWordStart(int fromLine, int fromColumn, bool onlyCurrentLine) const { QString line = getLine(fromLine); // the start of word pattern need to take m_extraWordCharacters into account if defined QString startOfWordPattern = QStringLiteral("\\b(\\w"); if (m_extraWordCharacters.length() > 0) { startOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1Char(']')); } startOfWordPattern.append(QLatin1Char(')')); QRegExp startOfWord(startOfWordPattern); // start of a word QRegExp nonSpaceAfterSpace(QLatin1String("\\s\\S")); // non-space right after space QRegExp nonWordAfterWord(QLatin1String("\\b(?!\\s)\\W")); // word-boundary followed by a non-word which is not a space QRegExp startOfLine(QLatin1String("^\\S")); // non-space at start of line int l = fromLine; int c = fromColumn; bool found = false; while (!found) { int c1 = startOfWord.lastIndexIn(line, -line.length() + c - 1); int c2 = nonSpaceAfterSpace.lastIndexIn(line, -line.length() + c - 2); int c3 = nonWordAfterWord.lastIndexIn(line, -line.length() + c - 1); int c4 = startOfLine.lastIndexIn(line, -line.length() + c - 1); if (c1 == -1 && c2 == -1 && c3 == -1 && c4 == -1) { if (onlyCurrentLine) { return KTextEditor::Cursor::invalid(); } else if (l <= 0) { return KTextEditor::Cursor::invalid(); } else { line = getLine(--l); c = line.length(); if (line.length() == 0) { c = 0; found = true; } continue; } } c2++; // the second regexp will match one character *before* the character we want to go to if (c1 <= 0) { c1 = 0; } if (c2 <= 0) { c2 = 0; } if (c3 <= 0) { c3 = 0; } if (c4 <= 0) { c4 = 0; } c = qMax(c1, qMax(c2, qMax(c3, c4))); found = true; } return KTextEditor::Cursor(l, c); } KTextEditor::Cursor ModeBase::findPrevWORDStart(int fromLine, int fromColumn, bool onlyCurrentLine) const { QString line = getLine(fromLine); QRegExp startOfWORD(QLatin1String("\\s\\S")); QRegExp startOfLineWORD(QLatin1String("^\\S")); int l = fromLine; int c = fromColumn; bool found = false; while (!found) { int c1 = startOfWORD.lastIndexIn(line, -line.length() + c - 2); int c2 = startOfLineWORD.lastIndexIn(line, -line.length() + c - 1); if (c1 == -1 && c2 == -1) { if (onlyCurrentLine) { return KTextEditor::Cursor::invalid(); } else if (l <= 0) { return KTextEditor::Cursor::invalid(); } else { line = getLine(--l); c = line.length(); if (line.length() == 0) { c = 0; found = true; } continue; } } c1++; // the startOfWORD pattern matches one character before the word c = qMax(c1, c2); if (c <= 0) { c = 0; } found = true; } return KTextEditor::Cursor(l, c); } KTextEditor::Cursor ModeBase::findWordEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const { QString line = getLine(fromLine); QString endOfWordPattern = QStringLiteral("\\S\\s|\\S$|\\w\\W|\\S\\b"); if (m_extraWordCharacters.length() > 0) { endOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1String("][^") + m_extraWordCharacters + QLatin1Char(']')); } QRegExp endOfWORD(endOfWordPattern); int l = fromLine; int c = fromColumn; bool found = false; while (!found) { int c1 = endOfWORD.indexIn(line, c + 1); if (c1 != -1) { found = true; c = c1; } else { if (onlyCurrentLine) { return KTextEditor::Cursor::invalid(); } else if (l >= doc()->lines() - 1) { c = line.length() - 1; return KTextEditor::Cursor::invalid(); } else { c = -1; line = getLine(++l); continue; } } } return KTextEditor::Cursor(l, c); } KTextEditor::Cursor ModeBase::findWORDEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const { QString line = getLine(fromLine); QRegExp endOfWORD(QLatin1String("\\S\\s|\\S$")); int l = fromLine; int c = fromColumn; bool found = false; while (!found) { int c1 = endOfWORD.indexIn(line, c + 1); if (c1 != -1) { found = true; c = c1; } else { if (onlyCurrentLine) { return KTextEditor::Cursor::invalid(); } else if (l >= doc()->lines() - 1) { c = line.length() - 1; return KTextEditor::Cursor::invalid(); } else { c = -1; line = getLine(++l); continue; } } } return KTextEditor::Cursor(l, c); } Range innerRange(Range range, bool inner) { Range r = range; if (inner) { const int columnDistance = qAbs(r.startColumn - r.endColumn); if ((r.startLine == r.endLine) && columnDistance == 1) { // Start and end are right next to each other; there is nothing inside them. return Range::invalid(); } r.startColumn++; r.endColumn--; } return r; } Range ModeBase::findSurroundingQuotes(const QChar &c, bool inner) const { KTextEditor::Cursor cursor(m_view->cursorPosition()); Range r; r.startLine = cursor.line(); r.endLine = cursor.line(); QString line = doc()->line(cursor.line()); // If cursor on the quote we should shoose the best direction. if (line.at(cursor.column()) == c) { int attribute = m_view->doc()->kateTextLine(cursor.line())->attribute(cursor.column()); // If at the beginning of the line - then we might search the end. if (doc()->kateTextLine(cursor.line())->attribute(cursor.column() + 1) == attribute && doc()->kateTextLine(cursor.line())->attribute(cursor.column() - 1) != attribute) { r.startColumn = cursor.column(); r.endColumn = line.indexOf(c, cursor.column() + 1); return innerRange(r, inner); } // If at the end of the line - then we might search the beginning. if (doc()->kateTextLine(cursor.line())->attribute(cursor.column() + 1) != attribute && doc()->kateTextLine(cursor.line())->attribute(cursor.column() - 1) == attribute) { r.startColumn = line.lastIndexOf(c, cursor.column() - 1); r.endColumn = cursor.column(); return innerRange(r, inner); } // Try to search the quote to right int c1 = line.indexOf(c, cursor.column() + 1); if (c1 != -1) { r.startColumn = cursor.column(); r.endColumn = c1; return innerRange(r, inner); } // Try to search the quote to left int c2 = line.lastIndexOf(c, cursor.column() - 1); if (c2 != -1) { r.startColumn = c2; r.endColumn = cursor.column(); return innerRange(r, inner); } // Nothing found - give up :) return Range::invalid(); } r.startColumn = line.lastIndexOf(c, cursor.column()); r.endColumn = line.indexOf(c, cursor.column()); if (r.startColumn == -1 || r.endColumn == -1 || r.startColumn > r.endColumn) { return Range::invalid(); } return innerRange(r, inner); } Range ModeBase::findSurroundingBrackets(const QChar &c1, const QChar &c2, bool inner, const QChar &nested1, const QChar &nested2) const { KTextEditor::Cursor cursor(m_view->cursorPosition()); Range r(cursor, InclusiveMotion); int line = cursor.line(); int column = cursor.column(); int catalan; // Chars should not differ. For equal chars use findSurroundingQuotes. Q_ASSERT(c1 != c2); const QString &l = m_view->doc()->line(line); if (column < l.size() && l.at(column) == c2) { r.endLine = line; r.endColumn = column; } else { if (column < l.size() && l.at(column) == c1) { column++; } for (catalan = 1; line < m_view->doc()->lines(); line++) { const QString &l = m_view->doc()->line(line); for (; column < l.size(); column++) { const QChar &c = l.at(column); if (c == nested1) { catalan++; } else if (c == nested2) { catalan--; } if (!catalan) { break; } } if (!catalan) { break; } column = 0; } if (catalan != 0) { return Range::invalid(); } r.endLine = line; r.endColumn = column; } // Same algorithm but backwards. line = cursor.line(); column = cursor.column(); if (column < l.size() && l.at(column) == c1) { r.startLine = line; r.startColumn = column; } else { if (column < l.size() && l.at(column) == c2) { column--; } for (catalan = 1; line >= 0; line--) { const QString &l = m_view->doc()->line(line); for (; column >= 0; column--) { const QChar &c = l.at(column); if (c == nested1) { catalan--; } else if (c == nested2) { catalan++; } if (!catalan) { break; } } if (!catalan || !line) { break; } column = m_view->doc()->line(line - 1).size() - 1; } if (catalan != 0) { return Range::invalid(); } r.startColumn = column; r.startLine = line; } return innerRange(r, inner); } Range ModeBase::findSurrounding(const QRegExp &c1, const QRegExp &c2, bool inner) const { KTextEditor::Cursor cursor(m_view->cursorPosition()); QString line = getLine(); int col1 = line.lastIndexOf(c1, cursor.column()); int col2 = line.indexOf(c2, cursor.column()); Range r(cursor.line(), col1, cursor.line(), col2, InclusiveMotion); if (col1 == -1 || col2 == -1 || col1 > col2) { return Range::invalid(); } if (inner) { r.startColumn++; r.endColumn--; } return r; } int ModeBase::findLineStartingWitchChar(const QChar &c, int count, bool forward) const { int line = m_view->cursorPosition().line(); int lines = doc()->lines(); int hits = 0; if (forward) { line++; } else { line--; } while (line < lines && line >= 0 && hits < count) { QString l = getLine(line); if (l.length() > 0 && l.at(0) == c) { hits++; } if (hits != count) { if (forward) { line++; } else { line--; } } } if (hits == getCount()) { return line; } return -1; } void ModeBase::updateCursor(const KTextEditor::Cursor &c) const { m_viInputModeManager->updateCursor(c); } /** * @return the register given for the command. If no register was given, defaultReg is returned. */ QChar ModeBase::getChosenRegister(const QChar &defaultReg) const { return (m_register != QChar::Null) ? m_register : defaultReg; } QString ModeBase::getRegisterContent(const QChar ®) { QString r = m_viInputModeManager->globalState()->registers()->getContent(reg); if (r.isNull()) { error(i18n("Nothing in register %1", reg)); } return r; } OperationMode ModeBase::getRegisterFlag(const QChar ®) const { return m_viInputModeManager->globalState()->registers()->getFlag(reg); } void ModeBase::fillRegister(const QChar ®, const QString &text, OperationMode flag) { m_viInputModeManager->globalState()->registers()->set(reg, text, flag); } KTextEditor::Cursor ModeBase::getNextJump(KTextEditor::Cursor cursor) const { return m_viInputModeManager->jumps()->next(cursor); } KTextEditor::Cursor ModeBase::getPrevJump(KTextEditor::Cursor cursor) const { return m_viInputModeManager->jumps()->prev(cursor); } Range ModeBase::goLineDown() { return goLineUpDown(getCount()); } Range ModeBase::goLineUp() { return goLineUpDown(-getCount()); } /** * method for moving up or down one or more lines * note: the sticky column is always a virtual column */ Range ModeBase::goLineUpDown(int lines) { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, InclusiveMotion); int tabstop = doc()->config()->tabWidth(); // if in an empty document, just return if (lines == 0) { return r; } r.endLine += lines; // limit end line to be from line 0 through the last line if (r.endLine < 0) { r.endLine = 0; } else if (r.endLine > doc()->lines() - 1) { r.endLine = doc()->lines() - 1; } Kate::TextLine startLine = doc()->plainKateTextLine(c.line()); Kate::TextLine endLine = doc()->plainKateTextLine(r.endLine); int endLineLen = doc()->lineLength(r.endLine) - 1; if (endLineLen < 0) { endLineLen = 0; } int endLineLenVirt = endLine->toVirtualColumn(endLineLen, tabstop); int virtColumnStart = startLine->toVirtualColumn(c.column(), tabstop); // if sticky column isn't set, set end column and set sticky column to its virtual column if (m_stickyColumn == -1) { r.endColumn = endLine->fromVirtualColumn(virtColumnStart, tabstop); m_stickyColumn = virtColumnStart; } else { // sticky is set - set end column to its value r.endColumn = endLine->fromVirtualColumn(m_stickyColumn, tabstop); } // make sure end column won't be after the last column of a line if (r.endColumn > endLineLen) { r.endColumn = endLineLen; } // if we move to a line shorter than the current column, go to its end if (virtColumnStart > endLineLenVirt) { r.endColumn = endLineLen; } return r; } Range ModeBase::goVisualLineUpDown(int lines) { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, InclusiveMotion); int tabstop = doc()->config()->tabWidth(); if (lines == 0) { // We're not moving anywhere. return r; } KateLayoutCache *cache = m_viInputModeManager->inputAdapter()->layoutCache(); // Work out the real and visual line pair of the beginning of the visual line we'd end up // on by moving lines visual lines. We ignore the column, for now. int finishVisualLine = cache->viewLine(m_view->cursorPosition()); int finishRealLine = m_view->cursorPosition().line(); int count = qAbs(lines); bool invalidPos = false; if (lines > 0) { // Find the beginning of the visual line "lines" visual lines down. while (count > 0) { finishVisualLine++; if (finishVisualLine >= cache->line(finishRealLine)->viewLineCount()) { finishRealLine++; finishVisualLine = 0; } if (finishRealLine >= doc()->lines()) { invalidPos = true; break; } count--; } } else { // Find the beginning of the visual line "lines" visual lines up. while (count > 0) { finishVisualLine--; if (finishVisualLine < 0) { finishRealLine--; if (finishRealLine < 0) { invalidPos = true; break; } finishVisualLine = cache->line(finishRealLine)->viewLineCount() - 1; } count--; } } if (invalidPos) { r.endLine = -1; r.endColumn = -1; return r; } // We know the final (real) line ... r.endLine = finishRealLine; // ... now work out the final (real) column. if (m_stickyColumn == -1 || !m_lastMotionWasVisualLineUpOrDown) { // Compute new sticky column. It is a *visual* sticky column. int startVisualLine = cache->viewLine(m_view->cursorPosition()); int startRealLine = m_view->cursorPosition().line(); const Kate::TextLine startLine = doc()->plainKateTextLine(c.line()); // Adjust for the fact that if the portion of the line before wrapping is indented, // the continuations are also "invisibly" (i.e. without any spaces in the text itself) indented. const bool isWrappedContinuation = (cache->textLayout(startRealLine, startVisualLine).lineLayout().lineNumber() != 0); const int numInvisibleIndentChars = isWrappedContinuation ? startLine->toVirtualColumn(cache->line(startRealLine)->textLine()->nextNonSpaceChar(0), tabstop) : 0; const int realLineStartColumn = cache->textLayout(startRealLine, startVisualLine).startCol(); const int lineStartVirtualColumn = startLine->toVirtualColumn(realLineStartColumn, tabstop); const int visualColumnNoInvisibleIndent = startLine->toVirtualColumn(c.column(), tabstop) - lineStartVirtualColumn; m_stickyColumn = visualColumnNoInvisibleIndent + numInvisibleIndentChars; Q_ASSERT(m_stickyColumn >= 0); } // The "real" (non-virtual) beginning of the current "line", which might be a wrapped continuation of a // "real" line. const int realLineStartColumn = cache->textLayout(finishRealLine, finishVisualLine).startCol(); const Kate::TextLine endLine = doc()->plainKateTextLine(r.endLine); // Adjust for the fact that if the portion of the line before wrapping is indented, // the continuations are also "invisibly" (i.e. without any spaces in the text itself) indented. const bool isWrappedContinuation = (cache->textLayout(finishRealLine, finishVisualLine).lineLayout().lineNumber() != 0); const int numInvisibleIndentChars = isWrappedContinuation ? endLine->toVirtualColumn(cache->line(finishRealLine)->textLine()->nextNonSpaceChar(0), tabstop) : 0; if (m_stickyColumn == (unsigned int)KateVi::EOL) { const int visualEndColumn = cache->textLayout(finishRealLine, finishVisualLine).lineLayout().textLength() - 1; r.endColumn = endLine->fromVirtualColumn(visualEndColumn + realLineStartColumn - numInvisibleIndentChars, tabstop); } else { // Algorithm: find the "real" column corresponding to the start of the line. Offset from that // until the "visual" column is equal to the "visual" sticky column. int realOffsetToVisualStickyColumn = 0; const int lineStartVirtualColumn = endLine->toVirtualColumn(realLineStartColumn, tabstop); while (true) { const int visualColumn = endLine->toVirtualColumn(realLineStartColumn + realOffsetToVisualStickyColumn, tabstop) - lineStartVirtualColumn + numInvisibleIndentChars; if (visualColumn >= m_stickyColumn) { break; } realOffsetToVisualStickyColumn++; } r.endColumn = realLineStartColumn + realOffsetToVisualStickyColumn; } m_currentMotionWasVisualLineUpOrDown = true; return r; } bool ModeBase::startNormalMode() { /* store the key presses for this "insert mode session" so that it can be repeated with the * '.' command * - ignore transition from Visual Modes */ if (!(m_viInputModeManager->isAnyVisualMode() || m_viInputModeManager->lastChangeRecorder()->isReplaying())) { m_viInputModeManager->storeLastChangeCommand(); m_viInputModeManager->clearCurrentChangeLog(); } m_viInputModeManager->viEnterNormalMode(); m_view->doc()->setUndoMergeAllEdits(false); emit m_view->viewModeChanged(m_view, m_view->viewMode()); return true; } bool ModeBase::startInsertMode() { m_viInputModeManager->viEnterInsertMode(); m_view->doc()->setUndoMergeAllEdits(true); emit m_view->viewModeChanged(m_view, m_view->viewMode()); return true; } bool ModeBase::startReplaceMode() { m_view->doc()->setUndoMergeAllEdits(true); m_viInputModeManager->viEnterReplaceMode(); emit m_view->viewModeChanged(m_view, m_view->viewMode()); return true; } bool ModeBase::startVisualMode() { if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode) { m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualMode); m_viInputModeManager->changeViMode(ViMode::VisualMode); } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) { m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualMode); m_viInputModeManager->changeViMode(ViMode::VisualMode); } else { m_viInputModeManager->viEnterVisualMode(); } emit m_view->viewModeChanged(m_view, m_view->viewMode()); return true; } bool ModeBase::startVisualBlockMode() { if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) { m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualBlockMode); m_viInputModeManager->changeViMode(ViMode::VisualBlockMode); } else { m_viInputModeManager->viEnterVisualMode(ViMode::VisualBlockMode); } emit m_view->viewModeChanged(m_view, m_view->viewMode()); return true; } bool ModeBase::startVisualLineMode() { if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) { m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualLineMode); m_viInputModeManager->changeViMode(ViMode::VisualLineMode); } else { m_viInputModeManager->viEnterVisualMode(ViMode::VisualLineMode); } emit m_view->viewModeChanged(m_view, m_view->viewMode()); return true; } void ModeBase::error(const QString &errorMsg) { delete m_infoMessage; m_infoMessage = new KTextEditor::Message(errorMsg, KTextEditor::Message::Error); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(2000); // 2 seconds m_infoMessage->setView(m_view); m_view->doc()->postMessage(m_infoMessage); } void ModeBase::message(const QString &msg) { delete m_infoMessage; m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Positive); m_infoMessage->setPosition(KTextEditor::Message::BottomInView); m_infoMessage->setAutoHide(2000); // 2 seconds m_infoMessage->setView(m_view); m_view->doc()->postMessage(m_infoMessage); } QString ModeBase::getVerbatimKeys() const { return m_keysVerbatim; } const QChar ModeBase::getCharAtVirtualColumn(const QString &line, int virtualColumn, int tabWidth) const { int column = 0; int tempCol = 0; // sanity check: if the line is empty, there are no chars if (line.length() == 0) { return QChar::Null; } while (tempCol < virtualColumn) { if (line.at(column) == QLatin1Char('\t')) { tempCol += tabWidth - (tempCol % tabWidth); } else { tempCol++; } if (tempCol <= virtualColumn) { column++; if (column >= line.length()) { return QChar::Null; } } } if (line.length() > column) { return line.at(column); } return QChar::Null; } void ModeBase::addToNumberUnderCursor(int count) { KTextEditor::Cursor c(m_view->cursorPosition()); QString line = getLine(); if (line.isEmpty()) { return; } int numberStartPos = -1; QString numberAsString; QRegExp numberRegex(QLatin1String("(0x)([0-9a-fA-F]+)|\\-?\\d+")); const int cursorColumn = c.column(); const int currentLineLength = doc()->lineLength(c.line()); const KTextEditor::Cursor prevWordStart = findPrevWordStart(c.line(), cursorColumn); int wordStartPos = prevWordStart.column(); if (prevWordStart.line() < c.line()) { // The previous word starts on the previous line: ignore. wordStartPos = 0; } if (wordStartPos > 0 && line.at(wordStartPos - 1) == QLatin1Char('-')) { wordStartPos--; } for (int searchFromColumn = wordStartPos; searchFromColumn < currentLineLength; searchFromColumn++) { numberStartPos = numberRegex.indexIn(line, searchFromColumn); numberAsString = numberRegex.cap(); const bool numberEndedBeforeCursor = (numberStartPos + numberAsString.length() <= c.column()); if (!numberEndedBeforeCursor) { // This is the first number-like string under or after the cursor - this'll do! break; } } if (numberStartPos == -1) { // None found. return; } bool parsedNumberSuccessfully = false; int base = numberRegex.cap(1).isEmpty() ? 10 : 16; if (base != 16 && numberAsString.startsWith(QLatin1String("0")) && numberAsString.length() > 1) { // If a non-hex number with a leading 0 can be parsed as octal, then assume // it is octal. numberAsString.toInt(&parsedNumberSuccessfully, 8); if (parsedNumberSuccessfully) { base = 8; } } const int originalNumber = numberAsString.toInt(&parsedNumberSuccessfully, base); if (!parsedNumberSuccessfully) { // conversion to int failed. give up. return; } QString basePrefix; if (base == 16) { basePrefix = QStringLiteral("0x"); } else if (base == 8) { basePrefix = QStringLiteral("0"); } const QString withoutBase = numberAsString.mid(basePrefix.length()); const int newNumber = originalNumber + count; // Create the new text string to be inserted. Prepend with “0x” if in base 16, and "0" if base 8. // For non-decimal numbers, try to keep the length of the number the same (including leading 0's). const QString newNumberPadded = (base == 10) ? QStringLiteral("%1").arg(newNumber, 0, base) : QStringLiteral("%1").arg(newNumber, withoutBase.length(), base, QLatin1Char('0')); const QString newNumberText = basePrefix + newNumberPadded; // Replace the old number string with the new. doc()->editStart(); doc()->removeText(KTextEditor::Range(c.line(), numberStartPos, c.line(), numberStartPos + numberAsString.length())); doc()->insertText(KTextEditor::Cursor(c.line(), numberStartPos), newNumberText); doc()->editEnd(); updateCursor(KTextEditor::Cursor(m_view->cursorPosition().line(), numberStartPos + newNumberText.length() - 1)); } void ModeBase::switchView(Direction direction) { QList visible_views; foreach (KTextEditor::ViewPrivate *view, KTextEditor::EditorPrivate::self()->views()) { if (view->isVisible()) { visible_views.push_back(view); } } QPoint current_point = m_view->mapToGlobal(m_view->pos()); int curr_x1 = current_point.x(); int curr_x2 = current_point.x() + m_view->width(); int curr_y1 = current_point.y(); int curr_y2 = current_point.y() + m_view->height(); const KTextEditor::Cursor cursorPos = m_view->cursorPosition(); const QPoint globalPos = m_view->mapToGlobal(m_view->cursorToCoordinate(cursorPos)); int curr_cursor_y = globalPos.y(); int curr_cursor_x = globalPos.x(); - KTextEditor::ViewPrivate *bestview = NULL; + KTextEditor::ViewPrivate *bestview = nullptr; int best_x1 = -1, best_x2 = -1, best_y1 = -1, best_y2 = -1, best_center_y = -1, best_center_x = -1; if (direction == Next && visible_views.count() != 1) { for (int i = 0; i < visible_views.count(); i++) { if (visible_views.at(i) == m_view) { if (i != visible_views.count() - 1) { bestview = visible_views.at(i + 1); } else { bestview = visible_views.at(0); } } } } else { foreach (KTextEditor::ViewPrivate *view, visible_views) { QPoint point = view->mapToGlobal(view->pos()); int x1 = point.x(); int x2 = point.x() + view->width(); int y1 = point.y(); int y2 = point.y() + m_view->height(); int center_y = (y1 + y2) / 2; int center_x = (x1 + x2) / 2; switch (direction) { case Left: if (view != m_view && x2 <= curr_x1 && (x2 > best_x2 || (x2 == best_x2 && qAbs(curr_cursor_y - center_y) < qAbs(curr_cursor_y - best_center_y)) || - bestview == NULL)) { + bestview == nullptr)) { bestview = view; best_x2 = x2; best_center_y = center_y; } break; case Right: if (view != m_view && x1 >= curr_x2 && (x1 < best_x1 || (x1 == best_x1 && qAbs(curr_cursor_y - center_y) < qAbs(curr_cursor_y - best_center_y)) || - bestview == NULL)) { + bestview == nullptr)) { bestview = view; best_x1 = x1; best_center_y = center_y; } break; case Down: if (view != m_view && y1 >= curr_y2 && (y1 < best_y1 || (y1 == best_y1 && qAbs(curr_cursor_x - center_x) < qAbs(curr_cursor_x - best_center_x)) || - bestview == NULL)) { + bestview == nullptr)) { bestview = view; best_y1 = y1; best_center_x = center_x; } + break; case Up: if (view != m_view && y2 <= curr_y1 && (y2 > best_y2 || (y2 == best_y2 && qAbs(curr_cursor_x - center_x) < qAbs(curr_cursor_x - best_center_x)) || - bestview == NULL)) { + bestview == nullptr)) { bestview = view; best_y2 = y2; best_center_x = center_x; } break; default: return; } } } - if (bestview != NULL) { + if (bestview != nullptr) { bestview->setFocus(); bestview->setInputMode(KTextEditor::View::ViInputMode); } } Range ModeBase::motionFindPrev() { return m_viInputModeManager->searcher()->motionFindPrev(getCount()); } Range ModeBase::motionFindNext() { return m_viInputModeManager->searcher()->motionFindNext(getCount()); } void ModeBase::goToPos(const Range &r) { KTextEditor::Cursor c; c.setLine(r.endLine); c.setColumn(r.endColumn); if (r.jump) { m_viInputModeManager->jumps()->add(m_view->cursorPosition()); } if (c.line() >= doc()->lines()) { c.setLine(doc()->lines() - 1); } updateCursor(c); } unsigned int ModeBase::linesDisplayed() const { return m_viInputModeManager->inputAdapter()->linesDisplayed(); } void ModeBase::scrollViewLines(int l) { m_viInputModeManager->inputAdapter()->scrollViewLines(l); } int ModeBase::getCount() const { if (m_oneTimeCountOverride != -1) { return m_oneTimeCountOverride; } return (m_count > 0) ? m_count : 1; } diff --git a/src/vimode/modes/normalvimode.cpp b/src/vimode/modes/normalvimode.cpp index e8dbb4f4..6e4e38c9 100644 --- a/src/vimode/modes/normalvimode.cpp +++ b/src/vimode/modes/normalvimode.cpp @@ -1,4149 +1,4147 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008-2009 Erlend Hamberg * Copyright (C) 2008 Evgeniy Ivanov * Copyright (C) 2009 Paul Gideon Dann * Copyright (C) 2011 Svyatoslav Kuzmich * Copyright (C) 2012 - 2013 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 "katebuffer.h" #include "katecompletionwidget.h" #include "kateconfig.h" #include "kateglobal.h" #include "katepartdebug.h" #include "kateundomanager.h" #include #include #include "kateviewhelpers.h" #include "kateviewinternal.h" #include #include #include #include #include #include #include #include #include #include #include "katecmd.h" #include #include "kateviinputmode.h" #include #include #include #include #include #include #include #include using namespace KateVi; #define ADDCMD(STR, FUNC, FLGS) m_commands.push_back( \ new Command( this, QStringLiteral(STR), &NormalViMode::FUNC, FLGS ) ); #define ADDMOTION(STR, FUNC, FLGS) m_motions.push_back( \ new Motion( this, QStringLiteral(STR), &NormalViMode::FUNC, FLGS ) ); NormalViMode::NormalViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal) : ModeBase() { m_view = view; m_viewInternal = viewInternal; m_viInputModeManager = viInputModeManager; m_stickyColumn = -1; m_lastMotionWasVisualLineUpOrDown = false; m_currentMotionWasVisualLineUpOrDown = false; // FIXME: make configurable m_extraWordCharacters = QString(); m_matchingItems[QStringLiteral("/*")] = QStringLiteral("*/"); m_matchingItems[QStringLiteral("*/")] = QStringLiteral("-/*"); m_matchItemRegex = generateMatchingItemRegex(); m_scroll_count_limit = 1000; // Limit of count for scroll commands. initializeCommands(); m_pendingResetIsDueToExit = false; m_isRepeatedTFcommand = false; m_lastMotionWasLinewiseInnerBlock = false; m_motionCanChangeWholeVisualModeSelection = false; resetParser(); // initialise with start configuration m_isUndo = false; connect(doc()->undoManager(), SIGNAL(undoStart(KTextEditor::Document*)), this, SLOT(undoBeginning())); connect(doc()->undoManager(), SIGNAL(undoEnd(KTextEditor::Document*)), this, SLOT(undoEnded())); updateYankHighlightAttrib(); connect(view, SIGNAL(configChanged()), this, SLOT(updateYankHighlightAttrib())); connect(doc(), SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearYankHighlight())); connect(doc(), SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(aboutToDeleteMovingInterfaceContent())); } NormalViMode::~NormalViMode() { qDeleteAll(m_commands); qDeleteAll(m_motions); qDeleteAll(m_highlightedYanks); } /** * parses a key stroke to check if it's a valid (part of) a command * @return true if a command was completed and executed, false otherwise */ bool NormalViMode::handleKeypress(const QKeyEvent *e) { const int keyCode = e->key(); // ignore modifier keys alone if (keyCode == Qt::Key_Shift || keyCode == Qt::Key_Control || keyCode == Qt::Key_Alt || keyCode == Qt::Key_Meta) { return false; } clearYankHighlight(); if (keyCode == Qt::Key_Escape || (keyCode == Qt::Key_C && e->modifiers() == Qt::ControlModifier) || (keyCode == Qt::Key_BracketLeft && e->modifiers() == Qt::ControlModifier)) { m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block); m_pendingResetIsDueToExit = true; // Vim in weird as if we e.g. i it claims (in the status bar) to still be in insert mode, // but behaves as if it's in normal mode. I'm treating the status bar thing as a bug and just exiting // insert mode altogether. m_viInputModeManager->setTemporaryNormalMode(false); reset(); return true; } const QChar key = KeyParser::self()->KeyEventToQChar(*e); const QChar lastChar = m_keys.isEmpty() ? QChar::Null : m_keys.at(m_keys.size() - 1); const bool waitingForRegisterOrCharToSearch = this->waitingForRegisterOrCharToSearch(); // Use replace caret when reading a character for "r" if (key == QLatin1Char('r') && !waitingForRegisterOrCharToSearch) { m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Underline); } m_keysVerbatim.append(KeyParser::self()->decodeKeySequence(key)); if ((keyCode >= Qt::Key_0 && keyCode <= Qt::Key_9 && lastChar != QLatin1Char('"')) // key 0-9 && (m_countTemp != 0 || keyCode != Qt::Key_0) // first digit can't be 0 && (!waitingForRegisterOrCharToSearch) // Not in the middle of "find char" motions or replacing char. && e->modifiers() == Qt::NoModifier) { m_countTemp *= 10; m_countTemp += keyCode - Qt::Key_0; return true; } else if (m_countTemp != 0) { m_count = getCount() * m_countTemp; m_countTemp = 0; m_iscounted = true; } m_keys.append(key); if (m_viInputModeManager->macroRecorder()->isRecording() && key == QLatin1Char('q')) { // Need to special case this "finish macro" q, as the "begin macro" q // needs a parameter whereas the finish macro does not. m_viInputModeManager->macroRecorder()->stop(); resetParser(); return true; } if ((key == QLatin1Char('/') || key == QLatin1Char('?')) && !waitingForRegisterOrCharToSearch) { // Special case for "/" and "?": these should be motions, but this is complicated by // the fact that the user must interact with the search bar before the range of the // motion can be determined. // We hack around this by showing the search bar immediately, and, when the user has // finished interacting with it, have the search bar send a "synthetic" keypresses // that will either abort everything (if the search was aborted) or "complete" the motion // otherwise. m_positionWhenIncrementalSearchBegan = m_view->cursorPosition(); if (key == QLatin1Char('/')) { commandSearchForward(); } else { commandSearchBackward(); } return true; } // Special case: "cw" and "cW" work the same as "ce" and "cE" if the cursor is // on a non-blank. This is because Vim interprets "cw" as change-word, and a // word does not include the following white space. (:help cw in vim) if ((m_keys == QLatin1String("cw") || m_keys == QLatin1String("cW")) && !getCharUnderCursor().isSpace()) { // Special case of the special case: :-) // If the cursor is at the end of the current word rewrite to "cl" const bool isWORD = (m_keys.at(1) == QLatin1Char('W')); const KTextEditor::Cursor currentPosition(m_view->cursorPosition()); const KTextEditor::Cursor endOfWordOrWORD = (isWORD ? findWORDEnd(currentPosition.line(), currentPosition.column() - 1, true) : findWordEnd(currentPosition.line(), currentPosition.column() - 1, true)); if (currentPosition == endOfWordOrWORD) { m_keys = QStringLiteral("cl"); } else { if (isWORD) { m_keys = QStringLiteral("cE"); } else { m_keys = QStringLiteral("ce"); } } } if (m_keys[ 0 ] == Qt::Key_QuoteDbl) { if (m_keys.size() < 2) { return true; // waiting for a register } else { QChar r = m_keys[ 1 ].toLower(); if ((r >= QLatin1Char('0') && r <= QLatin1Char('9')) || (r >= QLatin1Char('a') && r <= QLatin1Char('z')) || r == QLatin1Char('_') || r == QLatin1Char('+') || r == QLatin1Char('*') || r == QLatin1Char('#') || r == QLatin1Char('^')) { m_register = r; m_keys.clear(); return true; } else { resetParser(); return true; } } } // if we have any matching commands so far, check which ones still match if (m_matchingCommands.size() > 0) { int n = m_matchingCommands.size() - 1; // remove commands not matching anymore for (int i = n; i >= 0; i--) { if (!m_commands.at(m_matchingCommands.at(i))->matches(m_keys)) { if (m_commands.at(m_matchingCommands.at(i))->needsMotion()) { // "cache" command needing a motion for later m_motionOperatorIndex = m_matchingCommands.at(i); } m_matchingCommands.remove(i); } } // check if any of the matching commands need a motion/text object, if so // push the current command length to m_awaitingMotionOrTextObject so one // knows where to split the command between the operator and the motion for (int i = 0; i < m_matchingCommands.size(); i++) { if (m_commands.at(m_matchingCommands.at(i))->needsMotion()) { m_awaitingMotionOrTextObject.push(m_keys.size()); break; } } } else { // go through all registered commands and put possible matches in m_matchingCommands for (int i = 0; i < m_commands.size(); i++) { if (m_commands.at(i)->matches(m_keys)) { m_matchingCommands.push_back(i); if (m_commands.at(i)->needsMotion() && m_commands.at(i)->pattern().length() == m_keys.size()) { m_awaitingMotionOrTextObject.push(m_keys.size()); } } } } // this indicates where in the command string one should start looking for a motion command int checkFrom = (m_awaitingMotionOrTextObject.isEmpty() ? 0 : m_awaitingMotionOrTextObject.top()); // Use operator-pending caret when reading a motion for an operator // in normal mode. We need to check that we are indeed in normal mode // since visual mode inherits from it. if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode && !m_awaitingMotionOrTextObject.isEmpty()) { m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Half); } // look for matching motion commands from position 'checkFrom' // FIXME: if checkFrom hasn't changed, only motions whose index is in // m_matchingMotions should be checked bool motionExecuted = false; if (checkFrom < m_keys.size()) { for (int i = 0; i < m_motions.size(); i++) { if (m_motions.at(i)->matches(m_keys.mid(checkFrom))) { m_lastMotionWasLinewiseInnerBlock = false; m_matchingMotions.push_back(i); // if it matches exact, we have found the motion command to execute if (m_motions.at(i)->matchesExact(m_keys.mid(checkFrom))) { m_currentMotionWasVisualLineUpOrDown = false; motionExecuted = true; if (checkFrom == 0) { // no command given before motion, just move the cursor to wherever // the motion says it should go to Range r = m_motions.at(i)->execute(); m_motionCanChangeWholeVisualModeSelection = m_motions.at(i)->canChangeWholeVisualModeSelection(); // jump over folding regions since we are just moving the cursor int currLine = m_view->cursorPosition().line(); int delta = r.endLine - currLine; int vline = m_view->textFolding().lineToVisibleLine(currLine); r.endLine = m_view->textFolding().visibleLineToLine(qMax(vline + delta, 0) /* ensure we have a valid line */); if (r.endLine >= doc()->lines()) { r.endLine = doc()->lines() - 1; } // make sure the position is valid before moving the cursor there // TODO: can this be simplified? :/ if (r.valid && r.endLine >= 0 && (r.endLine == 0 || r.endLine <= doc()->lines() - 1) && r.endColumn >= 0) { if (r.endColumn >= doc()->lineLength(r.endLine) && doc()->lineLength(r.endLine) > 0) { r.endColumn = doc()->lineLength(r.endLine) - 1; } goToPos(r); // in the case of VisualMode we need to remember the motion commands as well. if (!m_viInputModeManager->isAnyVisualMode()) { m_viInputModeManager->clearCurrentChangeLog(); } } else { qCDebug(LOG_KTE) << "Invalid position: (" << r.endLine << "," << r.endColumn << ")"; } resetParser(); // if normal mode was started by using Ctrl-O in insert mode, // it's time to go back to insert mode. if (m_viInputModeManager->getTemporaryNormalMode()) { startInsertMode(); m_viewInternal->repaint(); } m_lastMotionWasVisualLineUpOrDown = m_currentMotionWasVisualLineUpOrDown; break; } else { // execute the specified command and supply the position returned from // the motion m_commandRange = m_motions.at(i)->execute(); m_linewiseCommand = m_motions.at(i)->isLineWise(); // if we didn't get an explicit start position, use the current cursor position if (m_commandRange.startLine == -1) { KTextEditor::Cursor c(m_view->cursorPosition()); m_commandRange.startLine = c.line(); m_commandRange.startColumn = c.column(); } // special case: When using the "w" motion in combination with an operator and // the last word moved over is at the end of a line, the end of that word // becomes the end of the operated text, not the first word in the next line. if (m_motions.at(i)->pattern() == QLatin1String("w") || m_motions.at(i)->pattern() == QLatin1String("W")) { if (m_commandRange.endLine != m_commandRange.startLine && m_commandRange.endColumn == getFirstNonBlank(m_commandRange.endLine)) { m_commandRange.endLine--; m_commandRange.endColumn = doc()->lineLength(m_commandRange.endLine); } } m_commandWithMotion = true; if (m_commandRange.valid) { executeCommand(m_commands.at(m_motionOperatorIndex)); } else { qCDebug(LOG_KTE) << "Invalid range: " << "from (" << m_commandRange.startLine << "," << m_commandRange.startColumn << ")" << "to (" << m_commandRange.endLine << "," << m_commandRange.endColumn << ")"; } if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block); } m_commandWithMotion = false; reset(); break; } } } } } if (this->waitingForRegisterOrCharToSearch()) { // If we are waiting for a char to search or a new register, // don't translate next character; we need the actual character so that e.g. // 'ab' is translated to 'fb' if the mappings 'a' -> 'f' and 'b' -> something else // exist. m_viInputModeManager->keyMapper()->setDoNotMapNextKeypress(); } if (motionExecuted) { return true; } // if we have only one match, check if it is a perfect match and if so, execute it // if it's not waiting for a motion or a text object if (m_matchingCommands.size() == 1) { if (m_commands.at(m_matchingCommands.at(0))->matchesExact(m_keys) && !m_commands.at(m_matchingCommands.at(0))->needsMotion()) { if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block); } Command *cmd = m_commands.at(m_matchingCommands.at(0)); executeCommand(cmd); // check if reset() should be called. some commands in visual mode should not end visual mode if (cmd->shouldReset()) { reset(); m_view->setBlockSelection(false); } resetParser(); return true; } } else if (m_matchingCommands.size() == 0 && m_matchingMotions.size() == 0) { resetParser(); // A bit ugly: we haven't made use of the key event, // but don't want "typeable" keypresses (e.g. a, b, 3, etc) to be marked // as unused as they will then be added to the document, but we don't // want to swallow all keys in case this was a shortcut. // So say we made use of it if and only if it was *not* a shortcut. return e->type() != QEvent::ShortcutOverride; } m_matchingMotions.clear(); return true; // TODO - need to check this - it's currently required for making tests pass, but seems odd. } /** * (re)set to start configuration. This is done when a command is completed * executed or when a command is aborted */ void NormalViMode::resetParser() { m_keys.clear(); m_keysVerbatim.clear(); m_count = 0; m_oneTimeCountOverride = -1; m_iscounted = false; m_countTemp = 0; m_register = QChar::Null; m_findWaitingForChar = false; m_matchingCommands.clear(); m_matchingMotions.clear(); m_awaitingMotionOrTextObject.clear(); m_motionOperatorIndex = 0; m_commandWithMotion = false; m_linewiseCommand = true; m_deleteCommand = false; m_commandShouldKeepSelection = false; m_currentChangeEndMarker = KTextEditor::Cursor::invalid(); if(m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block); } } // reset the command parser void NormalViMode::reset() { resetParser(); m_commandRange.startLine = -1; m_commandRange.startColumn = -1; } void NormalViMode::beginMonitoringDocumentChanges() { connect(doc(), &KTextEditor::DocumentPrivate::textInserted, this, &NormalViMode::textInserted); connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &NormalViMode::textRemoved); } void NormalViMode::executeCommand(const Command *cmd) { const ViMode originalViMode = m_viInputModeManager->getCurrentViMode(); cmd->execute(); // if normal mode was started by using Ctrl-O in insert mode, // it's time to go back to insert mode. if (m_viInputModeManager->getTemporaryNormalMode()) { startInsertMode(); m_viewInternal->repaint(); } // if the command was a change, and it didn't enter insert mode, store the key presses so that // they can be repeated with '.' if (m_viInputModeManager->getCurrentViMode() != ViMode::InsertMode && m_viInputModeManager->getCurrentViMode() != ViMode::ReplaceMode) { if (cmd->isChange() && !m_viInputModeManager->lastChangeRecorder()->isReplaying()) { m_viInputModeManager->storeLastChangeCommand(); } // when we transition to visual mode, remember the command in the keys history (V, v, ctrl-v, ...) // this will later result in buffer filled with something like this "Vjj>" which we can use later with repeat "." const bool commandSwitchedToVisualMode = ((originalViMode == ViMode::NormalMode) && m_viInputModeManager->isAnyVisualMode()); if (!commandSwitchedToVisualMode) { m_viInputModeManager->clearCurrentChangeLog(); } } // make sure the cursor does not end up after the end of the line KTextEditor::Cursor c(m_view->cursorPosition()); if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { int lineLength = doc()->lineLength(c.line()); if (c.column() >= lineLength) { if (lineLength == 0) { c.setColumn(0); } else { c.setColumn(lineLength - 1); } } updateCursor(c); } } //////////////////////////////////////////////////////////////////////////////// // COMMANDS AND OPERATORS //////////////////////////////////////////////////////////////////////////////// /** * enter insert mode at the cursor position */ bool NormalViMode::commandEnterInsertMode() { m_stickyColumn = -1; m_viInputModeManager->getViInsertMode()->setCount(getCount()); return startInsertMode(); } /** * enter insert mode after the current character */ bool NormalViMode::commandEnterInsertModeAppend() { KTextEditor::Cursor c(m_view->cursorPosition()); c.setColumn(c.column() + 1); // if empty line, the cursor should start at column 0 if (doc()->lineLength(c.line()) == 0) { c.setColumn(0); } // cursor should never be in a column > number of columns if (c.column() > doc()->lineLength(c.line())) { c.setColumn(doc()->lineLength(c.line())); } updateCursor(c); m_stickyColumn = -1; m_viInputModeManager->getViInsertMode()->setCount(getCount()); return startInsertMode(); } /** * start insert mode after the last character of the line */ bool NormalViMode::commandEnterInsertModeAppendEOL() { KTextEditor::Cursor c(m_view->cursorPosition()); c.setColumn(doc()->lineLength(c.line())); updateCursor(c); m_stickyColumn = -1; m_viInputModeManager->getViInsertMode()->setCount(getCount()); return startInsertMode(); } bool NormalViMode::commandEnterInsertModeBeforeFirstNonBlankInLine() { KTextEditor::Cursor cursor(m_view->cursorPosition()); int c = getFirstNonBlank(); cursor.setColumn(c); updateCursor(cursor); m_stickyColumn = -1; m_viInputModeManager->getViInsertMode()->setCount(getCount()); return startInsertMode(); } /** * enter insert mode at the last insert position */ bool NormalViMode::commandEnterInsertModeLast() { KTextEditor::Cursor c = m_viInputModeManager->marks()->getInsertStopped(); if (c.isValid()) { updateCursor(c); } m_stickyColumn = -1; return startInsertMode(); } bool NormalViMode::commandEnterVisualLineMode() { if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) { reset(); return true; } return startVisualLineMode(); } bool NormalViMode::commandEnterVisualBlockMode() { if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) { reset(); return true; } return startVisualBlockMode(); } bool NormalViMode::commandReselectVisual() { // start last visual mode and set start = `< and cursor = `> KTextEditor::Cursor c1 = m_viInputModeManager->marks()->getSelectionStart(); KTextEditor::Cursor c2 = m_viInputModeManager->marks()->getSelectionFinish(); // we should either get two valid cursors or two invalid cursors Q_ASSERT(c1.isValid() == c2.isValid()); if (c1.isValid() && c2.isValid()) { m_viInputModeManager->getViVisualMode()->setStart(c1); bool returnValue = false; switch (m_viInputModeManager->getViVisualMode()->getLastVisualMode()) { case ViMode::VisualMode: returnValue = commandEnterVisualMode(); break; case ViMode::VisualLineMode: returnValue = commandEnterVisualLineMode(); break; case ViMode::VisualBlockMode: returnValue = commandEnterVisualBlockMode(); break; default: Q_ASSERT("invalid visual mode"); } m_viInputModeManager->getViVisualMode()->goToPos(c2); return returnValue; } else { error(QStringLiteral("No previous visual selection")); } return false; } bool NormalViMode::commandEnterVisualMode() { if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) { reset(); return true; } return startVisualMode(); } bool NormalViMode::commandToOtherEnd() { if (m_viInputModeManager->isAnyVisualMode()) { m_viInputModeManager->getViVisualMode()->switchStartEnd(); return true; } return false; } bool NormalViMode::commandEnterReplaceMode() { m_stickyColumn = -1; m_viInputModeManager->getViReplaceMode()->setCount(getCount()); return startReplaceMode(); } bool NormalViMode::commandDeleteLine() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r; r.startLine = c.line(); r.endLine = c.line() + getCount() - 1; int column = c.column(); bool ret = deleteRange(r, LineWise); c = m_view->cursorPosition(); if (column > doc()->lineLength(c.line()) - 1) { column = doc()->lineLength(c.line()) - 1; } if (column < 0) { column = 0; } if (c.line() > doc()->lines() - 1) { c.setLine(doc()->lines() - 1); } c.setColumn(column); m_stickyColumn = -1; updateCursor(c); m_deleteCommand = true; return ret; } bool NormalViMode::commandDelete() { m_deleteCommand = true; return deleteRange(m_commandRange, getOperationMode()); } bool NormalViMode::commandDeleteToEOL() { KTextEditor::Cursor c(m_view->cursorPosition()); OperationMode m = CharWise; m_commandRange.endColumn = KateVi::EOL; switch (m_viInputModeManager->getCurrentViMode()) { case ViMode::NormalMode: m_commandRange.startLine = c.line(); m_commandRange.startColumn = c.column(); m_commandRange.endLine = c.line() + getCount() - 1; break; case ViMode::VisualMode: case ViMode::VisualLineMode: m = LineWise; break; case ViMode::VisualBlockMode: m_commandRange.normalize(); m = Block; break; default: /* InsertMode and ReplaceMode will never call this method. */ Q_ASSERT(false); } bool r = deleteRange(m_commandRange, m); switch (m) { case CharWise: c.setColumn(doc()->lineLength(c.line()) - 1); break; case LineWise: c.setLine(m_commandRange.startLine); c.setColumn(getFirstNonBlank(qMin(doc()->lastLine(), m_commandRange.startLine))); break; case Block: c.setLine(m_commandRange.startLine); c.setColumn(m_commandRange.startColumn - 1); break; } // make sure cursor position is valid after deletion if (c.line() < 0) { c.setLine(0); } if (c.line() > doc()->lastLine()) { c.setLine(doc()->lastLine()); } if (c.column() > doc()->lineLength(c.line()) - 1) { c.setColumn(doc()->lineLength(c.line()) - 1); } if (c.column() < 0) { c.setColumn(0); } updateCursor(c); m_deleteCommand = true; return r; } bool NormalViMode::commandMakeLowercase() { KTextEditor::Cursor c = m_view->cursorPosition(); OperationMode m = getOperationMode(); QString text = getRange(m_commandRange, m); if (m == LineWise) { text = text.left(text.size() - 1); // don't need '\n' at the end; } QString lowerCase = text.toLower(); m_commandRange.normalize(); KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn); KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn); KTextEditor::Range range(start, end); doc()->replaceText(range, lowerCase, m == Block); if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { updateCursor(start); } else { updateCursor(c); } return true; } bool NormalViMode::commandMakeLowercaseLine() { KTextEditor::Cursor c(m_view->cursorPosition()); if (doc()->lineLength(c.line()) == 0) { // Nothing to do. return true; } m_commandRange.startLine = c.line(); m_commandRange.endLine = c.line() + getCount() - 1; m_commandRange.startColumn = 0; m_commandRange.endColumn = doc()->lineLength(c.line()) - 1; return commandMakeLowercase(); } bool NormalViMode::commandMakeUppercase() { if (!m_commandRange.valid) { return false; } KTextEditor::Cursor c = m_view->cursorPosition(); OperationMode m = getOperationMode(); QString text = getRange(m_commandRange, m); if (m == LineWise) { text = text.left(text.size() - 1); // don't need '\n' at the end; } QString upperCase = text.toUpper(); m_commandRange.normalize(); KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn); KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn); KTextEditor::Range range(start, end); doc()->replaceText(range, upperCase, m == Block); if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { updateCursor(start); } else { updateCursor(c); } return true; } bool NormalViMode::commandMakeUppercaseLine() { KTextEditor::Cursor c(m_view->cursorPosition()); if (doc()->lineLength(c.line()) == 0) { // Nothing to do. return true; } m_commandRange.startLine = c.line(); m_commandRange.endLine = c.line() + getCount() - 1; m_commandRange.startColumn = 0; m_commandRange.endColumn = doc()->lineLength(c.line()) - 1; return commandMakeUppercase(); } bool NormalViMode::commandChangeCase() { switchView(); QString text; KTextEditor::Range range; KTextEditor::Cursor c(m_view->cursorPosition()); // in visual mode, the range is from start position to end position... if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode || m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) { KTextEditor::Cursor c2 = m_viInputModeManager->getViVisualMode()->getStart(); if (c2 > c) { c2.setColumn(c2.column() + 1); } else { c.setColumn(c.column() + 1); } range.setRange(c, c2); // ... in visual line mode, the range is from column 0 on the first line to // the line length of the last line... } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode) { KTextEditor::Cursor c2 = m_viInputModeManager->getViVisualMode()->getStart(); if (c2 > c) { c2.setColumn(doc()->lineLength(c2.line())); c.setColumn(0); } else { c.setColumn(doc()->lineLength(c.line())); c2.setColumn(0); } range.setRange(c, c2); // ... and in normal mode the range is from the current position to the // current position + count } else { KTextEditor::Cursor c2 = c; c2.setColumn(c.column() + getCount()); if (c2.column() > doc()->lineLength(c.line())) { c2.setColumn(doc()->lineLength(c.line())); } range.setRange(c, c2); } bool block = m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode; // get the text the command should operate on text = doc()->text(range, block); // for every character, switch its case for (int i = 0; i < text.length(); i++) { if (text.at(i).isUpper()) { text[i] = text.at(i).toLower(); } else if (text.at(i).isLower()) { text[i] = text.at(i).toUpper(); } } // replace the old text with the modified text doc()->replaceText(range, text, block); // in normal mode, move the cursor to the right, in visual mode move the // cursor to the start of the selection if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { updateCursor(range.end()); } else { updateCursor(range.start()); } return true; } bool NormalViMode::commandChangeCaseRange() { OperationMode m = getOperationMode(); QString changedCase = getRange(m_commandRange, m); if (m == LineWise) { changedCase = changedCase.left(changedCase.size() - 1); // don't need '\n' at the end; } KTextEditor::Range range = KTextEditor::Range(m_commandRange.startLine, m_commandRange.startColumn, m_commandRange.endLine, m_commandRange.endColumn); // get the text the command should operate on // for every character, switch its case for (int i = 0; i < changedCase.length(); i++) { if (changedCase.at(i).isUpper()) { changedCase[i] = changedCase.at(i).toLower(); } else if (changedCase.at(i).isLower()) { changedCase[i] = changedCase.at(i).toUpper(); } } doc()->replaceText(range, changedCase, m == Block); return true; } bool NormalViMode::commandChangeCaseLine() { KTextEditor::Cursor c(m_view->cursorPosition()); if (doc()->lineLength(c.line()) == 0) { // Nothing to do. return true; } m_commandRange.startLine = c.line(); m_commandRange.endLine = c.line() + getCount() - 1; m_commandRange.startColumn = 0; m_commandRange.endColumn = doc()->lineLength(c.line()) - 1; // -1 is for excluding '\0' if (!commandChangeCaseRange()) { return false; } KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn); if (getCount() > 1) { updateCursor(c); } else { updateCursor(start); } return true; } bool NormalViMode::commandOpenNewLineUnder() { doc()->setUndoMergeAllEdits(true); KTextEditor::Cursor c(m_view->cursorPosition()); c.setColumn(doc()->lineLength(c.line())); updateCursor(c); doc()->newLine(m_view); m_stickyColumn = -1; startInsertMode(); m_viInputModeManager->getViInsertMode()->setCount(getCount()); m_viInputModeManager->getViInsertMode()->setCountedRepeatsBeginOnNewLine(true); - m_viewInternal->repaint(); return true; } bool NormalViMode::commandOpenNewLineOver() { doc()->setUndoMergeAllEdits(true); KTextEditor::Cursor c(m_view->cursorPosition()); if (c.line() == 0) { doc()->insertLine(0, QString()); c.setColumn(0); c.setLine(0); updateCursor(c); } else { c.setLine(c.line() - 1); c.setColumn(getLine(c.line()).length()); updateCursor(c); doc()->newLine(m_view); } m_stickyColumn = -1; startInsertMode(); m_viInputModeManager->getViInsertMode()->setCount(getCount()); m_viInputModeManager->getViInsertMode()->setCountedRepeatsBeginOnNewLine(true); - m_viewInternal->repaint(); return true; } bool NormalViMode::commandJoinLines() { KTextEditor::Cursor c(m_view->cursorPosition()); unsigned int from = c.line(); unsigned int to = c.line() + ((getCount() == 1) ? 1 : getCount() - 1); // if we were given a range of lines, this information overrides the previous if (m_commandRange.startLine != -1 && m_commandRange.endLine != -1) { m_commandRange.normalize(); c.setLine(m_commandRange.startLine); from = m_commandRange.startLine; to = m_commandRange.endLine; } if (to >= (unsigned int)doc()->lines()) { return false; } bool nonEmptyLineFound = false; for (unsigned int lineNum = from; lineNum <= to; lineNum++) { if (!doc()->line(lineNum).isEmpty()) { nonEmptyLineFound = true; } } const int firstNonWhitespaceOnLastLine = doc()->kateTextLine(to)->firstChar(); QString leftTrimmedLastLine; if (firstNonWhitespaceOnLastLine != -1) { leftTrimmedLastLine = doc()->line(to).mid(firstNonWhitespaceOnLastLine); } joinLines(from, to); if (nonEmptyLineFound && leftTrimmedLastLine.isEmpty()) { // joinLines won't have added a trailing " ", whereas Vim does - follow suit. doc()->insertText(KTextEditor::Cursor(from, doc()->lineLength(from)), QLatin1String(" ")); } // Position cursor just before first non-whitesspace character of what was the last line joined. c.setColumn(doc()->lineLength(from) - leftTrimmedLastLine.length() - 1); if (c.column() >= 0) { updateCursor(c); } m_deleteCommand = true; return true; } bool NormalViMode::commandChange() { KTextEditor::Cursor c(m_view->cursorPosition()); OperationMode m = getOperationMode(); doc()->setUndoMergeAllEdits(true); commandDelete(); if (m == LineWise) { // if we deleted several lines, insert an empty line and put the cursor there. doc()->insertLine(m_commandRange.startLine, QString()); c.setLine(m_commandRange.startLine); c.setColumn(0); } else if (m == Block) { // block substitute can be simulated by first deleting the text // (done above) and then starting block prepend. return commandPrependToBlock(); } else { if (m_commandRange.startLine < m_commandRange.endLine) { c.setLine(m_commandRange.startLine); } c.setColumn(m_commandRange.startColumn); } updateCursor(c); setCount(0); // The count was for the motion, not the insertion. commandEnterInsertMode(); // correct indentation level if (m == LineWise) { m_view->align(); } m_deleteCommand = true; return true; } bool NormalViMode::commandChangeToEOL() { commandDeleteToEOL(); if (getOperationMode() == Block) { return commandPrependToBlock(); } m_deleteCommand = true; return commandEnterInsertModeAppend(); } bool NormalViMode::commandChangeLine() { m_deleteCommand = true; KTextEditor::Cursor c(m_view->cursorPosition()); c.setColumn(0); updateCursor(c); doc()->setUndoMergeAllEdits(true); // if count >= 2 start by deleting the whole lines if (getCount() >= 2) { Range r(c.line(), 0, c.line() + getCount() - 2, 0, InclusiveMotion); deleteRange(r); } // ... then delete the _contents_ of the last line, but keep the line Range r(c.line(), c.column(), c.line(), doc()->lineLength(c.line()) - 1, InclusiveMotion); deleteRange(r, CharWise, true); // ... then enter insert mode if (getOperationMode() == Block) { return commandPrependToBlock(); } commandEnterInsertModeAppend(); // correct indentation level m_view->align(); return true; } bool NormalViMode::commandSubstituteChar() { if (commandDeleteChar()) { // The count is only used for deletion of chars; the inserted text is not repeated setCount(0); return commandEnterInsertMode(); } m_deleteCommand = true; return false; } bool NormalViMode::commandSubstituteLine() { m_deleteCommand = true; return commandChangeLine(); } bool NormalViMode::commandYank() { bool r = false; QString yankedText; OperationMode m = getOperationMode(); yankedText = getRange(m_commandRange, m); highlightYank(m_commandRange, m); QChar chosen_register = getChosenRegister(ZeroRegister); fillRegister(chosen_register, yankedText, m); yankToClipBoard(chosen_register, yankedText); return r; } bool NormalViMode::commandYankLine() { KTextEditor::Cursor c(m_view->cursorPosition()); QString lines; int linenum = c.line(); for (int i = 0; i < getCount(); i++) { lines.append(getLine(linenum + i) + QLatin1Char('\n')); } Range yankRange(linenum, 0, linenum + getCount() - 1, getLine(linenum + getCount() - 1).length(), InclusiveMotion); highlightYank(yankRange); QChar chosen_register = getChosenRegister(ZeroRegister); fillRegister(chosen_register, lines, LineWise); yankToClipBoard(chosen_register, lines); return true; } bool NormalViMode::commandYankToEOL() { OperationMode m = CharWise; KTextEditor::Cursor c(m_view->cursorPosition()); MotionType motion = m_commandRange.motionType; m_commandRange.endLine = c.line() + getCount() - 1; m_commandRange.endColumn = doc()->lineLength(m_commandRange.endLine) - 1; m_commandRange.motionType = InclusiveMotion; switch (m_viInputModeManager->getCurrentViMode()) { case ViMode::NormalMode: m_commandRange.startLine = c.line(); m_commandRange.startColumn = c.column(); break; case ViMode::VisualMode: case ViMode::VisualLineMode: m = LineWise; { VisualViMode *visual = static_cast(this); visual->setStart(KTextEditor::Cursor(visual->getStart().line(), 0)); } break; case ViMode::VisualBlockMode: m = Block; break; default: /* InsertMode and ReplaceMode will never call this method. */ Q_ASSERT(false); } const QString &yankedText = getRange(m_commandRange, m); m_commandRange.motionType = motion; highlightYank(m_commandRange); QChar chosen_register = getChosenRegister(ZeroRegister); fillRegister(chosen_register, yankedText, m); yankToClipBoard(chosen_register, yankedText); return true; } // Insert the text in the given register after the cursor position. // This is the non-g version of paste, so the cursor will usually // end up on the last character of the pasted text, unless the text // was multi-line or linewise in which case it will end up // on the *first* character of the pasted text(!) // If linewise, will paste after the current line. bool NormalViMode::commandPaste() { return paste(AfterCurrentPosition, false, false); } // As with commandPaste, except that the text is pasted *at* the cursor position bool NormalViMode::commandPasteBefore() { return paste(AtCurrentPosition, false, false); } // As with commandPaste, except that the cursor will generally be placed *after* the // last pasted character (assuming the last pasted character is not at the end of the line). // If linewise, cursor will be at the beginning of the line *after* the last line of pasted text, // unless that line is the last line of the document; then it will be placed at the beginning of the // last line pasted. bool NormalViMode::commandgPaste() { return paste(AfterCurrentPosition, true, false); } // As with commandgPaste, except that it pastes *at* the current cursor position or, if linewise, // at the current line. bool NormalViMode::commandgPasteBefore() { return paste(AtCurrentPosition, true, false); } bool NormalViMode::commandIndentedPaste() { return paste(AfterCurrentPosition, false, true); } bool NormalViMode::commandIndentedPasteBefore() { return paste(AtCurrentPosition, false, true); } bool NormalViMode::commandDeleteChar() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c.line(), c.column(), c.line(), c.column() + getCount(), ExclusiveMotion); if (m_commandRange.startLine != -1 && m_commandRange.startColumn != -1) { r = m_commandRange; } else { if (r.endColumn > doc()->lineLength(r.startLine)) { r.endColumn = doc()->lineLength(r.startLine); } } // should delete entire lines if in visual line mode and selection in visual block mode OperationMode m = CharWise; if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) { m = LineWise; } else if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) { m = Block; } m_deleteCommand = true; return deleteRange(r, m); } bool NormalViMode::commandDeleteCharBackward() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c.line(), c.column() - getCount(), c.line(), c.column(), ExclusiveMotion); if (m_commandRange.startLine != -1 && m_commandRange.startColumn != -1) { r = m_commandRange; } else { if (r.startColumn < 0) { r.startColumn = 0; } } // should delete entire lines if in visual line mode and selection in visual block mode OperationMode m = CharWise; if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) { m = LineWise; } else if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) { m = Block; } m_deleteCommand = true; return deleteRange(r, m); } bool NormalViMode::commandReplaceCharacter() { QString key = KeyParser::self()->decodeKeySequence(m_keys.right(1)); // Filter out some special keys. const int keyCode = KeyParser::self()->encoded2qt(m_keys.right(1)); switch (keyCode) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_PageUp: case Qt::Key_PageDown: case Qt::Key_Delete: case Qt::Key_Insert: case Qt::Key_Backspace: case Qt::Key_CapsLock: return true; case Qt::Key_Return: case Qt::Key_Enter: key = QStringLiteral("\n"); } bool r; if (m_viInputModeManager->isAnyVisualMode()) { OperationMode m = getOperationMode(); QString text = getRange(m_commandRange, m); if (m == LineWise) { text = text.left(text.size() - 1); // don't need '\n' at the end; } text.replace(QRegExp(QLatin1String("[^\n]")), key); m_commandRange.normalize(); KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn); KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn); KTextEditor::Range range(start, end); r = doc()->replaceText(range, text, m == Block); } else { KTextEditor::Cursor c1(m_view->cursorPosition()); KTextEditor::Cursor c2(m_view->cursorPosition()); c2.setColumn(c2.column() + getCount()); if (c2.column() > doc()->lineLength(m_view->cursorPosition().line())) { return false; } r = doc()->replaceText(KTextEditor::Range(c1, c2), key.repeated(getCount())); updateCursor(c1); } return r; } bool NormalViMode::commandSwitchToCmdLine() { QString initialText; if (m_viInputModeManager->isAnyVisualMode()) { // if in visual mode, make command range == visual selection m_viInputModeManager->getViVisualMode()->saveRangeMarks(); initialText = QStringLiteral("'<,'>"); } else if (getCount() != 1) { // if a count is given, the range [current line] to [current line] + // count should be prepended to the command line initialText = QLatin1String(".,.+") + QString::number(getCount() - 1); } m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar(); m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::Command, initialText); m_commandShouldKeepSelection = true; return true; } bool NormalViMode::commandSearchBackward() { m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar(); m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::SearchBackward); return true; } bool NormalViMode::commandSearchForward() { m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar(); m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::SearchForward); return true; } bool NormalViMode::commandUndo() { // See BUG #328277 m_viInputModeManager->clearCurrentChangeLog(); if (doc()->undoCount() > 0) { const bool mapped = m_viInputModeManager->keyMapper()->isExecutingMapping(); if (mapped) doc()->editEnd(); doc()->undo(); if (mapped) doc()->editBegin(); if (m_viInputModeManager->isAnyVisualMode()) { m_viInputModeManager->getViVisualMode()->setStart(KTextEditor::Cursor(-1, -1)); m_view->clearSelection(); startNormalMode(); } return true; } return false; } bool NormalViMode::commandRedo() { if (doc()->redoCount() > 0) { const bool mapped = m_viInputModeManager->keyMapper()->isExecutingMapping(); if (mapped) doc()->editEnd(); doc()->redo(); if (mapped) doc()->editBegin(); if (m_viInputModeManager->isAnyVisualMode()) { m_viInputModeManager->getViVisualMode()->setStart(KTextEditor::Cursor(-1, -1)); m_view->clearSelection(); startNormalMode(); } return true; } return false; } bool NormalViMode::commandSetMark() { KTextEditor::Cursor c(m_view->cursorPosition()); QChar mark = m_keys.at(m_keys.size() - 1); m_viInputModeManager->marks()->setUserMark(mark, c); return true; } bool NormalViMode::commandIndentLine() { KTextEditor::Cursor c(m_view->cursorPosition()); doc()->indent(KTextEditor::Range(c.line(), 0, c.line() + getCount(), 0), 1); return true; } bool NormalViMode::commandUnindentLine() { KTextEditor::Cursor c(m_view->cursorPosition()); doc()->indent(KTextEditor::Range(c.line(), 0, c.line() + getCount(), 0), -1); return true; } bool NormalViMode::commandIndentLines() { const bool downwards = m_commandRange.startLine < m_commandRange.endLine; m_commandRange.normalize(); int line1 = m_commandRange.startLine; int line2 = m_commandRange.endLine; int col = getLine(line2).length(); doc()->indent(KTextEditor::Range(line1, 0, line2, col), getCount()); if (downwards) { updateCursor(KTextEditor::Cursor(m_commandRange.startLine, m_commandRange.startColumn)); } else { updateCursor(KTextEditor::Cursor(m_commandRange.endLine, m_commandRange.endColumn)); } return true; } bool NormalViMode::commandUnindentLines() { const bool downwards = m_commandRange.startLine < m_commandRange.endLine; m_commandRange.normalize(); int line1 = m_commandRange.startLine; int line2 = m_commandRange.endLine; doc()->indent(KTextEditor::Range(line1, 0, line2, doc()->lineLength(line2)), -getCount()); if (downwards) { updateCursor(KTextEditor::Cursor(m_commandRange.startLine, m_commandRange.startColumn)); } else { updateCursor(KTextEditor::Cursor(m_commandRange.endLine, m_commandRange.endColumn)); } return true; } bool NormalViMode::commandScrollPageDown() { if (getCount() < m_scroll_count_limit) { for (int i = 0; i < getCount(); i++) { m_view->pageDown(); } } return true; } bool NormalViMode::commandScrollPageUp() { if (getCount() < m_scroll_count_limit) { for (int i = 0; i < getCount(); i++) { m_view->pageUp(); } } return true; } bool NormalViMode::commandScrollHalfPageUp() { if (getCount() < m_scroll_count_limit) { for (int i = 0; i < getCount(); i++) { m_viewInternal->pageUp(false, true); } } return true; } bool NormalViMode::commandScrollHalfPageDown() { if (getCount() < m_scroll_count_limit) { for (int i = 0; i < getCount(); i++) { m_viewInternal->pageDown(false, true); } } return true; } bool NormalViMode::commandCenterView(bool onFirst) { KTextEditor::Cursor c(m_view->cursorPosition()); const int virtualCenterLine = m_viewInternal->startLine() + linesDisplayed() / 2; const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line()); scrollViewLines(virtualCursorLine - virtualCenterLine); if (onFirst) { c.setColumn(getFirstNonBlank()); updateCursor(c); } return true; } bool NormalViMode::commandCenterViewOnNonBlank() { return commandCenterView(true); } bool NormalViMode::commandCenterViewOnCursor() { return commandCenterView(false); } bool NormalViMode::commandTopView(bool onFirst) { KTextEditor::Cursor c(m_view->cursorPosition()); const int virtualCenterLine = m_viewInternal->startLine(); const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line()); scrollViewLines(virtualCursorLine - virtualCenterLine); if (onFirst) { c.setColumn(getFirstNonBlank()); updateCursor(c); } return true; } bool NormalViMode::commandTopViewOnNonBlank() { return commandTopView(true); } bool NormalViMode::commandTopViewOnCursor() { return commandTopView(false); } bool NormalViMode::commandBottomView(bool onFirst) { KTextEditor::Cursor c(m_view->cursorPosition()); const int virtualCenterLine = m_viewInternal->endLine(); const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line()); scrollViewLines(virtualCursorLine - virtualCenterLine); if (onFirst) { c.setColumn(getFirstNonBlank()); updateCursor(c); } return true; } bool NormalViMode::commandBottomViewOnNonBlank() { return commandBottomView(true); } bool NormalViMode::commandBottomViewOnCursor() { return commandBottomView(false); } bool NormalViMode::commandAbort() { m_pendingResetIsDueToExit = true; reset(); return true; } bool NormalViMode::commandPrintCharacterCode() { QChar ch = getCharUnderCursor(); if (ch == QChar::Null) { message(QStringLiteral("NUL")); } else { int code = ch.unicode(); QString dec = QString::number(code); QString hex = QString::number(code, 16); QString oct = QString::number(code, 8); if (oct.length() < 3) { oct.prepend(QLatin1Char('0')); } if (code > 0x80 && code < 0x1000) { hex.prepend((code < 0x100 ? QLatin1String("00") : QLatin1String("0"))); } message(i18n("'%1' %2, Hex %3, Octal %4", ch, dec, hex, oct)); } return true; } bool NormalViMode::commandRepeatLastChange() { const int repeatCount = getCount(); resetParser(); if (repeatCount > 1) { m_oneTimeCountOverride = repeatCount; } doc()->editStart(); m_viInputModeManager->repeatLastChange(); doc()->editEnd(); return true; } bool NormalViMode::commandAlignLine() { const int line = m_view->cursorPosition().line(); KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0)); doc()->align(m_view, alignRange); return true; } bool NormalViMode::commandAlignLines() { m_commandRange.normalize(); KTextEditor::Cursor start(m_commandRange.startLine, 0); KTextEditor::Cursor end(m_commandRange.endLine, 0); doc()->align(m_view, KTextEditor::Range(start, end)); return true; } bool NormalViMode::commandAddToNumber() { addToNumberUnderCursor(getCount()); return true; } bool NormalViMode::commandSubtractFromNumber() { addToNumberUnderCursor(-getCount()); return true; } bool NormalViMode::commandPrependToBlock() { KTextEditor::Cursor c(m_view->cursorPosition()); // move cursor to top left corner of selection m_commandRange.normalize(); c.setColumn(m_commandRange.startColumn); c.setLine(m_commandRange.startLine); updateCursor(c); m_stickyColumn = -1; m_viInputModeManager->getViInsertMode()->setBlockPrependMode(m_commandRange); return startInsertMode(); } bool NormalViMode::commandAppendToBlock() { KTextEditor::Cursor c(m_view->cursorPosition()); m_commandRange.normalize(); if (m_stickyColumn == (unsigned int)KateVi::EOL) { // append to EOL // move cursor to end of first line c.setLine(m_commandRange.startLine); c.setColumn(doc()->lineLength(c.line())); updateCursor(c); m_viInputModeManager->getViInsertMode()->setBlockAppendMode(m_commandRange, AppendEOL); } else { m_viInputModeManager->getViInsertMode()->setBlockAppendMode(m_commandRange, Append); // move cursor to top right corner of selection c.setColumn(m_commandRange.endColumn + 1); c.setLine(m_commandRange.startLine); updateCursor(c); } m_stickyColumn = -1; return startInsertMode(); } bool NormalViMode::commandGoToNextJump() { KTextEditor::Cursor c = getNextJump(m_view->cursorPosition()); updateCursor(c); return true; } bool NormalViMode::commandGoToPrevJump() { KTextEditor::Cursor c = getPrevJump(m_view->cursorPosition()); updateCursor(c); return true; } bool NormalViMode::commandSwitchToLeftView() { switchView(Left); return true; } bool NormalViMode::commandSwitchToDownView() { switchView(Down); return true; } bool NormalViMode::commandSwitchToUpView() { switchView(Up); return true; } bool NormalViMode::commandSwitchToRightView() { switchView(Right); return true; } bool NormalViMode::commandSwitchToNextView() { switchView(Next); return true; } bool NormalViMode::commandSplitHoriz() { return executeKateCommand(QStringLiteral("split")); } bool NormalViMode::commandSplitVert() { return executeKateCommand(QStringLiteral("vsplit")); } bool NormalViMode::commandCloseView() { return executeKateCommand(QStringLiteral("close")); } bool NormalViMode::commandSwitchToNextTab() { QString command = QStringLiteral("bn"); if (m_iscounted) { command = command + QLatin1Char(' ') + QString::number(getCount()); } return executeKateCommand(command); } bool NormalViMode::commandSwitchToPrevTab() { QString command = QStringLiteral("bp"); if (m_iscounted) { command = command + QLatin1Char(' ') + QString::number(getCount()); } return executeKateCommand(command); } bool NormalViMode::commandFormatLine() { KTextEditor::Cursor c(m_view->cursorPosition()); reformatLines(c.line(), c.line() + getCount() - 1); return true; } bool NormalViMode::commandFormatLines() { reformatLines(m_commandRange.startLine, m_commandRange.endLine); return true; } bool NormalViMode::commandCollapseToplevelNodes() { #if 0 //FIXME FOLDING doc()->foldingTree()->collapseToplevelNodes(); #endif return true; } bool NormalViMode::commandStartRecordingMacro() { const QChar reg = m_keys[m_keys.size() - 1]; m_viInputModeManager->macroRecorder()->start(reg); return true; } bool NormalViMode::commandReplayMacro() { // "@" will have been added to the log; it needs to be cleared // *before* we replay the macro keypresses, else it can cause an infinite loop // if the macro contains a "." m_viInputModeManager->clearCurrentChangeLog(); const QChar reg = m_keys[m_keys.size() - 1]; const unsigned int count = getCount(); resetParser(); doc()->editBegin(); for (unsigned int i = 0; i < count; i++) { m_viInputModeManager->macroRecorder()->replay(reg); } doc()->editEnd(); return true; } bool NormalViMode::commandCloseNocheck() { return executeKateCommand(QStringLiteral("q!")); } bool NormalViMode::commandCloseWrite() { return executeKateCommand(QStringLiteral("wq")); } bool NormalViMode::commandCollapseLocal() { #if 0 //FIXME FOLDING KTextEditor::Cursor c(m_view->cursorPosition()); doc()->foldingTree()->collapseOne(c.line(), c.column()); #endif return true; } bool NormalViMode::commandExpandAll() { #if 0 //FIXME FOLDING doc()->foldingTree()->expandAll(); #endif return true; } bool NormalViMode::commandExpandLocal() { #if 0 //FIXME FOLDING KTextEditor::Cursor c(m_view->cursorPosition()); doc()->foldingTree()->expandOne(c.line() + 1, c.column()); #endif return true; } bool NormalViMode::commandToggleRegionVisibility() { #if 0 //FIXME FOLDING KTextEditor::Cursor c(m_view->cursorPosition()); doc()->foldingTree()->toggleRegionVisibility(c.line()); #endif return true; } //////////////////////////////////////////////////////////////////////////////// // MOTIONS //////////////////////////////////////////////////////////////////////////////// Range NormalViMode::motionDown() { return goLineDown(); } Range NormalViMode::motionUp() { return goLineUp(); } Range NormalViMode::motionLeft() { KTextEditor::Cursor cursor(m_view->cursorPosition()); m_stickyColumn = -1; Range r(cursor, ExclusiveMotion); r.endColumn -= getCount(); if (r.endColumn < 0) { r.endColumn = 0; } return r; } Range NormalViMode::motionRight() { KTextEditor::Cursor cursor(m_view->cursorPosition()); m_stickyColumn = -1; Range r(cursor, ExclusiveMotion); r.endColumn += getCount(); // make sure end position isn't > line length if (r.endColumn > doc()->lineLength(r.endLine)) { r.endColumn = doc()->lineLength(r.endLine); } return r; } Range NormalViMode::motionPageDown() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, InclusiveMotion); r.endLine += linesDisplayed(); if (r.endLine >= doc()->lines()) { r.endLine = doc()->lines() - 1; } return r; } Range NormalViMode::motionPageUp() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, InclusiveMotion); r.endLine -= linesDisplayed(); if (r.endLine < 0) { r.endLine = 0; } return r; } Range NormalViMode::motionHalfPageDown() { if (commandScrollHalfPageDown()) { KTextEditor::Cursor c = m_view->cursorPosition(); m_commandRange.endLine = c.line(); m_commandRange.endColumn = c.column(); return m_commandRange; } return Range::invalid(); } Range NormalViMode::motionHalfPageUp() { if (commandScrollHalfPageUp()) { KTextEditor::Cursor c = m_view->cursorPosition(); m_commandRange.endLine = c.line(); m_commandRange.endColumn = c.column(); return m_commandRange; } return Range::invalid(); } Range NormalViMode::motionDownToFirstNonBlank() { Range r = goLineDown(); r.endColumn = getFirstNonBlank(r.endLine); return r; } Range NormalViMode::motionUpToFirstNonBlank() { Range r = goLineUp(); r.endColumn = getFirstNonBlank(r.endLine); return r; } Range NormalViMode::motionWordForward() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, ExclusiveMotion); m_stickyColumn = -1; // Special case: If we're already on the very last character in the document, the motion should be // inclusive so the last character gets included if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) { r.motionType = InclusiveMotion; } else { for (int i = 0; i < getCount(); i++) { c = findNextWordStart(c.line(), c.column()); // stop when at the last char in the document if (!c.isValid()) { c = doc()->documentEnd(); // if we still haven't "used up the count", make the motion inclusive, so that the last char // is included if (i < getCount()) { r.motionType = InclusiveMotion; } break; } } } r.endColumn = c.column(); r.endLine = c.line(); return r; } Range NormalViMode::motionWordBackward() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, ExclusiveMotion); m_stickyColumn = -1; for (int i = 0; i < getCount(); i++) { c = findPrevWordStart(c.line(), c.column()); if (!c.isValid()) { c = KTextEditor::Cursor(0, 0); break; } } r.endColumn = c.column(); r.endLine = c.line(); return r; } Range NormalViMode::motionWORDForward() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, ExclusiveMotion); m_stickyColumn = -1; for (int i = 0; i < getCount(); i++) { c = findNextWORDStart(c.line(), c.column()); // stop when at the last char in the document if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) { break; } } r.endColumn = c.column(); r.endLine = c.line(); return r; } Range NormalViMode::motionWORDBackward() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, ExclusiveMotion); m_stickyColumn = -1; for (int i = 0; i < getCount(); i++) { c = findPrevWORDStart(c.line(), c.column()); if (!c.isValid()) { c = KTextEditor::Cursor(0, 0); } } r.endColumn = c.column(); r.endLine = c.line(); return r; } Range NormalViMode::motionToEndOfWord() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, InclusiveMotion); m_stickyColumn = -1; for (int i = 0; i < getCount(); i++) { c = findWordEnd(c.line(), c.column()); } if (!c.isValid()) { c = doc()->documentEnd(); } r.endColumn = c.column(); r.endLine = c.line(); return r; } Range NormalViMode::motionToEndOfWORD() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, InclusiveMotion); m_stickyColumn = -1; for (int i = 0; i < getCount(); i++) { c = findWORDEnd(c.line(), c.column()); } if (!c.isValid()) { c = doc()->documentEnd(); } r.endColumn = c.column(); r.endLine = c.line(); return r; } Range NormalViMode::motionToEndOfPrevWord() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, InclusiveMotion); m_stickyColumn = -1; for (int i = 0; i < getCount(); i++) { c = findPrevWordEnd(c.line(), c.column()); if (c.isValid()) { r.endColumn = c.column(); r.endLine = c.line(); } else { r.endColumn = 0; r.endLine = 0; break; } } return r; } Range NormalViMode::motionToEndOfPrevWORD() { KTextEditor::Cursor c(m_view->cursorPosition()); Range r(c, InclusiveMotion); m_stickyColumn = -1; for (int i = 0; i < getCount(); i++) { c = findPrevWORDEnd(c.line(), c.column()); if (c.isValid()) { r.endColumn = c.column(); r.endLine = c.line(); } else { r.endColumn = 0; r.endLine = 0; break; } } return r; } Range NormalViMode::motionToEOL() { KTextEditor::Cursor c(m_view->cursorPosition()); // set sticky column to a ridiculously high value so that the cursor will stick to EOL, // but only if it's a regular motion if (m_keys.size() == 1) { m_stickyColumn = KateVi::EOL; } unsigned int line = c.line() + (getCount() - 1); Range r(line, doc()->lineLength(line) - 1, InclusiveMotion); return r; } Range NormalViMode::motionToColumn0() { m_stickyColumn = -1; KTextEditor::Cursor cursor(m_view->cursorPosition()); Range r(cursor.line(), 0, ExclusiveMotion); return r; } Range NormalViMode::motionToFirstCharacterOfLine() { m_stickyColumn = -1; KTextEditor::Cursor cursor(m_view->cursorPosition()); int c = getFirstNonBlank(); Range r(cursor.line(), c, ExclusiveMotion); return r; } Range NormalViMode::motionFindChar() { m_lastTFcommand = m_keys; KTextEditor::Cursor cursor(m_view->cursorPosition()); QString line = getLine(); m_stickyColumn = -1; int matchColumn = cursor.column(); for (int i = 0; i < getCount(); i++) { matchColumn = line.indexOf(m_keys.rightRef(1), matchColumn + 1); if (matchColumn == -1) { break; } } Range r; if (matchColumn != -1) { r.endColumn = matchColumn; r.endLine = cursor.line(); } else { return Range::invalid(); } return r; } Range NormalViMode::motionFindCharBackward() { m_lastTFcommand = m_keys; KTextEditor::Cursor cursor(m_view->cursorPosition()); QString line = getLine(); m_stickyColumn = -1; int matchColumn = -1; int hits = 0; int i = cursor.column() - 1; while (hits != getCount() && i >= 0) { if (line.at(i) == m_keys.at(m_keys.size() - 1)) { hits++; } if (hits == getCount()) { matchColumn = i; } i--; } Range r(cursor, ExclusiveMotion); if (matchColumn != -1) { r.endColumn = matchColumn; r.endLine = cursor.line(); } else { return Range::invalid(); } return r; } Range NormalViMode::motionToChar() { m_lastTFcommand = m_keys; KTextEditor::Cursor cursor(m_view->cursorPosition()); QString line = getLine(); m_stickyColumn = -1; Range r; r.endColumn = -1; r.endLine = -1; int matchColumn = cursor.column() + (m_isRepeatedTFcommand ? 2 : 1); for (int i = 0; i < getCount(); i++) { const int lastColumn = matchColumn; matchColumn = line.indexOf(m_keys.right(1), matchColumn + ((i > 0) ? 1 : 0)); if (matchColumn == -1) { if (m_isRepeatedTFcommand) { matchColumn = lastColumn; } else { return Range::invalid(); } break; } } r.endColumn = matchColumn - 1; r.endLine = cursor.line(); m_isRepeatedTFcommand = false; return r; } Range NormalViMode::motionToCharBackward() { m_lastTFcommand = m_keys; KTextEditor::Cursor cursor(m_view->cursorPosition()); QString line = getLine(); const int originalColumn = cursor.column(); m_stickyColumn = -1; int matchColumn = originalColumn - 1; int hits = 0; int i = cursor.column() - (m_isRepeatedTFcommand ? 2 : 1); Range r(cursor, ExclusiveMotion); while (hits != getCount() && i >= 0) { if (line.at(i) == m_keys.at(m_keys.size() - 1)) { hits++; } if (hits == getCount()) { matchColumn = i; } i--; } if (hits == getCount()) { r.endColumn = matchColumn + 1; r.endLine = cursor.line(); } else { r.valid = false; } m_isRepeatedTFcommand = false; return r; } Range NormalViMode::motionRepeatlastTF() { if (!m_lastTFcommand.isEmpty()) { m_isRepeatedTFcommand = true; m_keys = m_lastTFcommand; if (m_keys.at(0) == QLatin1Char('f')) { return motionFindChar(); } else if (m_keys.at(0) == QLatin1Char('F')) { return motionFindCharBackward(); } else if (m_keys.at(0) == QLatin1Char('t')) { return motionToChar(); } else if (m_keys.at(0) == QLatin1Char('T')) { return motionToCharBackward(); } } // there was no previous t/f command return Range::invalid(); } Range NormalViMode::motionRepeatlastTFBackward() { if (!m_lastTFcommand.isEmpty()) { m_isRepeatedTFcommand = true; m_keys = m_lastTFcommand; if (m_keys.at(0) == QLatin1Char('f')) { return motionFindCharBackward(); } else if (m_keys.at(0) == QLatin1Char('F')) { return motionFindChar(); } else if (m_keys.at(0) == QLatin1Char('t')) { return motionToCharBackward(); } else if (m_keys.at(0) == QLatin1Char('T')) { return motionToChar(); } } // there was no previous t/f command return Range::invalid(); } Range NormalViMode::motionToLineFirst() { Range r(getCount() - 1, 0, InclusiveMotion); m_stickyColumn = -1; if (r.endLine > doc()->lines() - 1) { r.endLine = doc()->lines() - 1; } r.jump = true; return r; } Range NormalViMode::motionToLineLast() { Range r(doc()->lines() - 1, 0, InclusiveMotion); m_stickyColumn = -1; // don't use getCount() here, no count and a count of 1 is different here... if (m_count != 0) { r.endLine = m_count - 1; } if (r.endLine > doc()->lines() - 1) { r.endLine = doc()->lines() - 1; } r.jump = true; return r; } Range NormalViMode::motionToScreenColumn() { m_stickyColumn = -1; KTextEditor::Cursor c(m_view->cursorPosition()); int column = getCount() - 1; if (doc()->lineLength(c.line()) - 1 < (int)getCount() - 1) { column = doc()->lineLength(c.line()) - 1; } return Range(c.line(), column, ExclusiveMotion); } Range NormalViMode::motionToMark() { Range r; m_stickyColumn = -1; QChar reg = m_keys.at(m_keys.size() - 1); KTextEditor::Cursor c = m_viInputModeManager->marks()->getMarkPosition(reg); if (c.isValid()) { r.endLine = c.line(); r.endColumn = c.column(); } else { error(i18n("Mark not set: %1", m_keys.right(1))); r.valid = false; } r.jump = true; return r; } Range NormalViMode::motionToMarkLine() { Range r = motionToMark(); r.endColumn = getFirstNonBlank(r.endLine); r.jump = true; m_stickyColumn = -1; return r; } Range NormalViMode::motionToMatchingItem() { Range r; int lines = doc()->lines(); // If counted, then it's not a motion to matching item anymore, // but a motion to the N'th percentage of the document if (isCounted()) { int count = getCount(); if (count > 100) { return r; } r.endLine = qRound(lines * count / 100.0) - 1; r.endColumn = 0; return r; } KTextEditor::Cursor c(m_view->cursorPosition()); QString l = getLine(); int n1 = l.indexOf(m_matchItemRegex, c.column()); m_stickyColumn = -1; if (n1 < 0) { return Range::invalid(); } QRegExp brackets(QLatin1String("[(){}\\[\\]]")); // use Kate's built-in matching bracket finder for brackets if (brackets.indexIn(l, n1) == n1) { // findMatchingBracket requires us to move the cursor to the // first bracket, but we don't want the cursor to really move // in case this is e.g. a yank, so restore it to its original // position afterwards. c.setColumn(n1 + 1); const KTextEditor::Cursor oldCursorPos = m_view->cursorPosition(); updateCursor(c); // find the matching one c = m_viewInternal->findMatchingBracket(); if (c > m_view->cursorPosition()) { c.setColumn(c.column() - 1); } m_view->setCursorPosition(oldCursorPos); } else { // text item we want to find a matching item for int n2 = l.indexOf(QRegExp(QLatin1String("\\b|\\s|$")), n1); QString item = l.mid(n1, n2 - n1); QString matchingItem = m_matchingItems[ item ]; int toFind = 1; int line = c.line(); int column = n2 - item.length(); bool reverse = false; if (matchingItem.left(1) == QLatin1String("-")) { matchingItem.remove(0, 1); // remove the '-' reverse = true; } // make sure we don't hit the text item we started the search from if (column == 0 && reverse) { column -= item.length(); } int itemIdx; int matchItemIdx; while (toFind > 0) { if (reverse) { itemIdx = l.lastIndexOf(item, column - 1); matchItemIdx = l.lastIndexOf(matchingItem, column - 1); if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx > matchItemIdx)) { ++toFind; } } else { itemIdx = l.indexOf(item, column); matchItemIdx = l.indexOf(matchingItem, column); if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx < matchItemIdx)) { ++toFind; } } if (matchItemIdx != -1 || itemIdx != -1) { if (!reverse) { column = qMin((unsigned int)itemIdx, (unsigned int)matchItemIdx); } else { column = qMax(itemIdx, matchItemIdx); } } if (matchItemIdx != -1) { // match on current line if (matchItemIdx == column) { --toFind; c.setLine(line); c.setColumn(column); } } else { // no match, advance one line if possible (reverse) ? --line : ++line; column = 0; if ((!reverse && line >= lines) || (reverse && line < 0)) { r.valid = false; break; } else { l = getLine(line); } } } } r.endLine = c.line(); r.endColumn = c.column(); r.jump = true; return r; } Range NormalViMode::motionToNextBraceBlockStart() { Range r; m_stickyColumn = -1; int line = findLineStartingWitchChar(QLatin1Char('{'), getCount()); if (line == -1) { return Range::invalid(); } r.endLine = line; r.endColumn = 0; r.jump = true; if (motionWillBeUsedWithCommand()) { // Delete from cursor (inclusive) to the '{' (exclusive). // If we are on the first column, then delete the entire current line. r.motionType = ExclusiveMotion; if (m_view->cursorPosition().column() != 0) { r.endLine--; r.endColumn = doc()->lineLength(r.endLine); } } return r; } Range NormalViMode::motionToPreviousBraceBlockStart() { Range r; m_stickyColumn = -1; int line = findLineStartingWitchChar(QLatin1Char('{'), getCount(), false); if (line == -1) { return Range::invalid(); } r.endLine = line; r.endColumn = 0; r.jump = true; if (motionWillBeUsedWithCommand()) { // With a command, do not include the { or the cursor position. r.motionType = ExclusiveMotion; } return r; } Range NormalViMode::motionToNextBraceBlockEnd() { Range r; m_stickyColumn = -1; int line = findLineStartingWitchChar(QLatin1Char('}'), getCount()); if (line == -1) { return Range::invalid(); } r.endLine = line; r.endColumn = 0; r.jump = true; if (motionWillBeUsedWithCommand()) { // Delete from cursor (inclusive) to the '}' (exclusive). // If we are on the first column, then delete the entire current line. r.motionType = ExclusiveMotion; if (m_view->cursorPosition().column() != 0) { r.endLine--; r.endColumn = doc()->lineLength(r.endLine); } } return r; } Range NormalViMode::motionToPreviousBraceBlockEnd() { Range r; m_stickyColumn = -1; int line = findLineStartingWitchChar(QLatin1Char('}'), getCount(), false); if (line == -1) { return Range::invalid(); } r.endLine = line; r.endColumn = 0; r.jump = true; if (motionWillBeUsedWithCommand()) { r.motionType = ExclusiveMotion; } return r; } Range NormalViMode::motionToNextOccurrence() { const QString word = getWordUnderCursor(); const Range match = m_viInputModeManager->searcher()->findWordForMotion(word, false, getWordRangeUnderCursor().start(), getCount()); return Range(match.startLine, match.startColumn, ExclusiveMotion); } Range NormalViMode::motionToPrevOccurrence() { const QString word = getWordUnderCursor(); const Range match = m_viInputModeManager->searcher()->findWordForMotion(word, true, getWordRangeUnderCursor().start(), getCount()); return Range(match.startLine, match.startColumn, ExclusiveMotion); } Range NormalViMode::motionToFirstLineOfWindow() { int lines_to_go; if (linesDisplayed() <= (unsigned int) m_viewInternal->endLine()) { lines_to_go = m_viewInternal->endLine() - linesDisplayed() - m_view->cursorPosition().line() + 1; } else { lines_to_go = - m_view->cursorPosition().line(); } Range r = goLineUpDown(lines_to_go); r.endColumn = getFirstNonBlank(r.endLine); return r; } Range NormalViMode::motionToMiddleLineOfWindow() { int lines_to_go; if (linesDisplayed() <= (unsigned int) m_viewInternal->endLine()) { lines_to_go = m_viewInternal->endLine() - linesDisplayed() / 2 - m_view->cursorPosition().line(); } else { lines_to_go = m_viewInternal->endLine() / 2 - m_view->cursorPosition().line(); } Range r = goLineUpDown(lines_to_go); r.endColumn = getFirstNonBlank(r.endLine); return r; } Range NormalViMode::motionToLastLineOfWindow() { int lines_to_go; if (linesDisplayed() <= (unsigned int) m_viewInternal->endLine()) { lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line(); } else { lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line(); } Range r = goLineUpDown(lines_to_go); r.endColumn = getFirstNonBlank(r.endLine); return r; } Range NormalViMode::motionToNextVisualLine() { return goVisualLineUpDown(getCount()); } Range NormalViMode::motionToPrevVisualLine() { return goVisualLineUpDown(-getCount()); } Range NormalViMode::motionToPreviousSentence() { KTextEditor::Cursor c = findSentenceStart(); int linenum = c.line(), column; const bool skipSpaces = doc()->line(linenum).isEmpty(); if (skipSpaces) { linenum--; if (linenum >= 0) { column = doc()->line(linenum).size() - 1; } } else { column = c.column(); } for (int i = linenum; i >= 0; i--) { const QString &line = doc()->line(i); if (line.isEmpty() && !skipSpaces) { return Range(i, 0, InclusiveMotion); } if (column < 0 && line.size() > 0) { column = line.size() - 1; } for (int j = column; j >= 0; j--) { if (skipSpaces || QStringLiteral(".?!").indexOf(line.at(j)) != -1) { c.setLine(i); c.setColumn(j); updateCursor(c); c = findSentenceStart(); return Range(c, InclusiveMotion); } } column = line.size() - 1; } return Range(0, 0, InclusiveMotion); } Range NormalViMode::motionToNextSentence() { KTextEditor::Cursor c = findSentenceEnd(); int linenum = c.line(), column = c.column() + 1; const bool skipSpaces = doc()->line(linenum).isEmpty(); for (int i = linenum; i < doc()->lines(); i++) { const QString &line = doc()->line(i); if (line.isEmpty() && !skipSpaces) { return Range(i, 0, InclusiveMotion); } for (int j = column; j < line.size(); j++) { if (!line.at(j).isSpace()) { return Range(i, j, InclusiveMotion); } } column = 0; } c = doc()->documentEnd(); return Range(c, InclusiveMotion); } Range NormalViMode::motionToBeforeParagraph() { KTextEditor::Cursor c(m_view->cursorPosition()); int line = c.line(); m_stickyColumn = -1; for (int i = 0; i < getCount(); i++) { // advance at least one line, but if there are consecutive blank lines // skip them all do { line--; } while (line >= 0 && getLine(line + 1).length() == 0); while (line > 0 && getLine(line).length() != 0) { line--; } } if (line < 0) { line = 0; } Range r(line, 0, InclusiveMotion); return r; } Range NormalViMode::motionToAfterParagraph() { KTextEditor::Cursor c(m_view->cursorPosition()); int line = c.line(); m_stickyColumn = -1; for (int i = 0; i < getCount(); i++) { // advance at least one line, but if there are consecutive blank lines // skip them all do { line++; } while (line <= doc()->lines() - 1 && getLine(line - 1).length() == 0); while (line < doc()->lines() - 1 && getLine(line).length() != 0) { line++; } } if (line >= doc()->lines()) { line = doc()->lines() - 1; } // if we ended up on the last line, the cursor should be placed on the last column int column = (line == doc()->lines() - 1) ? qMax(getLine(line).length() - 1, 0) : 0; return Range(line, column, InclusiveMotion); } Range NormalViMode::motionToIncrementalSearchMatch() { return Range(m_positionWhenIncrementalSearchBegan.line(), m_positionWhenIncrementalSearchBegan.column(), m_view->cursorPosition().line(), m_view->cursorPosition().column(), ExclusiveMotion); } //////////////////////////////////////////////////////////////////////////////// // TEXT OBJECTS //////////////////////////////////////////////////////////////////////////////// Range NormalViMode::textObjectAWord() { KTextEditor::Cursor c(m_view->cursorPosition()); KTextEditor::Cursor c1 = c; bool startedOnSpace = false; if (doc()->characterAt(c).isSpace()) { startedOnSpace = true; } else { c1 = findPrevWordStart(c.line(), c.column() + 1, true); if (!c1.isValid()) { c1 = KTextEditor::Cursor(0, 0); } } KTextEditor::Cursor c2 = KTextEditor::Cursor(c.line(), c.column() - 1); for (int i = 1; i <= getCount(); i++) { c2 = findWordEnd(c2.line(), c2.column()); } if (!c1.isValid() || !c2.isValid()) { return Range::invalid(); } // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not. // Don't ask ;) const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column()); if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) { if (!startedOnSpace) { c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1); } } else { c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1); } bool swallowCarriageReturnAtEndOfLine = false; if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) { // Greedily descend to the next line, so as to swallow the carriage return on this line. c2 = KTextEditor::Cursor(c2.line() + 1, 0); swallowCarriageReturnAtEndOfLine = true; } const bool swallowPrecedingSpaces = (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine; if (swallowPrecedingSpaces) { if (c1.column() != 0) { const KTextEditor::Cursor previousNonSpace = findPrevWordEnd(c.line(), c.column()); if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) { c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1); } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) { c1 = KTextEditor::Cursor(c1.line(), 0); } } } return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion); } Range NormalViMode::textObjectInnerWord() { KTextEditor::Cursor c(m_view->cursorPosition()); KTextEditor::Cursor c1 = findPrevWordStart(c.line(), c.column() + 1, true); if (!c1.isValid()) { c1 = KTextEditor::Cursor(0, 0); } // need to start search in column-1 because it might be a one-character word KTextEditor::Cursor c2(c.line(), c.column() - 1); for (int i = 0; i < getCount(); i++) { c2 = findWordEnd(c2.line(), c2.column(), true); } if (!c2.isValid()) { c2 = doc()->documentEnd(); } // sanity check if (c1.line() != c2.line() || c1.column() > c2.column()) { return Range::invalid(); } return Range(c1, c2, InclusiveMotion); } Range NormalViMode::textObjectAWORD() { KTextEditor::Cursor c(m_view->cursorPosition()); KTextEditor::Cursor c1 = c; bool startedOnSpace = false; if (doc()->characterAt(c).isSpace()) { startedOnSpace = true; } else { c1 = findPrevWORDStart(c.line(), c.column() + 1, true); if (!c1.isValid()) { c1 = KTextEditor::Cursor(0, 0); } } KTextEditor::Cursor c2 = KTextEditor::Cursor(c.line(), c.column() - 1); for (int i = 1; i <= getCount(); i++) { c2 = findWORDEnd(c2.line(), c2.column()); } if (!c1.isValid() || !c2.isValid()) { return Range::invalid(); } // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not. // Don't ask ;) const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column()); if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) { if (!startedOnSpace) { c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1); } } else { c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1); } bool swallowCarriageReturnAtEndOfLine = false; if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) { // Greedily descend to the next line, so as to swallow the carriage return on this line. c2 = KTextEditor::Cursor(c2.line() + 1, 0); swallowCarriageReturnAtEndOfLine = true; } const bool swallowPrecedingSpaces = (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine; if (swallowPrecedingSpaces) { if (c1.column() != 0) { const KTextEditor::Cursor previousNonSpace = findPrevWORDEnd(c.line(), c.column()); if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) { c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1); } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) { c1 = KTextEditor::Cursor(c1.line(), 0); } } } return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion); } Range NormalViMode::textObjectInnerWORD() { KTextEditor::Cursor c(m_view->cursorPosition()); KTextEditor::Cursor c1 = findPrevWORDStart(c.line(), c.column() + 1, true); if (!c1.isValid()) { c1 = KTextEditor::Cursor(0, 0); } KTextEditor::Cursor c2(c); for (int i = 0; i < getCount(); i++) { c2 = findWORDEnd(c2.line(), c2.column(), true); } if (!c2.isValid()) { c2 = doc()->documentEnd(); } // sanity check if (c1.line() != c2.line() || c1.column() > c2.column()) { return Range::invalid(); } return Range(c1, c2, InclusiveMotion); } KTextEditor::Cursor NormalViMode::findSentenceStart() { KTextEditor::Cursor c(m_view->cursorPosition()); int linenum = c.line(), column = c.column(); int prev = column; for (int i = linenum; i >= 0; i--) { const QString &line = doc()->line(i); if (i != linenum) { column = line.size() - 1; } // An empty line is the end of a paragraph. if (line.isEmpty()) { return KTextEditor::Cursor((i != linenum) ? i + 1 : i, prev); } prev = column; for (int j = column; j >= 0; j--) { if (line.at(j).isSpace()) { int lastSpace = j--; for (; j >= 0 && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j--); if (j >= 0 && QStringLiteral(".!?").indexOf(line.at(j)) != -1) { return KTextEditor::Cursor(i, prev); } j = lastSpace; } else prev = j; } } return KTextEditor::Cursor(0, 0); } KTextEditor::Cursor NormalViMode::findSentenceEnd() { KTextEditor::Cursor c(m_view->cursorPosition()); int linenum = c.line(), column = c.column(); int j = 0, prev = 0; for (int i = linenum; i < doc()->lines(); i++) { const QString &line = doc()->line(i); // An empty line is the end of a paragraph. if (line.isEmpty()) { return KTextEditor::Cursor(linenum, j); } // Iterating over the line to reach any '.', '!', '?' for (j = column; j < line.size(); j++) { if (QStringLiteral(".!?").indexOf(line.at(j)) != -1) { prev = j++; // Skip possible closing characters. for (; j < line.size() && QString::fromLatin1("\"')]").indexOf(line.at(j)) != -1; j++); if (j >= line.size()) { return KTextEditor::Cursor(i, j - 1); } // And hopefully we're done... if (line.at(j).isSpace()) { return KTextEditor::Cursor(i, j - 1); } j = prev; } } linenum = i; prev = column; column = 0; } return KTextEditor::Cursor(linenum, j - 1); } KTextEditor::Cursor NormalViMode::findParagraphStart() { KTextEditor::Cursor c(m_view->cursorPosition()); const bool firstBlank = doc()->line(c.line()).isEmpty(); int prev = c.line(); for (int i = prev; i >= 0; i--) { if (doc()->line(i).isEmpty()) { if (i != prev) { prev = i + 1; } /* Skip consecutive empty lines. */ if (firstBlank) { i--; for (; i >= 0 && doc()->line(i).isEmpty(); i--, prev--); } return KTextEditor::Cursor(prev, 0); } } return KTextEditor::Cursor(0, 0); } KTextEditor::Cursor NormalViMode::findParagraphEnd() { KTextEditor::Cursor c(m_view->cursorPosition()); int prev = c.line(), lines = doc()->lines(); const bool firstBlank = doc()->line(prev).isEmpty(); for (int i = prev; i < lines; i++) { if (doc()->line(i).isEmpty()) { if (i != prev) { prev = i - 1; } /* Skip consecutive empty lines. */ if (firstBlank) { i++; for (; i < lines && doc()->line(i).isEmpty(); i++, prev++); } int length = doc()->lineLength(prev); return KTextEditor::Cursor(prev, (length <= 0) ? 0 : length - 1); } } return doc()->documentEnd(); } Range NormalViMode::textObjectInnerSentence() { Range r; KTextEditor::Cursor c1 = findSentenceStart(); KTextEditor::Cursor c2 = findSentenceEnd(); updateCursor(c1); r.startLine = c1.line(); r.startColumn = c1.column(); r.endLine = c2.line(); r.endColumn = c2.column(); return r; } Range NormalViMode::textObjectASentence() { int i; Range r = textObjectInnerSentence(); const QString &line = doc()->line(r.endLine); // Skip whitespaces and tabs. for (i = r.endColumn + 1; i < line.size(); i++) { if (!line.at(i).isSpace()) { break; } } r.endColumn = i - 1; // Remove preceding spaces. if (r.startColumn != 0) { if (r.endColumn == line.size() - 1 && !line.at(r.endColumn).isSpace()) { const QString &line = doc()->line(r.startLine); for (i = r.startColumn - 1; i >= 0; i--) { if (!line.at(i).isSpace()) { break; } } r.startColumn = i + 1; } } return r; } Range NormalViMode::textObjectInnerParagraph() { Range r; KTextEditor::Cursor c1 = findParagraphStart(); KTextEditor::Cursor c2 = findParagraphEnd(); updateCursor(c1); r.startLine = c1.line(); r.startColumn = c1.column(); r.endLine = c2.line(); r.endColumn = c2.column(); return r; } Range NormalViMode::textObjectAParagraph() { Range r = textObjectInnerParagraph(); int lines = doc()->lines(); if (r.endLine + 1 < lines) { // If the next line is empty, remove all subsequent empty lines. // Otherwise we'll grab the next paragraph. if (doc()->line(r.endLine + 1).isEmpty()) { for (int i = r.endLine + 1; i < lines && doc()->line(i).isEmpty(); i++) { r.endLine++; } r.endColumn = 0; } else { KTextEditor::Cursor prev = m_view->cursorPosition(); KTextEditor::Cursor c(r.endLine + 1, 0); updateCursor(c); c = findParagraphEnd(); updateCursor(prev); r.endLine = c.line(); r.endColumn = c.column(); } } else if (doc()->lineLength(r.startLine) > 0) { // We went too far, but maybe we can grab previous empty lines. for (int i = r.startLine - 1; i >= 0 && doc()->line(i).isEmpty(); i--) { r.startLine--; } r.startColumn = 0; updateCursor(KTextEditor::Cursor(r.startLine, r.startColumn)); } else { // We went too far and we're on empty lines, do nothing. return Range::invalid(); } return r; } Range NormalViMode::textObjectAQuoteDouble() { return findSurroundingQuotes(QLatin1Char('"'), false); } Range NormalViMode::textObjectInnerQuoteDouble() { return findSurroundingQuotes(QLatin1Char('"'), true); } Range NormalViMode::textObjectAQuoteSingle() { return findSurroundingQuotes(QLatin1Char('\''), false); } Range NormalViMode::textObjectInnerQuoteSingle() { return findSurroundingQuotes(QLatin1Char('\''), true); } Range NormalViMode::textObjectABackQuote() { return findSurroundingQuotes(QLatin1Char('`'), false); } Range NormalViMode::textObjectInnerBackQuote() { return findSurroundingQuotes(QLatin1Char('`'), true); } Range NormalViMode::textObjectAParen() { return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), false, QLatin1Char('('), QLatin1Char(')')); } Range NormalViMode::textObjectInnerParen() { return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), true, QLatin1Char('('), QLatin1Char(')')); } Range NormalViMode::textObjectABracket() { return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), false, QLatin1Char('['), QLatin1Char(']')); } Range NormalViMode::textObjectInnerBracket() { return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), true, QLatin1Char('['), QLatin1Char(']')); } Range NormalViMode::textObjectACurlyBracket() { return findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), false, QLatin1Char('{'), QLatin1Char('}')); } Range NormalViMode::textObjectInnerCurlyBracket() { const Range allBetweenCurlyBrackets = findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), true, QLatin1Char('{'), QLatin1Char('}')); // Emulate the behaviour of vim, which tries to leave the closing bracket on its own line // if it was originally on a line different to that of the opening bracket. Range innerCurlyBracket(allBetweenCurlyBrackets); if (innerCurlyBracket.startLine != innerCurlyBracket.endLine) { const bool openingBraceIsLastCharOnLine = innerCurlyBracket.startColumn == doc()->line(innerCurlyBracket.startLine).length(); const bool stuffToDeleteIsAllOnEndLine = openingBraceIsLastCharOnLine && innerCurlyBracket.endLine == innerCurlyBracket.startLine + 1; const QString textLeadingClosingBracket = doc()->line(innerCurlyBracket.endLine).mid(0, innerCurlyBracket.endColumn + 1); const bool closingBracketHasLeadingNonWhitespace = !textLeadingClosingBracket.trimmed().isEmpty(); if (stuffToDeleteIsAllOnEndLine) { if (!closingBracketHasLeadingNonWhitespace) { // Nothing there to select - abort. return Range::invalid(); } else { // Shift the beginning of the range to the start of the line containing the closing bracket. innerCurlyBracket.startLine++; innerCurlyBracket.startColumn = 0; } } else { if (openingBraceIsLastCharOnLine && !closingBracketHasLeadingNonWhitespace) { innerCurlyBracket.startLine++; innerCurlyBracket.startColumn = 0; m_lastMotionWasLinewiseInnerBlock = true; } { // The line containing the end bracket is left alone if the end bracket is preceded by just whitespace, // else we need to delete everything (i.e. end up with "{}") if (!closingBracketHasLeadingNonWhitespace) { // Shrink the endpoint of the range so that it ends at the end of the line above, // leaving the closing bracket on its own line. innerCurlyBracket.endLine--; innerCurlyBracket.endColumn = doc()->line(innerCurlyBracket.endLine).length(); } } } } return innerCurlyBracket; } Range NormalViMode::textObjectAInequalitySign() { return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), false, QLatin1Char('<'), QLatin1Char('>')); } Range NormalViMode::textObjectInnerInequalitySign() { return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), true, QLatin1Char('<'), QLatin1Char('>')); } Range NormalViMode::textObjectAComma() { return textObjectComma(false); } Range NormalViMode::textObjectInnerComma() { return textObjectComma(true); } // add commands // when adding commands here, remember to add them to visual mode too (if applicable) void NormalViMode::initializeCommands() { ADDCMD("a", commandEnterInsertModeAppend, IS_CHANGE); ADDCMD("A", commandEnterInsertModeAppendEOL, IS_CHANGE); ADDCMD("i", commandEnterInsertMode, IS_CHANGE); ADDCMD("", commandEnterInsertMode, IS_CHANGE); ADDCMD("I", commandEnterInsertModeBeforeFirstNonBlankInLine, IS_CHANGE); ADDCMD("gi", commandEnterInsertModeLast, IS_CHANGE); ADDCMD("v", commandEnterVisualMode, 0); ADDCMD("V", commandEnterVisualLineMode, 0); ADDCMD("", commandEnterVisualBlockMode, 0); ADDCMD("gv", commandReselectVisual, SHOULD_NOT_RESET); ADDCMD("o", commandOpenNewLineUnder, IS_CHANGE); ADDCMD("O", commandOpenNewLineOver, IS_CHANGE); ADDCMD("J", commandJoinLines, IS_CHANGE); ADDCMD("c", commandChange, IS_CHANGE | NEEDS_MOTION); ADDCMD("C", commandChangeToEOL, IS_CHANGE); ADDCMD("cc", commandChangeLine, IS_CHANGE); ADDCMD("s", commandSubstituteChar, IS_CHANGE); ADDCMD("S", commandSubstituteLine, IS_CHANGE); ADDCMD("dd", commandDeleteLine, IS_CHANGE); ADDCMD("d", commandDelete, IS_CHANGE | NEEDS_MOTION); ADDCMD("D", commandDeleteToEOL, IS_CHANGE); ADDCMD("x", commandDeleteChar, IS_CHANGE); ADDCMD("", commandDeleteChar, IS_CHANGE); ADDCMD("X", commandDeleteCharBackward, IS_CHANGE); ADDCMD("gu", commandMakeLowercase, IS_CHANGE | NEEDS_MOTION); ADDCMD("guu", commandMakeLowercaseLine, IS_CHANGE); ADDCMD("gU", commandMakeUppercase, IS_CHANGE | NEEDS_MOTION); ADDCMD("gUU", commandMakeUppercaseLine, IS_CHANGE); ADDCMD("y", commandYank, NEEDS_MOTION); ADDCMD("yy", commandYankLine, 0); ADDCMD("Y", commandYankToEOL, 0); ADDCMD("p", commandPaste, IS_CHANGE); ADDCMD("P", commandPasteBefore, IS_CHANGE); ADDCMD("gp", commandgPaste, IS_CHANGE); ADDCMD("gP", commandgPasteBefore, IS_CHANGE); ADDCMD("]p", commandIndentedPaste, IS_CHANGE); ADDCMD("[p", commandIndentedPasteBefore, IS_CHANGE); ADDCMD("r.", commandReplaceCharacter, IS_CHANGE | REGEX_PATTERN); ADDCMD("R", commandEnterReplaceMode, IS_CHANGE); ADDCMD(":", commandSwitchToCmdLine, 0); ADDCMD("u", commandUndo, 0); ADDCMD("", commandRedo, 0); ADDCMD("U", commandRedo, 0); ADDCMD("m.", commandSetMark, REGEX_PATTERN); ADDCMD(">>", commandIndentLine, IS_CHANGE); ADDCMD("<<", commandUnindentLine, IS_CHANGE); ADDCMD(">", commandIndentLines, IS_CHANGE | NEEDS_MOTION); ADDCMD("<", commandUnindentLines, IS_CHANGE | NEEDS_MOTION); ADDCMD("", commandScrollPageDown, 0); ADDCMD("", commandScrollPageDown, 0); ADDCMD("", commandScrollPageUp, 0); ADDCMD("", commandScrollPageUp, 0); ADDCMD("", commandScrollHalfPageUp, 0); ADDCMD("", commandScrollHalfPageDown, 0); ADDCMD("z.", commandCenterViewOnNonBlank, 0); ADDCMD("zz", commandCenterViewOnCursor, 0); ADDCMD("z", commandTopViewOnNonBlank, 0); ADDCMD("zt", commandTopViewOnCursor, 0); ADDCMD("z-", commandBottomViewOnNonBlank, 0); ADDCMD("zb", commandBottomViewOnCursor, 0); ADDCMD("ga", commandPrintCharacterCode, SHOULD_NOT_RESET); ADDCMD(".", commandRepeatLastChange, 0); ADDCMD("==", commandAlignLine, IS_CHANGE); ADDCMD("=", commandAlignLines, IS_CHANGE | NEEDS_MOTION); ADDCMD("~", commandChangeCase, IS_CHANGE); ADDCMD("g~", commandChangeCaseRange, IS_CHANGE | NEEDS_MOTION); ADDCMD("g~~", commandChangeCaseLine, IS_CHANGE); ADDCMD("", commandAddToNumber, IS_CHANGE); ADDCMD("", commandSubtractFromNumber, IS_CHANGE); ADDCMD("", commandGoToPrevJump, 0); ADDCMD("", commandGoToNextJump, 0); ADDCMD("h", commandSwitchToLeftView, 0); ADDCMD("", commandSwitchToLeftView, 0); ADDCMD("", commandSwitchToLeftView, 0); ADDCMD("j", commandSwitchToDownView, 0); ADDCMD("", commandSwitchToDownView, 0); ADDCMD("", commandSwitchToDownView, 0); ADDCMD("k", commandSwitchToUpView, 0); ADDCMD("", commandSwitchToUpView, 0); ADDCMD("", commandSwitchToUpView, 0); ADDCMD("l", commandSwitchToRightView, 0); ADDCMD("", commandSwitchToRightView, 0); ADDCMD("", commandSwitchToRightView, 0); ADDCMD("w", commandSwitchToNextView, 0); ADDCMD("", commandSwitchToNextView, 0); ADDCMD("s", commandSplitHoriz, 0); ADDCMD("S", commandSplitHoriz, 0); ADDCMD("", commandSplitHoriz, 0); ADDCMD("v", commandSplitVert, 0); ADDCMD("", commandSplitVert, 0); ADDCMD("c", commandCloseView, 0); ADDCMD("gt", commandSwitchToNextTab, 0); ADDCMD("gT", commandSwitchToPrevTab, 0); ADDCMD("gqq", commandFormatLine, IS_CHANGE); ADDCMD("gq", commandFormatLines, IS_CHANGE | NEEDS_MOTION); ADDCMD("zo", commandExpandLocal, 0); ADDCMD("zc", commandCollapseLocal, 0); ADDCMD("za", commandToggleRegionVisibility, 0); ADDCMD("zr", commandExpandAll, 0); ADDCMD("zm", commandCollapseToplevelNodes, 0); ADDCMD("q.", commandStartRecordingMacro, REGEX_PATTERN); ADDCMD("@.", commandReplayMacro, REGEX_PATTERN); ADDCMD("ZZ", commandCloseWrite, 0); ADDCMD("ZQ", commandCloseNocheck, 0); // regular motions ADDMOTION("h", motionLeft, 0); ADDMOTION("", motionLeft, 0); ADDMOTION("", motionLeft, 0); ADDMOTION("j", motionDown, 0); ADDMOTION("", motionDown, 0); ADDMOTION("", motionDownToFirstNonBlank, 0); ADDMOTION("", motionDownToFirstNonBlank, 0); ADDMOTION("k", motionUp, 0); ADDMOTION("", motionUp, 0); ADDMOTION("-", motionUpToFirstNonBlank, 0); ADDMOTION("l", motionRight, 0); ADDMOTION("", motionRight, 0); ADDMOTION(" ", motionRight, 0); ADDMOTION("$", motionToEOL, 0); ADDMOTION("", motionToEOL, 0); ADDMOTION("0", motionToColumn0, 0); ADDMOTION("", motionToColumn0, 0); ADDMOTION("^", motionToFirstCharacterOfLine, 0); ADDMOTION("f.", motionFindChar, REGEX_PATTERN); ADDMOTION("F.", motionFindCharBackward, REGEX_PATTERN); ADDMOTION("t.", motionToChar, REGEX_PATTERN); ADDMOTION("T.", motionToCharBackward, REGEX_PATTERN); ADDMOTION(";", motionRepeatlastTF, 0); ADDMOTION(",", motionRepeatlastTFBackward, 0); ADDMOTION("n", motionFindNext, 0); ADDMOTION("N", motionFindPrev, 0); ADDMOTION("gg", motionToLineFirst, 0); ADDMOTION("G", motionToLineLast, 0); ADDMOTION("w", motionWordForward, IS_NOT_LINEWISE); ADDMOTION("W", motionWORDForward, IS_NOT_LINEWISE); ADDMOTION("", motionWordForward, IS_NOT_LINEWISE); ADDMOTION("", motionWordBackward, IS_NOT_LINEWISE); ADDMOTION("b", motionWordBackward, 0); ADDMOTION("B", motionWORDBackward, 0); ADDMOTION("e", motionToEndOfWord, 0); ADDMOTION("E", motionToEndOfWORD, 0); ADDMOTION("ge", motionToEndOfPrevWord, 0); ADDMOTION("gE", motionToEndOfPrevWORD, 0); ADDMOTION("|", motionToScreenColumn, 0); ADDMOTION("%", motionToMatchingItem, IS_NOT_LINEWISE); ADDMOTION("`[a-zA-Z^><\\.\\[\\]]", motionToMark, REGEX_PATTERN); ADDMOTION("'[a-zA-Z^><]", motionToMarkLine, REGEX_PATTERN); ADDMOTION("[[", motionToPreviousBraceBlockStart, IS_NOT_LINEWISE); ADDMOTION("]]", motionToNextBraceBlockStart, IS_NOT_LINEWISE); ADDMOTION("[]", motionToPreviousBraceBlockEnd, IS_NOT_LINEWISE); ADDMOTION("][", motionToNextBraceBlockEnd, IS_NOT_LINEWISE); ADDMOTION("*", motionToNextOccurrence, 0); ADDMOTION("#", motionToPrevOccurrence, 0); ADDMOTION("H", motionToFirstLineOfWindow, 0); ADDMOTION("M", motionToMiddleLineOfWindow, 0); ADDMOTION("L", motionToLastLineOfWindow, 0); ADDMOTION("gj", motionToNextVisualLine, 0); ADDMOTION("gk", motionToPrevVisualLine, 0); ADDMOTION("(", motionToPreviousSentence, 0 ); ADDMOTION(")", motionToNextSentence, 0 ); ADDMOTION("{", motionToBeforeParagraph, 0); ADDMOTION("}", motionToAfterParagraph, 0); // text objects ADDMOTION("iw", textObjectInnerWord, 0); ADDMOTION("aw", textObjectAWord, IS_NOT_LINEWISE); ADDMOTION("iW", textObjectInnerWORD, 0); ADDMOTION("aW", textObjectAWORD, IS_NOT_LINEWISE); ADDMOTION("is", textObjectInnerSentence, IS_NOT_LINEWISE ); ADDMOTION("as", textObjectASentence, IS_NOT_LINEWISE ); ADDMOTION("ip", textObjectInnerParagraph, IS_NOT_LINEWISE ); ADDMOTION("ap", textObjectAParagraph, IS_NOT_LINEWISE ); ADDMOTION("i\"", textObjectInnerQuoteDouble, IS_NOT_LINEWISE); ADDMOTION("a\"", textObjectAQuoteDouble, IS_NOT_LINEWISE); ADDMOTION("i'", textObjectInnerQuoteSingle, IS_NOT_LINEWISE); ADDMOTION("a'", textObjectAQuoteSingle, IS_NOT_LINEWISE); ADDMOTION("i`", textObjectInnerBackQuote, IS_NOT_LINEWISE); ADDMOTION("a`", textObjectABackQuote, IS_NOT_LINEWISE); ADDMOTION("i[()b]", textObjectInnerParen, REGEX_PATTERN | IS_NOT_LINEWISE); ADDMOTION("a[()b]", textObjectAParen, REGEX_PATTERN | IS_NOT_LINEWISE); ADDMOTION("i[{}B]", textObjectInnerCurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE); ADDMOTION("a[{}B]", textObjectACurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE); ADDMOTION("i[><]", textObjectInnerInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE); ADDMOTION("a[><]", textObjectAInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE); ADDMOTION("i[\\[\\]]", textObjectInnerBracket, REGEX_PATTERN | IS_NOT_LINEWISE); ADDMOTION("a[\\[\\]]", textObjectABracket, REGEX_PATTERN | IS_NOT_LINEWISE); ADDMOTION("i,", textObjectInnerComma, IS_NOT_LINEWISE); ADDMOTION("a,", textObjectAComma, IS_NOT_LINEWISE); ADDMOTION("/", motionToIncrementalSearchMatch, IS_NOT_LINEWISE); ADDMOTION("?", motionToIncrementalSearchMatch, IS_NOT_LINEWISE); } QRegExp NormalViMode::generateMatchingItemRegex() const { QString pattern(QStringLiteral("\\[|\\]|\\{|\\}|\\(|\\)|")); QList keys = m_matchingItems.keys(); for (int i = 0; i < keys.size(); i++) { QString s = m_matchingItems[ keys[ i ] ]; s = s.replace(QRegExp(QLatin1String("^-")), QChar()); s = s.replace(QRegExp(QLatin1String("\\*")), QStringLiteral("\\*")); s = s.replace(QRegExp(QLatin1String("\\+")), QStringLiteral("\\+")); s = s.replace(QRegExp(QLatin1String("\\[")), QStringLiteral("\\[")); s = s.replace(QRegExp(QLatin1String("\\]")), QStringLiteral("\\]")); s = s.replace(QRegExp(QLatin1String("\\(")), QStringLiteral("\\(")); s = s.replace(QRegExp(QLatin1String("\\)")), QStringLiteral("\\)")); s = s.replace(QRegExp(QLatin1String("\\{")), QStringLiteral("\\{")); s = s.replace(QRegExp(QLatin1String("\\}")), QStringLiteral("\\}")); pattern.append(s); if (i != keys.size() - 1) { pattern.append(QLatin1Char('|')); } } return QRegExp(pattern); } // returns the operation mode that should be used. this is decided by using the following heuristic: // 1. if we're in visual block mode, it should be Block // 2. if we're in visual line mode OR the range spans several lines, it should be LineWise // 3. if neither of these is true, CharWise is returned // 4. there are some motion that makes all operator charwise, if we have one of them mode will be CharWise OperationMode NormalViMode::getOperationMode() const { OperationMode m = CharWise; if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) { m = Block; } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode || (m_commandRange.startLine != m_commandRange.endLine && m_viInputModeManager->getCurrentViMode() != ViMode::VisualMode)) { m = LineWise; } if (m_commandWithMotion && !m_linewiseCommand) { m = CharWise; } if (m_lastMotionWasLinewiseInnerBlock) { m = LineWise; } return m; } bool NormalViMode::paste(PasteLocation pasteLocation, bool isgPaste, bool isIndentedPaste) { KTextEditor::Cursor pasteAt(m_view->cursorPosition()); KTextEditor::Cursor cursorAfterPaste = pasteAt; QChar reg = getChosenRegister(UnnamedRegister); OperationMode m = getRegisterFlag(reg); QString textToInsert = getRegisterContent(reg); const bool isTextMultiLine = textToInsert.split(QStringLiteral("\n")).count() > 1; // In temporary normal mode, p/P act as gp/gP. isgPaste |= m_viInputModeManager->getTemporaryNormalMode(); if (textToInsert.isEmpty()) { error(i18n("Nothing in register %1", reg)); return false; } if (getCount() > 1) { textToInsert = textToInsert.repeated(getCount()); // FIXME: does this make sense for blocks? } if (m == LineWise) { pasteAt.setColumn(0); if (isIndentedPaste) { // Note that this does indeed work if there is no non-whitespace on the current line or if // the line is empty! const QString leadingWhiteSpaceOnCurrentLine = doc()->line(pasteAt.line()).mid(0, doc()->line(pasteAt.line()).indexOf(QRegExp(QLatin1String("[^\\s]")))); const QString leadingWhiteSpaceOnFirstPastedLine = textToInsert.mid(0, textToInsert.indexOf(QRegExp(QLatin1String("[^\\s]")))); // QString has no "left trim" method, bizarrely. while (textToInsert[0].isSpace()) { textToInsert = textToInsert.mid(1); } textToInsert.prepend(leadingWhiteSpaceOnCurrentLine); // Remove the last \n, temporarily: we're going to alter the indentation of each pasted line // by doing a search and replace on '\n's, but don't want to alter this one. textToInsert.chop(1); textToInsert.replace(QLatin1Char('\n') + leadingWhiteSpaceOnFirstPastedLine, QLatin1Char('\n') + leadingWhiteSpaceOnCurrentLine); textToInsert.append(QLatin1Char('\n')); // Re-add the temporarily removed last '\n'. } if (pasteLocation == AfterCurrentPosition) { textToInsert.chop(1); // remove the last \n pasteAt.setColumn(doc()->lineLength(pasteAt.line())); // paste after the current line and ... textToInsert.prepend(QLatin1Char('\n')); // ... prepend a \n, so the text starts on a new line cursorAfterPaste.setLine(cursorAfterPaste.line() + 1); } if (isgPaste) { cursorAfterPaste.setLine(cursorAfterPaste.line() + textToInsert.split(QStringLiteral("\n")).length() - 1); } } else { if (pasteLocation == AfterCurrentPosition) { // Move cursor forward one before we paste. The position after the paste must also // be updated accordingly. if (getLine(pasteAt.line()).length() > 0) { pasteAt.setColumn(pasteAt.column() + 1); } cursorAfterPaste = pasteAt; } const bool leaveCursorAtStartOfPaste = isTextMultiLine && !isgPaste; if (!leaveCursorAtStartOfPaste) { cursorAfterPaste = cursorPosAtEndOfPaste(pasteAt, textToInsert); if (!isgPaste) { cursorAfterPaste.setColumn(cursorAfterPaste.column() - 1); } } } doc()->editBegin(); if (m_view->selection()) { pasteAt = m_view->selectionRange().start(); doc()->removeText(m_view->selectionRange()); } doc()->insertText(pasteAt, textToInsert, m == Block); doc()->editEnd(); if (cursorAfterPaste.line() >= doc()->lines()) { cursorAfterPaste.setLine(doc()->lines() - 1); } updateCursor(cursorAfterPaste); return true; } KTextEditor::Cursor NormalViMode::cursorPosAtEndOfPaste(const KTextEditor::Cursor &pasteLocation, const QString &pastedText) const { KTextEditor::Cursor cAfter = pasteLocation; const QStringList textLines = pastedText.split(QStringLiteral("\n")); if (textLines.length() == 1) { cAfter.setColumn(cAfter.column() + pastedText.length()); } else { cAfter.setColumn(textLines.last().length() - 0); cAfter.setLine(cAfter.line() + textLines.length() - 1); } return cAfter; } void NormalViMode::joinLines(unsigned int from, unsigned int to) const { // make sure we don't try to join lines past the document end if (to >= (unsigned int)(doc()->lines())) { to = doc()->lines() - 1; } // joining one line is a no-op if (from == to) { return; } doc()->joinLines(from, to); } void NormalViMode::reformatLines(unsigned int from, unsigned int to) const { joinLines(from, to); doc()->wrapText(from, to); } int NormalViMode::getFirstNonBlank(int line) const { if (line < 0) { line = m_view->cursorPosition().line(); } // doc()->plainKateTextLine returns NULL if the line is out of bounds. Kate::TextLine l = doc()->plainKateTextLine(line); Q_ASSERT(l); int c = l->firstChar(); return (c < 0) ? 0 : c; } // Tries to shrinks toShrink so that it fits tightly around rangeToShrinkTo. void NormalViMode::shrinkRangeAroundCursor(Range &toShrink, const Range &rangeToShrinkTo) const { if (!toShrink.valid || !rangeToShrinkTo.valid) { return; } KTextEditor::Cursor cursorPos = m_view->cursorPosition(); if (rangeToShrinkTo.startLine >= cursorPos.line()) { if (rangeToShrinkTo.startLine > cursorPos.line()) { // Does not surround cursor; aborting. return; } Q_ASSERT(rangeToShrinkTo.startLine == cursorPos.line()); if (rangeToShrinkTo.startColumn > cursorPos.column()) { // Does not surround cursor; aborting. return; } } if (rangeToShrinkTo.endLine <= cursorPos.line()) { if (rangeToShrinkTo.endLine < cursorPos.line()) { // Does not surround cursor; aborting. return; } Q_ASSERT(rangeToShrinkTo.endLine == cursorPos.line()); if (rangeToShrinkTo.endColumn < cursorPos.column()) { // Does not surround cursor; aborting. return; } } if (toShrink.startLine <= rangeToShrinkTo.startLine) { if (toShrink.startLine < rangeToShrinkTo.startLine) { toShrink.startLine = rangeToShrinkTo.startLine; toShrink.startColumn = rangeToShrinkTo.startColumn; } Q_ASSERT(toShrink.startLine == rangeToShrinkTo.startLine); if (toShrink.startColumn < rangeToShrinkTo.startColumn) { toShrink.startColumn = rangeToShrinkTo.startColumn; } } if (toShrink.endLine >= rangeToShrinkTo.endLine) { if (toShrink.endLine > rangeToShrinkTo.endLine) { toShrink.endLine = rangeToShrinkTo.endLine; toShrink.endColumn = rangeToShrinkTo.endColumn; } Q_ASSERT(toShrink.endLine == rangeToShrinkTo.endLine); if (toShrink.endColumn > rangeToShrinkTo.endColumn) { toShrink.endColumn = rangeToShrinkTo.endColumn; } } } Range NormalViMode::textObjectComma(bool inner) const { // Basic algorithm: look left and right of the cursor for all combinations // of enclosing commas and the various types of brackets, and pick the pair // closest to the cursor that surrounds the cursor. Range r(0, 0, m_view->doc()->lines(), m_view->doc()->line(m_view->doc()->lastLine()).length(), InclusiveMotion); shrinkRangeAroundCursor(r, findSurroundingQuotes(QLatin1Char(','), inner)); shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')'))); shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}'))); shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')'))); shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(']'), inner, QLatin1Char('['), QLatin1Char(']'))); shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}'))); shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(','), inner, QLatin1Char('('), QLatin1Char(')'))); shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('['), QLatin1Char(','), inner, QLatin1Char('['), QLatin1Char(']'))); shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char(','), inner, QLatin1Char('{'), QLatin1Char('}'))); return r; } void NormalViMode::updateYankHighlightAttrib() { if (!m_highlightYankAttribute) { m_highlightYankAttribute = new KTextEditor::Attribute; } const QColor &yankedColor = m_view->renderer()->config()->savedLineColor(); m_highlightYankAttribute->setBackground(yankedColor); KTextEditor::Attribute::Ptr mouseInAttribute(new KTextEditor::Attribute()); mouseInAttribute->setFontBold(true); m_highlightYankAttribute->setDynamicAttribute(KTextEditor::Attribute::ActivateMouseIn, mouseInAttribute); m_highlightYankAttribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)->setBackground(yankedColor); } void NormalViMode::highlightYank(const Range &range, const OperationMode mode) { clearYankHighlight(); // current MovingRange doesn't support block mode selection so split the // block range into per-line ranges if (mode == Block) { for (int i = range.startLine; i <= range.endLine; i++) { addHighlightYank(KTextEditor::Range(i, range.startColumn, i, range.endColumn)); } } else { addHighlightYank(KTextEditor::Range(range.startLine, range.startColumn, range.endLine, range.endColumn)); } } void NormalViMode::addHighlightYank(const KTextEditor::Range &yankRange) { KTextEditor::MovingRange *highlightedYank = m_view->doc()->newMovingRange(yankRange, Kate::TextRange::DoNotExpand); highlightedYank->setView(m_view); // show only in this view highlightedYank->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface highlightedYank->setZDepth(-10000.0); highlightedYank->setAttribute(m_highlightYankAttribute); highlightedYankForDocument().insert(highlightedYank); } void NormalViMode::clearYankHighlight() { QSet &pHighlightedYanks = highlightedYankForDocument(); qDeleteAll(pHighlightedYanks); pHighlightedYanks.clear(); } void NormalViMode::aboutToDeleteMovingInterfaceContent() { QSet &pHighlightedYanks = highlightedYankForDocument(); // Prevent double-deletion in case this NormalMode is deleted. pHighlightedYanks.clear(); } QSet &NormalViMode::highlightedYankForDocument() { // Work around the fact that both Normal and Visual mode will have their own m_highlightedYank - // make Normal's the canonical one. return m_viInputModeManager->getViNormalMode()->m_highlightedYanks; } bool NormalViMode::waitingForRegisterOrCharToSearch() { // r, q, @ are never preceded by operators. There will always be a keys size of 1 for them. // f, t, F, T can be preceded by a delete/replace/yank/indent operator. size = 2 in that case. // f, t, F, T can be preceded by 'g' case/formatting operators. size = 3 in that case. const int keysSize = m_keys.size(); if (keysSize < 1) { // Just being defensive there. return false; } if (keysSize > 1) { // Multi-letter operation. QChar cPrefix = m_keys[0]; if (keysSize == 2) { // delete/replace/yank/indent operator? if (cPrefix != QLatin1Char('c') && cPrefix != QLatin1Char('d') && cPrefix != QLatin1Char('y') && cPrefix != QLatin1Char('=') && cPrefix != QLatin1Char('>') && cPrefix != QLatin1Char('<')) { return false; } } else if (keysSize == 3) { // We need to look deeper. Is it a g motion? QChar cNextfix = m_keys[1]; if (cPrefix != QLatin1Char('g') || ( cNextfix != QLatin1Char('U') && cNextfix != QLatin1Char('u') && cNextfix != QLatin1Char('~') && cNextfix != QLatin1Char('q') && cNextfix != QLatin1Char('w') && cNextfix != QLatin1Char('@'))) { return false; } } else { return false; } } QChar ch = m_keys[keysSize - 1]; return (ch == QLatin1Char('f') || ch == QLatin1Char('t') || ch == QLatin1Char('F') || ch == QLatin1Char('T') // c/d prefix unapplicable for the following cases. || (keysSize == 1 && (ch == QLatin1Char('r') || ch == QLatin1Char('q') || ch == QLatin1Char('@')))); } void NormalViMode::textInserted(KTextEditor::Document *document, KTextEditor::Range range) { Q_UNUSED(document); const bool isInsertReplaceMode = (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode); const bool continuesInsertion = range.start().line() == m_currentChangeEndMarker.line() && range.start().column() == m_currentChangeEndMarker.column(); const bool beginsWithNewline = doc()->text(range).at(0) == QLatin1Char('\n'); if (!continuesInsertion) { KTextEditor::Cursor newBeginMarkerPos = range.start(); if (beginsWithNewline && !isInsertReplaceMode) { // Presumably a linewise paste, in which case we ignore the leading '\n' newBeginMarkerPos = KTextEditor::Cursor(newBeginMarkerPos.line() + 1, 0); } m_viInputModeManager->marks()->setStartEditYanked(newBeginMarkerPos); } m_viInputModeManager->marks()->setLastChange(range.start()); KTextEditor::Cursor editEndMarker = range.end(); if (!isInsertReplaceMode) { editEndMarker.setColumn(editEndMarker.column() - 1); } m_viInputModeManager->marks()->setFinishEditYanked(editEndMarker); m_currentChangeEndMarker = range.end(); if (m_isUndo) { const bool addsMultipleLines = range.start().line() != range.end().line(); m_viInputModeManager->marks()->setStartEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line(), 0)); if (addsMultipleLines) { m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + 1, 0)); m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + 1, 0)); } else { m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line(), 0)); m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line(), 0)); } } } void NormalViMode::textRemoved(KTextEditor::Document *document, KTextEditor::Range range) { Q_UNUSED(document); const bool isInsertReplaceMode = (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode); m_viInputModeManager->marks()->setLastChange(range.start()); if (!isInsertReplaceMode) { // Don't go resetting [ just because we did a Ctrl-h! m_viInputModeManager->marks()->setStartEditYanked(range.start()); } else { // Don't go disrupting our continued insertion just because we did a Ctrl-h! m_currentChangeEndMarker = range.start(); } m_viInputModeManager->marks()->setFinishEditYanked(range.start()); if (m_isUndo) { // Slavishly follow Vim's weird rules: if an undo removes several lines, then all markers should // be at the beginning of the line after the last line removed, else they should at the beginning // of the line above that. const int markerLineAdjustment = (range.start().line() != range.end().line()) ? 1 : 0; m_viInputModeManager->marks()->setStartEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line() + markerLineAdjustment, 0)); m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + markerLineAdjustment, 0)); m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + markerLineAdjustment, 0)); } } void NormalViMode::undoBeginning() { m_isUndo = true; } void NormalViMode::undoEnded() { m_isUndo = false; } bool NormalViMode::executeKateCommand(const QString &command) { KTextEditor::Command *p = KateCmd::self()->queryCommand(command); if (!p) { return false; } QString msg; return p->exec(m_view, command, msg); } diff --git a/src/vimode/motion.cpp b/src/vimode/motion.cpp index f95ca1d1..dba311e9 100644 --- a/src/vimode/motion.cpp +++ b/src/vimode/motion.cpp @@ -1,35 +1,35 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2008 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. */ #include using namespace KateVi; Motion::Motion(NormalViMode *parent, const QString &pattern, Range(NormalViMode::*commandMethod)(), unsigned int flags) - : Command(parent, pattern, 0, flags) + : Command(parent, pattern, nullptr, flags) { m_ptr2commandMethod = commandMethod; } Range Motion::execute() const { return (m_parent->*m_ptr2commandMethod)(); }