From 6871fd3b654d98cb01b17f404f5d096fd5ff82d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Mon, 3 Jul 2023 12:31:51 -0300 Subject: [PATCH] ecode: Fix in LSP Client, incorrectly unbinding commands from document. Emscripten improvements (clipboard). --- premake4.lua | 11 +- premake5.lua | 5 +- .../window/backend/SDL2/clipboardsdl2.cpp | 34 ++++- src/eepp/window/backend/SDL2/windowsdl2.cpp | 4 +- .../emscripten-browser-clipboard/LICENSE | 21 +++ .../emscripten-browser-clipboard/README.md | 138 ++++++++++++++++++ .../emscripten_browser_clipboard.h | 99 +++++++++++++ src/tools/ecode/ecode.cpp | 9 +- .../ecode/plugins/lsp/lspclientplugin.cpp | 11 +- 9 files changed, 313 insertions(+), 19 deletions(-) create mode 100644 src/thirdparty/emscripten-browser-clipboard/LICENSE create mode 100644 src/thirdparty/emscripten-browser-clipboard/README.md create mode 100644 src/thirdparty/emscripten-browser-clipboard/emscripten_browser_clipboard.h diff --git a/premake4.lua b/premake4.lua index e7705de59..62fcb6f31 100644 --- a/premake4.lua +++ b/premake4.lua @@ -591,7 +591,11 @@ function build_link_configuration( package_name, use_ee_icon ) end if os.is_real("emscripten") then - linkoptions{ "--profiling --profiling-funcs -s DEMANGLE_SUPPORT=1 -s NO_DISABLE_EXCEPTION_CATCHING" } + linkoptions{ "--profiling --profiling-funcs -s DEMANGLE_SUPPORT=1 -s NO_DISABLE_EXCEPTION_CATCHING -sALLOW_MEMORY_GROWTH=1" } + if _OPTIONS["with-emscripten-pthreads"] then + buildoptions { "-s USE_PTHREADS=1" } + linkoptions { "-s USE_PTHREADS=1 -sPTHREAD_POOL_SIZE=8" } + end end fix_shared_lib_linking_path( package_name, "libeepp-debug" ) @@ -625,13 +629,12 @@ function build_link_configuration( package_name, use_ee_icon ) add_cross_config_links() configuration "emscripten" - linkoptions { "-s TOTAL_MEMORY=67108864" } - linkoptions { "-s USE_SDL=2" } + linkoptions { "-s TOTAL_MEMORY=536870912 -s ALLOW_MEMORY_GROWTH=1 -s USE_SDL=2" } buildoptions { "-s USE_SDL=2" } if _OPTIONS["with-emscripten-pthreads"] then buildoptions { "-s USE_PTHREADS=1" } - linkoptions { "-s USE_PTHREADS=1" } + linkoptions { "-s USE_PTHREADS=1 -sPTHREAD_POOL_SIZE=8" } end if _OPTIONS["with-gles1"] and ( not _OPTIONS["with-gles2"] or _OPTIONS["force-gles1"] ) then diff --git a/premake5.lua b/premake5.lua index 874bc1489..756354788 100644 --- a/premake5.lua +++ b/premake5.lua @@ -371,13 +371,12 @@ function build_link_configuration( package_name, use_ee_icon ) filter "system:emscripten" targetname ( package_name .. extension ) - linkoptions { "-O3 -s TOTAL_MEMORY=67108864" } - linkoptions { "-s USE_SDL=2" } + linkoptions { "-O3 -s TOTAL_MEMORY=536870912 -s ALLOW_MEMORY_GROWTH=1 -s USE_SDL=2" } buildoptions { "-O3 -s USE_SDL=2 -s PRECISE_F32=1 -s ENVIRONMENT=worker,web" } if _OPTIONS["with-emscripten-pthreads"] then buildoptions { "-s USE_PTHREADS=1" } - linkoptions { "-s USE_PTHREADS=1" } + linkoptions { "-s USE_PTHREADS=1 -sPTHREAD_POOL_SIZE=8" } end if _OPTIONS["with-gles1"] and ( not _OPTIONS["with-gles2"] or _OPTIONS["force-gles1"] ) then diff --git a/src/eepp/window/backend/SDL2/clipboardsdl2.cpp b/src/eepp/window/backend/SDL2/clipboardsdl2.cpp index 781b6c288..92e9d0d04 100644 --- a/src/eepp/window/backend/SDL2/clipboardsdl2.cpp +++ b/src/eepp/window/backend/SDL2/clipboardsdl2.cpp @@ -1,32 +1,56 @@ +#include #include #include +#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN +#include +#endif + #ifdef EE_BACKEND_SDL2 namespace EE { namespace Window { namespace Backend { namespace SDL2 { +#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN +static std::string sContent; +#endif + ClipboardSDL::ClipboardSDL( EE::Window::Window* window ) : Clipboard( window ) {} ClipboardSDL::~ClipboardSDL() {} -void ClipboardSDL::init() {} +void ClipboardSDL::init() { +#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN + Log::info( "Initialized emscripten clipboard" ); + emscripten_browser_clipboard::paste( + []( std::string const& paste_data, void* callback_data [[maybe_unused]] ) { + Log::info( "Browser pasted: %s", paste_data.c_str() ); + sContent = std::move( paste_data ); + } ); +#endif +} void ClipboardSDL::setText( const std::string& text ) { +#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN + sContent = text; + emscripten_browser_clipboard::copy( text ); +#else SDL_SetClipboardText( text.c_str() ); +#endif } std::string ClipboardSDL::getText() { +#if EE_PLATFORM == EE_PLATFORM_EMSCRIPTEN + return sContent; +#else char* text = SDL_GetClipboardText(); std::string str( text ); SDL_free( text ); return str; +#endif } String ClipboardSDL::getWideText() { - char* text = SDL_GetClipboardText(); - String str( String::fromUtf8( text ) ); - SDL_free( text ); - return str; + return String::fromUtf8( getText() ); } }}}} // namespace EE::Window::Backend::SDL2 diff --git a/src/eepp/window/backend/SDL2/windowsdl2.cpp b/src/eepp/window/backend/SDL2/windowsdl2.cpp index 9cda3d2fa..5538141ea 100644 --- a/src/eepp/window/backend/SDL2/windowsdl2.cpp +++ b/src/eepp/window/backend/SDL2/windowsdl2.cpp @@ -405,10 +405,10 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) { } /// Init the clipboard after the window creation - reinterpret_cast( mClipboard )->init(); + static_cast( mClipboard )->init(); /// Init the input after the window creation - reinterpret_cast( mInput )->init(); + static_cast( mInput )->init(); mCursorManager->set( Cursor::SysArrow ); diff --git a/src/thirdparty/emscripten-browser-clipboard/LICENSE b/src/thirdparty/emscripten-browser-clipboard/LICENSE new file mode 100644 index 000000000..cccf0789c --- /dev/null +++ b/src/thirdparty/emscripten-browser-clipboard/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Armchair-Software + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/thirdparty/emscripten-browser-clipboard/README.md b/src/thirdparty/emscripten-browser-clipboard/README.md new file mode 100644 index 000000000..d682f03c0 --- /dev/null +++ b/src/thirdparty/emscripten-browser-clipboard/README.md @@ -0,0 +1,138 @@ +# Emscripten Browser Clipboard Library + +Header-only C++ library providing easy browser clipboard access, for programs built with Emscripten. All code in a single header. + +Intended for use with Emscripten code, this enables copy and paste functionality to and from the browser's clipboard, into your program. + +There are two separate ways to copy data to the clipboard - a callback, or an instantaneous function - the right choice will depend on your use case. + +This offers several advantages over existing solutions - it uses modern JS APIs, and there is no need to create text elements alongside your canvas on your page HTML, or make any other changes to the page content. Everything is handled by a single include in your C++ code. + + +## Functionality + +* `emscripten_browser_clipboard::paste()` +* `emscripten_browser_clipboard::copy()` (callback and instantaneous overloads) + +### Paste + +The `paste` function sets a callback which handles paste events generated by the browser. The callback can be a free function, a member function using `std::bind`, or a lambda. + +If you want your callback to communicate with your program, the function accepts an optional void pointer which is passed on to your callback. Use this to pass arbitrary data of your own to your callback, if needed. + +#### Example + +```cpp +#include + +// ... + + // set a lambda as a callback to handle paste data: + emscripten_browser_clipboard::paste([](std::string const &paste_data, void *callback_data [[maybe_unused]]){ + std::cout << "Copied clipboard data: " << paste_data << std::endl; + }); +``` + +The `paste` call takes the following arguments: +```cpp + void paste(paste_handler callback, // a callback to call with the paste data + void *callback_data); // optional data pointer to pass to your callback +``` + +The callback must have the following signature (defined as `emscripten_browser_clipboard::paste_handler`): + +```cpp + void my_paste_handler(std::string const &paste_data, // text content of the pasted data sent to the browser + void *callback_data); // the data pointer you passed to the paste function +``` + +### Copy + +There are two ways of handling copy - by callback, or setting instantaneously. Each has important limitations to be aware of. + +- By callback. This allows you to set a callback, just like the `paste` function above, which activates when the browser generates a `copy` event. Your callback then returns the data that should be copied to the browser clipboard. The callback can be a free function, a member function using `std::bind`, or a lambda. + - The limitation with this method is that browser events fire first, before your program has a chance to process any input - in that case, even though a browser event for a copy has fired, it may be difficult to determine what it is that is currently selected, that the user wants to copy. This is the case with ImGui (see below) - in that case it's better to prefer the instantaneous method. +- Instantaneously. This allows you to (attempt to) set clipboard data at will in your program, for example in response to a user-generated event in your GUI. The function is simply called with a string containing the data you wish to set. It uses the async clipboard API, and returns immediately. + - A limitation with this method is that some browsers may block the attempt unless there is a corresponding user-generated copy event registered at the same time, usually if the user presses ctrl-C or similar. Without it, you may be unable to set system clipboard data arbitrarily (so, clicking on a "copy to clipboard" button may not work, and you may need to require your users to press ctrl-C to copy). + +It is usually best to prefer the callback method, unless you have your own GUI implementing clipboard operations that you wish to tie in with. For more on this, see the ImGui example below. + +#### Example: callback + +```cpp +#include + +// ... + + std::string my_content{"This is something for the clipboard."}; + + // set a lambda as a callback to handle copy events: + emscripten_browser_clipboard::copy([](void *callback_data [[maybe_unused]]){ + return my_content.c_str(); + }); + +``` + +#### Example: instantaneous + +```cpp +#include + +// ... + + std::string my_content{"This is something for the clipboard."}; + + emscripten_browser_clipboard::copy(my_content); // attempt to set clipboard content immediately +``` + +## Use with ImGui + +Following is a simplified example of using Emscripten Browser Clipboard with ImGui. If you already have an application using ImGui text inputs, the following is all that is needed to enable seamless copy and paste operation between the browser (and hence the user's system) and your application, for those text input fields: + +```cpp +#include +#include +#include + +std::string content; // this stores the content for our internal clipboard + +char const *get_content_for_imgui(void *user_data [[maybe_unused]]) { + /// Callback for imgui, to return clipboard content + std::cout << "ImGui requested clipboard content, returning " << std::quoted(content) << std::endl; + return content.c_str(); +} + +void set_content_from_imgui(void *user_data [[maybe_unused]], char const *text) { + /// Callback for imgui, to set clipboard content + content = text; + std::cout << "ImGui setting clipboard content to " << std::quoted(content) << std::endl; + emscripten_browser_clipboard::copy(content); // send clipboard data to the browser +} + +// ... + + emscripten_browser_clipboard::paste([](std::string const &paste_data, void *callback_data [[maybe_unused]]){ + /// Callback to handle clipboard paste from browser + std::cout << "Clipboard updated from paste data: " << std::quoted(paste_data) << std::endl; + content = std::move(paste_data); + }); + + // set ImGui callbacks for clipboard access: + ImGuiIO &imgui_io = ImGui::GetIO(); + imgui_io.GetClipboardTextFn = get_content_for_imgui; + imgui_io.SetClipboardTextFn = set_content_from_imgui; + +``` + +## Limitations / future expansion + +At the time of writing, this only handles copying and pasting plain text. It can be easily extended to handle arbitrary data - if I don't get round to doing this soon, please feel free to submit a pull request. + +The async copy implementation currently ignores the result of the async operation, which may fail for various reasons. The library could be extended to call `on_success` / `on_failure` callbacks as a consequence of the operation, if desired. + +## Other useful libraries + +You may also find the following Emscripten helper libraries useful: + +- [Emscripten Browser File Library](https://github.com/Armchair-Software/emscripten-browser-file) - allows you to transfer files using the browser upload / download interface, into memory in your C++ program. +- [Emscripten Browser Cursor](https://github.com/Armchair-Software/emscripten-browser-cursor) - easy manipulation of browser mouse pointer cursors from C++. diff --git a/src/thirdparty/emscripten-browser-clipboard/emscripten_browser_clipboard.h b/src/thirdparty/emscripten-browser-clipboard/emscripten_browser_clipboard.h new file mode 100644 index 000000000..9e77261d7 --- /dev/null +++ b/src/thirdparty/emscripten-browser-clipboard/emscripten_browser_clipboard.h @@ -0,0 +1,99 @@ +#ifndef EMSCRIPTEN_BROWSER_CLIPBOARD_H_INCLUDED +#define EMSCRIPTEN_BROWSER_CLIPBOARD_H_INCLUDED + +#include +#include + +#define _EM_JS_INLINE(ret, c_name, js_name, params, code) \ + extern "C" { \ + ret c_name params EM_IMPORT(js_name); \ + EMSCRIPTEN_KEEPALIVE \ + __attribute__((section("em_js"), aligned(1))) inline char __em_js__##js_name[] = \ + #params "<::>" code; \ + } + +#define EM_JS_INLINE(ret, name, params, ...) _EM_JS_INLINE(ret, name, name, params, #__VA_ARGS__) + +namespace emscripten_browser_clipboard { + +/////////////////////////////////// Interface ////////////////////////////////// + +using paste_handler = void(*)(std::string const&, void*); +using copy_handler = char const*(*)(void*); + +inline void paste(paste_handler callback, void *callback_data = nullptr); +inline void copy(copy_handler callback, void *callback_data = nullptr); +inline void copy(std::string const &content); + +///////////////////////////////// Implementation /////////////////////////////// + +namespace { + +EM_JS_INLINE(void, paste_js, (paste_handler callback, void *callback_data), { + /// Register the given callback to handle paste events. Callback data pointer is passed through to the callback. + /// Paste handler callback signature is: + /// void my_handler(std::string const &paste_data, void *callback_data = nullptr); + document.addEventListener('paste', (event) => { + Module["ccall"]('paste_return', 'number', ['string', 'number', 'number'], [event.clipboardData.getData('text/plain'), callback, callback_data]); + }); +}); + +EM_JS_INLINE(void, copy_js, (copy_handler callback, void *callback_data), { + /// Register the given callback to handle copy events. Callback data pointer is passed through to the callback. + /// Copy handler callback signature is: + /// char const *my_handler(void *callback_data = nullptr); + document.addEventListener('copy', (event) => { + const content_ptr = Module["ccall"]('copy_return', 'number', ['number', 'number'], [callback, callback_data]); + event.clipboardData.setData('text/plain', UTF8ToString(content_ptr)); + event.preventDefault(); + }); +}); + +EM_JS_INLINE(void, copy_async_js, (char const *content_ptr), { + /// Attempt to copy the provided text asynchronously + navigator.clipboard.writeText(UTF8ToString(content_ptr)); +}); + +} + +inline void paste(paste_handler callback, void *callback_data) { + /// C++ wrapper for javascript paste call + paste_js(callback, callback_data); +} + +inline void copy(copy_handler callback, void *callback_data) { + /// C++ wrapper for javascript copy call + copy_js(callback, callback_data); +} + +inline void copy(std::string const &content) { + /// C++ wrapper for javascript copy call + copy_async_js(content.c_str()); +} + +namespace { + +extern "C" { + +EMSCRIPTEN_KEEPALIVE inline int paste_return(char const *paste_data, paste_handler callback, void *callback_data); + +EMSCRIPTEN_KEEPALIVE inline int paste_return(char const *paste_data, paste_handler callback, void *callback_data) { + /// Call paste callback - this function is called from javascript when the paste event occurs + callback(paste_data, callback_data); + return 1; +} + +EMSCRIPTEN_KEEPALIVE inline char const *copy_return(copy_handler callback, void *callback_data); + +EMSCRIPTEN_KEEPALIVE inline char const *copy_return(copy_handler callback, void *callback_data) { + /// Call paste callback - this function is called from javascript when the paste event occurs + return callback(callback_data); +} + +} + +} + +} + +#endif // EMSCRIPTEN_BROWSER_CLIPBOARD_H_INCLUDED diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index e67665f23..a66a2e9e3 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -618,8 +618,15 @@ void App::onTextDropped( String text ) { App::App( const size_t& jobs, const std::vector& args ) : mArgs( args ), +#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN mThreadPool( - ThreadPool::createShared( jobs > 0 ? jobs : eemax( 2, Sys::getCPUCount() ) ) ) {} + ThreadPool::createShared( jobs > 0 ? jobs : eemax( 2, Sys::getCPUCount() ) ) ) { +} +#elif defined( __EMSCRIPTEN_PTHREADS__ ) + mThreadPool( + ThreadPool::createShared( jobs > 0 ? jobs : eemin( 8, Sys::getCPUCount() ) ) ) { +} +#endif App::~App() { if ( mProjectBuildManager ) diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 7c1532a24..afb044523 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -1183,14 +1183,12 @@ void LSPClientPlugin::getSymbolInfo( UICodeEditor* editor ) { } void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { - for ( auto& kb : mKeyBindings ) { + for ( auto& kb : mKeyBindings ) editor->getKeyBindings().removeCommandKeybind( kb.first ); - if ( editor->hasDocument() ) - editor->getDocument().removeCommand( kb.first ); - } if ( mShuttingDown ) return; + Lock l( mDocMutex ); TextDocument* doc = &editor->getDocument(); const auto& cbs = mEditors[editor]; @@ -1204,6 +1202,11 @@ void LSPClientPlugin::onUnregister( UICodeEditor* editor ) { return; } + if ( editor->hasDocument() ) { + for ( auto& kb : mKeyBindings ) + editor->getDocument().removeCommand( kb.first ); + } + { Lock lds( mDocSymbolsMutex ); mDocSymbols.erase( doc->getURI() );