diff --git a/CMakeLists.txt b/CMakeLists.txt index e7e3df1..1cab530 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,121 +1,122 @@ #cmake < 3.1 has no sane way of checking C++11 features and needed flags cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(kdiff3) set(CMAKE_CXX_EXTENSIONS OFF ) #don't use non-standard extensions set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(ECM_MIN_VERSION "5.10.0") set(QT_MIN_VERSION "5.6.0") set(KF5_MIN_VERSION "5.23.0") find_package(ECM ${ECM_MIN_VERSION} CONFIG REQUIRED) set( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings NO_POLICY_SCOPE) include(FeatureSummary) include(ECMInstallIcons) include(ECMAddAppIcon) include(ECMSetupVersion) +include(ECMAddTests) ecm_setup_version(1.9.70 VARIABLE_PREFIX KDIFF3 VERSION_HEADER ${CMAKE_BINARY_DIR}/src/version.h) find_package( Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Gui Widgets PrintSupport ) find_package( KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS I18n CoreAddons Crash IconThemes OPTIONAL_COMPONENTS DocTools ) set_package_properties(KF5DocTools PROPERTIES PURPOSE "Allows generating and installing docs.") option(ENABLE_AUTO "Enable kdiff3's '--auto' flag" ON) option(ENABLE_CLANG_TIDY "Run clang-tidy if available and cmake version >=3.6" OFF) set(KDiff3_LIBRARIES ${Qt5PrintSupport_LIBRARIES} KF5::I18n KF5::CoreAddons KF5::IconThemes ) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") #Adjust clang specific warnings set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wshadow") set(CLANG_WARNING_FLAGS "-Wno-invalid-pp-token -Wno-comment -Wshorten-64-to-32 -Wstring-conversion -Wc++11-narrowing -fstack-protector-all") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CLANG_WARNING_FLAGS}") elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") add_definitions(-DNOMINMAX) #Suppress MSVCs min/max macros elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-check") if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wduplicated-cond -Wduplicated-branches -Wshadow") endif() endif() #new in cmake 3.6+ integrate clang-tidy if(ENABLE_CLANG_TIDY AND NOT ${CMAKE_VERSION} VERSION_LESS "3.6.0") find_program(CLANG_TIDY_EXE NAMES "clang-tidy" "clang-tidy-7" "clang-tidy-6.0" "clang-tidy-6" DOC "Path to clang-tidy executable") if(NOT CLANG_TIDY_EXE) message(STATUS "clang-tidy not found disabling integration.") else() message(STATUS "Found clang-tidy: ${CLANG_TIDY_EXE}") set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE}" "-header-filter=.*") endif() endif() set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS}") set( needed_features cxx_nullptr cxx_override cxx_nonstatic_member_init cxx_inheriting_constructors cxx_static_assert ) if(ENABLE_AUTO) add_definitions( -DENABLE_AUTO ) endif() add_definitions( -DQT_DEPRECATED_WARNINGS #Get warnings from QT about deprecated functions. -DQT_NO_URL_CAST_FROM_STRING # casting from string to url does not always behave as you might think -DQT_RESTRICTED_CAST_FROM_ASCII #casting from char*/QByteArray to QString can produce unexpected results for non-latin characters. -DQT_NO_CAST_TO_ASCII ) add_subdirectory(src) if(KF5DocTools_FOUND) add_subdirectory(doc) kdoctools_install(po) else() message(WARNING "DocTools not found.") endif() ki18n_install(po) add_subdirectory(kdiff3fileitemactionplugin) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 29c0f06..6ddde90 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,72 +1,76 @@ ########### kdiff3 KPart ############### find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Parts WidgetsAddons) set(kdiff3part_PART_SRCS kdiff3_part.cpp kdiff3.cpp directorymergewindow.cpp merger.cpp pdiff.cpp difftextwindow.cpp diff.cpp optiondialog.cpp mergeresultwindow.cpp fileaccess.cpp gnudiff_analyze.cpp gnudiff_io.cpp gnudiff_xmalloc.cpp common.cpp smalldialogs.cpp progress.cpp ProgressProxyExtender.cpp PixMapUtils.cpp MergeFileInfos.cpp Utils.cpp selection.cpp cvsignorelist.cpp SourceData.cpp Overview.cpp Logging.cpp FileNameLineEdit.cpp MergeEditLine.cpp - Options.cpp ) + Options.cpp + CommentParser.cpp ) add_library(kdiff3part MODULE ${kdiff3part_PART_SRCS}) set_target_properties(kdiff3part PROPERTIES DEFINE_SYMBOL KDIFF3_PART) target_compile_features(kdiff3part PRIVATE ${needed_features}) target_link_libraries(kdiff3part ${KDiff3_LIBRARIES} KF5::Parts KF5::Crash) target_compile_definitions(kdiff3part PRIVATE -DTRANSLATION_DOMAIN=\"kdiff3\") install(TARGETS kdiff3part DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/parts ) ########### kdiff3 executable ############### find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Parts WidgetsAddons Config) set(kdiff3_SRCS main.cpp kdiff3_shell.cpp ${kdiff3part_PART_SRCS} ) +if(BUILD_TESTING) + add_subdirectory( autotests ) +endif() #cann't use add_subdirectory because it changes the scope. include(icons/CMakeLists.txt) add_executable(kdiff3 ${kdiff3_SRCS}) target_link_libraries(kdiff3 KF5::ConfigCore KF5::ConfigGui KF5::Parts KF5::Crash ${KDiff3_LIBRARIES} ) target_compile_features(kdiff3 PRIVATE ${needed_features}) install(TARGETS kdiff3 ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install( FILES kdiff3part.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install( FILES kdiff3_part.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kdiff3part ) install( FILES kdiff3_shell.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kdiff3 ) #install( PROGRAMS kdiff3.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( PROGRAMS org.kde.kdiff3.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install( FILES org.kde.kdiff3.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) diff --git a/src/CommentParser.cpp b/src/CommentParser.cpp new file mode 100644 index 0000000..6e50c80 --- /dev/null +++ b/src/CommentParser.cpp @@ -0,0 +1,124 @@ +/** + * Copyright (C) 2019 Michael Reeves + * + * This file is part of KDiff3. + * + * KDiff3 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * KDiff3 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KDiff3. If not, see . + */ + +#include "CommentParser.h" + +#include +#include +#include +#include + +void DefaultCommentParser::processChar(const QString &line, const QChar &inChar) +{ + if(!bIsEscaped) + { + switch(inChar.unicode()) + { + case '\\': + if(bInString) + bIsEscaped = true; + break; + case '\'': + case '"': + if(!inComment()) + { + if(!bInString) + { + bInString = true; + mStartChar = inChar; + } + else if(mStartChar == inChar) + { + bInString = false; + } + } + break; + case '/': + if(bInString) + break; + + if(!inComment() && mLastChar == '/') + { + mCommentType = singleLine; + mIsPureComment = line.startsWith("//") ? yes : no; + } + else if(mLastChar == '*' && mCommentType == multiLine) + { + //ending multi-line comment + mCommentType = none; + if(!isFirstLine) + mIsPureComment = line.endsWith("*/") ? yes : mIsPureComment; + } + break; + case '*': + if(bInString) + break; + + if(mLastChar == '/' && !inComment()) + { + mCommentType = multiLine; + mIsPureComment = line.startsWith("/*") ? yes : mIsPureComment; + isFirstLine = true; + } + break; + case '\n': + if(mCommentType == singleLine) + { + mCommentType = none; + } + + if(mCommentType == multiLine && !isFirstLine) + { + mIsPureComment = yes; + } + isFirstLine = false; + + break; + default: + if(inComment()) + { + break; + } + + mIsPureComment = no; + break; + } + + mLastChar = inChar; + } + else + { + bIsEscaped = false; + mLastChar = QChar(); + } +}; + +void DefaultCommentParser::processLine(const QString &line) +{ + for(const QChar &c : line) + { + processChar(line, c); + } + + processChar(line, '\n'); +} + +void DefaultCommentParser::removeComments() +{ +} diff --git a/src/CommentParser.h b/src/CommentParser.h new file mode 100644 index 0000000..5ca388e --- /dev/null +++ b/src/CommentParser.h @@ -0,0 +1,67 @@ +/** + * Copyright (C) 2019 Michael Reeves + * + * This file is part of KDiff3. + * + * KDiff3 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * KDiff3 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KDiff3. If not, see . + */ +#ifndef COMMENTPARSER_H +#define COMMENTPARSER_H + +#include +#include + +class CommentParser +{ + public: + inline virtual void processChar(const QString &line, const QChar &inChar) = 0; + inline virtual void processLine(const QString &line) = 0; + inline virtual bool inComment() const = 0; + inline virtual bool isPureComment() const = 0; + inline virtual void removeComments() = 0; + virtual ~CommentParser(){}; +}; + +class DefaultCommentParser : public CommentParser +{ + private: + typedef enum {none, singleLine, multiLine}CommentType; + typedef enum {no, yes, unknown}TriState; + + public: + void processLine(const QString &line) override; + + inline bool inComment() const override { return mCommentType != none; }; + inline bool isPureComment() const override { return mIsPureComment == yes; }; + inline void removeComments() override; + protected: + friend class CommentParserTest; + + void processChar(const QString &line, const QChar &inChar) override; + //For tests only. + inline bool isEscaped(){ return bIsEscaped; } + inline bool inString(){ return bInString; } + private: + QChar mLastChar, mStartChar; + + bool isFirstLine = false; + TriState mIsPureComment = unknown; + bool bInString = false; + bool bIsEscaped = false; + + CommentType mCommentType = none; + quint32 pos = 0; +}; + +#endif // !COMMENTPASER_H diff --git a/src/SourceData.cpp b/src/SourceData.cpp index c58c474..e256c7b 100644 --- a/src/SourceData.cpp +++ b/src/SourceData.cpp @@ -1,951 +1,956 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * ***************************************************************************/ /* Features of class SourceData: - Read a file (from the given URL) or accept data via a string. - Allocate and free buffers as necessary. - Run a preprocessor, when specified. - Run the line-matching preprocessor, when specified. - Run other preprocessing steps: Uppercase, ignore comments, remove carriage return, ignore numbers. Order of operation: 1. If data was given via a string then save it to a temp file. (see setData()) 2. If the specified file is nonlocal (URL) copy it to a temp file. 3. If a preprocessor was specified, run the input file through it. 4. Read the output of the preprocessor. 5. If Uppercase was specified: Turn the read data to uppercase. 6. Write the result to a temp file. 7. If a line-matching preprocessor was specified, run the temp file through it. 8. Read the output of the line-matching preprocessor. 9. If ignore numbers was specified, strip the LMPP-output of all numbers. 10. If ignore comments was specified, strip the LMPP-output of comments. Optimizations: Skip unneeded steps. */ #include "SourceData.h" +#include "CommentParser.h" #include "Utils.h" #include "diff.h" #include "Logging.h" +#include #include #include #include #include #include #include #include SourceData::SourceData() { m_pOptions = nullptr; reset(); } SourceData::~SourceData() { reset(); } void SourceData::reset() { m_pEncoding = nullptr; m_fileAccess = FileAccess(); m_normalData.reset(); m_lmppData.reset(); if(!m_tempInputFileName.isEmpty()) { m_tempFile.remove(); m_tempInputFileName = ""; } } void SourceData::setFilename(const QString& filename) { if(filename.isEmpty()) { reset(); } else { FileAccess fa(filename); setFileAccess(fa); } } bool SourceData::isEmpty() { return getFilename().isEmpty(); } bool SourceData::hasData() { return m_normalData.m_pBuf != nullptr; } bool SourceData::isValid() { return isEmpty() || hasData(); } void SourceData::setOptions(const QSharedPointer &pOptions) { m_pOptions = pOptions; } QString SourceData::getFilename() { return m_fileAccess.absoluteFilePath(); } QString SourceData::getAliasName() { return m_aliasName.isEmpty() ? m_fileAccess.prettyAbsPath() : m_aliasName; } void SourceData::setAliasName(const QString& name) { m_aliasName = name; } void SourceData::setFileAccess(const FileAccess& fileAccess) { m_fileAccess = fileAccess; m_aliasName = QString(); if(!m_tempInputFileName.isEmpty()) { m_tempFile.remove(); m_tempInputFileName = ""; } } void SourceData::setEncoding(QTextCodec* pEncoding) { m_pEncoding = pEncoding; } QStringList SourceData::setData(const QString& data) { QStringList errors; // Create a temp file for preprocessing: if(m_tempInputFileName.isEmpty()) { FileAccess::createTempFile(m_tempFile); m_tempInputFileName = m_tempFile.fileName(); } FileAccess f(m_tempInputFileName); QByteArray ba = QTextCodec::codecForName("UTF-8")->fromUnicode(data); bool bSuccess = f.writeFile(ba.constData(), ba.length()); if(!bSuccess) { errors.append(i18n("Writing clipboard data to temp file failed.")); } else { m_aliasName = i18n("From Clipboard"); m_fileAccess = FileAccess(""); // Effect: m_fileAccess.isValid() is false } return errors; } const QVector* SourceData::getLineDataForDiff() const { if(m_lmppData.m_pBuf == nullptr) return m_normalData.m_v.size() > 0 ? &m_normalData.m_v : nullptr; else return m_lmppData.m_v.size() > 0 ? &m_lmppData.m_v : nullptr; } const QVector* SourceData::getLineDataForDisplay() const { return m_normalData.m_v.size() > 0 ? &m_normalData.m_v : nullptr; } LineRef SourceData::getSizeLines() const { return (LineRef)(m_normalData.m_vSize); } qint64 SourceData::getSizeBytes() const { return m_normalData.m_size; } const char* SourceData::getBuf() const { return m_normalData.m_pBuf; } const QString& SourceData::getText() const { return *m_normalData.m_unicodeBuf; } bool SourceData::isText() { return m_normalData.isText() || m_normalData.isEmpty(); } bool SourceData::isIncompleteConversion() { return m_normalData.m_bIncompleteConversion; } bool SourceData::isFromBuffer() { return !m_fileAccess.isValid(); } bool SourceData::isBinaryEqualWith(const SourceData& other) const { return m_fileAccess.exists() && other.m_fileAccess.exists() && getSizeBytes() == other.getSizeBytes() && (getSizeBytes() == 0 || memcmp(getBuf(), other.getBuf(), getSizeBytes()) == 0); } void SourceData::FileData::reset() { delete[](char*) m_pBuf; m_pBuf = nullptr; m_v.clear(); m_size = 0; m_vSize = 0; m_bIsText = false; m_bIncompleteConversion = false; m_eLineEndStyle = eLineEndStyleUndefined; } bool SourceData::FileData::readFile(FileAccess& file) { reset(); if(file.fileName().isEmpty()) { return true; } //FileAccess fa(filename); if(!file.isNormal()) return true; m_size = file.sizeForReading(); char* pBuf; m_pBuf = pBuf = new char[m_size + 100]; // Alloc 100 byte extra: Safety hack, not nice but does no harm. // Some extra bytes at the end of the buffer are needed by // the diff algorithm. See also GnuDiff::diff_2_files(). bool bSuccess = file.readFile(pBuf, m_size); if(!bSuccess) { delete[] pBuf; m_pBuf = nullptr; m_size = 0; } else { //null terminate buffer pBuf[m_size + 1] = 0; pBuf[m_size + 2] = 0; pBuf[m_size + 3] = 0; pBuf[m_size + 4] = 0; } return bSuccess; } bool SourceData::FileData::readFile(const QString& filename) { reset(); if(filename.isEmpty()) { return true; } FileAccess fa(filename); if(!fa.isNormal()) return true; m_size = fa.sizeForReading(); char* pBuf; m_pBuf = pBuf = new char[m_size + 100]; // Alloc 100 byte extra: Safety hack, not nice but does no harm. // Some extra bytes at the end of the buffer are needed by // the diff algorithm. See also GnuDiff::diff_2_files(). bool bSuccess = fa.readFile(pBuf, m_size); if(!bSuccess) { delete[] pBuf; m_pBuf = nullptr; m_size = 0; } return bSuccess; } bool SourceData::saveNormalDataAs(const QString& fileName) { return m_normalData.writeFile(fileName); } bool SourceData::FileData::writeFile(const QString& filename) { if(filename.isEmpty()) { return true; } FileAccess fa(filename); bool bSuccess = fa.writeFile(m_pBuf, m_size); return bSuccess; } //Depriated void SourceData::FileData::copyBufFrom(const FileData& src) //TODO: Remove me. { reset(); char* pBuf; m_size = src.m_size; m_pBuf = pBuf = new char[m_size + 100]; Q_ASSERT(src.m_pBuf != nullptr); memcpy(pBuf, src.m_pBuf, m_size); } QTextCodec* SourceData::detectEncoding(const QString& fileName, QTextCodec* pFallbackCodec) { QFile f(fileName); if(f.open(QIODevice::ReadOnly)) { char buf[200]; qint64 size = f.read(buf, sizeof(buf)); qint64 skipBytes = 0; QTextCodec* pCodec = detectEncoding(buf, size, skipBytes); if(pCodec) return pCodec; } return pFallbackCodec; } QStringList SourceData::readAndPreprocess(QTextCodec* pEncoding, bool bAutoDetectUnicode) { m_pEncoding = pEncoding; QTemporaryFile fileIn1, fileOut1; QString fileNameIn1; QString fileNameOut1; QString fileNameIn2; QString fileNameOut2; QStringList errors; if(m_fileAccess.isValid() && !m_fileAccess.isNormal()) { errors.append(i18n("%1 is not a normal file.", m_fileAccess.prettyAbsPath())); return errors; } bool bTempFileFromClipboard = !m_fileAccess.isValid(); // Detect the input for the preprocessing operations if(!bTempFileFromClipboard) { if(m_fileAccess.isLocal()) { fileNameIn1 = m_fileAccess.absoluteFilePath(); } else // File is not local: create a temporary local copy: { if(m_tempInputFileName.isEmpty()) { m_fileAccess.createLocalCopy(); m_tempInputFileName = m_fileAccess.getTempName(); } fileNameIn1 = m_tempInputFileName; } if(bAutoDetectUnicode) { m_pEncoding = detectEncoding(fileNameIn1, pEncoding); } } else // The input was set via setData(), probably from clipboard. { fileNameIn1 = m_tempInputFileName; m_pEncoding = QTextCodec::codecForName("UTF-8"); } QTextCodec* pEncoding1 = m_pEncoding; QTextCodec* pEncoding2 = m_pEncoding; m_normalData.reset(); m_lmppData.reset(); FileAccess faIn(fileNameIn1); qint64 fileInSize = faIn.size(); if(faIn.exists()) { // Run the first preprocessor if(m_pOptions->m_PreProcessorCmd.isEmpty()) { // No preprocessing: Read the file directly: if(!m_normalData.readFile(faIn)) { errors.append(faIn.getStatusText()); return errors; } } else { QTemporaryFile tmpInPPFile; QString fileNameInPP = fileNameIn1; if(pEncoding1 != m_pOptions->m_pEncodingPP) { // Before running the preprocessor convert to the format that the preprocessor expects. FileAccess::createTempFile(tmpInPPFile); fileNameInPP = tmpInPPFile.fileName(); pEncoding1 = m_pOptions->m_pEncodingPP; convertFileEncoding(fileNameIn1, pEncoding, fileNameInPP, pEncoding1); } QString ppCmd = m_pOptions->m_PreProcessorCmd; FileAccess::createTempFile(fileOut1); fileNameOut1 = fileOut1.fileName(); QProcess ppProcess; ppProcess.setStandardInputFile(fileNameInPP); ppProcess.setStandardOutputFile(fileNameOut1); QString program; QStringList args; QString errorReason = Utils::getArguments(ppCmd, program, args); if(errorReason.isEmpty()) { ppProcess.start(program, args); ppProcess.waitForFinished(-1); } else errorReason = "\n(" + errorReason + ')'; bool bSuccess = errorReason.isEmpty() && m_normalData.readFile(fileNameOut1); if(fileInSize > 0 && (!bSuccess || m_normalData.m_size == 0)) { //Don't fail the preprocessor command if the file cann't be read. if(!m_normalData.readFile(faIn)) { errors.append(faIn.getStatusText()); errors.append(i18n(" Temp file is: %1", fileNameIn1)); return errors; } errors.append( i18n("Preprocessing possibly failed. Check this command:\n\n %1" "\n\nThe preprocessing command will be disabled now.", ppCmd) + errorReason); m_pOptions->m_PreProcessorCmd = ""; pEncoding1 = m_pEncoding; } } if(!m_normalData.preprocess(pEncoding1)) { errors.append(i18n("File %1 too large to process. Skipping.", fileNameIn1)); return errors; } //exit early for non text data further processing assumes a text file as input if(!m_normalData.isText()) return errors; // LineMatching Preprocessor if(!m_pOptions->m_LineMatchingPreProcessorCmd.isEmpty()) { QTemporaryFile tempOut2, fileInPP; fileNameIn2 = fileNameOut1.isEmpty() ? fileNameIn1 : fileNameOut1; QString fileNameInPP = fileNameIn2; pEncoding2 = pEncoding1; if(pEncoding2 != m_pOptions->m_pEncodingPP) { // Before running the preprocessor convert to the format that the preprocessor expects. FileAccess::createTempFile(fileInPP); fileNameInPP = fileInPP.fileName(); pEncoding2 = m_pOptions->m_pEncodingPP; convertFileEncoding(fileNameIn2, pEncoding1, fileNameInPP, pEncoding2); } QString ppCmd = m_pOptions->m_LineMatchingPreProcessorCmd; FileAccess::createTempFile(tempOut2); fileNameOut2 = tempOut2.fileName(); QProcess ppProcess; ppProcess.setStandardInputFile(fileNameInPP); ppProcess.setStandardOutputFile(fileNameOut2); QString program; QStringList args; QString errorReason = Utils::getArguments(ppCmd, program, args); if(errorReason.isEmpty()) { ppProcess.start(program, args); ppProcess.waitForFinished(-1); } else errorReason = "\n(" + errorReason + ')'; bool bSuccess = errorReason.isEmpty() && m_lmppData.readFile(fileNameOut2); if(FileAccess(fileNameIn2).size() > 0 && (!bSuccess || m_lmppData.m_size == 0)) { errors.append( i18n("The line-matching-preprocessing possibly failed. Check this command:\n\n %1" "\n\nThe line-matching-preprocessing command will be disabled now.", ppCmd) + errorReason); m_pOptions->m_LineMatchingPreProcessorCmd = ""; if(!m_lmppData.readFile(fileNameIn2)) { errors.append(i18n("Failed to read file: %1", fileNameIn2)); return errors; } } } else if(m_pOptions->m_bIgnoreComments || m_pOptions->m_bIgnoreCase) { // We need a copy of the normal data. m_lmppData.copyBufFrom(m_normalData); } } else { //exit early for nonexistent files return errors; } if(!m_lmppData.preprocess(pEncoding2)) { errors.append(i18n("File %1 too large to process. Skipping.", fileNameIn1)); return errors; } Q_ASSERT(m_lmppData.isText()); //TODO: Needed? if(m_lmppData.m_vSize < m_normalData.m_vSize) { // Preprocessing command may result in smaller data buffer so adjust size for(qint64 i = m_lmppData.m_vSize; i < m_normalData.m_vSize; ++i) { // Set all empty lines to point to the end of the buffer. m_lmppData.m_v.push_back(LineData(m_lmppData.m_unicodeBuf, m_lmppData.m_unicodeBuf->length())); } m_lmppData.m_vSize = m_normalData.m_vSize; } // Ignore comments if(m_pOptions->m_bIgnoreComments && hasData()) { m_lmppData.removeComments(); qint64 vSize = std::min(m_normalData.m_vSize, m_lmppData.m_vSize); Q_ASSERT(vSize < TYPE_MAX(int)); for(int i = 0; i < vSize; ++i) { m_normalData.m_v[i].setPureComment(m_lmppData.m_v[i].isPureComment()); //Don't crash if vSize is too large. if(i == TYPE_MAX(int)) break; } } return errors; } /** Prepare the linedata vector for every input line.*/ bool SourceData::FileData::preprocess(QTextCodec* pEncoding) { if(m_pBuf == nullptr) return true; if(pEncoding == nullptr) return false; QString line; QChar curChar; LineCount lineCount = 0; qint64 lastOffset = 0; qint64 skipBytes = 0; + QScopedPointer parser(new DefaultCommentParser()); // detect line end style QVector vOrigDataLineEndStyle; m_eLineEndStyle = eLineEndStyleUndefined; QTextCodec* pCodec = detectEncoding(m_pBuf, m_size, skipBytes); if(pCodec != pEncoding) skipBytes = 0; if(m_size - skipBytes > TYPE_MAX(QtNumberType)) return false; QByteArray ba = QByteArray::fromRawData(m_pBuf + skipBytes, (int)(m_size - skipBytes)); QTextStream ts(ba, QIODevice::ReadOnly); //Don't use text mode we need to see the actual line ending. ts.setCodec(pEncoding); ts.setAutoDetectUnicode(false); m_bIncompleteConversion = false; m_unicodeBuf->clear(); Q_ASSERT(m_unicodeBuf->length() == 0); while(!ts.atEnd()) { line.clear(); if(lineCount >= TYPE_MAX(LineCount) - 5) return false; ts >> curChar; quint32 firstNonwhite=0; //QTextStream::readLine doesn't tell us abount line endings. while(curChar != '\n' && curChar != '\r') { if(curChar.isNull() || curChar.isNonCharacter()) return true; if(curChar == QChar::ReplacementCharacter) m_bIncompleteConversion = true; if(!curChar.isSpace()) firstNonwhite = line.length(); line.append(curChar); if(ts.atEnd()) break; ts >> curChar; } ++lineCount; switch(curChar.unicode()) { case '\n': vOrigDataLineEndStyle.push_back(eLineEndStyleUnix); break; case '\r': if(lastOffset < m_size) { //workaround for lack of peak API in QTextStream. qint64 j; for(j = 0; j < 4 && lastOffset + j < m_size; ++j) { if(m_pBuf[lastOffset + j] != '\0') break; } if(m_pBuf[lastOffset + j] == '\n') { ts >> curChar; vOrigDataLineEndStyle.push_back(eLineEndStyleDos); lastOffset = ts.pos(); break; } } //old mac style ending. vOrigDataLineEndStyle.push_back(eLineEndStyleUndefined); break; } + parser->processLine(line); //kdiff3 internally uses only unix style endings for simplicity. - m_v.push_back(LineData(m_unicodeBuf, lastOffset, line.length())); + m_v.push_back(LineData(m_unicodeBuf, lastOffset, line.length(), parser->isPureComment())); m_unicodeBuf->append(line).append('\n'); + lastOffset = m_unicodeBuf->length(); } m_v.push_back(LineData(m_unicodeBuf, lastOffset)); Q_ASSERT(m_v.size() < 2 || m_v[m_v.size() - 1].getOffset() != m_v[m_v.size() - 2].getOffset()); m_bIsText = true; if(!vOrigDataLineEndStyle.isEmpty()) m_eLineEndStyle = vOrigDataLineEndStyle[0]; m_vSize = lineCount; return true; } // Depriated - move to comment handler // Must not be entered, when within a comment. // Returns either at a newline-character p[i]=='\n' or when i==size. // A line that contains only comments is still "white". // Comments in white lines must remain, while comments in // non-white lines are overwritten with spaces. void SourceData::FileData::checkLineForComments( const QChar* p, // pointer to start of buffer int& i, // index of current position (in, out) int size, // size of buffer bool& bWhite, // false if this line contains nonwhite characters (in, out) bool& bCommentInLine, // true if any comment is within this line (in, out) bool& bStartsOpenComment // true if the line ends within an comment (out) ) {//TODO: Phase out used only in remove comment. bStartsOpenComment = false; for(; i < size; ++i) { // A single apostroph ' has prio over a double apostroph " (e.g. '"') // (if not in a string) if(p[i] == '\'') { bWhite = false; ++i; for(; !isLineOrBufEnd(p, i, size) && p[i] != '\''; ++i) ; if(p[i] == '\'') ++i; } // Strings have priority over comments: e.g. "/* Not a comment, but a string. */" else if(p[i] == '"') { bWhite = false; ++i; for(; !isLineOrBufEnd(p, i, size) && !(p[i] == '"' && p[i - 1] != '\\'); ++i) ; if(p[i] == '"') ++i; } // C++-comment else if(p[i] == '/' && i + 1 < size && p[i + 1] == '/') { int commentStart = i; bCommentInLine = true; i += 2; for(; !isLineOrBufEnd(p, i, size); ++i) ; if(!bWhite) { size = i - commentStart; m_unicodeBuf->replace(commentStart, size, QString(" ").repeated(size)); } return; } // C-comment else if(p[i] == '/' && i + 1 < size && p[i + 1] == '*') { int commentStart = i; bCommentInLine = true; i += 2; for(; !isLineOrBufEnd(p, i, size); ++i) { if(i + 1 < size && p[i] == '*' && p[i + 1] == '/') // end of the comment { i += 2; // More comments in the line? checkLineForComments(p, i, size, bWhite, bCommentInLine, bStartsOpenComment); if(!bWhite) { size = i - commentStart; m_unicodeBuf->replace(commentStart, size, QString(" ").repeated(size)); } return; } } bStartsOpenComment = true; return; } if(isLineOrBufEnd(p, i, size)) { return; } else if(!p[i].isSpace()) { bWhite = false; } } } // Depriated // Modifies the input data, and replaces C/C++ comments with whitespace // when the line contains other data too. If the line contains only // a comment or white data, remember this in the flag bContainsPureComment. void SourceData::FileData::removeComments() {//TODO: Phase out int line = 0; const QChar* p = m_unicodeBuf->unicode(); bool bWithinComment = false; int size = m_unicodeBuf->length(); qCDebug(kdiffCore) << "m_v.size() = " << m_v.size() << ", size = " << size; Q_ASSERT(m_v.size() > 0); for(int i = 0; i < size; ++i) { qCDebug(kdiffCore) << "line = " << QString(&p[i], m_v[line].size()); bool bWhite = true; bool bCommentInLine = false; if(bWithinComment) { int commentStart = i; bCommentInLine = true; for(; !isLineOrBufEnd(p, i, size); ++i) { if(i + 1 < size && p[i] == '*' && p[i + 1] == '/') // end of the comment { i += 2; // More comments in the line? checkLineForComments(p, i, size, bWhite, bCommentInLine, bWithinComment); if(!bWhite) { size = i - commentStart; m_unicodeBuf->replace(commentStart, size, QString(" ").repeated(size)); } break; } } } else { checkLineForComments(p, i, size, bWhite, bCommentInLine, bWithinComment); } // end of line Q_ASSERT(isLineOrBufEnd(p, i, size)); m_v[line].setPureComment(bCommentInLine && bWhite); /* std::cout << line << " : " << ( bCommentInLine ? "c" : " " ) << ( bWhite ? "w " : " ") << std::string(pLD[line].pLine, pLD[line].size) << std::endl;*/ ++line; } } bool SourceData::isLineOrBufEnd(const QChar* p, int i, int size) { return i >= size // End of file || Utils::isEndOfLine(p[i]) // Normal end of line // No support for Mac-end of line yet, because incompatible with GNU-diff-routines. // || ( p[i]=='\r' && (i>=size-1 || p[i+1]!='\n') // && (i==0 || p[i-1]!='\n') ) // Special case: '\r' without '\n' ; } // Convert the input file from input encoding to output encoding and write it to the output file. bool SourceData::convertFileEncoding(const QString& fileNameIn, QTextCodec* pCodecIn, const QString& fileNameOut, QTextCodec* pCodecOut) { QFile in(fileNameIn); if(!in.open(QIODevice::ReadOnly)) return false; QTextStream inStream(&in); inStream.setCodec(pCodecIn); inStream.setAutoDetectUnicode(false); QFile out(fileNameOut); if(!out.open(QIODevice::WriteOnly)) return false; QTextStream outStream(&out); outStream.setCodec(pCodecOut); QString data = inStream.readAll(); outStream << data; return true; } QTextCodec* SourceData::getEncodingFromTag(const QByteArray& s, const QByteArray& encodingTag) { int encodingPos = s.indexOf(encodingTag); if(encodingPos >= 0) { int apostrophPos = s.indexOf('"', encodingPos + encodingTag.length()); int apostroph2Pos = s.indexOf('\'', encodingPos + encodingTag.length()); char apostroph = '"'; if(apostroph2Pos >= 0 && (apostrophPos < 0 || apostroph2Pos < apostrophPos)) { apostroph = '\''; apostrophPos = apostroph2Pos; } int encodingEnd = s.indexOf(apostroph, apostrophPos + 1); if(encodingEnd >= 0) // e.g.: or { QByteArray encoding = s.mid(apostrophPos + 1, encodingEnd - (apostrophPos + 1)); return QTextCodec::codecForName(encoding); } else // e.g.: { QByteArray encoding = s.mid(encodingPos + encodingTag.length(), apostrophPos - (encodingPos + encodingTag.length())); return QTextCodec::codecForName(encoding); } } return nullptr; } QTextCodec* SourceData::detectEncoding(const char* buf, qint64 size, qint64& skipBytes) { if(size >= 2) { if(buf[0] == '\xFF' && buf[1] == '\xFE') { skipBytes = 2; return QTextCodec::codecForName("UTF-16LE"); } if(buf[0] == '\xFE' && buf[1] == '\xFF') { skipBytes = 2; return QTextCodec::codecForName("UTF-16BE"); } } if(size >= 3) { if(buf[0] == '\xEF' && buf[1] == '\xBB' && buf[2] == '\xBF') { skipBytes = 3; return QTextCodec::codecForName("UTF-8-BOM"); } } skipBytes = 0; QByteArray s; /* We don't need the whole file here just the header. ] */ if(size <= 5000) s = QByteArray(buf, (int)size); else s = QByteArray(buf, 5000); int xmlHeaderPos = s.indexOf("= 0) { int xmlHeaderEnd = s.indexOf("?>", xmlHeaderPos); if(xmlHeaderEnd >= 0) { QTextCodec* pCodec = getEncodingFromTag(s.mid(xmlHeaderPos, xmlHeaderEnd - xmlHeaderPos), "encoding="); if(pCodec) return pCodec; } } else // HTML { int metaHeaderPos = s.indexOf("= 0) { int metaHeaderEnd = s.indexOf(">", metaHeaderPos); if(metaHeaderEnd >= 0) { QTextCodec* pCodec = getEncodingFromTag(s.mid(metaHeaderPos, metaHeaderEnd - metaHeaderPos), "charset="); if(pCodec) return pCodec; metaHeaderPos = s.indexOf(" + * + * This file is part of KDiff3. + * + * KDiff3 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * KDiff3 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KDiff3. If not, see . + */ + +#include + +#include "../CommentParser.h" + +class CommentParserTest : public QObject +{ + Q_OBJECT + private slots: + void init() + { + DefaultCommentParser parser; + + //Sanity check defaults. + QVERIFY(!parser.isPureComment()); + QVERIFY(!parser.isEscaped()); + QVERIFY(!parser.inString()); + QVERIFY(!parser.inComment()); + } + + void singleLineComment1() + { + DefaultCommentParser test1; + + test1.processLine("//ddj ?*8"); + QVERIFY(!test1.inComment()); + QVERIFY(test1.isPureComment()); + } + + void singleLineComment2() + { + DefaultCommentParser test1; + + test1.processLine("//comment with quotes embeded \"\""); + QVERIFY(!test1.inComment()); + QVERIFY(test1.isPureComment()); + } + + void singleLineComment3() + { + DefaultCommentParser test1; + + test1.processLine("anythis//endof line comment"); + QVERIFY(!test1.inComment()); + QVERIFY(!test1.isPureComment()); + } + + void singleLineComment4() + { + DefaultCommentParser test1; + + test1.processLine("anythis//ignore embeded multiline squence /*"); + QVERIFY(!test1.inComment()); + QVERIFY(!test1.isPureComment()); + } + + void inComment() + { + DefaultCommentParser test; + + test.mCommentType = DefaultCommentParser::multiLine; + QVERIFY(test.inComment()); + + test.mCommentType = DefaultCommentParser::singleLine; + QVERIFY(test.inComment()); + + test.mCommentType = DefaultCommentParser::none; + QVERIFY(!test.inComment()); + } + + void multiLineComment() + { + DefaultCommentParser test; + + //mutiline syntax on one line + test.processLine("/* kjd*/"); + QVERIFY(test.isPureComment()); + QVERIFY(!test.inComment()); + + //mid line comment start. + test = DefaultCommentParser(); + test.processLine("fskk /* kjd */"); + QVERIFY(!test.inComment()); + QVERIFY(!test.isPureComment()); + + //mid line comment start. mutiple lines + test = DefaultCommentParser(); + test.processLine("fskk /* kjd "); + QVERIFY(test.inComment()); + QVERIFY(!test.isPureComment()); + + test.processLine(" comment line "); + QVERIFY(test.inComment()); + QVERIFY(test.isPureComment()); + + test.processLine(" comment */ not comment "); + QVERIFY(!test.inComment()); + QVERIFY(!test.isPureComment()); + + //mid line comment start. mutiple lines + test = DefaultCommentParser(); + test.processLine("fskk /* kjd "); + QVERIFY(test.inComment()); + QVERIFY(!test.isPureComment()); + + test.processLine(" comment line "); + QVERIFY(test.inComment()); + QVERIFY(test.isPureComment()); + //embeded single line character squence should not end comment + test.processLine(" comment line //"); + QVERIFY(test.inComment()); + QVERIFY(test.isPureComment()); + + test.processLine(" comment */"); + QVERIFY(!test.inComment()); + QVERIFY(test.isPureComment()); + + //Escape squeances not relavate to comments + test.processLine("/* comment \\*/"); + QVERIFY(!test.inComment()); + QVERIFY(test.isPureComment()); + + //invalid in C++ should not be flagged as pure comment + test.processLine("/* comment */ */"); + QVERIFY(!test.inComment()); + QVERIFY(!test.isPureComment()); + } + + void stringTest() + { + DefaultCommentParser test; + + test.processLine("\"quoted string // \""); + QVERIFY(!test.inString()); + QVERIFY(!test.inComment()); + QVERIFY(!test.isPureComment()); + + test = DefaultCommentParser(); + test.processLine("\"quoted string /* \""); + QVERIFY(!test.inString()); + QVERIFY(!test.inComment()); + QVERIFY(!test.isPureComment()); + + //test only escape squeance we care about + test = DefaultCommentParser(); + test.processChar("\"", '"'); + QVERIFY(!test.isEscaped()); + QVERIFY(test.inString()); + + test.processChar("\"", '\\'); + QVERIFY(test.isEscaped()); + QVERIFY(test.inString()); + + test.processChar("\"\\\"", '"'); + QVERIFY(!test.isEscaped()); + QVERIFY(test.inString()); + + test.processChar("\"\\\"\"", '"'); + QVERIFY(!test.isEscaped()); + QVERIFY(!test.inString()); + } +}; + +QTEST_MAIN(CommentParserTest); + +#include "commentparser.moc" diff --git a/src/diff.h b/src/diff.h index 5da276c..0e24fa2 100644 --- a/src/diff.h +++ b/src/diff.h @@ -1,462 +1,463 @@ /*************************************************************************** * Copyright (C) 2003-2007 by Joachim Eibl * * Copyright (C) 2018 Michael Reeves reeves.87@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef DIFF_H #define DIFF_H #include "common.h" #include "fileaccess.h" #include "LineRef.h" #include "SourceData.h" #include "Logging.h" #include class Options; //enum must be sequential with no gaps to allow loop interiation of values enum e_SrcSelector { Min = -1, Invalid=-1, None=0, A = 1, B = 2, C = 3, Max=C }; enum e_MergeDetails { eDefault, eNoChange, eBChanged, eCChanged, eBCChanged, // conflict eBCChangedAndEqual, // possible conflict eBDeleted, eCDeleted, eBCDeleted, // possible conflict eBChanged_CDeleted, // conflict eCChanged_BDeleted, // conflict eBAdded, eCAdded, eBCAdded, // conflict eBCAddedAndEqual // possible conflict }; // Each range with matching elements is followed by a range with differences on either side. // Then again range of matching elements should follow. class Diff { private: qint32 nofEquals = 0; qint64 mDiff1 = 0; qint64 mDiff2 = 0; public: Diff(qint32 eq, const qint64 inDiff1, const qint64 inDiff2) { nofEquals = eq; mDiff1 = inDiff1; mDiff2 = inDiff2; } inline qint32 numberOfEquals() const { return nofEquals; }; inline qint64 diff1() const { return mDiff1; }; inline qint64 diff2() const { return mDiff2; }; inline void setNumberOfEquals(const qint32 inNumOfEquals) { nofEquals = inNumOfEquals; } inline void adjustNumberOfEquals(const qint64 delta) { nofEquals += delta; } inline void adjustDiff1(const qint64 delta) { mDiff1 += delta; } inline void adjustDiff2(const qint64 delta) { mDiff2 += delta; } }; typedef std::list DiffList; class LineData { private: QSharedPointer mBuffer; //QString pLine; QtNumberType mFirstNonWhiteChar = 0; qint64 mOffset = 0; QtNumberType mSize = 0; bool bContainsPureComment = false;//TODO: Move me public: explicit LineData() = default; // needed for Qt internal reasons should not be used. - inline LineData(const QSharedPointer &buffer, const qint64 inOffset, QtNumberType inSize = 0) + inline LineData(const QSharedPointer &buffer, const qint64 inOffset, QtNumberType inSize = 0, bool inIsPureComment=false) { mBuffer = buffer; mOffset = inOffset; mSize = inSize; + bContainsPureComment = inIsPureComment; } Q_REQUIRED_RESULT inline int size() const { return mSize; } inline void setFirstNonWhiteChar(const qint32 firstNonWhiteChar) { mFirstNonWhiteChar = firstNonWhiteChar;} Q_REQUIRED_RESULT inline qint32 getFirstNonWhiteChar() const { return mFirstNonWhiteChar; } /* QString::fromRawData allows us to create a light weight QString backed by the buffer memmory. */ Q_REQUIRED_RESULT inline const QString getLine() const { return QString::fromRawData(mBuffer->data() + mOffset, mSize); } Q_REQUIRED_RESULT inline const QSharedPointer& getBuffer() const { return mBuffer; } Q_REQUIRED_RESULT inline qint64 getOffset() const { return mOffset; } Q_REQUIRED_RESULT int width(int tabSize) const; // Calcs width considering tabs. //int occurrences; inline bool whiteLine() const { return mFirstNonWhiteChar == mSize - 1; } inline bool isPureComment() const { return bContainsPureComment; } inline void setPureComment(const bool bPureComment) { bContainsPureComment = bPureComment; } static bool equal(const LineData& l1, const LineData& l2, bool bStrict); }; class Diff3LineList; class Diff3LineVector; class DiffBufferInfo { public: const QVector* m_pLineDataA; const QVector* m_pLineDataB; const QVector* m_pLineDataC; LineCount m_sizeA; LineCount m_sizeB; LineCount m_sizeC; const Diff3LineList* m_pDiff3LineList; const Diff3LineVector* m_pDiff3LineVector; void init(Diff3LineList* d3ll, const Diff3LineVector* d3lv, const QVector* pldA, LineCount sizeA, const QVector* pldB, LineCount sizeB, const QVector* pldC, LineCount sizeC); }; class Diff3Line { private: LineRef lineA; LineRef lineB; LineRef lineC; public: bool bAEqC = false; // These are true if equal or only white-space changes exist. bool bBEqC = false; bool bAEqB = false; bool bWhiteLineA = false; bool bWhiteLineB = false; bool bWhiteLineC = false; DiffList* pFineAB = nullptr; // These are 0 only if completely equal or if either source doesn't exist. DiffList* pFineBC = nullptr; DiffList* pFineCA = nullptr; int linesNeededForDisplay = 1; // Due to wordwrap int sumLinesNeededForDisplay = 0; // For fast conversion to m_diff3WrapLineVector DiffBufferInfo* m_pDiffBufferInfo = nullptr; // For convenience ~Diff3Line() { if(pFineAB != nullptr) delete pFineAB; if(pFineBC != nullptr) delete pFineBC; if(pFineCA != nullptr) delete pFineCA; pFineAB = nullptr; pFineBC = nullptr; pFineCA = nullptr; } LineRef getLineA() const { return lineA; } LineRef getLineB() const { return lineB; } LineRef getLineC() const { return lineC; } inline void setLineA(const LineRef& line) { lineA = line; } inline void setLineB(const LineRef& line) { lineB = line; } inline void setLineC(const LineRef& line) { lineC = line; } inline bool isEqualAB() const { return bAEqB; } inline bool isEqualAC() const { return bAEqC; } inline bool isEqualBC() const { return bBEqC; } bool operator==(const Diff3Line& d3l) const { return lineA == d3l.lineA && lineB == d3l.lineB && lineC == d3l.lineC && bAEqB == d3l.bAEqB && bAEqC == d3l.bAEqC && bBEqC == d3l.bBEqC; } const LineData* getLineData(e_SrcSelector src) const { Q_ASSERT(m_pDiffBufferInfo != nullptr); if(src == A && lineA >= 0) return &(*m_pDiffBufferInfo->m_pLineDataA)[lineA]; if(src == B && lineB >= 0) return &(*m_pDiffBufferInfo->m_pLineDataB)[lineB]; if(src == C && lineC >= 0) return &(*m_pDiffBufferInfo->m_pLineDataC)[lineC]; return nullptr; } const QString getString(const e_SrcSelector src) const { const LineData* pld = getLineData(src); if(pld) return pld->getLine(); else return QString(); } LineRef getLineInFile(e_SrcSelector src) const { if(src == A) return lineA; if(src == B) return lineB; if(src == C) return lineC; return -1; } bool fineDiff(bool bTextsTotalEqual, const e_SrcSelector selector, const QVector* v1, const QVector* v2); void mergeOneLine(e_MergeDetails& mergeDetails, bool& bConflict, bool& bLineRemoved, e_SrcSelector& src, bool bTwoInputs) const; void getLineInfo(const e_SrcSelector winIdx, const bool isTriple, int& lineIdx, DiffList*& pFineDiff1, DiffList*& pFineDiff2, // return values int& changed, int& changed2) const; private: void setFineDiff(const e_SrcSelector selector, DiffList* pDiffList) { Q_ASSERT(selector == A || selector == B || selector == C); if(selector == A) { if(pFineAB != nullptr) delete pFineAB; pFineAB = pDiffList; } else if(selector == B) { if(pFineBC != nullptr) delete pFineBC; pFineBC = pDiffList; } else if(selector == C) { if(pFineCA) delete pFineCA; pFineCA = pDiffList; } } }; class Diff3LineList : public std::list { public: bool fineDiff(const e_SrcSelector selector, const QVector* v1, const QVector* v2); void calcDiff3LineVector(Diff3LineVector& d3lv); void calcWhiteDiff3Lines(const QVector* pldA, const QVector* pldB, const QVector* pldC); //TODO: Add safety guards to prevent list from getting too large. Same problem as with QLinkedList. int size() const { if(std::list::size() > (size_t)std::numeric_limits::max())//explicit cast to silence gcc { qCDebug(kdiffMain) << "Diff3Line: List too large. size=" << std::list::size(); Q_ASSERT(false); //Unsupported size return 0; } return (int)std::list::size(); } //safe for small files same limit as exited with QLinkedList. This should ultimatly be removed. void debugLineCheck(const LineCount size, const e_SrcSelector srcSelector) const; }; class Diff3LineVector : public QVector { }; class Diff3WrapLine { public: Diff3Line* pD3L; int diff3LineIndex; int wrapLineOffset; int wrapLineLength; }; typedef QVector Diff3WrapLineVector; class TotalDiffStatus { public: inline void reset() { bBinaryAEqC = false; bBinaryBEqC = false; bBinaryAEqB = false; bTextAEqC = false; bTextBEqC = false; bTextAEqB = false; nofUnsolvedConflicts = 0; nofSolvedConflicts = 0; nofWhitespaceConflicts = 0; } inline int getUnsolvedConflicts() const { return nofUnsolvedConflicts; } inline void setUnsolvedConflicts(const int unsolved) { nofUnsolvedConflicts = unsolved; } inline int getSolvedConflicts() const { return nofSolvedConflicts; } inline void setSolvedConflicts(const int solved) { nofSolvedConflicts = solved; } inline int getWhitespaceConflicts() const { return nofWhitespaceConflicts; } inline void setWhitespaceConflicts(const int wintespace) { nofWhitespaceConflicts = wintespace; } inline int getNonWhitespaceConflicts() { return getUnsolvedConflicts() + getSolvedConflicts() - getWhitespaceConflicts(); } bool isBinaryEqualAC() const { return bBinaryAEqC; } bool isBinaryEqualBC() const { return bBinaryBEqC; } bool isBinaryEqualAB() const { return bBinaryAEqB; } bool bBinaryAEqC = false; bool bBinaryBEqC = false; bool bBinaryAEqB = false; bool bTextAEqC = false; bool bTextBEqC = false; bool bTextAEqB = false; private: int nofUnsolvedConflicts = 0; int nofSolvedConflicts = 0; int nofWhitespaceConflicts = 0; }; class ManualDiffHelpList; // A list of corresponding ranges // Three corresponding ranges. (Minimum size of a valid range is one line.) class ManualDiffHelpEntry { private: LineRef lineA1; LineRef lineA2; LineRef lineB1; LineRef lineB2; LineRef lineC1; LineRef lineC2; public: LineRef& firstLine(e_SrcSelector winIdx) { return winIdx == A ? lineA1 : (winIdx == B ? lineB1 : lineC1); } LineRef& lastLine(e_SrcSelector winIdx) { return winIdx == A ? lineA2 : (winIdx == B ? lineB2 : lineC2); } bool isLineInRange(LineRef line, e_SrcSelector winIdx) { return line >= 0 && line >= firstLine(winIdx) && line <= lastLine(winIdx); } bool operator==(const ManualDiffHelpEntry& r) const { return lineA1 == r.lineA1 && lineB1 == r.lineB1 && lineC1 == r.lineC1 && lineA2 == r.lineA2 && lineB2 == r.lineB2 && lineC2 == r.lineC2; } int calcManualDiffFirstDiff3LineIdx(const Diff3LineVector& d3lv); void getRangeForUI(const e_SrcSelector winIdx, int *rangeLine1, int *rangeLine2) const { if(winIdx == A) { *rangeLine1 = lineA1; *rangeLine2 = lineA2; } if(winIdx == B) { *rangeLine1 = lineB1; *rangeLine2 = lineB2; } if(winIdx == C) { *rangeLine1 = lineC1; *rangeLine2 = lineC2; } } inline int getLine1(const e_SrcSelector winIdx) const { return winIdx == A ? lineA1 : winIdx == B ? lineB1 : lineC1;} inline int getLine2(const e_SrcSelector winIdx) const { return winIdx == A ? lineA2 : winIdx == B ? lineB2 : lineC2;} bool isValidMove(int line1, int line2, e_SrcSelector winIdx1, e_SrcSelector winIdx2) const; }; // A list of corresponding ranges class ManualDiffHelpList: public std::list { public: bool isValidMove(int line1, int line2, e_SrcSelector winIdx1, e_SrcSelector winIdx2) const; void insertEntry(e_SrcSelector winIdx, LineRef firstLine, LineRef lastLine); bool runDiff(const QVector* p1, LineRef size1, const QVector* p2, LineRef size2, DiffList& diffList, e_SrcSelector winIdx1, e_SrcSelector winIdx2, const QSharedPointer &pOptions); }; void calcDiff(const QString &line1, const QString &line2, DiffList& diffList, int match, int maxSearchRange); void calcDiff3LineListUsingAB( const DiffList* pDiffListAB, Diff3LineList& d3ll); void calcDiff3LineListUsingAC( const DiffList* pDiffListAC, Diff3LineList& d3ll); void calcDiff3LineListUsingBC( const DiffList* pDiffListBC, Diff3LineList& d3ll); void correctManualDiffAlignment(Diff3LineList& d3ll, ManualDiffHelpList* pManualDiffHelpList); void calcDiff3LineListTrim(Diff3LineList& d3ll, const QVector* pldA, const QVector* pldB, const QVector* pldC, ManualDiffHelpList* pManualDiffHelpList); bool fineDiff( Diff3LineList& diff3LineList, int selector, const QVector* v1, const QVector* v2); inline bool isWhite(QChar c) { return c == ' ' || c == '\t' || c == '\r'; } /** Returns the number of equivalent spaces at position outPos. */ inline int tabber(int outPos, int tabSize) { return tabSize - (outPos % tabSize); } /** Returns a line number where the linerange [line, line+nofLines] can be displayed best. If it fits into the currently visible range then the returned value is the current firstLine. */ int getBestFirstLine(int line, int nofLines, int firstLine, int visibleLines); extern bool g_bIgnoreWhiteSpace; extern bool g_bIgnoreTrivialMatches; extern bool g_bAutoSolve; // Cursor conversions that consider g_tabSize. int convertToPosInText(const QString& s, int posOnScreen, int tabSize); int convertToPosOnScreen(const QString& s, int posInText, int tabSize); enum e_CoordType { eFileCoords, eD3LLineCoords, eWrapCoords }; void calcTokenPos(const QString&, int posOnScreen, int& pos1, int& pos2, int tabSize); QString calcHistorySortKey(const QString& keyOrder, QRegExp& matchedRegExpr, const QStringList& parenthesesGroupList); bool findParenthesesGroups(const QString& s, QStringList& sl); #endif