diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,12 @@ 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) @@ -213,6 +219,11 @@ 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 diff --git a/src/document/editorconfig.h b/src/document/editorconfig.h new file mode 100644 --- /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/editorconfig.cpp b/src/document/editorconfig.cpp new file mode 100644 --- /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/katedocument.cpp b/src/document/katedocument.cpp --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -51,6 +51,10 @@ #include "kateabstractinputmode.h" #include "katetemplatehandler.h" +#ifdef EDITORCONFIG_FOUND +#include "editorconfig.h" +#endif + #include #include @@ -2545,7 +2549,7 @@ } /** - * search .kateconfig upwards + * first search .kateconfig upwards * with recursion guard */ QSet seenDirectories; @@ -2571,7 +2575,7 @@ linesRead++; } - break; + return; } /** @@ -2581,6 +2585,14 @@ 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)