#include "formattermodule.hpp" #include "thirdparty/json.hpp" #include "thirdparty/subprocess.h" #include #include #include using json = nlohmann::json; #if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN || defined( __EMSCRIPTEN_PTHREADS__ ) #define FORMATTER_THREADED 1 #else #define FORMATTER_THREADED 0 #endif FormatterModule::FormatterModule( const std::string& formattersPath, std::shared_ptr pool ) : mPool( pool ) { #if FORMATTER_THREADED mPool->run( [&, formattersPath] { load( formattersPath ); }, [] {} ); #else load( formattersPath ); #endif } FormatterModule::~FormatterModule() { mClosing = true; for ( auto editor : mEditors ) editor->unregisterModule( this ); } void FormatterModule::onRegister( UICodeEditor* editor ) { mEditors.insert( editor ); auto& doc = editor->getDocument(); if ( doc.hasCommand( "format-doc" ) ) return; doc.setCommand( "format-doc", [&, editor]() { formatDoc( editor ); } ); editor->addEventListener( Event::OnDocumentSave, [&]( const Event* event ) { if ( mAutoFormatOnSave && event->getNode()->isType( UI_TYPE_CODEEDITOR ) ) formatDoc( event->getNode()->asType() ); } ); } void FormatterModule::onUnregister( UICodeEditor* editor ) { if ( mClosing ) return; mEditors.erase( editor ); } bool FormatterModule::getAutoFormatOnSave() const { return mAutoFormatOnSave; } void FormatterModule::setAutoFormatOnSave( bool autoFormatOnSave ) { mAutoFormatOnSave = autoFormatOnSave; } void FormatterModule::load( const std::string& formatterPath ) { if ( !FileSystem::fileExists( formatterPath ) ) return; try { std::ifstream stream( formatterPath ); json j; stream >> j; for ( auto& obj : j ) { Formatter formatter; auto fp = obj["file_patterns"]; for ( auto& pattern : fp ) formatter.files.push_back( pattern.get() ); formatter.command = obj["command"].get(); if ( obj.contains( "type" ) ) { std::string typeStr( obj["type"].get() ); String::toLowerInPlace( typeStr ); String::trimInPlace( typeStr ); formatter.type = "inplace" == typeStr ? FormatterType::Inplace : FormatterType::Output; } mFormatters.emplace_back( std::move( formatter ) ); } mReady = true; } catch ( json::exception& e ) { mReady = false; Log::error( "Parsing formatter failed:\n%s", e.what() ); } } void FormatterModule::formatDoc( UICodeEditor* editor ) { if ( !mReady ) return; Clock clock; std::shared_ptr doc = editor->getDocumentRef(); auto formatter = supportsFormatter( doc ); if ( formatter.command.empty() || doc->getFilePath().empty() ) return; IOStreamString fileString; std::string path; if ( doc->isDirty() || !doc->hasFilepath() || formatter.type == FormatterType::Inplace ) { std::string tmpPath; if ( !doc->hasFilepath() ) { tmpPath = Sys::getTempPath() + ".ecode-" + doc->getFilename() + "." + String::randString( 8 ); } else { std::string fileDir( FileSystem::fileRemoveFileName( doc->getFilePath() ) ); FileSystem::dirAddSlashAtEnd( fileDir ); tmpPath = fileDir + "." + String::randString( 8 ) + "." + doc->getFilename(); } doc->save( fileString, true ); FileSystem::fileWrite( tmpPath, (Uint8*)fileString.getStreamPointer(), fileString.getSize() ); runFormatter( editor, formatter, tmpPath ); if ( formatter.type == FormatterType::Inplace ) { std::string data; FileSystem::fileGet( tmpPath, data ); editor->runOnMainThread( [&, data, editor]() { std::shared_ptr doc = editor->getDocumentRef(); TextPosition pos = doc->getSelection().start(); doc->selectAll(); doc->textInput( data ); doc->setSelection( pos ); } ); } FileSystem::fileRemove( tmpPath ); path = tmpPath; } else { runFormatter( editor, formatter, doc->getFilePath() ); path = doc->getFilePath(); } Log::info( "FormatterModule::formatDoc for %s took %.2fms", path.c_str(), clock.getElapsedTime().asMilliseconds() ); } void FormatterModule::runFormatter( UICodeEditor* editor, const Formatter& formatter, const std::string& path ) { std::string cmd( formatter.command ); String::replaceAll( cmd, "$FILENAME", path ); std::vector cmdArr = String::split( cmd, ' ' ); std::vector strings; for ( size_t i = 0; i < cmdArr.size(); ++i ) strings.push_back( cmdArr[i].c_str() ); strings.push_back( NULL ); struct subprocess_s subprocess; int result = subprocess_create( strings.data(), subprocess_option_inherit_environment, &subprocess ); if ( 0 == result ) { std::string buffer( 1024, '\0' ); std::string data; unsigned bytesRead = 0; do { bytesRead = subprocess_read_stdout( &subprocess, &buffer[0], buffer.size() ); data += buffer.substr( 0, bytesRead ); } while ( bytesRead != 0 ); int ret; subprocess_join( &subprocess, &ret ); subprocess_destroy( &subprocess ); // Log::info( "Formatter result:\n%s", data.c_str() ); if ( formatter.type == FormatterType::Output ) { editor->runOnMainThread( [&, data, editor]() { std::shared_ptr doc = editor->getDocumentRef(); TextPosition pos = doc->getSelection().start(); doc->selectAll(); doc->textInput( data ); doc->setSelection( pos ); } ); } } } FormatterModule::Formatter FormatterModule::supportsFormatter( std::shared_ptr doc ) { std::string filePath( doc->getFilePath() ); std::string extension( FileSystem::fileExtension( filePath ) ); if ( extension.empty() ) extension = FileSystem::fileNameFromPath( filePath ); const auto& def = doc->getSyntaxDefinition(); for ( auto& formatter : mFormatters ) { for ( auto& ext : formatter.files ) { auto& files = def.getFiles(); if ( std::find( files.begin(), files.end(), ext ) != files.end() ) { return formatter; } } } return {}; }