diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,9 @@ # libgit2 integration, at least 0.22 with proper git_libgit2_init() find_package(LibGit2 "0.22.0") +# EditorConfig support (0.12.0 was released 2014-10-12) +find_package(editorconfig "0.12.0") + # vi mode on per default option (BUILD_VIMODE "Build vimode in" ON) 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 --- a/src/document/editorconfig.h +++ b/src/document/editorconfig.h @@ -0,0 +1,61 @@ +/* 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(); + int parse(); + +private: + /** + * @return whether 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 whether a string value could be converted to a integer value. The + * value is put in *result. + */ + static bool checkIntValue(QString value, int *result); + + KTextEditor::DocumentPrivate *m_document; + editorconfig_handle m_handle; +}; + +#endif diff --git a/src/document/editorconfig.cpp b/src/document/editorconfig.cpp --- a/src/document/editorconfig.cpp +++ b/src/document/editorconfig.cpp @@ -0,0 +1,163 @@ +/* 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" + +EditorConfig::EditorConfig(KTextEditor::DocumentPrivate *document) + : m_document(document) + , m_handle(editorconfig_handle_init()) +{ +} + +EditorConfig::~EditorConfig() +{ + editorconfig_handle_destroy(m_handle); +} + +bool EditorConfig::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 EditorConfig::checkIntValue(QString val, int *result) +{ + bool ret(false); + *result = val.toInt(&ret); + return ret; +} + +// implemented options: charset, end_of_line, indent_size, indent_style, +// insert_final_newline, max_line_length, tab_width, trim_trailing_whitespace +// see also: +// https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties +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 { + 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); + + // raw values from EditorConfig + const char *key, *value; + key = nullptr; + value = nullptr; + + // their Qt counterparts, for comparisons + QLatin1String keyString, valueString; + + // buffers for integer/boolean values + int intValue; + bool boolValue; + + // 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) { + // fetch next key-value pair + editorconfig_handle_get_name_value(m_handle, i, &key, &value); + + keyString = QLatin1String(key); + valueString = QLatin1String(value); + + if (QLatin1String("charset") == keyString) { + m_document->setEncoding(valueString); + } else if (QLatin1String("end_of_line") == keyString) { + QStringList eols; + + // NOTE: EOLs are declared in Kate::TextBuffer::EndOfLineMode + eols << QLatin1String("lf") << QLatin1String("crlf") << QLatin1String("cr"); + + if ((intValue = eols.indexOf(valueString)) != -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") == keyString) { + if (QLatin1String("tab") == valueString) { + setIndentSizeAsTabWidth = true; + } else if (checkIntValue(valueString, &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") == keyString) { + if (QLatin1String("tab") == valueString) { + m_document->config()->setReplaceTabsDyn(false); + } else if (QLatin1String("space") == valueString) { + m_document->config()->setReplaceTabsDyn(true); + } else { + qCDebug(LOG_KTE) << "Indent style in .editorconfig other than tab or space"; + } + } else if (QLatin1String("insert_final_newline") == keyString && checkBoolValue(valueString, &boolValue)) { + m_document->config()->setNewLineAtEof(boolValue); + } else if (QLatin1String("max_line_length") == keyString && checkIntValue(valueString, &intValue)) { + m_document->config()->setWordWrapAt(intValue); + } else if (QLatin1String("tab_width") == keyString && checkIntValue(valueString, &intValue)) { + m_document->config()->setTabWidth(intValue); + tabWidth = intValue; + tabWidthSet = true; + } else if (QLatin1String("trim_trailing_whitespace") == keyString && checkBoolValue(valueString, &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)