Files
eepp/src/tools/ecode/formattermodule.cpp
2022-04-01 00:38:10 -03:00

204 lines
5.8 KiB
C++

#include "formattermodule.hpp"
#include "thirdparty/json.hpp"
#include "thirdparty/subprocess.h"
#include <eepp/system/filesystem.hpp>
#include <eepp/system/iostreamstring.hpp>
#include <random>
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<ThreadPool> 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<UICodeEditor>() );
} );
}
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<std::string>() );
formatter.command = obj["command"].get<std::string>();
if ( obj.contains( "type" ) ) {
std::string typeStr( obj["type"].get<std::string>() );
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<TextDocument> 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<TextDocument> 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<std::string> cmdArr = String::split( cmd, ' ' );
std::vector<const char*> 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<TextDocument> doc = editor->getDocumentRef();
TextPosition pos = doc->getSelection().start();
doc->selectAll();
doc->textInput( data );
doc->setSelection( pos );
} );
}
}
}
FormatterModule::Formatter FormatterModule::supportsFormatter( std::shared_ptr<TextDocument> 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 {};
}