diff --git a/bin/assets/colorschemes/colorschemes.conf b/bin/assets/colorschemes/colorschemes.conf index 51c7bb87a..aab18d3c6 100644 --- a/bin/assets/colorschemes/colorschemes.conf +++ b/bin/assets/colorschemes/colorschemes.conf @@ -12,6 +12,8 @@ line_break_column = #54575b99 matching_bracket = #FFFFFF33 matching_selection = #FFFFFF66 matching_search = #181b1e +suggestion = #97979c,#252529 +suggestion_selected = #e1e1e6,#252529 normal = #e1e1e6 symbol = #e1e1e6 @@ -39,6 +41,8 @@ line_break_column = #54575b99 matching_bracket = #FFFFFF33 matching_selection = #3e596e matching_search = #181b1e +suggestion = #e1e1e6,#1d1f27 +suggestion_selected = #ffffff,#2f3240 normal = #e1e1e6 symbol = #e1e1e6 diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index 9dc8836e3..c20684374 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -109,6 +109,11 @@ SelectButton:selectedpressed { background-color: var(--primary); } +PushButton::icon, +SelectButton::icon { + margin-right: 4dp; +} + CheckBox::active, CheckBox::inactive { width: 12dp; diff --git a/include/eepp/scene/event.hpp b/include/eepp/scene/event.hpp index 86ea73842..14b3c3b7d 100644 --- a/include/eepp/scene/event.hpp +++ b/include/eepp/scene/event.hpp @@ -31,7 +31,10 @@ class EE_API Event { OnAlphaChange, OnTextChanged, OnFontChanged, + OnDocumentLoaded, OnDocumentChanged, + OnDocumentClosed, + OnDocumentSyntaxDefinitionChange, OnFontStyleChanged, OnPressEnter, OnValueChange, diff --git a/include/eepp/system/threadpool.hpp b/include/eepp/system/threadpool.hpp index 6457d3771..ffa552a3e 100644 --- a/include/eepp/system/threadpool.hpp +++ b/include/eepp/system/threadpool.hpp @@ -15,7 +15,11 @@ namespace EE { namespace System { class EE_API ThreadPool : NonCopyable { public: - static std::unique_ptr create( Uint32 numThreads ); + static std::shared_ptr createShared( Uint32 numThreads ); + + static std::unique_ptr createUnique( Uint32 numThreads ); + + static ThreadPool* createRaw( Uint32 numThreads ); virtual ~ThreadPool(); @@ -33,6 +37,8 @@ class EE_API ThreadPool : NonCopyable { void threadFunc(); + static ThreadPool* create( ThreadPool* pool, Uint32 numThreads ); + std::vector> mThreads; std::deque> mWork; bool mShuttingDown = false; diff --git a/include/eepp/ui/doc/syntaxcolorscheme.hpp b/include/eepp/ui/doc/syntaxcolorscheme.hpp index 8123d860e..6997e99bc 100644 --- a/include/eepp/ui/doc/syntaxcolorscheme.hpp +++ b/include/eepp/ui/doc/syntaxcolorscheme.hpp @@ -22,7 +22,7 @@ namespace EE { namespace UI { namespace Doc { * "selection", "line_number_background", * "line_number", "line_number2", "line_highlight", * "line_number_background", "whitespace", "line_break_column", - * "matching_bracket", "matching_selection" + * "matching_bracket", "matching_selection", "suggestion", "suggestion_selected" * * Following the lite editor syntax colors (https://github.com/rxi/lite). */ diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 368a685e3..e910689cd 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -41,10 +41,13 @@ class EE_API TextDocument { const size_t& newCount ) = 0; virtual void onDocumentLineChanged( const Int64& lineIndex ) = 0; virtual void onDocumentSaved() = 0; + virtual void onDocumentClosed( TextDocument* ) {} }; TextDocument(); + ~TextDocument(); + bool isNonWord( String::StringBaseType ch ) const; bool hasFilepath(); @@ -357,6 +360,8 @@ class EE_API TextDocument { void notifyDocumentSaved(); + void notifyDocumentClosed(); + void notifyLineCountChanged( const size_t& lastCount, const size_t& newCount ); void notifyLineChanged( const Int64& lineIndex ); @@ -375,6 +380,8 @@ class EE_API TextDocument { void appendLineIfLastLine( Int64 line ); void guessIndentType(); + + bool loadFromStream( IOStream& file, std::string path ); }; }}} // namespace EE::UI::Doc diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 8283de9b2..968ccf289 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -17,8 +17,58 @@ class Font; namespace EE { namespace UI { +class UICodeEditor; class UIScrollBar; +class UICodeEditorModule { + public: + virtual void onRegister( UICodeEditor* ) = 0; + virtual void onUnregister( UICodeEditor* ) = 0; + virtual bool onKeyDown( UICodeEditor*, const KeyEvent& ) { return false; } + virtual bool onKeyUp( UICodeEditor*, const KeyEvent& ) { return false; } + virtual bool onTextInput( UICodeEditor*, const TextInputEvent& ) { return false; } + virtual void update( UICodeEditor* ) {} + virtual void preDraw( UICodeEditor*, const Vector2f& /*startScroll*/, + const Float& /*lineHeight*/, const TextPosition& /*cursor*/ ) {} + virtual void postDraw( UICodeEditor*, const Vector2f& /*startScroll*/, + const Float& /*lineHeight*/, const TextPosition& /*cursor*/ ) {} + virtual void onFocus( UICodeEditor* ) {} + virtual void onFocusLoss( UICodeEditor* ) {} + virtual bool onMouseDown( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; } + virtual bool onMouseMove( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; } + virtual bool onMouseUp( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; } + virtual bool onMouseClick( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; } + virtual bool onMouseDoubleClick( UICodeEditor*, const Vector2i&, const Uint32& ) { + return false; + } + virtual bool onMouseOver( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; } + virtual bool onMouseLeave( UICodeEditor*, const Vector2i&, const Uint32& ) { return false; } +}; + +class EE_API DocEvent : public Event { + public: + DocEvent( Node* node, TextDocument* doc, const Uint32& eventType ) : + Event( node, eventType ), doc( doc ) {} + TextDocument* getDoc() const { return doc; } + + protected: + TextDocument* doc; +}; + +class EE_API DocSyntaxDefEvent : public DocEvent { + public: + DocSyntaxDefEvent( Node* node, TextDocument* doc, const Uint32& eventType, + const std::string& oldLang, const std::string& newLang ) : + DocEvent( node, doc, eventType ), oldLang( oldLang ), newLang( newLang ) {} + const std::string& getOldLang() const { return oldLang; } + const std::string& getNewLang() const { return newLang; } + + protected: + TextDocument* doc; + std::string oldLang; + std::string newLang; +}; + class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { public: static UICodeEditor* New(); @@ -287,6 +337,26 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void setHighlightTextRange( const TextRange& highlightSelection ); + void registerModule( UICodeEditorModule* module ); + + void unregisterModule( UICodeEditorModule* module ); + + virtual Int64 getColFromXOffset( Int64 line, const Float& x ) const; + + virtual Float getColXOffset( TextPosition position ); + + virtual Float getXOffsetCol( const TextPosition& position ); + + virtual Float getLineWidth( const Int64& lineIndex ); + + Float getTextWidth( const String& text ) const; + + Float getLineHeight() const; + + Float getCharacterSize() const; + + Float getGlyphWidth() const; + protected: struct LastXOffset { TextPosition position; @@ -339,6 +409,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Clock mLongestLineWidthLastUpdate; String mHighlightWord; TextRange mHighlightTextRange; + std::vector mModules; UICodeEditor( const std::string& elementTag, const bool& autoRegisterBaseCommands = true, const bool& autoRegisterBaseKeybindings = true ); @@ -355,8 +426,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { virtual void findLongestLine(); - virtual Float getLineWidth( const Int64& lineIndex ); - virtual Uint32 onFocus(); virtual Uint32 onFocusLoss(); @@ -365,6 +434,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { virtual Uint32 onKeyDown( const KeyEvent& event ); + virtual Uint32 onKeyUp( const KeyEvent& event ); + virtual Uint32 onMouseDown( const Vector2i& position, const Uint32& flags ); virtual Uint32 onMouseMove( const Vector2i& position, const Uint32& flags ); @@ -383,6 +454,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { virtual void onPaddingChange(); + virtual void onCursorPosChange(); + void updateEditor(); virtual void onDocumentTextChanged(); @@ -399,6 +472,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { virtual void onDocumentSaved(); + virtual void onDocumentClosed( TextDocument* doc ); + std::pair getVisibleLineRange(); int getVisibleLinesCount(); @@ -409,20 +484,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void setScrollY( const Float& val, bool emmitEvent = true ); - virtual Int64 getColFromXOffset( Int64 line, const Float& x ) const; - - virtual Float getColXOffset( TextPosition position ); - - virtual Float getXOffsetCol( const TextPosition& position ); - - Float getTextWidth( const String& text ) const; - - Float getLineHeight() const; - - Float getCharacterSize() const; - - Float getGlyphWidth() const; - void resetCursor(); TextPosition resolveScreenPosition( const Vector2f& position ) const; diff --git a/include/eepp/ui/uitextedit.hpp b/include/eepp/ui/uitextedit.hpp index e683dc58d..b71875e3a 100644 --- a/include/eepp/ui/uitextedit.hpp +++ b/include/eepp/ui/uitextedit.hpp @@ -26,6 +26,14 @@ class EE_API UITextEdit : public UICodeEditor { void setText( const String& text ); + virtual Int64 getColFromXOffset( Int64 line, const Float& x ) const; + + virtual Float getColXOffset( TextPosition position ); + + virtual Float getXOffsetCol( const TextPosition& position ); + + virtual Float getLineWidth( const Int64& lineIndex ); + protected: struct TextLine { Text text; @@ -41,14 +49,6 @@ class EE_API UITextEdit : public UICodeEditor { virtual void drawLineText( const Int64& index, Vector2f position, const Float& fontSize, const Float& lineHeight ); - virtual Int64 getColFromXOffset( Int64 line, const Float& x ) const; - - virtual Float getColXOffset( TextPosition position ); - - virtual Float getXOffsetCol( const TextPosition& position ); - - virtual Float getLineWidth( const Int64& lineIndex ); - void ensureLineUpdated( const Int64& lineIndex ); virtual bool applyProperty( const StyleSheetProperty& attribute ); diff --git a/projects/linux/ee.creator.user b/projects/linux/ee.creator.user index 1fed8afda..2a5da4a2d 100644 --- a/projects/linux/ee.creator.user +++ b/projects/linux/ee.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -89,7 +89,7 @@ {6d057187-158a-4883-8d5b-d470a6b6b025} 10 0 - 15 + 19 ../../make/linux @@ -1854,7 +1854,7 @@ false false false - true + false false %{buildDir}../../../bin/ diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 93e03e1fc..9cad74458 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -972,6 +972,8 @@ ../../src/thirdparty/SOIL2/src/SOIL2/stbi_pkm.h ../../src/thirdparty/SOIL2/src/SOIL2/stbi_pvr_c.h ../../src/thirdparty/SOIL2/src/SOIL2/stbi_pvr.h +../../src/tools/codeeditor/autocompletemodule.cpp +../../src/tools/codeeditor/autocompletemodule.hpp ../../src/tools/codeeditor/codeeditor.cpp ../../src/tools/codeeditor/codeeditor.hpp ../../src/tools/codeeditor/uicodeeditorsplitter.cpp diff --git a/src/eepp/system/resourceloader.cpp b/src/eepp/system/resourceloader.cpp index cfbb70af9..5db271a46 100644 --- a/src/eepp/system/resourceloader.cpp +++ b/src/eepp/system/resourceloader.cpp @@ -106,7 +106,7 @@ void ResourceLoader::setLoaded() { void ResourceLoader::taskRunner() { { - auto pool = ThreadPool::create( eemin( mThreads, (Uint32)mTasks.size() ) ); + auto pool = ThreadPool::createUnique( eemin( mThreads, (Uint32)mTasks.size() ) ); for ( auto& task : mTasks ) { pool->run( task, [&] { mTotalLoaded++; } ); diff --git a/src/eepp/system/threadpool.cpp b/src/eepp/system/threadpool.cpp index d6f4d094a..e0cfc63a7 100644 --- a/src/eepp/system/threadpool.cpp +++ b/src/eepp/system/threadpool.cpp @@ -2,15 +2,28 @@ namespace EE { namespace System { -std::unique_ptr ThreadPool::create( Uint32 numThreads ) { - std::unique_ptr pool( new ThreadPool() ); +std::shared_ptr ThreadPool::createShared( Uint32 numThreads ) { + std::shared_ptr pool( new ThreadPool() ); + create( pool.get(), numThreads ); + return pool; +} +std::unique_ptr ThreadPool::createUnique( Uint32 numThreads ) { + std::unique_ptr pool( new ThreadPool() ); + create( pool.get(), numThreads ); + return pool; +} + +ThreadPool* ThreadPool::createRaw( Uint32 numThreads ) { + ThreadPool* pool = eeNew( ThreadPool, () ); + return create( pool, numThreads ); +} + +ThreadPool* ThreadPool::create( ThreadPool* pool, Uint32 numThreads ) { for ( Uint32 i = 0; i < numThreads; ++i ) { - pool->mThreads.emplace_back( - std::make_unique( &ThreadPool::threadFunc, pool.get() ) ); + pool->mThreads.emplace_back( std::make_unique( &ThreadPool::threadFunc, pool ) ); pool->mThreads.back().get()->launch(); } - return pool; } diff --git a/src/eepp/ui/doc/syntaxcolorscheme.cpp b/src/eepp/ui/doc/syntaxcolorscheme.cpp index e251ff6f4..9b2cdec67 100644 --- a/src/eepp/ui/doc/syntaxcolorscheme.cpp +++ b/src/eepp/ui/doc/syntaxcolorscheme.cpp @@ -20,6 +20,8 @@ namespace EE { namespace UI { namespace Doc { // "matching_bracket" (the background color drawn in the matching brackets) // "matching_selection" (the background color drawn in the text matching the current selected text) // "matching_search" (the background color drawn in the text matching the current searched text) +// "suggestion" (the auto-complete suggestion box text and background color +// "suggestion_selected" (the auto-complete selected suggestion box text and background color SyntaxColorScheme SyntaxColorScheme::getDefault() { return {"lite", @@ -38,7 +40,7 @@ SyntaxColorScheme SyntaxColorScheme::getDefault() { }, { {"background", Color( "#2e2e32" )}, - {"text", Color( "#e1e1e6" )}, + {"text", Color( "#97979c" )}, {"caret", Color( "#93DDFA" )}, {"selection", Color( "#48484f" )}, {"line_highlight", Color( "#343438" )}, @@ -51,6 +53,8 @@ SyntaxColorScheme SyntaxColorScheme::getDefault() { {"matching_bracket", Color( "#FFFFFF33" )}, {"matching_selection", Color( "#FFFFFF33" )}, {"matching_search", Color( "#181b1e" )}, + {"suggestion", {Color( "#97979c" ), Color( "#252529" ), Text::Regular}}, + {"suggestion_selected", {Color( "#e1e1e6" ), Color( "#252529" ), Text::Regular}}, }}; } @@ -148,6 +152,7 @@ SyntaxColorScheme::SyntaxColorScheme( const std::string& name, mName( name ), mSyntaxColors( syntaxColors ), mEditorColors( editorColors ) {} static const SyntaxColorScheme::Style StyleEmpty = {Color::White}; +static const SyntaxColorScheme StyleDefault = SyntaxColorScheme::getDefault(); const SyntaxColorScheme::Style& SyntaxColorScheme::getSyntaxStyle( const std::string& type ) const { auto it = mSyntaxColors.find( type ); @@ -177,6 +182,10 @@ SyntaxColorScheme::getEditorSyntaxStyle( const std::string& type ) const { else if ( type == "guide" || type == "line_break_column" || type == "matching_bracket" || type == "matching_selection" ) return getEditorSyntaxStyle( "selection" ); + else if ( type == "suggestion" ) + return StyleDefault.getEditorSyntaxStyle( "suggestion" ); + else if ( type == "suggestion_selected" ) + return StyleDefault.getEditorSyntaxStyle( "suggestion_selected" ); return StyleEmpty; } diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index f2d273799..15ded651f 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -31,6 +31,10 @@ TextDocument::TextDocument() : reset(); } +TextDocument::~TextDocument() { + notifyDocumentClosed(); +} + bool TextDocument::hasFilepath() { return mDefaultFileName != mFilePath; } @@ -62,6 +66,10 @@ static String ptrGetLine( char* data, const size_t& size, size_t& position ) { } bool TextDocument::loadFromStream( IOStream& file ) { + return loadFromStream( file, "untitled" ); +} + +bool TextDocument::loadFromStream( IOStream& file, std::string path ) { Clock clock; reset(); mLines.clear(); @@ -140,8 +148,7 @@ bool TextDocument::loadFromStream( IOStream& file ) { notifyTextChanged(); - eePRINTL( "Document \"%s\" loaded in %.2fms.", - mFilePath.empty() ? "untitled" : mFilePath.c_str(), + eePRINTL( "Document \"%s\" loaded in %.2fms.", path.c_str(), clock.getElapsedTime().asMilliseconds() ); return true; } @@ -246,7 +253,7 @@ bool TextDocument::loadFromFile( const std::string& path ) { } IOStreamFile file( path, "rb" ); - bool ret = loadFromStream( file ); + bool ret = loadFromStream( file, path ); mFilePath = path; resetSyntax(); return ret; @@ -254,7 +261,7 @@ bool TextDocument::loadFromFile( const std::string& path ) { bool TextDocument::loadFromMemory( const Uint8* data, const Uint32& size ) { IOStreamMemory stream( (const char*)data, size ); - return loadFromStream( stream ); + return loadFromStream( stream, mFilePath ); } bool TextDocument::loadFromPack( Pack* pack, std::string filePackPath ) { @@ -1322,6 +1329,12 @@ void TextDocument::notifyDocumentSaved() { } } +void TextDocument::notifyDocumentClosed() { + for ( auto& client : mClients ) { + client->onDocumentClosed( this ); + } +} + void TextDocument::notifyLineCountChanged( const size_t& lastCount, const size_t& newCount ) { for ( auto& client : mClients ) { client->onDocumentLineCountChange( lastCount, newCount ); diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 18eb8c88a..613616dbe 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -158,7 +158,15 @@ UICodeEditor::UICodeEditor( const bool& autoRegisterBaseCommands, UICodeEditor( "codeeditor", autoRegisterBaseCommands, autoRegisterBaseKeybindings ) {} UICodeEditor::~UICodeEditor() { - mDoc->unregisterClient( this ); + if ( mDoc.use_count() == 1 ) { + onDocumentClosed( mDoc.get() ); + mDoc->unregisterClient( this ); + mDoc.reset(); + } else { + mDoc->unregisterClient( this ); + } + for ( auto& module : mModules ) + module->onUnregister( this ); } Uint32 UICodeEditor::getType() const { @@ -175,14 +183,16 @@ void UICodeEditor::setTheme( UITheme* Theme ) { } void UICodeEditor::draw() { + if ( !mVisible || mAlpha == 0 ) + return; + UIWidget::draw(); if ( mFont == NULL ) return; - if ( mDirtyEditor ) { + if ( mDirtyEditor ) updateEditor(); - } Color col; std::pair lineRange = getVisibleLineRange(); @@ -197,6 +207,9 @@ void UICodeEditor::draw() { Primitives primitives; TextPosition cursor( mDoc->getSelection().start() ); + for ( auto& module : mModules ) + module->preDraw( this, startScroll, lineHeight, cursor ); + if ( !mLocked && mHighlightCurrentLine ) { primitives.setColor( Color( mCurrentLineBackgroundColor ).blendAlpha( mAlpha ) ); primitives.drawRectangle( Rectf( @@ -248,6 +261,9 @@ void UICodeEditor::draw() { drawLineNumbers( lineRange, startScroll, screenStart, lineHeight, lineNumberWidth, lineNumberDigits, charSize ); } + + for ( auto& module : mModules ) + module->postDraw( this, startScroll, lineHeight, cursor ); } void UICodeEditor::scheduledUpdate( const Time& ) { @@ -277,6 +293,9 @@ void UICodeEditor::scheduledUpdate( const Time& ) { mLongestLineWidthLastUpdate.getElapsedTime() > mFindLongestLineWidthUpdateFrequency ) { updateLongestLineWidth(); } + + for ( auto& module : mModules ) + module->update( this ); } void UICodeEditor::updateLongestLineWidth() { @@ -302,6 +321,8 @@ bool UICodeEditor::loadFromFile( const std::string& path ) { updateLongestLineWidth(); mHighlighter.changeDoc( mDoc.get() ); invalidateDraw(); + DocEvent event( this, mDoc.get(), Event::OnDocumentLoaded ); + sendEvent( &event ); return ret; } @@ -339,7 +360,8 @@ UICodeEditor* UICodeEditor::setFont( Font* font ) { void UICodeEditor::onFontChanged() {} void UICodeEditor::onDocumentChanged() { - sendCommonEvent( Event::OnDocumentChanged ); + DocEvent event( this, mDoc.get(), Event::OnDocumentChanged ); + sendEvent( &event ); } Uint32 UICodeEditor::onMessage( const NodeMessage* msg ) { @@ -566,6 +588,8 @@ TextDocument& UICodeEditor::getDocument() { void UICodeEditor::setDocument( std::shared_ptr doc ) { if ( mDoc.get() != doc.get() ) { mDoc->unregisterClient( this ); + if ( mDoc.use_count() == 1 ) + onDocumentClosed( mDoc.get() ); mDoc = doc; mDoc->registerClient( this ); mHighlighter.changeDoc( mDoc.get() ); @@ -594,6 +618,8 @@ Uint32 UICodeEditor::onFocus() { resetCursor(); mDoc->setActiveClient( this ); } + for ( auto& module : mModules ) + module->onFocus( this ); return UIWidget::onFocus(); } @@ -603,6 +629,8 @@ Uint32 UICodeEditor::onFocusLoss() { getSceneNode()->getWindow()->stopTextInput(); if ( mDoc->getActiveClient() == this ) mDoc->setActiveClient( nullptr ); + for ( auto& module : mModules ) + module->onFocusLoss( this ); return UIWidget::onFocusLoss(); } @@ -616,12 +644,22 @@ Uint32 UICodeEditor::onTextInput( const TextInputEvent& event ) { return 1; mDoc->textInput( event.getText() ); + + for ( auto& module : mModules ) + if ( module->onTextInput( this, event ) ) + return 0; + return 1; } Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { if ( NULL == mFont ) return 1; + + for ( auto& module : mModules ) + if ( module->onKeyDown( this, event ) ) + return 0; + std::string cmd = mKeyBindings.getCommandFromKeyBind( {event.getKeyCode(), event.getMod()} ); if ( !cmd.empty() ) { // Allow copy selection on locked mode @@ -633,6 +671,13 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { return 1; } +Uint32 UICodeEditor::onKeyUp( const KeyEvent& event ) { + for ( auto& module : mModules ) + if ( module->onKeyUp( this, event ) ) + return 0; + return UIWidget::onKeyUp( event ); +} + TextPosition UICodeEditor::resolveScreenPosition( const Vector2f& position ) const { Vector2f localPos( position ); worldToNode( localPos ); @@ -663,6 +708,10 @@ Sizef UICodeEditor::getMaxScroll() const { } Uint32 UICodeEditor::onMouseDown( const Vector2i& position, const Uint32& flags ) { + for ( auto& module : mModules ) + if ( module->onMouseDown( this, position, flags ) ) + return UIWidget::onMouseDown( position, flags ); + if ( isTextSelectionEnabled() && !getEventDispatcher()->isNodeDragging() && NULL != mFont && !mMouseDown && getEventDispatcher()->getMouseDownNode() == this && ( flags & EE_BUTTON_LMASK ) ) { @@ -680,6 +729,10 @@ Uint32 UICodeEditor::onMouseDown( const Vector2i& position, const Uint32& flags } Uint32 UICodeEditor::onMouseMove( const Vector2i& position, const Uint32& flags ) { + for ( auto& module : mModules ) + if ( module->onMouseMove( this, position, flags ) ) + return UIWidget::onMouseMove( position, flags ); + if ( isTextSelectionEnabled() && !getUISceneNode()->getEventDispatcher()->isNodeDragging() && NULL != mFont && mMouseDown && ( flags & EE_BUTTON_LMASK ) ) { TextRange selection = mDoc->getSelection(); @@ -690,6 +743,10 @@ Uint32 UICodeEditor::onMouseMove( const Vector2i& position, const Uint32& flags } Uint32 UICodeEditor::onMouseUp( const Vector2i& position, const Uint32& flags ) { + for ( auto& module : mModules ) + if ( module->onMouseUp( this, position, flags ) ) + return UIWidget::onMouseUp( position, flags ); + if ( NULL == mFont ) return UIWidget::onMouseUp( position, flags ); @@ -716,7 +773,11 @@ Uint32 UICodeEditor::onMouseUp( const Vector2i& position, const Uint32& flags ) return UIWidget::onMouseUp( position, flags ); } -Uint32 UICodeEditor::onMouseClick( const Vector2i&, const Uint32& flags ) { +Uint32 UICodeEditor::onMouseClick( const Vector2i& position, const Uint32& flags ) { + for ( auto& module : mModules ) + if ( module->onMouseClick( this, position, flags ) ) + return UIWidget::onMouseClick( position, flags ); + if ( ( flags & EE_BUTTON_LMASK ) && mLastDoubleClick.getElapsedTime() < Milliseconds( 300.f ) ) { mDoc->selectLine(); @@ -724,7 +785,11 @@ Uint32 UICodeEditor::onMouseClick( const Vector2i&, const Uint32& flags ) { return 1; } -Uint32 UICodeEditor::onMouseDoubleClick( const Vector2i&, const Uint32& flags ) { +Uint32 UICodeEditor::onMouseDoubleClick( const Vector2i& position, const Uint32& flags ) { + for ( auto& module : mModules ) + if ( module->onMouseDoubleClick( this, position, flags ) ) + return UIWidget::onMouseDoubleClick( position, flags ); + if ( mLocked || NULL == mFont ) return 1; @@ -737,11 +802,17 @@ Uint32 UICodeEditor::onMouseDoubleClick( const Vector2i&, const Uint32& flags ) } Uint32 UICodeEditor::onMouseOver( const Vector2i& position, const Uint32& flags ) { + for ( auto& module : mModules ) + if ( module->onMouseOver( this, position, flags ) ) + return UIWidget::onMouseOver( position, flags ); getUISceneNode()->setCursor( !mLocked ? Cursor::IBeam : Cursor::Arrow ); return UIWidget::onMouseOver( position, flags ); } Uint32 UICodeEditor::onMouseLeave( const Vector2i& position, const Uint32& flags ) { + for ( auto& module : mModules ) + if ( module->onMouseLeave( this, position, flags ) ) + return UIWidget::onMouseLeave( position, flags ); getUISceneNode()->setCursor( Cursor::Arrow ); return UIWidget::onMouseLeave( position, flags ); } @@ -878,6 +949,7 @@ void UICodeEditor::onDocumentCursorChange( const Doc::TextPosition& ) { checkMatchingBrackets(); invalidateEditor(); invalidateDraw(); + onCursorPosChange(); } void UICodeEditor::onDocumentSelectionChange( const Doc::TextRange& ) { @@ -902,6 +974,11 @@ void UICodeEditor::onDocumentSaved() { sendCommonEvent( Event::OnSave ); } +void UICodeEditor::onDocumentClosed( TextDocument* doc ) { + DocEvent event( this, doc, Event::OnDocumentClosed ); + sendEvent( &event ); +} + std::pair UICodeEditor::getVisibleLineRange() { Float lineHeight = getLineHeight(); Float minLine = eemax( 0.f, eefloor( mScroll.y / lineHeight ) ); @@ -1314,9 +1391,13 @@ void UICodeEditor::setEnableColorPickerOnSelection( const bool& enableColorPicke } void UICodeEditor::setSyntaxDefinition( const SyntaxDefinition& definition ) { + std::string oldLang( mDoc->getSyntaxDefinition().getLanguageName() ); mDoc->setSyntaxDefinition( definition ); mHighlighter.reset(); invalidateDraw(); + DocSyntaxDefEvent event( this, mDoc.get(), Event::OnDocumentSyntaxDefinitionChange, oldLang, + mDoc->getSyntaxDefinition().getLanguageName() ); + sendEvent( &event ); } const SyntaxDefinition& UICodeEditor::getSyntaxDefinition() const { @@ -1522,6 +1603,20 @@ void UICodeEditor::setHighlightTextRange( const TextRange& highlightSelection ) } } +void UICodeEditor::registerModule( UICodeEditorModule* module ) { + auto it = std::find( mModules.begin(), mModules.end(), module ); + if ( it == mModules.end() ) + mModules.push_back( module ); + module->onRegister( this ); +} + +void UICodeEditor::unregisterModule( UICodeEditorModule* module ) { + auto it = std::find( mModules.begin(), mModules.end(), module ); + if ( it != mModules.end() ) + mModules.erase( it ); + module->onUnregister( this ); +} + const Time& UICodeEditor::getFindLongestLineWidthUpdateFrequency() const { return mFindLongestLineWidthUpdateFrequency; } @@ -1750,4 +1845,9 @@ void UICodeEditor::registerKeybindings() { mKeyBindings.addKeybinds( getDefaultKeybindings() ); } +void UICodeEditor::onCursorPosChange() { + sendCommonEvent( Event::OnCursorPosChange ); + invalidateDraw(); +} + }} // namespace EE::UI diff --git a/src/tools/codeeditor/autocompletemodule.cpp b/src/tools/codeeditor/autocompletemodule.cpp new file mode 100644 index 000000000..2955bda94 --- /dev/null +++ b/src/tools/codeeditor/autocompletemodule.cpp @@ -0,0 +1,434 @@ +#include "autocompletemodule.hpp" +#include +#include +#include +#include +#include +using namespace EE::Graphics; +using namespace EE::System; + +#if EE_PLATFORM != EE_PLATFORM_EMSCRIPTEN +#define AUTO_COMPLETE_THREADED 1 +#else +#define AUTO_COMPLETE_THREADED 0 +#endif + +AutoCompleteModule::AutoCompleteModule() : +#if AUTO_COMPLETE_THREADED + AutoCompleteModule( ThreadPool::createShared( eemin( 2, Sys::getCPUCount() ) ) ) +#else + AutoCompleteModule( nullptr ) +#endif +{ +} + +AutoCompleteModule::AutoCompleteModule( std::shared_ptr pool ) : + mBoxPadding( PixelDensity::dpToPx( Rectf( 4, 4, 4, 4 ) ) ), mPool( pool ) {} + +AutoCompleteModule::~AutoCompleteModule() { + mClosing = true; + Lock l( mDocMutex ); + for ( auto editor : mEditors ) { + for ( auto listeners : editor.second ) + editor.first->removeEventListener( listeners ); + editor.first->unregisterModule( this ); + } +} + +void AutoCompleteModule::onRegister( UICodeEditor* editor ) { + Lock l( mDocMutex ); + std::vector listeners; + listeners.push_back( editor->addEventListener( Event::OnDocumentLoaded, + [&]( const Event* ) { mDirty = true; } ) ); + + listeners.push_back( + editor->addEventListener( Event::OnDocumentClosed, [&]( const Event* event ) { + Lock l( mDocMutex ); + const DocEvent* docEvent = static_cast( event ); + TextDocument* doc = docEvent->getDoc(); + mDocs.erase( doc ); + mDocCache.erase( doc ); + mDirty = true; + } ) ); + + listeners.push_back( + editor->addEventListener( Event::OnDocumentChanged, [&, editor]( const Event* ) { + TextDocument* oldDoc = mEditorDocs[editor]; + TextDocument* newDoc = editor->getDocumentRef().get(); + Lock l( mDocMutex ); + mDocs.erase( oldDoc ); + mDocCache.erase( oldDoc ); + mEditorDocs[editor] = newDoc; + mDirty = true; + } ) ); + + listeners.push_back( + editor->addEventListener( Event::OnCursorPosChange, [&, editor]( const Event* ) { + if ( !mReplacing ) + resetSuggestions( editor ); + } ) ); + + listeners.push_back( + editor->addEventListener( Event::OnDocumentSyntaxDefinitionChange, [&]( const Event* ev ) { + const DocSyntaxDefEvent* event = static_cast( ev ); + std::string oldLang = event->getOldLang(); + std::string newLang = event->getNewLang(); +#if AUTO_COMPLETE_THREADED + mPool->run( + [&, oldLang, newLang] { + updateLangCache( oldLang ); + updateLangCache( newLang ); + }, + [] {} ); +#else + updateLangCache( oldLang ); + updateLangCache( newLang ); +#endif + } ) ); + + mEditors.insert( {editor, listeners} ); + mDocs.insert( editor->getDocumentRef().get() ); + mEditorDocs[editor] = editor->getDocumentRef().get(); +} + +void AutoCompleteModule::onUnregister( UICodeEditor* editor ) { + if ( mClosing ) + return; + if ( mSuggestionsEditor == editor ) + resetSuggestions( editor ); + Lock l( mDocMutex ); + mEditors.erase( editor ); + mEditorDocs.erase( editor ); +} + +bool AutoCompleteModule::onKeyDown( UICodeEditor* editor, const KeyEvent& event ) { + if ( !mSuggestions.empty() ) { + int max = eemin( mSuggestionsMaxVisible, mSuggestions.size() ); + if ( event.getKeyCode() == KEY_DOWN ) { + if ( mSuggestionIndex + 1 < max ) { + mSuggestionIndex += 1; + } else { + mSuggestionIndex = 0; + } + editor->invalidateDraw(); + return true; + } else if ( event.getKeyCode() == KEY_UP ) { + if ( mSuggestionIndex - 1 < 0 ) { + mSuggestionIndex = max - 1; + } else { + mSuggestionIndex -= 1; + } + editor->invalidateDraw(); + return true; + } else if ( event.getKeyCode() == KEY_ESCAPE ) { + resetSuggestions( editor ); + editor->invalidateDraw(); + return true; + } else if ( event.getKeyCode() == KEY_HOME ) { + mSuggestionIndex = 0; + editor->invalidateDraw(); + return true; + } else if ( event.getKeyCode() == KEY_END ) { + mSuggestionIndex = max - 1; + editor->invalidateDraw(); + return true; + } else if ( event.getKeyCode() == KEY_PAGEUP ) { + mSuggestionIndex = eemax( mSuggestionIndex - (int)mSuggestionsMaxVisible, 0 ); + editor->invalidateDraw(); + return true; + } else if ( event.getKeyCode() == KEY_PAGEDOWN ) { + mSuggestionIndex = + eemin( mSuggestionIndex + (int)mSuggestionsMaxVisible, max - 1 ); + editor->invalidateDraw(); + return true; + } else if ( event.getKeyCode() == KEY_TAB || event.getKeyCode() == KEY_RETURN || + event.getKeyCode() == KEY_KP_ENTER ) { + pickSuggestion( editor ); + return true; + } + } else if ( event.getKeyCode() == KEY_SPACE && ( event.getMod() & KEYMOD_CTRL ) ) { + std::string partialSymbol( getPartialSymbol( &editor->getDocument() ) ); + if ( partialSymbol.size() >= 3 ) { + updateSuggestions( partialSymbol, editor ); + return true; + } + } + return false; +} + +bool AutoCompleteModule::onKeyUp( UICodeEditor*, const KeyEvent& ) { + return false; +} + +bool AutoCompleteModule::onTextInput( UICodeEditor* editor, const TextInputEvent& ) { + std::string partialSymbol( getPartialSymbol( &editor->getDocument() ) ); + if ( partialSymbol.size() >= 3 ) { + updateSuggestions( partialSymbol, editor ); + } else { + resetSuggestions( editor ); + } + return false; +} + +void AutoCompleteModule::updateDocCache( TextDocument* doc ) { + Lock l( mDocMutex ); + Clock clock; + auto docCache = mDocCache.find( doc ); + if ( docCache == mDocCache.end() ) + return; + auto& cache = docCache->second; + cache.changeId = doc->getCurrentChangeId(); + cache.symbols = getDocumentSymbols( doc ); + std::string langName( doc->getSyntaxDefinition().getLanguageName() ); + auto& lang = mLangCache[langName]; + { + Lock l( mLangSymbolsMutex ); + lang.clear(); + for ( auto d : mDocCache ) { + if ( d.first->getSyntaxDefinition().getLanguageName() == langName ) + lang.insert( d.second.symbols.begin(), d.second.symbols.end() ); + } + } + eePRINTL( "Dictionary for %s updated in: %.2fms", doc->getFilename().c_str(), + clock.getElapsedTime().asMilliseconds() ); +} + +void AutoCompleteModule::updateLangCache( const std::string& langName ) { + Clock clock; + auto& lang = mLangCache[langName]; + Lock l( mLangSymbolsMutex ); + lang.clear(); + for ( auto d : mDocCache ) { + if ( d.first->getSyntaxDefinition().getLanguageName() == langName ) + lang.insert( d.second.symbols.begin(), d.second.symbols.end() ); + } + eePRINTL( "Lang dictionary for %s updated in: %.2fms", langName.c_str(), + clock.getElapsedTime().asMilliseconds() ); +} + +void AutoCompleteModule::pickSuggestion( UICodeEditor* editor ) { + mReplacing = true; + editor->getDocument().execute( "delete-to-previous-word" ); + editor->getDocument().textInput( mSuggestions[mSuggestionIndex] ); + mReplacing = false; + resetSuggestions( editor ); +} + +std::string AutoCompleteModule::getPartialSymbol( TextDocument* doc ) { + TextPosition end = doc->getSelection().end(); + TextPosition start = doc->startOfWord( end ); + return doc->getText( {start, end} ).toUtf8(); +} + +void AutoCompleteModule::update( UICodeEditor* ) { + if ( mClock.getElapsedTime() >= mUpdateFreq || mDirty ) { + mClock.restart(); + mDirty = false; + Lock l( mDocMutex ); + for ( auto& doc : mDocs ) { + if ( mDocCache[doc].changeId != doc->getCurrentChangeId() ) { +#if AUTO_COMPLETE_THREADED + mPool->run( [&, doc] { updateDocCache( doc ); }, [] {} ); +#else + updateDocCache( doc ); +#endif + } + } + } +} + +void AutoCompleteModule::preDraw( UICodeEditor*, const Vector2f&, const Float&, + const TextPosition& ) {} + +void AutoCompleteModule::postDraw( UICodeEditor* editor, const Vector2f& startScroll, + const Float& lineHeight, const TextPosition& cursor ) { + std::vector suggestions; + { + Lock l( mSuggestionsMutex ); + if ( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor ) + return; + suggestions = mSuggestions; + } + + Primitives primitives; + TextPosition start = + editor->getDocument().startOfWord( editor->getDocument().startOfWord( cursor ) ); + Vector2f cursorPos( startScroll.x + editor->getXOffsetCol( start ), + startScroll.y + cursor.line() * lineHeight + lineHeight ); + size_t largestString = 0; + size_t max = eemin( mSuggestionsMaxVisible, suggestions.size() ); + const SyntaxColorScheme& scheme = editor->getColorScheme(); + mRowHeight = lineHeight + mBoxPadding.Top + mBoxPadding.Bottom; + const auto& normalStyle = scheme.getEditorSyntaxStyle( "suggestion" ); + const auto& selectedStyle = scheme.getEditorSyntaxStyle( "suggestion_selected" ); + if ( cursorPos.y + mRowHeight * max > editor->getPixelsSize().getHeight() ) + cursorPos.y -= lineHeight + mRowHeight * max; + for ( size_t i = 0; i < max; i++ ) + largestString = eemax( largestString, editor->getTextWidth( suggestions[i] ) ); + + mBoxRect = + Rectf( Vector2f( cursorPos.x, cursorPos.y ), + Sizef( largestString + mBoxPadding.Left + mBoxPadding.Right, mRowHeight * max ) ); + + for ( size_t i = 0; i < max; i++ ) { + Text text( "", editor->getFont(), editor->getFontSize() ); + text.setFillColor( mSuggestionIndex == (int)i ? selectedStyle.color : normalStyle.color ); + text.setStyle( mSuggestionIndex == (int)i ? selectedStyle.style : normalStyle.style ); + text.setString( suggestions[i] ); + primitives.setColor( + Color( mSuggestionIndex == (int)i ? selectedStyle.background : normalStyle.background ) + .blendAlpha( editor->getAlpha() ) ); + primitives.drawRectangle( + Rectf( Vector2f( cursorPos.x, cursorPos.y + mRowHeight * i ), + Sizef( largestString + mBoxPadding.Left + mBoxPadding.Right, mRowHeight ) ) ); + text.draw( cursorPos.x + mBoxPadding.Left, cursorPos.y + mRowHeight * i + mBoxPadding.Top ); + } +} + +bool AutoCompleteModule::onMouseDown( UICodeEditor* editor, const Vector2i& position, + const Uint32& flags ) { + if ( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor || + !( flags & EE_BUTTON_LMASK ) ) + return false; + + Vector2f localPos( editor->convertToNodeSpace( position.asFloat() ) ); + if ( mBoxRect.contains( localPos ) ) + return true; + return false; +} + +bool AutoCompleteModule::onMouseClick( UICodeEditor* editor, const Vector2i& position, + const Uint32& flags ) { + if ( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor || + !( flags & EE_BUTTON_LMASK ) ) + return false; + + Vector2f localPos( editor->convertToNodeSpace( position.asFloat() ) ); + if ( mBoxRect.contains( localPos ) ) { + localPos -= {mBoxRect.Left, mBoxRect.Top}; + mSuggestionIndex = localPos.y / mRowHeight; + editor->invalidateDraw(); + return true; + } + return false; +} + +bool AutoCompleteModule::onMouseDoubleClick( UICodeEditor* editor, const Vector2i& position, + const Uint32& flags ) { + if ( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor || + !( flags & EE_BUTTON_LMASK ) ) + return false; + + Vector2f localPos( editor->convertToNodeSpace( position.asFloat() ) ); + if ( mBoxRect.contains( localPos ) ) { + pickSuggestion( editor ); + return true; + } + return false; +} + +bool AutoCompleteModule::onMouseMove( UICodeEditor* editor, const Vector2i& position, + const Uint32& ) { + if ( mSuggestions.empty() || !mSuggestionsEditor || mSuggestionsEditor != editor ) + return false; + + Vector2f localPos( editor->convertToNodeSpace( position.asFloat() ) ); + if ( mBoxRect.contains( localPos ) ) + editor->getUISceneNode()->setCursor( Cursor::Hand ); + else + editor->getUISceneNode()->setCursor( !editor->isLocked() ? Cursor::IBeam : Cursor::Arrow ); + return false; +} + +const Rectf& AutoCompleteModule::getBoxPadding() const { + return mBoxPadding; +} + +void AutoCompleteModule::setBoxPadding( const Rectf& boxPadding ) { + mBoxPadding = boxPadding; +} + +const Uint32& AutoCompleteModule::getSuggestionsMaxVisible() const { + return mSuggestionsMaxVisible; +} + +void AutoCompleteModule::setSuggestionsMaxVisible( const Uint32& suggestionsMaxVisible ) { + mSuggestionsMaxVisible = suggestionsMaxVisible; +} + +const Time& AutoCompleteModule::getUpdateFreq() const { + return mUpdateFreq; +} + +void AutoCompleteModule::setUpdateFreq( const Time& updateFreq ) { + mUpdateFreq = updateFreq; +} + +void AutoCompleteModule::resetSuggestions( UICodeEditor* editor ) { + Lock l( mSuggestionsMutex ); + mSuggestionIndex = 0; + mSuggestionsEditor = nullptr; + mSuggestions.clear(); + editor->getUISceneNode()->setCursor( !editor->isLocked() ? Cursor::IBeam : Cursor::Arrow ); +} + +AutoCompleteModule::SymbolsList AutoCompleteModule::getDocumentSymbols( TextDocument* doc ) { + LuaPattern pattern( "[%a][%w_]*" ); + AutoCompleteModule::SymbolsList symbols; + Int64 lc = doc->linesCount(); + std::string current( getPartialSymbol( doc ) ); + TextPosition end = doc->getSelection().end(); + for ( Int64 i = 0; i < lc; i++ ) { + const auto& string = doc->line( i ).getText().toUtf8(); + for ( auto& match : pattern.gmatch( string ) ) { + // Ignore the symbol if is actually the current symbol being written + if ( end.line() == i && current == match[0] ) + continue; + symbols.insert( match[0] ); + } + } + return symbols; +} + +static std::vector fuzzyMatchSymbols( const AutoCompleteModule::SymbolsList& symbols, + const std::string& match, const size_t& max ) { + std::multimap> matchesMap; + std::vector matches; + int score; + for ( const auto& symbol : symbols ) { + if ( ( score = String::fuzzyMatch( symbol, match ) ) > 0 ) { + matchesMap.insert( {score, symbol} ); + } + } + for ( auto& res : matchesMap ) { + if ( matches.size() < max ) + matches.emplace_back( res.second ); + } + return matches; +} + +void AutoCompleteModule::runUpdateSuggestions( const std::string& symbol, + const SymbolsList& symbols, UICodeEditor* editor ) { + Lock l( mLangSymbolsMutex ); + Lock l2( mSuggestionsMutex ); + mSuggestions = fuzzyMatchSymbols( symbols, symbol, mSuggestionsMaxVisible ); + mSuggestionsEditor = editor; + editor->runOnMainThread( [editor] { editor->invalidateDraw(); } ); +} + +void AutoCompleteModule::updateSuggestions( const std::string& symbol, UICodeEditor* editor ) { + const std::string& lang = editor->getDocument().getSyntaxDefinition().getLanguageName(); + auto langSuggestions = mLangCache.find( lang ); + if ( langSuggestions == mLangCache.end() ) + return; + auto& symbols = langSuggestions->second; + { +#if AUTO_COMPLETE_THREADED + mPool->run( + [this, symbol, symbols, editor] { runUpdateSuggestions( symbol, symbols, editor ); }, + [] {} ); +#else + runUpdateSuggestions( symbol, symbols, editor ); +#endif + } +} diff --git a/src/tools/codeeditor/autocompletemodule.hpp b/src/tools/codeeditor/autocompletemodule.hpp new file mode 100644 index 000000000..36e663ec0 --- /dev/null +++ b/src/tools/codeeditor/autocompletemodule.hpp @@ -0,0 +1,99 @@ +#ifndef AUTOCOMPLETEMODULE_HPP +#define AUTOCOMPLETEMODULE_HPP + +#include +#include +#include +#include +#include +#include +#include +using namespace EE; +using namespace EE::System; +using namespace EE::UI; + +class AutoCompleteModule : public UICodeEditorModule { + public: + typedef std::unordered_set SymbolsList; + + AutoCompleteModule(); + + AutoCompleteModule( std::shared_ptr pool ); + + virtual ~AutoCompleteModule(); + + void onRegister( UICodeEditor* ); + void onUnregister( UICodeEditor* ); + bool onKeyDown( UICodeEditor*, const KeyEvent& ); + bool onKeyUp( UICodeEditor*, const KeyEvent& ); + bool onTextInput( UICodeEditor*, const TextInputEvent& ); + void update( UICodeEditor* ); + void preDraw( UICodeEditor*, const Vector2f&, const Float&, const TextPosition& ); + void postDraw( UICodeEditor*, const Vector2f& startScroll, const Float& lineHeight, + const TextPosition& cursor ); + bool onMouseDown( UICodeEditor*, const Vector2i&, const Uint32& ); + bool onMouseClick( UICodeEditor*, const Vector2i&, const Uint32& ); + bool onMouseDoubleClick( UICodeEditor*, const Vector2i&, const Uint32& ); + bool onMouseMove( UICodeEditor*, const Vector2i&, const Uint32& ); + + const Rectf& getBoxPadding() const; + + void setBoxPadding( const Rectf& boxPadding ); + + const Uint32& getSuggestionsMaxVisible() const; + + void setSuggestionsMaxVisible( const Uint32& suggestionsMaxVisible ); + + const Time& getUpdateFreq() const; + + void setUpdateFreq( const Time& updateFreq ); + + protected: + Rectf mBoxPadding; + std::shared_ptr mPool; + Clock mClock; + Mutex mLangSymbolsMutex; + Mutex mSuggestionsMutex; + Mutex mDocMutex; + Time mUpdateFreq{Seconds( 5 )}; + std::unordered_map> mEditors; + std::set mDocs; + std::unordered_map mEditorDocs; + bool mDirty{false}; + bool mClosing{false}; + bool mReplacing{false}; + struct DocCache { + Uint64 changeId{static_cast( -1 )}; + SymbolsList symbols; + }; + std::unordered_map mDocCache; + std::unordered_map mLangCache; + SymbolsList mLangDirty; + + int mSuggestionIndex{0}; + std::vector mSuggestions; + Uint32 mSuggestionsMaxVisible{6}; + UICodeEditor* mSuggestionsEditor{nullptr}; + + Float mRowHeight{0}; + Rectf mBoxRect; + + void resetSuggestions( UICodeEditor* editor ); + + void updateSuggestions( const std::string& symbol, UICodeEditor* editor ); + + SymbolsList getDocumentSymbols( TextDocument* ); + + void updateDocCache( TextDocument* doc ); + + std::string getPartialSymbol( TextDocument* doc ); + + void runUpdateSuggestions( const std::string& symbol, const SymbolsList& symbols, + UICodeEditor* editor ); + + void updateLangCache( const std::string& langName ); + + void pickSuggestion( UICodeEditor* editor ); +}; + +#endif // AUTOCOMPLETEMODULE_HPP diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index 6f4a8cad5..c80bdbe7a 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -1,4 +1,5 @@ #include "codeeditor.hpp" +#include "autocompletemodule.hpp" #include #include @@ -498,9 +499,12 @@ void App::onTextDropped( String text ) { } } +App::App() {} + App::~App() { saveConfig(); eeSAFE_DELETE( mEditorSplitter ); + eeSAFE_DELETE( mAutoCompleteModule ); eeSAFE_DELETE( mConsole ); } @@ -1044,6 +1048,10 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { editor->getKeyBindings().reset(); editor->getKeyBindings().addKeybindsString( mKeybindings ); } + + if ( !mAutoCompleteModule ) + mAutoCompleteModule = eeNew( AutoCompleteModule, () ); + editor->registerModule( mAutoCompleteModule ); } void App::createSettingsMenu() { diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index dd3cdd1e1..7a2523b2e 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -64,8 +64,12 @@ struct SearchState { } }; +class AutoCompleteModule; + class App : public UICodeEditorSplitter::Client { public: + App(); + ~App(); void init( const std::string& file, const Float& pidelDensity ); @@ -129,6 +133,7 @@ class App : public UICodeEditorSplitter::Client { SearchState mSearchState; Float mDisplayDPI; std::string mResPath; + AutoCompleteModule* mAutoCompleteModule{nullptr}; void onFileDropped( String file );