Files
eepp/src/tools/ecode/featureshealth.cpp
Martín Lucas Golini 16c2ac84f0 Display preview while selecting indent character.
Load health status async.
Fix a crash when navigating a tree without model.
2024-09-10 19:33:53 -03:00

477 lines
16 KiB
C++

#include "featureshealth.hpp"
#include "plugins/formatter/formatterplugin.hpp"
#include "plugins/linter/linterplugin.hpp"
#include "plugins/lsp/lspclientplugin.hpp"
#include "plugins/pluginmanager.hpp"
#include <eepp/system/sys.hpp>
#include <eepp/ui/doc/syntaxdefinitionmanager.hpp>
#include <eepp/ui/uiiconthememanager.hpp>
#include <eepp/ui/uiscenenode.hpp>
#include <eepp/ui/uitableview.hpp>
#include <tabulate/tabulate.hpp>
using namespace EE::System;
using namespace EE::UI::Doc;
using namespace EE::UI;
using namespace tabulate;
namespace ecode {
inline static bool sortKey( const FeaturesHealth::LangHealth& struct1,
const FeaturesHealth::LangHealth& struct2 ) {
return ( struct1.lang < struct2.lang );
}
std::vector<FeaturesHealth::LangHealth> FeaturesHealth::getHealth( PluginManager* pluginManager,
const std::string& langFilter ) {
std::vector<FeaturesHealth::LangHealth> langs;
bool ownsLinter = false;
bool ownsFormatter = false;
bool ownsLSP = false;
const auto& definitions = SyntaxDefinitionManager::instance()->getDefinitions();
LinterPlugin* linter = static_cast<LinterPlugin*>( pluginManager->get( "linter" ) );
FormatterPlugin* formatter =
static_cast<FormatterPlugin*>( pluginManager->get( "autoformatter" ) );
LSPClientPlugin* lsp = static_cast<LSPClientPlugin*>( pluginManager->get( "lspclient" ) );
if ( !linter ) {
ownsLinter = true;
linter = static_cast<LinterPlugin*>( LinterPlugin::NewSync( pluginManager ) );
}
if ( !formatter ) {
ownsFormatter = true;
formatter = static_cast<FormatterPlugin*>( FormatterPlugin::NewSync( pluginManager ) );
}
if ( !lsp ) {
ownsLSP = true;
lsp = static_cast<LSPClientPlugin*>( LSPClientPlugin::NewSync( pluginManager ) );
}
for ( const auto& def : definitions ) {
if ( !def.isVisible() )
continue;
if ( !langFilter.empty() && langFilter != def.getLSPName() )
continue;
FeaturesHealth::LangHealth lang;
lang.lang = def.getLSPName();
lang.syntaxHighlighting = true;
if ( linter ) {
Linter found = linter->getLinterForLang( def.getLSPName() );
if ( !found.command.empty() ) {
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() || found.isNative;
lang.linter.url = found.url;
}
}
if ( formatter ) {
FormatterPlugin::Formatter found = formatter->getFormatterForLang( def.getLSPName() );
if ( !found.command.empty() ) {
lang.formatter.name = found.type == FormatterPlugin::FormatterType::Native
? "native"
: String::split( found.command, ' ' )[0];
lang.formatter.path = Sys::which( lang.formatter.name );
lang.formatter.found = !lang.formatter.path.empty() ||
found.type == FormatterPlugin::FormatterType::Native;
lang.formatter.url = found.url;
}
}
if ( lsp ) {
LSPDefinition found = lsp->getClientManager().getLSPForLang( def.getLSPName() );
if ( !found.command.empty() ) {
lang.lsp.name = found.name;
lang.lsp.url = found.url;
lang.lsp.path = Sys::which( String::split( found.command, ' ' )[0] );
lang.lsp.found = !lang.lsp.path.empty();
}
}
langs.emplace_back( std::move( lang ) );
}
if ( ownsLinter )
eeSAFE_DELETE( linter );
if ( ownsFormatter )
eeSAFE_DELETE( formatter );
if ( ownsLSP )
eeSAFE_DELETE( lsp );
std::sort( langs.begin(), langs.end(), sortKey );
return langs;
}
std::string FeaturesHealth::generateHealthStatus( PluginManager* pluginManager,
OutputFormat format ) {
auto status( getHealth( pluginManager ) );
Table table;
table.format().border_top( "" ).border_bottom( "" ).border_left( "" ).border_right( "" ).corner(
"" );
size_t numRows = 5;
table.add_row( { "Language", "Highlight", "LSP", "Linter", "Formatter" } );
for ( size_t i = 0; i < numRows; ++i ) {
table[0][i]
.format()
.font_color( tabulate::Color::white )
.font_align( FontAlign::center )
.font_style( { FontStyle::bold } );
}
#if EE_PLATFORM == EE_PLATFORM_WIN
std::string check = "Yes";
#else
std::string check = "";
#endif
for ( const auto& ht : status ) {
std::string lspName = ht.lsp.name.empty() ? "None" : ht.lsp.name;
if ( OutputFormat::Markdown == format && !ht.lsp.name.empty() && !ht.lsp.url.empty() ) {
lspName = "[" + ht.lsp.name + "](" + ht.lsp.url + ")";
}
std::string linterName = ht.linter.name.empty() ? "None" : ht.linter.name;
if ( OutputFormat::Markdown == format && !ht.linter.name.empty() &&
!ht.linter.url.empty() ) {
linterName = "[" + ht.linter.name + "](" + ht.linter.url + ")";
}
std::string formatterName = ht.formatter.name.empty() ? "None" : ht.formatter.name;
if ( OutputFormat::Markdown == format && !ht.formatter.name.empty() &&
!ht.formatter.url.empty() ) {
formatterName = "[" + ht.formatter.name + "](" + ht.formatter.url + ")";
}
table.add_row( { ht.lang, check, lspName, linterName, formatterName } );
auto& row = table[table.size() - 1];
row[1].format().font_color( tabulate::Color::green );
if ( !ht.lsp.name.empty() ) {
row[2].format().font_color( ht.lsp.found ? tabulate::Color::green
: tabulate::Color::red );
} else {
row[2].format().font_color( tabulate::Color::yellow );
}
if ( !ht.linter.name.empty() ) {
row[3].format().font_color( ht.linter.found ? tabulate::Color::green
: tabulate::Color::red );
} else {
row[3].format().font_color( tabulate::Color::yellow );
}
if ( !ht.formatter.name.empty() ) {
row[4].format().font_color( ht.formatter.found ? tabulate::Color::green
: tabulate::Color::red );
} else {
row[4].format().font_color( tabulate::Color::yellow );
}
}
if ( OutputFormat::Markdown == format ) {
MarkdownExporter exporter;
return exporter.dump( table );
} else if ( OutputFormat::AsciiDoc == format ) {
AsciiDocExporter exporter;
return exporter.dump( table );
} else if ( OutputFormat::Terminal == format ) {
std::cout << table << "\n";
return "";
}
return table.str();
}
void FeaturesHealth::doHealth( PluginManager* pluginManager, const std::string& lang,
const OutputFormat& format ) {
if ( lang.empty() ) {
if ( format != FeaturesHealth::OutputFormat::Terminal ) {
std::cout << FeaturesHealth::generateHealthStatus( pluginManager, format ) << "\n";
} else {
FeaturesHealth::generateHealthStatus( pluginManager, format );
}
} else {
auto healthRes = FeaturesHealth::getHealth( pluginManager, lang );
if ( healthRes.empty() )
return;
auto& hr = healthRes[0];
const std::string notFound = "Not found in $PATH";
std::string lspName = hr.lsp.name.empty() ? "\033[33mNone" : "\033[32m" + hr.lsp.name;
std::string lspBinary = hr.lsp.found ? "\033[32m" + hr.lsp.path : "\033[31m" + notFound;
std::cout << "Highlight: \033[32mFound\n\033[00m";
std::cout << "Configured language server: " << lspName << "\n\033[00m";
if ( !hr.lsp.name.empty() )
std::cout << "Binary for language server: " << lspBinary << "\n\033[00m";
std::string linterName =
hr.linter.name.empty() ? "\033[33mNone" : "\033[32m" + hr.linter.name;
std::string linterBinary =
hr.linter.found ? "\033[32m" + hr.linter.path : "\033[31m" + notFound;
std::cout << "Configured linter: " << linterName << "\n\033[00m";
if ( !hr.linter.name.empty() )
std::cout << "Binary for linter: " << linterBinary << "\n\033[00m";
std::string formatterName =
hr.formatter.name.empty() ? "\033[33mNone" : "\033[32m" + hr.formatter.name;
std::string formatterBinary =
hr.formatter.found ? "\033[32m" + hr.formatter.path : "\033[31m" + notFound;
std::cout << "Configured formatter: " << formatterName << "\n\033[00m";
if ( !hr.formatter.name.empty() )
std::cout << "Binary for formatter: " << formatterBinary << "\n\033[00m";
}
}
class HealthModel : public Model {
public:
static std::shared_ptr<HealthModel> create( std::vector<FeaturesHealth::LangHealth>&& data,
UISceneNode* sceneNode ) {
return std::make_shared<HealthModel>( std::move( data ), sceneNode );
}
HealthModel( std::vector<FeaturesHealth::LangHealth>&& data, UISceneNode* sceneNode ) :
mData( data ), mSceneNode( sceneNode ) {}
virtual size_t rowCount( const ModelIndex& ) const { return mData.size(); }
virtual size_t columnCount( const ModelIndex& ) const { return 5; }
virtual std::string columnName( const size_t& idx ) const {
static const std::vector<std::string> columns = { "language", "highlight", "LSP", "linter",
"formatter" };
if ( idx < columns.size() )
return mSceneNode->i18n( columns[idx], String::capitalize( columns[idx] ) );
return "";
}
virtual Variant data( const ModelIndex& index, ModelRole role = ModelRole::Display ) const {
static const char* HEALTH_SUCCESS = "theme-success";
static const char* HEALTH_ERROR = "theme-error";
static const char* HEALTH_NONE = "theme-none";
eeASSERT( index.row() < (Int64)mData.size() );
static std::string none;
static UIIcon* icon = nullptr;
if ( none.empty() )
none = mSceneNode->i18n( "none", "None" );
if ( icon == nullptr )
icon = mSceneNode->findIcon( "ok" );
if ( index.row() >= (Int64)mData.size() )
return {};
const FeaturesHealth::LangHealth& lang = mData[index.row()];
switch ( role ) {
case ModelRole::Icon: {
if ( index.column() == 1 )
return Variant( icon );
break;
}
case ModelRole::Display: {
switch ( index.column() ) {
case 0:
return Variant( lang.lang );
case 1:
break;
case 2:
return Variant( lang.lsp.name.empty() ? none : lang.lsp.name );
case 3:
return Variant( lang.linter.name.empty() ? none : lang.linter.name );
case 4:
return Variant( lang.formatter.name.empty() ? none : lang.formatter.name );
default: {
}
}
break;
}
case ModelRole::Class: {
switch ( index.column() ) {
case 1:
return Variant( HEALTH_SUCCESS );
case 2:
if ( !lang.lsp.name.empty() )
return Variant( lang.lsp.found ? HEALTH_SUCCESS : HEALTH_ERROR );
else
return Variant( HEALTH_NONE );
break;
case 3:
if ( !lang.linter.name.empty() )
return Variant( lang.linter.found ? HEALTH_SUCCESS : HEALTH_ERROR );
else
return Variant( HEALTH_NONE );
break;
case 4:
if ( !lang.formatter.name.empty() )
return Variant( lang.formatter.found ? HEALTH_SUCCESS : HEALTH_ERROR );
else
return Variant( HEALTH_NONE );
break;
default: {
}
}
}
default: {
}
}
return {};
}
virtual bool classModelRoleEnabled() { return true; }
const FeaturesHealth::LangHealth& getHealthRow( size_t idx ) {
eeASSERT( idx < mData.size() );
return mData[idx];
}
protected:
std::vector<FeaturesHealth::LangHealth> mData;
UISceneNode* mSceneNode{ nullptr };
};
#define I18N( key, val ) sceneNode->i18n( key, val ).toUtf8().c_str()
void FeaturesHealth::displayHealth( PluginManager* pluginManager, UISceneNode* sceneNode ) {
UIWindow* win = sceneNode
->loadLayoutFromString( R"xml(
<window
id="health-window"
lw="600dp" lh="600dp"
padding="8dp"
window-title="@string(languages_health, Languages Health)"
window-flags="default|maximize|shadow"
window-min-size="300dp 300dp">
<RelativeLayout lw="mp" lh="mp">
<vbox id="health_container" lw="mp" lh="mp" visible="false">
<TableView id="health_table" lw="mp" lh="0" lw8="1" />
<vbox id="health_lang_info" lw="mp" lh="wc" min-height="118dp" visible="false"></vbox>
</vbox>
<Loader id="health_loader" lw="64dp" lh="64dp" outline-thickness="6dp" lg="center" visible="true" />
</RelativeLayout>
</window>
)xml" )
->asType<UIWindow>();
auto healthLoader = win->find( "health_loader" );
auto healthContainer = win->find( "health_container" );
UITableView* table = win->find<UITableView>( "health_table" );
table->setAutoColumnsWidth( true );
table->setFitAllColumnsToWidget( true );
const auto loadModel = [table, sceneNode, pluginManager, healthLoader, healthContainer]() {
auto health = FeaturesHealth::getHealth( pluginManager );
auto model = HealthModel::create( std::move( health ), sceneNode );
table->setModel( model );
healthLoader->runOnMainThread( [healthLoader] { healthLoader->setVisible( false ); } );
healthContainer->runOnMainThread(
[healthContainer] { healthContainer->setVisible( true ); } );
};
if ( sceneNode->hasThreadPool() ) {
sceneNode->getThreadPool()->run( [loadModel] { loadModel(); } );
} else {
loadModel();
}
auto healthLangInfo = win->find( "health_lang_info" );
table->setOnSelectionChange( [table, healthLangInfo, sceneNode]() {
if ( table->getSelection().isEmpty() || nullptr == table->getModel() ) {
healthLangInfo->setVisible( false );
return;
}
ModelIndex index = table->getSelection().first();
static const std::string none = sceneNode->i18n( "none", "None" );
static const std::string notFound =
sceneNode->i18n( "not_found_in_path", "Not found in $PATH" );
static const std::string patherr =
String::format( "%s: %s", I18N( "path_is", "PATH is" ), std::getenv( "PATH" ) );
HealthModel* model = static_cast<HealthModel*>( table->getModel() );
const auto& lang = model->getHealthRow( index.row() );
healthLangInfo->childsCloseAll();
std::string type =
lang.lsp.url.empty() ? "TextView" : String::format( "Anchor href='%s'", lang.lsp.url );
std::string l = String::format(
R"xml(
<hbox><TextView text='%s: ' /><TextView text='%s' font-style="bold" /></hbox>
<hbox><TextView text='%s: ' /><TextView text='%s' class="success" /></hbox>
<hbox><TextView text='%s: ' /><%s text='%s' class='%s' /></hbox>
)xml",
I18N( "language", "Language" ), lang.lang, I18N( "highlight", "Highlight" ),
I18N( "found", "Found" ),
I18N( "configured_language_server", "Configured language server" ), type,
lang.lsp.name.empty() ? none : lang.lsp.name,
lang.lsp.found ? "success" : ( lang.lsp.name.empty() ? "none" : "error" ) );
if ( !lang.lsp.name.empty() ) {
l += String::format( "<hbox><TextView text='%s: ' /><TextView text='%s' class='%s' "
"tooltip='%s' /></hbox>",
I18N( "binary_for_language_server", "Binary for language server" ),
lang.lsp.path.empty() ? notFound : lang.lsp.path,
!lang.lsp.path.empty() ? "success" : "error",
lang.lsp.path.empty() ? patherr : "" );
}
type = lang.linter.url.empty() ? "TextView"
: String::format( "Anchor href='%s'", lang.linter.url );
l += String::format( "<hbox><TextView text='%s: ' /><%s text='%s' class='%s' /></hbox>",
I18N( "configured_linter", "Configured linter" ), type,
lang.linter.name.empty() ? none : lang.linter.name,
lang.linter.found ? "success"
: ( lang.linter.name.empty() ? "none" : "error" ) );
if ( !lang.linter.name.empty() ) {
l += String::format( "<hbox><TextView text='%s: ' /><TextView text='%s' class='%s' "
"tooltip='%s' /></hbox>",
I18N( "binary_for_linter", "Binary for linter" ),
lang.linter.path.empty() ? notFound : lang.linter.path,
!lang.linter.path.empty() ? "success" : "error",
lang.linter.path.empty() ? patherr : "" );
}
type = lang.formatter.url.empty()
? "TextView"
: String::format( "Anchor href='%s'", lang.formatter.url );
l += String::format(
"<hbox><TextView text='%s: ' /><%s text='%s' class='%s' /></hbox>",
I18N( "configured_formatter", "Configured formatter" ), type,
lang.formatter.name.empty() ? none : lang.formatter.name,
lang.formatter.found ? "success" : ( lang.formatter.name.empty() ? "none" : "error" ) );
if ( !lang.formatter.name.empty() ) {
l += String::format( "<hbox><TextView text='%s: ' /><TextView text='%s' class='%s' "
"tooltip='%s' /></hbox>",
I18N( "binary_for_formatter", "Binary for formatter" ),
lang.formatter.path.empty() ? notFound : lang.formatter.path,
!lang.formatter.path.empty() ? "success" : "error",
lang.formatter.path.empty() ? patherr : "" );
}
sceneNode->loadLayoutFromString( l, healthLangInfo );
healthLangInfo->setVisible( true );
} );
win->setKeyBindingCommand( "close-window", [win]() { win->closeWindow(); } );
win->addKeyBinding( { KEY_ESCAPE }, "close-window" );
win->showWhenReady();
win->center();
}
} // namespace ecode