diff --git a/bin/assets/plugins/linters.json b/bin/assets/plugins/linters.json index 65117a76b..e08a72e98 100644 --- a/bin/assets/plugins/linters.json +++ b/bin/assets/plugins/linters.json @@ -120,6 +120,14 @@ "warning_pattern_order": { "line": 1, "col": 2, "message": 4, "type": 3 }, "command": "hlint --color=never -j $FILENAME", "url": "https://github.com/ndmitchell/hlint" + }, + { + "language": "xml", + "file_patterns": ["%.xml$", "%.svg$"], + "warning_pattern": "", + "command": "xml", + "type": "native", + "url": "#native" } ] } diff --git a/src/tools/ecode/featureshealth.cpp b/src/tools/ecode/featureshealth.cpp index 8c31e121f..d7fc2b2a4 100644 --- a/src/tools/ecode/featureshealth.cpp +++ b/src/tools/ecode/featureshealth.cpp @@ -66,16 +66,16 @@ std::vector FeaturesHealth::getHealth( PluginManager if ( linter ) { Linter found = linter->getLinterForLang( def.getLSPName() ); if ( !found.command.empty() ) { - lang.linter.name = String::split( found.command, ' ' )[0]; + lang.linter.name = + found.isNative ? "native" : String::split( found.command, ' ' )[0]; lang.linter.path = Sys::which( lang.linter.name ); - lang.linter.found = !lang.linter.path.empty(); + lang.linter.found = !lang.linter.path.empty() || found.isNative; lang.linter.url = found.url; } } if ( formatter ) { - FormatterPlugin::Formatter found = - formatter->getFormatterForLang( def.getLSPName() ); + FormatterPlugin::Formatter found = formatter->getFormatterForLang( def.getLSPName() ); if ( !found.command.empty() ) { lang.formatter.name = found.type == FormatterPlugin::FormatterType::Native ? "native" @@ -88,8 +88,7 @@ std::vector FeaturesHealth::getHealth( PluginManager } if ( lsp ) { - LSPDefinition found = - lsp->getClientManager().getLSPForLang( def.getLSPName() ); + LSPDefinition found = lsp->getClientManager().getLSPForLang( def.getLSPName() ); if ( !found.command.empty() ) { lang.lsp.name = found.name; lang.lsp.url = found.url; diff --git a/src/tools/ecode/plugins/formatter/formatterplugin.hpp b/src/tools/ecode/plugins/formatter/formatterplugin.hpp index d46774367..e6f22c253 100644 --- a/src/tools/ecode/plugins/formatter/formatterplugin.hpp +++ b/src/tools/ecode/plugins/formatter/formatterplugin.hpp @@ -34,7 +34,7 @@ class FormatterPlugin : public Plugin { static PluginDefinition Definition() { return { "autoformatter", "Auto Formatter", "Enables the code formatter/prettifier plugin.", - FormatterPlugin::New, { 0, 2, 3 }, FormatterPlugin::NewSync }; + FormatterPlugin::New, { 0, 2, 4 }, FormatterPlugin::NewSync }; } static Plugin* New( PluginManager* pluginManager ); @@ -78,7 +78,7 @@ class FormatterPlugin : public Plugin { std::unordered_map mEditorDocs; std::mutex mWorkMutex; std::condition_variable mWorkerCondition; - std::map> + std::unordered_map> mNativeFormatters; Int32 mWorkersCount{ 0 }; std::map mKeyBindings; /* cmd, shortcut */ diff --git a/src/tools/ecode/plugins/linter/linterplugin.cpp b/src/tools/ecode/plugins/linter/linterplugin.cpp index a1a8cce24..7c4e504b5 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.cpp +++ b/src/tools/ecode/plugins/linter/linterplugin.cpp @@ -13,6 +13,8 @@ #include #include #include +#define PUGIXML_HEADER_ONLY +#include using json = nlohmann::json; @@ -235,6 +237,20 @@ void LinterPlugin::loadLinterConfig( const std::string& path, bool updateConfigF linter.command = obj["command"].get(); linter.url = obj.value( "url", "" ); + if ( obj.contains( "type" ) ) { + std::string typeStr( obj["type"].get() ); + String::toLowerInPlace( typeStr ); + String::trimInPlace( typeStr ); + if ( "native" == typeStr ) { + linter.isNative = true; + if ( mNativeLinters.find( linter.command ) == mNativeLinters.end() ) { + Log::error( "Requested native linter: '%s' does not exists.", + linter.command.c_str() ); + continue; + } + } + } + if ( obj.contains( "expected_exitcodes" ) ) { auto ee = obj["expected_exitcodes"]; if ( ee.is_array() ) { @@ -561,6 +577,8 @@ void LinterPlugin::load( PluginManager* pluginManager ) { [this]( const auto& notification ) -> PluginRequestHandle { return processMessage( notification ); } ); + registerNativeLinters(); + std::vector paths; std::string path( pluginManager->getResourcesPath() + "plugins/linters.json" ); if ( FileSystem::fileExists( path ) ) @@ -797,6 +815,10 @@ void LinterPlugin::runLinter( std::shared_ptr doc, const Linter& l Clock clock; std::string cmd( linter.command ); String::replaceAll( cmd, "$FILENAME", "\"" + path + "\"" ); + if ( linter.isNative && mNativeLinters.find( cmd ) != mNativeLinters.end() ) { + mNativeLinters[cmd]( doc, path ); + return; + } Process process; TextDocument* docPtr = doc.get(); ScopedOp op( @@ -829,7 +851,8 @@ void LinterPlugin::runLinter( std::shared_ptr doc, const Linter& l if ( linter.hasNoErrorsExitCode && linter.noErrorsExitCode == returnCode ) { Lock matchesLock( mMatchesMutex ); - mMatches[doc.get()] = {}; + std::map> empty; + setMatches( doc.get(), MatchOrigin::Linter, empty ); return; } @@ -1318,4 +1341,56 @@ bool LinterPlugin::onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu, return false; } +void LinterPlugin::registerNativeLinter( + const std::string& cmd, + const std::function, const std::string& )>& nativeLinter ) { + mNativeLinters[cmd] = nativeLinter; +} + +void LinterPlugin::unregisterNativeLinter( const std::string& cmd ) { + mNativeLinters.erase( cmd ); +} + +static size_t countLines( const std::string_view& text ) { + const char* startPtr = text.data(); + const char* endPtr = text.data() + text.size(); + size_t count = 0; + if ( startPtr != endPtr ) { + count = 1 + *startPtr == '\n' ? 1 : 0; + while ( ++startPtr && startPtr != endPtr ) + count += ( '\n' == *startPtr ) ? 1 : 0; + } + return count; +} + +void LinterPlugin::registerNativeLinters() { + if ( !mNativeLinters.empty() ) + return; + mNativeLinters["xml"] = [this]( std::shared_ptr doc, const std::string& path ) { + pugi::xml_document xmlDoc; + pugi::xml_parse_result result = xmlDoc.load_file( path.c_str() ); + std::map> matches; + if ( !result ) { + std::string file; + FileSystem::fileGet( path, file ); + std::string_view filesv{ file }; + Int64 line = countLines( filesv.substr( 0, result.offset ) ); + Int64 offset = 0; + auto lastNL = filesv.substr( 0, result.offset ).find_last_of( '\n' ); + if ( lastNL != std::string_view::npos ) + offset = result.offset - lastNL; + LinterMatch match; + match.range = { { line, offset }, { line, offset } }; + match.range = { doc->nextWordBoundary( match.range.start(), false ), + doc->previousWordBoundary( match.range.start(), false ) }; + match.text = result.description(); + match.type = getLinterTypeFromSeverity( LSPDiagnosticSeverity::Error ); + match.lineCache = doc->line( match.range.start().line() ).getHash(); + match.origin = MatchOrigin::Linter; + matches[line] = { match }; + } + setMatches( doc.get(), MatchOrigin::Linter, matches ); + }; +} + } // namespace ecode diff --git a/src/tools/ecode/plugins/linter/linterplugin.hpp b/src/tools/ecode/plugins/linter/linterplugin.hpp index f6d0ec390..4cb6ebf7e 100644 --- a/src/tools/ecode/plugins/linter/linterplugin.hpp +++ b/src/tools/ecode/plugins/linter/linterplugin.hpp @@ -38,6 +38,7 @@ struct Linter { std::vector expectedExitCodes{}; int noErrorsExitCode{ 0 }; std::string url; + bool isNative{ false }; }; struct LinterMatch { @@ -46,7 +47,7 @@ struct LinterMatch { LinterType type{ LinterType::Error }; String::HashType lineCache{ 0 }; MatchOrigin origin{ MatchOrigin::Linter }; - std::map box; + std::unordered_map box; LSPDiagnostic diagnostic; }; @@ -58,7 +59,7 @@ class LinterPlugin : public Plugin { "Use static code analysis tool used to flag programming errors, bugs, " "stylistic errors, and suspicious constructs.", LinterPlugin::New, - { 0, 2, 3 }, + { 0, 2, 4 }, LinterPlugin::NewSync }; } @@ -111,6 +112,12 @@ class LinterPlugin : public Plugin { virtual bool onCreateContextMenu( UICodeEditor* editor, UIPopUpMenu* menu, const Vector2i& position, const Uint32& flags ); + void registerNativeLinter( const std::string& cmd, + const std::function doc, + const std::string& file )>& nativeLinter ); + + void unregisterNativeLinter( const std::string& cmd ); + protected: std::vector mLinters; std::unordered_map> mEditors; @@ -127,6 +134,9 @@ class LinterPlugin : public Plugin { std::map mKeyBindings; /* cmd, shortcut */ std::mutex mRunningProcessesMutex; std::unordered_map mRunningProcesses; + std::unordered_map doc, + const std::string& file )>> + mNativeLinters; bool mHoveringMatch{ false }; bool mEnableLSPDiagnostics{ true }; @@ -177,6 +187,8 @@ class LinterPlugin : public Plugin { void goToNextError( UICodeEditor* editor ); void goToPrevError( UICodeEditor* editor ); + + void registerNativeLinters(); }; } // namespace ecode