diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp index 8602b0540..5aade26e5 100644 --- a/include/eepp/ui/tools/uicodeeditorsplitter.hpp +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -16,28 +16,6 @@ class EE_API UICodeEditorSplitter { static const std::map getLocalDefaultKeybindings(); - struct CodeEditorConfig { - std::string colorScheme{"lite"}; - StyleSheetLength fontSize{12, StyleSheetLength::Dp}; - bool showLineNumbers{true}; - bool showWhiteSpaces{true}; - bool highlightMatchingBracket{true}; - bool horizontalScrollbar{false}; - bool highlightCurrentLine{true}; - bool trimTrailingWhitespaces{false}; - bool forceNewLineAtEndOfFile{false}; - bool autoDetectIndentType{true}; - bool writeUnicodeBOM{false}; - bool indentSpaces{false}; - bool windowsLineEndings{false}; - bool highlightSelectionMatch{true}; - bool colorPickerSelection{false}; - bool colorPreview{false}; - int indentWidth{4}; - int tabWidth{4}; - int lineBreakingColumn{100}; - }; - enum class SplitDirection { Left, Right, Top, Bottom }; class EE_API Client { @@ -57,8 +35,6 @@ class EE_API UICodeEditorSplitter { virtual void onColorSchemeChanged( const std::string& currentColorScheme ) = 0; virtual void onDocumentLoaded( UICodeEditor* codeEditor, const std::string& path ) = 0; - - virtual const CodeEditorConfig& getCodeEditorConfig() const = 0; }; static UICodeEditorSplitter* New( UICodeEditorSplitter::Client* client, UISceneNode* sceneNode, diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 55570b27d..6d37d9c91 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -539,6 +539,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void checkMouseOverColor( const Vector2i& position ); + void resetPreviewColor(); + void disableEditorFeatures(); Float getViewportWidth( const bool& forceVScroll = false ) const; diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index 1521ee43c..a3c71d7b7 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -96,34 +96,8 @@ UISplitter* UICodeEditorSplitter::splitterFromEditor( UICodeEditor* editor ) { } UICodeEditor* UICodeEditorSplitter::createCodeEditor() { - UICodeEditor* codeEditor = UICodeEditor::NewOpt( false, true ); - TextDocument& doc = codeEditor->getDocument(); - const CodeEditorConfig& editorConfig = mClient->getCodeEditorConfig(); - DisplayManager* displayManager = Engine::instance()->getDisplayManager(); - codeEditor->setFontSize( - editorConfig.fontSize.asDp( 0, Sizef(), displayManager->getDisplayIndex( 0 )->getDPI() ) ); - codeEditor->setEnableColorPickerOnSelection( true ); - codeEditor->setColorScheme( mColorSchemes[mCurrentColorScheme] ); - codeEditor->setShowLineNumber( editorConfig.showLineNumbers ); - codeEditor->setShowWhitespaces( editorConfig.showWhiteSpaces ); - codeEditor->setHighlightMatchingBracket( editorConfig.highlightMatchingBracket ); - codeEditor->setHorizontalScrollBarEnabled( editorConfig.horizontalScrollbar ); - codeEditor->setHighlightCurrentLine( editorConfig.highlightCurrentLine ); - codeEditor->setTabWidth( editorConfig.tabWidth ); - codeEditor->setLineBreakingColumn( editorConfig.lineBreakingColumn ); - codeEditor->setHighlightSelectionMatch( editorConfig.highlightSelectionMatch ); - codeEditor->setEnableColorPickerOnSelection( editorConfig.colorPickerSelection ); - codeEditor->setColorPreview( editorConfig.colorPreview ); - doc.setAutoDetectIndentType( editorConfig.autoDetectIndentType ); - doc.setLineEnding( editorConfig.windowsLineEndings ? TextDocument::LineEnding::CRLF - : TextDocument::LineEnding::LF ); - doc.setTrimTrailingWhitespaces( editorConfig.trimTrailingWhitespaces ); - doc.setIndentType( editorConfig.indentSpaces ? TextDocument::IndentType::IndentSpaces - : TextDocument::IndentType::IndentTabs ); - doc.setForceNewLineAtEndOfFile( editorConfig.forceNewLineAtEndOfFile ); - doc.setIndentWidth( editorConfig.indentWidth ); - doc.setBOM( editorConfig.writeUnicodeBOM ); - + UICodeEditor* editor = UICodeEditor::NewOpt( false, true ); + TextDocument& doc = editor->getDocument(); /* global commands */ doc.setCommand( "move-to-previous-line", [&] { if ( mCurEditor ) @@ -190,8 +164,8 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() { mClient->onDocumentStateChanged( mCurEditor, mCurEditor->getDocument() ); } } ); - codeEditor->addUnlockedCommand( "copy" ); - codeEditor->addUnlockedCommand( "select-all" ); + editor->addUnlockedCommand( "copy" ); + editor->addUnlockedCommand( "select-all" ); /* global commands */ doc.setCommand( "switch-to-previous-colorscheme", [&] { @@ -257,31 +231,31 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() { if ( UISplitter* splitter = splitterFromEditor( mCurEditor ) ) splitter->swap(); } ); - codeEditor->addEventListener( Event::OnFocus, [&]( const Event* event ) { + editor->addEventListener( Event::OnFocus, [&]( const Event* event ) { setCurrentEditor( event->getNode()->asType() ); } ); - codeEditor->addEventListener( Event::OnTextChanged, [&]( const Event* event ) { + editor->addEventListener( Event::OnTextChanged, [&]( const Event* event ) { mClient->onDocumentModified( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); - codeEditor->addEventListener( Event::OnSelectionChanged, [&]( const Event* event ) { + editor->addEventListener( Event::OnSelectionChanged, [&]( const Event* event ) { mClient->onDocumentSelectionChange( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); - codeEditor->addKeyBinds( getLocalDefaultKeybindings() ); - codeEditor->addUnlockedCommands( + editor->addKeyBinds( getLocalDefaultKeybindings() ); + editor->addUnlockedCommands( {"lock-toggle", "create-new", "close-doc", "next-doc", "previous-doc", "split-left", "split-right", "split-top", "split-bottom", "split-swap", "switch-to-previous-split", "switch-to-next-split", "switch-to-previous-colorscheme", "switch-to-next-colorscheme"} ); if ( nullptr == mCurEditor ) - setCurrentEditor( codeEditor ); + setCurrentEditor( editor ); - mClient->onCodeEditorCreated( codeEditor, doc ); + mClient->onCodeEditorCreated( editor, doc ); - return codeEditor; + return editor; } const std::string& UICodeEditorSplitter::getCurrentColorScheme() const { diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 03b6be621..2fe0ae88e 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -1620,16 +1620,18 @@ void UICodeEditor::setHighlightTextRange( const TextRange& highlightSelection ) void UICodeEditor::registerModule( UICodeEditorModule* module ) { auto it = std::find( mModules.begin(), mModules.end(), module ); - if ( it == mModules.end() ) + if ( it == mModules.end() ) { mModules.push_back( module ); - module->onRegister( this ); + module->onRegister( this ); + } } void UICodeEditor::unregisterModule( UICodeEditorModule* module ) { auto it = std::find( mModules.begin(), mModules.end(), module ); - if ( it != mModules.end() ) + if ( it != mModules.end() ) { mModules.erase( it ); - module->onUnregister( this ); + module->onUnregister( this ); + } } const Time& UICodeEditor::getFindLongestLineWidthUpdateFrequency() const { @@ -1880,8 +1882,12 @@ void UICodeEditor::checkMouseOverColor( const Vector2i& position ) { if ( !mColorPreview ) return; TextPosition pos( resolveScreenPosition( position.asFloat() ) ); + const String& line = mDoc->line( pos.line() ).getText(); + if ( pos.column() >= (Int64)line.size() - 1 ) { + resetPreviewColor(); + return; + } TextPosition start( mDoc->previousWordBoundary( pos ) ); - const String& line = mDoc->line( start.line() ).getText(); if ( start.column() > 0 && start.column() < (Int64)line.size() ) { TextPosition end( mDoc->nextWordBoundary( pos ) ); TextRange wordPos = {{start.line(), start.column() - 1}, end}; @@ -1911,15 +1917,17 @@ void UICodeEditor::checkMouseOverColor( const Vector2i& position ) { mPreviewColorRange = wordPos; invalidateDraw(); } else { - mPreviewColorRange = TextRange(); - mPreviewColor = Color::Transparent; - invalidateDraw(); + resetPreviewColor(); } } else if ( mPreviewColorRange.isValid() ) { - mPreviewColorRange = TextRange(); - mPreviewColor = Color::Transparent; - invalidateDraw(); + resetPreviewColor(); } } +void UICodeEditor::resetPreviewColor() { + mPreviewColorRange = TextRange(); + mPreviewColor = Color::Transparent; + invalidateDraw(); +} + }} // namespace EE::UI diff --git a/src/tools/codeeditor/autocompletemodule.cpp b/src/tools/codeeditor/autocompletemodule.cpp index 2955bda94..23fb289d0 100644 --- a/src/tools/codeeditor/autocompletemodule.cpp +++ b/src/tools/codeeditor/autocompletemodule.cpp @@ -23,14 +23,18 @@ AutoCompleteModule::AutoCompleteModule() : } AutoCompleteModule::AutoCompleteModule( std::shared_ptr pool ) : - mBoxPadding( PixelDensity::dpToPx( Rectf( 4, 4, 4, 4 ) ) ), mPool( pool ) {} + mSymbolPattern( "[%a][%w_]*" ), + mBoxPadding( PixelDensity::dpToPx( Rectf( 4, 4, 4, 4 ) ) ), + mPool( pool ) {} AutoCompleteModule::~AutoCompleteModule() { mClosing = true; Lock l( mDocMutex ); + Lock l2( mLangSymbolsMutex ); + Lock l3( mSuggestionsMutex ); for ( auto editor : mEditors ) { - for ( auto listeners : editor.second ) - editor.first->removeEventListener( listeners ); + for ( auto listener : editor.second ) + editor.first->removeEventListener( listener ); editor.first->unregisterModule( this ); } } @@ -89,6 +93,7 @@ void AutoCompleteModule::onRegister( UICodeEditor* editor ) { mEditors.insert( {editor, listeners} ); mDocs.insert( editor->getDocumentRef().get() ); mEditorDocs[editor] = editor->getDocumentRef().get(); + mDirty = true; } void AutoCompleteModule::onUnregister( UICodeEditor* editor ) { @@ -97,8 +102,18 @@ void AutoCompleteModule::onUnregister( UICodeEditor* editor ) { if ( mSuggestionsEditor == editor ) resetSuggestions( editor ); Lock l( mDocMutex ); + TextDocument* doc = mEditorDocs[editor]; + auto cbs = mEditors[editor]; + for ( auto listener : cbs ) + editor->removeEventListener( listener ); mEditors.erase( editor ); mEditorDocs.erase( editor ); + for ( auto editor : mEditorDocs ) + if ( editor.second == doc ) + return; + mDocs.erase( doc ); + mDocCache.erase( doc ); + mDirty = true; } bool AutoCompleteModule::onKeyDown( UICodeEditor* editor, const KeyEvent& event ) { @@ -197,6 +212,7 @@ void AutoCompleteModule::updateLangCache( const std::string& langName ) { Clock clock; auto& lang = mLangCache[langName]; Lock l( mLangSymbolsMutex ); + Lock l2( mDocMutex ); lang.clear(); for ( auto d : mDocCache ) { if ( d.first->getSyntaxDefinition().getLanguageName() == langName ) @@ -364,16 +380,35 @@ void AutoCompleteModule::setUpdateFreq( const Time& updateFreq ) { mUpdateFreq = updateFreq; } +const std::string& AutoCompleteModule::getSymbolPattern() const { + return mSymbolPattern; +} + +void AutoCompleteModule::setSymbolPattern( const std::string& symbolPattern ) { + mSymbolPattern = symbolPattern; +} + +bool AutoCompleteModule::isDirty() const +{ + return mDirty; +} + +void AutoCompleteModule::setDirty(bool dirty) +{ + mDirty = dirty; +} + void AutoCompleteModule::resetSuggestions( UICodeEditor* editor ) { Lock l( mSuggestionsMutex ); mSuggestionIndex = 0; mSuggestionsEditor = nullptr; mSuggestions.clear(); - editor->getUISceneNode()->setCursor( !editor->isLocked() ? Cursor::IBeam : Cursor::Arrow ); + if ( editor ) + editor->getUISceneNode()->setCursor( !editor->isLocked() ? Cursor::IBeam : Cursor::Arrow ); } AutoCompleteModule::SymbolsList AutoCompleteModule::getDocumentSymbols( TextDocument* doc ) { - LuaPattern pattern( "[%a][%w_]*" ); + LuaPattern pattern( mSymbolPattern ); AutoCompleteModule::SymbolsList symbols; Int64 lc = doc->linesCount(); std::string current( getPartialSymbol( doc ) ); diff --git a/src/tools/codeeditor/autocompletemodule.hpp b/src/tools/codeeditor/autocompletemodule.hpp index 36e663ec0..8c2d8ba91 100644 --- a/src/tools/codeeditor/autocompletemodule.hpp +++ b/src/tools/codeeditor/autocompletemodule.hpp @@ -48,7 +48,16 @@ class AutoCompleteModule : public UICodeEditorModule { void setUpdateFreq( const Time& updateFreq ); + const std::string& getSymbolPattern() const; + + void setSymbolPattern( const std::string& symbolPattern ); + + bool isDirty() const; + + void setDirty( bool dirty ); + protected: + std::string mSymbolPattern; Rectf mBoxPadding; std::shared_ptr mPool; Clock mClock; @@ -72,7 +81,7 @@ class AutoCompleteModule : public UICodeEditorModule { int mSuggestionIndex{0}; std::vector mSuggestions; - Uint32 mSuggestionsMaxVisible{6}; + Uint32 mSuggestionsMaxVisible{8}; UICodeEditor* mSuggestionsEditor{nullptr}; Float mRowHeight{0}; diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index 7c3ea9410..4af1b18a5 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -283,6 +283,7 @@ void App::loadConfig() { mConfig.editor.colorPickerSelection = mIni.getValueB( "editor", "color_picker_selection", true ); mConfig.editor.colorPreview = mIni.getValueB( "editor", "color_preview", true ); + mConfig.editor.autoComplete = mIni.getValueB( "editor", "auto_complete", true ); } void App::saveConfig() { @@ -316,6 +317,7 @@ void App::saveConfig() { mIni.setValueB( "editor", "highlight_selection_match", mConfig.editor.highlightSelectionMatch ); mIni.setValueB( "editor", "color_picker_selection", mConfig.editor.colorPickerSelection ); mIni.setValueB( "editor", "color_preview", mConfig.editor.colorPreview ); + mIni.setValueB( "editor", "auto_complete", mConfig.editor.autoComplete ); mIni.writeFile(); mIniState.writeFile(); } @@ -564,6 +566,10 @@ UIMenu* App::createViewMenu() { ->setActive( mConfig.editor.colorPickerSelection ) ->setTooltipText( "Enables the color picker tool when a double click selection\n" "is done over a word representing a color." ); + mViewMenu->addCheckBox( "Enable Auto Complete" ) + ->setActive( mConfig.editor.autoComplete ) + ->setTooltipText( "Auto complete shows the completion popup as you type, so you can fill\n" + "in long words by typing only a few characters." ); mViewMenu->addSeparator(); mViewMenu->add( "Editor Font Size", findIcon( "font-size" ) ); mViewMenu->add( "UI Font Size", findIcon( "font-size" ) ); @@ -620,6 +626,8 @@ UIMenu* App::createViewMenu() { mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { editor->setEnableColorPickerOnSelection( mConfig.editor.colorPickerSelection ); } ); + } else if ( item->getText() == "Enable Auto Complete" ) { + setAutoComplete( item->asType()->isActive() ); } else if ( item->getText() == "Enable Color Preview" ) { mConfig.editor.colorPreview = item->asType()->isActive(); mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { @@ -977,7 +985,7 @@ void App::onDocumentLoaded( UICodeEditor* codeEditor, const std::string& path ) updateRecentFiles(); } -const UICodeEditorSplitter::CodeEditorConfig& App::getCodeEditorConfig() const { +const CodeEditorConfig& App::getCodeEditorConfig() const { return mConfig.editor; } @@ -1003,6 +1011,31 @@ std::map App::getLocalKeybindings() { } void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { + const CodeEditorConfig& config = mConfig.editor; + editor->setFontSize( config.fontSize.asDp( 0, Sizef(), mUISceneNode->getDPI() ) ); + editor->setEnableColorPickerOnSelection( true ); + editor->setColorScheme( + mEditorSplitter->getColorSchemes().at( mEditorSplitter->getCurrentColorScheme() ) ); + editor->setShowLineNumber( config.showLineNumbers ); + editor->setShowWhitespaces( config.showWhiteSpaces ); + editor->setHighlightMatchingBracket( config.highlightMatchingBracket ); + editor->setHorizontalScrollBarEnabled( config.horizontalScrollbar ); + editor->setHighlightCurrentLine( config.highlightCurrentLine ); + editor->setTabWidth( config.tabWidth ); + editor->setLineBreakingColumn( config.lineBreakingColumn ); + editor->setHighlightSelectionMatch( config.highlightSelectionMatch ); + editor->setEnableColorPickerOnSelection( config.colorPickerSelection ); + editor->setColorPreview( config.colorPreview ); + doc.setAutoDetectIndentType( config.autoDetectIndentType ); + doc.setLineEnding( config.windowsLineEndings ? TextDocument::LineEnding::CRLF + : TextDocument::LineEnding::LF ); + doc.setTrimTrailingWhitespaces( config.trimTrailingWhitespaces ); + doc.setIndentType( config.indentSpaces ? TextDocument::IndentType::IndentSpaces + : TextDocument::IndentType::IndentTabs ); + doc.setForceNewLineAtEndOfFile( config.forceNewLineAtEndOfFile ); + doc.setIndentWidth( config.indentWidth ); + doc.setBOM( config.writeUnicodeBOM ); + editor->addKeyBinds( getLocalKeybindings() ); editor->addUnlockedCommands( {"fullscreen-toggle", "open-file", "console-toggle", "close-app"} ); @@ -1072,9 +1105,25 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { editor->getKeyBindings().addKeybindsString( mKeybindings ); } - if ( !mAutoCompleteModule ) + if ( config.autoComplete && !mAutoCompleteModule ) + setAutoComplete( config.autoComplete ); + + if ( config.autoComplete && mAutoCompleteModule ) + editor->registerModule( mAutoCompleteModule ); +} + +bool App::setAutoComplete( bool enable ) { + mConfig.editor.autoComplete = enable; + if ( enable && !mAutoCompleteModule ) { mAutoCompleteModule = eeNew( AutoCompleteModule, () ); - editor->registerModule( mAutoCompleteModule ); + mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { + editor->registerModule( mAutoCompleteModule ); + } ); + return true; + } + if ( !enable && mAutoCompleteModule ) + eeSAFE_DELETE( mAutoCompleteModule ); + return false; } void App::createSettingsMenu() { diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index 7a2523b2e..7b8b4d868 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -46,9 +46,32 @@ struct WindowConfig { bool maximized{false}; }; +struct CodeEditorConfig { + std::string colorScheme{"lite"}; + StyleSheetLength fontSize{12, StyleSheetLength::Dp}; + bool showLineNumbers{true}; + bool showWhiteSpaces{true}; + bool highlightMatchingBracket{true}; + bool horizontalScrollbar{false}; + bool highlightCurrentLine{true}; + bool trimTrailingWhitespaces{false}; + bool forceNewLineAtEndOfFile{false}; + bool autoDetectIndentType{true}; + bool writeUnicodeBOM{false}; + bool indentSpaces{false}; + bool windowsLineEndings{false}; + bool highlightSelectionMatch{true}; + bool colorPickerSelection{false}; + bool colorPreview{false}; + bool autoComplete{true}; + int indentWidth{4}; + int tabWidth{4}; + int lineBreakingColumn{100}; +}; + struct AppConfig { WindowConfig window; - UICodeEditorSplitter::CodeEditorConfig editor; + CodeEditorConfig editor; UIConfig ui; }; @@ -191,13 +214,15 @@ class App : public UICodeEditorSplitter::Client { void onDocumentLoaded( UICodeEditor* codeEditor, const std::string& path ); - const UICodeEditorSplitter::CodeEditorConfig& getCodeEditorConfig() const; + const CodeEditorConfig& getCodeEditorConfig() const; void onCodeEditorCreated( UICodeEditor*, TextDocument& doc ); void onDocumentSelectionChange( UICodeEditor* editor, TextDocument& ); void onCodeEditorFocusChange( UICodeEditor* editor ); + + bool setAutoComplete( bool enable ); }; #endif // EE_TOOLS_CODEEDITOR_HPP