From 2df4e888361cfad42c293adfc300fc632a7e9290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 13 Jul 2025 19:22:31 -0300 Subject: [PATCH] Improvements when opening binary files from drag and drop and from the tree-view (SpartanJ/ecode#589). Fixes a bug that prevented signature help to not be displayed. --- include/eepp/ui/doc/textdocument.hpp | 6 +- src/eepp/ui/doc/textdocument.cpp | 28 ++++++ src/tools/ecode/ecode.cpp | 98 +++++++++++++++---- src/tools/ecode/ecode.hpp | 2 + src/tools/ecode/pathhelper.cpp | 27 +++++ src/tools/ecode/pathhelper.hpp | 8 ++ .../autocomplete/autocompleteplugin.cpp | 2 + src/tools/ecode/settingsmenu.cpp | 7 +- 8 files changed, 152 insertions(+), 26 deletions(-) create mode 100644 src/tools/ecode/pathhelper.cpp diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index cb9ea0285..25aa6e85f 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -37,9 +37,11 @@ struct DocumentContentChange { class EE_API TextDocument { public: - static bool isTextDocummentCommand( std::string_view cmd ); + static bool isTextDocummentCommand( std::string_view cmd ); - static bool isTextDocummentCommand( String::HashType cmdHash ); + static bool isTextDocummentCommand( String::HashType cmdHash ); + + static bool fileMightBeBinary( const std::string& file ); enum class UndoRedo { Undo, Redo }; diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 191009fbd..81806aed7 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -28,6 +28,34 @@ static constexpr char DEFAULT_NON_WORD_CHARS[] = " \t\n/\\()\"':,.;<>~!@#$%^&*|+ static UnorderedSet TEXT_DOCUMENT_COMMANDS = {}; +bool TextDocument::fileMightBeBinary( const std::string& file ) { + static constexpr std::array BINARY_STR = { 0, 0, 0, 0 }; + static constexpr size_t MAX_READ = 4096; + + IOStreamFile f( file ); + if ( !f.isOpen() ) { + // Handle file opening failure (e.g., return false or throw) + return false; + } + + std::array buffer; + size_t bytesToRead = std::min( MAX_READ, f.getSize() ); + if ( bytesToRead < BINARY_STR.size() ) { + // File is too small to contain the binary sequence + return false; + } + + size_t bytesRead = f.read( buffer.data(), bytesToRead ); + if ( bytesRead < BINARY_STR.size() ) { + // Not enough bytes read to contain the binary sequence + return false; + } + + auto res = std::search( buffer.begin(), buffer.begin() + bytesRead, BINARY_STR.begin(), + BINARY_STR.end() ); + return res != buffer.begin() + bytesRead; +} + bool TextDocument::isTextDocummentCommand( std::string_view cmd ) { return TEXT_DOCUMENT_COMMANDS.contains( String::hash( cmd ) ); } diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index c892897c5..dc306ccd9 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -2230,21 +2230,32 @@ void App::loadImageFromPath( const std::string& path ) { loadImageFromMedium( path, false ); } -static bool isVideoExtension( std::string_view ext ) { - static constexpr std::array videoExtensions = { - "mp4", "mov", "mkv", "avi", "wmv", "webm", "flv", "mpg", "mpeg", "m4v" }; - for ( const auto& videoExt : videoExtensions ) - if ( String::iequals( ext, videoExt ) ) - return true; - return false; -} - -static bool isDocumentExtension( std::string_view ext ) { - static constexpr std::array videoExtensions = { "doc", "docx", "pdf" }; - for ( const auto& videoExt : videoExtensions ) - if ( String::iequals( ext, videoExt ) ) - return true; - return false; +void App::openFileFromPath( const std::string& path ) { + std::string ext = FileSystem::fileExtension( path ); + bool canOpenBinaryFile = PathHelper::isOpenExternalExtension( ext ); + if ( !canOpenBinaryFile && TextDocument::fileMightBeBinary( path ) ) { + auto msgBox = UIMessageBox::New( + UIMessageBox::YES_NO, + i18n( "open_binary_file_warning", + "File looks like a binary file. Are you sure " + "you want to open it as a document?\nOtherwise it will try to open the " + "file with an external application." ) ); + msgBox->getButtonOK()->setText( i18n( "open_as_document", "Open as document" ) ); + msgBox->getButtonOK()->getIcon()->setVisible( false ); + msgBox->getButtonCancel()->setText( i18n( "open_externally", "Open externally" ) ); + msgBox->getButtonCancel()->getIcon()->setVisible( false ); + msgBox->on( Event::OnConfirm, [this, path]( auto ) { loadFileFromPath( path ); } ); + msgBox->on( Event::OnCancel, [path]( auto ) { + FileInfo f( path ); + if ( f.isExecutable() ) + Sys::execute( path ); + else + Engine::instance()->openURI( path ); + } ); + msgBox->showWhenReady(); + } else { + loadFileFromPath( path ); + } } bool App::loadFileFromPath( @@ -2263,7 +2274,7 @@ bool App::loadFileFromPath( return false; } - if ( isVideoExtension( ext ) || isDocumentExtension( ext ) ) { + if ( PathHelper::isOpenExternalExtension( ext ) ) { Engine::instance()->openURI( path ); } else if ( Image::isImageExtension( path ) && Image::isImage( path ) && ext != "svg" ) { loadImageFromPath( path ); @@ -3010,7 +3021,7 @@ void App::initProjectTreeViewUI() { if ( !tab ) { FileInfo fileInfo( path ); if ( fileInfo.exists() && fileInfo.isRegularFile() ) - loadFileFromPath( path ); + openFileFromPath( path ); } else { tab->getTabWidget()->setTabSelected( tab ); } @@ -3797,10 +3808,55 @@ void App::init( const LogLevel& logLevel, std::string file, const Float& pidelDe if ( onlyDirectories ) { loadFolder( mPathsToLoad[lastDirIdx] ); } else { - // Load only files even if there are directories - for ( const auto& file : mPathsToLoad ) { - if ( !FileSystem::isDirectory( file ) ) - onFileDropped( file ); + int mightBeBinaryCount = 0; + for ( const auto& file : mPathsToLoad ) + if ( !FileSystem::isDirectory( file ) && + TextDocument::fileMightBeBinary( file ) ) + mightBeBinaryCount++; + + if ( mightBeBinaryCount ) { + if ( mightBeBinaryCount == 1 && mPathsToLoad.size() == 1 ) { + FileInfo f( mPathsToLoad[0] ); + if ( f.isExecutable() ) + Sys::execute( mPathsToLoad[0] ); + else + Engine::instance()->openURI( mPathsToLoad[0] ); + } else { + UIMessageBox* msgBox = UIMessageBox::New( + UIMessageBox::OK_CANCEL, + i18n( "dropped_binary_files_open_warn", + "Some of the files seem to be binary.\nDo you want to " + "open them as document? Otherwise they will be ignored " + "for loading." ) ); + msgBox->getButtonOK()->setText( i18n( "open", "Open" ) ); + msgBox->getButtonOK()->getIcon()->setVisible( false ); + msgBox->getButtonCancel()->setText( i18n( "ignore", "Ignore" ) ); + msgBox->getButtonCancel()->getIcon()->setVisible( false ); + msgBox->on( Event::OnConfirm, + [this, pathsToLoad = mPathsToLoad]( const Event* ) { + for ( const auto& file : pathsToLoad ) { + if ( !FileSystem::isDirectory( file ) ) + onFileDropped( file ); + } + } ); + msgBox->on( Event::OnCancel, + [this, pathsToLoad = mPathsToLoad]( const Event* ) { + for ( const auto& file : pathsToLoad ) { + if ( !FileSystem::isDirectory( file ) && + !TextDocument::fileMightBeBinary( file ) ) + onFileDropped( file ); + } + } ); + msgBox->setTitle( i18n( "warning", "Warning" ) ); + msgBox->center(); + msgBox->showWhenReady(); + } + } else { + // Load only files even if there are directories + for ( const auto& file : mPathsToLoad ) { + if ( !FileSystem::isDirectory( file ) ) + onFileDropped( file ); + } } } diff --git a/src/tools/ecode/ecode.hpp b/src/tools/ecode/ecode.hpp index 659704b06..324f7c9b3 100644 --- a/src/tools/ecode/ecode.hpp +++ b/src/tools/ecode/ecode.hpp @@ -88,6 +88,8 @@ class App : public UICodeEditorSplitter::Client, public PluginContextProvider { std::shared_ptr getThreadPool() const; + void openFileFromPath( const std::string& path ); + bool loadFileFromPath( std::string path, bool inNewTab = true, UICodeEditor* codeEditor = nullptr, std::function onLoaded = diff --git a/src/tools/ecode/pathhelper.cpp b/src/tools/ecode/pathhelper.cpp new file mode 100644 index 000000000..739b5a690 --- /dev/null +++ b/src/tools/ecode/pathhelper.cpp @@ -0,0 +1,27 @@ +#include "pathhelper.hpp" + +namespace ecode { + +bool PathHelper::isVideoExtension( std::string_view ext ) { + static constexpr std::array extensions = { + "mp4", "mov", "mkv", "avi", "wmv", "webm", "flv", "mpg", "mpeg", "m4v" }; + for ( const auto& cext : extensions ) + if ( String::iequals( ext, cext ) ) + return true; + return false; +} + +bool PathHelper::isDocumentExtension( std::string_view ext ) { + static constexpr std::array extensions = { "doc", "docx", "pdf", "xls", + "xlsx" }; + for ( const auto& cext : extensions ) + if ( String::iequals( ext, cext ) ) + return true; + return false; +} + +bool PathHelper::isOpenExternalExtension( std::string_view ext ) { + return isVideoExtension( ext ) || isDocumentExtension( ext ); +} + +} // namespace ecode diff --git a/src/tools/ecode/pathhelper.hpp b/src/tools/ecode/pathhelper.hpp index fe71f19b1..342532f96 100644 --- a/src/tools/ecode/pathhelper.hpp +++ b/src/tools/ecode/pathhelper.hpp @@ -51,6 +51,14 @@ template static std::pair getPathAndPosition( cons return { path, { 0, 0 } }; } +struct PathHelper { + static bool isVideoExtension( std::string_view ext ); + + static bool isDocumentExtension( std::string_view ext ); + + static bool isOpenExternalExtension( std::string_view ext ); +}; + } // namespace ecode #endif // ECODE_PATHHELPER_HPP diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index 803e9c873..e661929dc 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -904,6 +904,8 @@ AutoCompletePlugin::processSignatureHelp( const LSPSignatureHelp& signatureHelp editor->runOnMainThread( [this, editor, signatures = std::move( signatures )] { mSignatureHelpVisible = true; mSignatureHelp = signatures; + if ( mSignatureHelpSelected >= static_cast( mSignatureHelp.signatures.size() ) ) + mSignatureHelpSelected = 0; if ( mSignatureHelp.signatures.empty() ) resetSignatureHelp(); editor->invalidateDraw(); diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index 2502708db..d543df25c 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -1,4 +1,5 @@ #include "settingsmenu.hpp" + #include namespace fs = std::filesystem; @@ -2354,7 +2355,7 @@ void SettingsMenu::createProjectTreeMenu( const FileInfo& file ) { } else if ( "new_folder" == id ) { mApp->newFolder( file ); } else if ( "open_file" == id ) { - mApp->loadFileFromPath( file.getFilepath() ); + mApp->openFileFromPath( file.getFilepath() ); } else if ( "remove" == id ) { deleteFileDialog( file ); } else if ( "duplicate_file" == id ) { @@ -2552,8 +2553,8 @@ UIMenu* SettingsMenu::createFontHintMenu() { const auto fontHintMenuRefresh = [this] { const auto& cfg = mApp->getConfig(); - auto el = - mFontHintMenu->find( "hint_" + FontTrueType::fontHintingToString( cfg.ui.fontHinting ) ); + auto el = mFontHintMenu->find( "hint_" + + FontTrueType::fontHintingToString( cfg.ui.fontHinting ) ); if ( el && el->isType( UI_TYPE_MENURADIOBUTTON ) ) el->asType()->setActive( true ); };