diff --git a/bin/assets/colorschemes/colorschemes.conf b/bin/assets/colorschemes/colorschemes.conf index 8202611d8..51c7bb87a 100644 --- a/bin/assets/colorschemes/colorschemes.conf +++ b/bin/assets/colorschemes/colorschemes.conf @@ -10,7 +10,8 @@ line_number_background = #2e2e32 whitespace = #54575b line_break_column = #54575b99 matching_bracket = #FFFFFF33 -matching_selection = #FFFFFF33 +matching_selection = #FFFFFF66 +matching_search = #181b1e normal = #e1e1e6 symbol = #e1e1e6 @@ -28,7 +29,7 @@ link = #93DDFA,underline background = #282a36 text = #e1e1e6 caret = #93DDFA -selection = #4c5163 +selection = #394484 line_highlight = #2d303d line_number = #525259 line_number2 = #83838f @@ -36,7 +37,8 @@ line_number_background = #282a36 whitespace = #54575b line_break_column = #54575b99 matching_bracket = #FFFFFF33 -matching_selection = #FFFFFF33 +matching_selection = #3e596e +matching_search = #181b1e normal = #e1e1e6 symbol = #e1e1e6 @@ -51,132 +53,132 @@ function = #00dc7f,shadow link = #6ae0f9,shadow,underline,#FFFFFF11 [fall] -background = #343233 -text = #c4b398 -caret = #61efce -accent = #ffd152 -selection = #454244 -line_number = #454244 -line_number2 = #615d5f -line_highlight = #383637 +background = #343233 +text = #c4b398 +caret = #61efce +accent = #ffd152 +selection = #454244 +line_number = #454244 +line_number2 = #615d5f +line_highlight = #383637 -normal = #efdab9 -symbol = #efdab9 -comment = #615d5f -keyword = #d36e2d -keyword2 = #ef6179 -number = #ffd152 -literal = #ffd152 -string = #ffd152 -operator = #efdab9 -function = #61efce +normal = #efdab9 +symbol = #efdab9 +comment = #615d5f +keyword = #d36e2d +keyword2 = #ef6179 +number = #ffd152 +literal = #ffd152 +string = #ffd152 +operator = #efdab9 +function = #61efce [summer] -background = #fbfbfb -text = #404040 -caret = #fc1785 -accent = #fc1785 -selection = #b7dce8 -line_number = #d0d0d0 -line_number2 = #808080 -line_highlight = #f2f2f2 +background = #fbfbfb +text = #404040 +caret = #fc1785 +accent = #fc1785 +selection = #b7dce8 +line_number = #d0d0d0 +line_number2 = #808080 +line_highlight = #f2f2f2 -normal = #181818 -symbol = #181818 -comment = #22a21f -keyword = #fb6620 -keyword2 = #fc1785 -number = #1586d2 -literal = #1586d2 -string = #1586d2 -operator = #fb6620 -function = #fc1785 +normal = #181818 +symbol = #181818 +comment = #22a21f +keyword = #fb6620 +keyword2 = #fc1785 +number = #1586d2 +literal = #1586d2 +string = #1586d2 +operator = #fb6620 +function = #fc1785 [dracula] -background = #282a36 -text = #7b81a6 -caret = #f8f8f0 -accent = #8be9fd -selection = #44475a -line_number = #53576e -line_number2 = #f8f8f0 -line_highlight = #313442 +background = #282a36 +text = #7b81a6 +caret = #f8f8f0 +accent = #8be9fd +selection = #44475a +line_number = #53576e +line_number2 = #f8f8f0 +line_highlight = #313442 -normal = #f8f8f2 -symbol = #f8f8f2 -comment = #6272a4 -keyword = #ff79c6 -keyword2 = #ff79c6 -number = #bd93f9 -literal = #f1fa8c -string = #f1fa8c -operator = #ff79c6 -function = #50fa7b +normal = #f8f8f2 +symbol = #f8f8f2 +comment = #6272a4 +keyword = #ff79c6 +keyword2 = #ff79c6 +number = #bd93f9 +literal = #f1fa8c +string = #f1fa8c +operator = #ff79c6 +function = #50fa7b [gruvbox dark] -background = #282828 -text = #928374 -caret = #fbf1c7 -accent = #ebdbb2 -selection = #3c3836 -line_number = #928374 -line_number2 = #ebdbb2 -line_highlight = #32302f +background = #282828 +text = #928374 +caret = #fbf1c7 +accent = #ebdbb2 +selection = #3c3836 +line_number = #928374 +line_number2 = #ebdbb2 +line_highlight = #32302f -normal = #ebdbb2 -symbol = #ebdbb2 -comment = #928374 -keyword = #fb4934 -keyword2 = #83a598 -number = #d3869b -literal = #d3869b -string = #b8bb26 -operator = #ebdbb2 -function = #8ec07c +normal = #ebdbb2 +symbol = #ebdbb2 +comment = #928374 +keyword = #fb4934 +keyword2 = #83a598 +number = #d3869b +literal = #d3869b +string = #b8bb26 +operator = #ebdbb2 +function = #8ec07c [liqube] -background = #13171e -text = #abb2bf -caret = #abb2bf -accent = #ffffff -selection = #3e4451 -line_number = #323641 -line_number2 = #596275 -line_highlight = #1c1f25 -whitespace = #1c1f25 +background = #13171e +text = #abb2bf +caret = #abb2bf +accent = #ffffff +selection = #3e4451 +line_number = #323641 +line_number2 = #596275 +line_highlight = #1c1f25 +whitespace = #1c1f25 -normal = #abb2bf -symbol = #71a9d7 -comment = #5c6370 -keyword = #98c875 -keyword2 = #ffffff -number = #ffffff -literal = #ea5964 -string = #ea5964 -operator = #657085 -function = #ffffff -preprocessor = #98c875 +normal = #abb2bf +symbol = #71a9d7 +comment = #5c6370 +keyword = #98c875 +keyword2 = #ffffff +number = #ffffff +literal = #ea5964 +string = #ea5964 +operator = #657085 +function = #ffffff +preprocessor = #98c875 [monodark] -background = #080808 -text = #707070 -caret = #ffffff -accent = #d0d0d0 -selection = #242424 -line_number = #202020 -line_number2 = #707070 -line_highlight = #101010 +background = #080808 +text = #707070 +caret = #ffffff +accent = #d0d0d0 +selection = #242424 +line_number = #202020 +line_number2 = #707070 +line_highlight = #101010 -normal = #a0a0a0 -symbol = #a0a0a0 -comment = #404040 -keyword = #f0f0f0 -keyword2 = #f0f0f0 -number = #f0f0f0 -literal = #f0f0f0 -string = #f0f0f0 -operator = #f0f0f0 -function = #a0a0a0 +normal = #a0a0a0 +symbol = #a0a0a0 +comment = #404040 +keyword = #f0f0f0 +keyword2 = #f0f0f0 +number = #f0f0f0 +literal = #f0f0f0 +string = #f0f0f0 +operator = #f0f0f0 +function = #a0a0a0 [monokai] background = #272822 @@ -200,64 +202,64 @@ operator = #F8F8F2 function = #A6E22E [winter] -background = #282a36 -text = #aab3e6 -caret = #f5faff -accent = #ffb86c -selection = #4c5163 -line_number = #44475a -line_number2 = #717796 -line_highlight = #2d303d +background = #282a36 +text = #aab3e6 +caret = #f5faff +accent = #ffb86c +selection = #4c5163 +line_number = #44475a +line_number2 = #717796 +line_highlight = #2d303d -normal = #f5faff -symbol = #f5faff -comment = #6272a4 -keyword = #ff79c6 -keyword2 = #8be9fd -number = #bd93f9 -literal = #bd93f9 -string = #f1fa8c -operator = #ff79c6 -function = #8be9fd +normal = #f5faff +symbol = #f5faff +comment = #6272a4 +keyword = #ff79c6 +keyword2 = #8be9fd +number = #bd93f9 +literal = #bd93f9 +string = #f1fa8c +operator = #ff79c6 +function = #8be9fd [github] -background = #fbfbfb -text = #404040 -caret = #181818 -accent = #0366d6 -selection = #b7dce8 -line_number = #d0d0d0 -line_number2 = #808080 -line_highlight = #f2f2f2 +background = #fbfbfb +text = #404040 +caret = #181818 +accent = #0366d6 +selection = #b7dce8 +line_number = #d0d0d0 +line_number2 = #808080 +line_highlight = #f2f2f2 -normal = #24292e -symbol = #24292e -comment = #6a737d -keyword = #d73a49 -keyword2 = #d73a49 -number = #005cc5 -literal = #005cc5 -string = #032f62 -operator = #d73a49 -function = #005cc5 +normal = #24292e +symbol = #24292e +comment = #6a737d +keyword = #d73a49 +keyword2 = #d73a49 +number = #005cc5 +literal = #005cc5 +string = #032f62 +operator = #d73a49 +function = #005cc5 [solarized light] -background = #fdf6e3 -text = #657b83 -caret = #657b83 -accent = #002b36 -selection = #eee8d5 -line_number = #93a1a1 -line_number2 = #002b36 -line_highlight = #eee8d5 +background = #fdf6e3 +text = #657b83 +caret = #657b83 +accent = #002b36 +selection = #eee8d5 +line_number = #93a1a1 +line_number2 = #002b36 +line_highlight = #eee8d5 -normal = #657b83 -symbol = #657b83 -comment = #93a1a1 -keyword = #859900 -keyword2 = #268bd2 -number = #d33682 -literal = #2aa198 -string = #2aa198 -operator = #859900 -function = #268bd2 +normal = #657b83 +symbol = #657b83 +comment = #93a1a1 +keyword = #859900 +keyword2 = #268bd2 +number = #d33682 +literal = #2aa198 +string = #2aa198 +operator = #859900 +function = #268bd2 diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index 39aef5056..68994eb57 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -759,6 +759,8 @@ class EE_API String { String& toUpper(); + StringBaseType lastChar() const; + private: friend EE_API bool operator==( const String& left, const String& right ); friend EE_API bool operator<( const String& left, const String& right ); diff --git a/include/eepp/system/inifile.hpp b/include/eepp/system/inifile.hpp index a84f05bec..bbb564868 100644 --- a/include/eepp/system/inifile.hpp +++ b/include/eepp/system/inifile.hpp @@ -17,6 +17,7 @@ #define CINIFILE_H #include +#include #include #define MAX_KEYNAME 128 @@ -210,6 +211,10 @@ class EE_API IniFile { /** Delete all header comments. */ void deleteHeaderComments() { mComments.clear(); } + std::map getKeyMap( const unsigned & keyID ) const; + + std::map getKeyMap( const std::string& keyname ) const; + /** Key comment functions. ** Key comments are those comments within a key. Any comments ** defined within value Names will be added to this list. Therefore, diff --git a/include/eepp/ui.hpp b/include/eepp/ui.hpp index 002b108fd..b50963368 100644 --- a/include/eepp/ui.hpp +++ b/include/eepp/ui.hpp @@ -1,8 +1,6 @@ #ifndef EEPP_UI_HPP #define EEPP_UI_HPP -#include -#include #include #include #include @@ -51,9 +49,8 @@ #include #include #include -#include - #include +#include #include #include @@ -66,4 +63,8 @@ #include #include +#include +#include +#include + #endif diff --git a/include/eepp/ui/doc/syntaxcolorscheme.hpp b/include/eepp/ui/doc/syntaxcolorscheme.hpp index e4873ac9f..8123d860e 100644 --- a/include/eepp/ui/doc/syntaxcolorscheme.hpp +++ b/include/eepp/ui/doc/syntaxcolorscheme.hpp @@ -14,12 +14,15 @@ namespace EE { namespace UI { namespace Doc { /** * Syntax colors types accepted/used are: * "normal", "symbol", "comment", "keyword", "keyword2", - * "number", "literal", "string", "operator", "function" + * "number", "literal", "string", "operator", "function", + * "link" * * Editor colors types accepted/used are: * "background", "text", "caret" * "selection", "line_number_background", - * "line_number", "line_number2", "line_highlight" + * "line_number", "line_number2", "line_highlight", + * "line_number_background", "whitespace", "line_break_column", + * "matching_bracket", "matching_selection" * * 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 0f811ef32..cfc4f2fde 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -126,6 +126,8 @@ class EE_API TextDocument { TextPosition endOfDoc() const; + TextRange getDocRange() const; + void deleteTo( TextPosition position ); void deleteTo( int offset ); @@ -232,15 +234,17 @@ class EE_API TextDocument { void setCommand( const std::string& command, DocumentCommand func ); - TextPosition find( String text, TextPosition from = {0, 0}, const bool& caseSensitive = true ); + TextPosition find( String text, TextPosition from = {0, 0}, const bool& caseSensitive = true, + TextRange restrictRange = TextRange() ); TextPosition findLast( String text, TextPosition from = {0, 0}, - const bool& caseSensitive = true ); + const bool& caseSensitive = true, + TextRange restrictRange = TextRange() ); TextPosition replaceSelection( const String& replace ); TextPosition replace( String search, const String& replace, TextPosition from = {0, 0}, - const bool& caseSensitive = true ); + const bool& caseSensitive = true, TextRange restrictRange = TextRange() ); String getIndentString(); @@ -312,6 +316,8 @@ class EE_API TextDocument { bool getBOM() const; + TextRange sanitizeRange( const TextRange& range ); + protected: friend class UndoStack; UndoStack mUndoStack; diff --git a/include/eepp/ui/doc/textposition.hpp b/include/eepp/ui/doc/textposition.hpp index a4f1e08c2..38beadfe9 100644 --- a/include/eepp/ui/doc/textposition.hpp +++ b/include/eepp/ui/doc/textposition.hpp @@ -35,6 +35,10 @@ class EE_API TextPosition { return mLine < other.mLine || ( mLine == other.mLine && mColumn < other.mColumn ); } + bool operator>( const TextPosition& other ) const { + return mLine > other.mLine || ( mLine == other.mLine && mColumn > other.mColumn ); + } + TextPosition operator+( const TextPosition& other ) const { return {mLine + other.line(), mColumn + other.column()}; } diff --git a/include/eepp/ui/doc/textrange.hpp b/include/eepp/ui/doc/textrange.hpp index 708aac283..ac01a4d31 100644 --- a/include/eepp/ui/doc/textrange.hpp +++ b/include/eepp/ui/doc/textrange.hpp @@ -54,6 +54,14 @@ class EE_API TextRange { return true; } + bool hasSelection() const { + return isValid() && mStart != mEnd; + } + + bool inSameLine() const { + return isValid() && mStart.line() == mEnd.line(); + } + std::string toString() { return String::format( "%s - %s", mStart.toString().c_str(), mEnd.toString().c_str() ); } diff --git a/include/eepp/ui/keyboardshortcut.hpp b/include/eepp/ui/keyboardshortcut.hpp index 9c07a84d3..da5f9f841 100644 --- a/include/eepp/ui/keyboardshortcut.hpp +++ b/include/eepp/ui/keyboardshortcut.hpp @@ -16,6 +16,8 @@ namespace EE { namespace UI { class UIWidget; +typedef std::map ShortcutMap; + class EE_API KeyBindings { public: struct Shortcut { @@ -58,10 +60,14 @@ class EE_API KeyBindings { std::string getCommandFromKeyBind( const Shortcut& keys ); + void reset(); + + const ShortcutMap& getShortcutMap() const; + + std::string getShortcutString( Shortcut shortcut ); + protected: const Window::Input* mInput; - /** Map first keys, then Mods (Shortcut) to get the command */ - typedef std::map ShortcutMap; ShortcutMap mShortcuts; }; diff --git a/include/eepp/ui/tools/uicodeeditorsplitter.hpp b/include/eepp/ui/tools/uicodeeditorsplitter.hpp new file mode 100644 index 000000000..e1f5e931c --- /dev/null +++ b/include/eepp/ui/tools/uicodeeditorsplitter.hpp @@ -0,0 +1,143 @@ +#ifndef EE_UI_TOOLS_UICODEEDITORSPLITTER_HPP +#define EE_UI_TOOLS_UICODEEDITORSPLITTER_HPP + +#include +#include +#include +#include + +using namespace EE::UI::Doc; + +namespace EE { namespace UI { namespace Tools { + +class EE_API UICodeEditorSplitter { + public: + struct CodeEditorConfig { + std::string colorScheme{"lite"}; + Float fontSize{11}; + 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}; + int indentWidth{4}; + int tabWidth{4}; + int lineBreakingColumn{100}; + }; + + enum class SplitDirection { Left, Right, Top, Bottom }; + + class EE_API Client { + public: + virtual ~Client(){}; + + virtual void onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) = 0; + + virtual void onCodeEditorFocusChange( UICodeEditor* editor ) = 0; + + virtual void onDocumentStateChanged( UICodeEditor* editor, TextDocument& doc ) = 0; + + virtual void onDocumentModified( UICodeEditor* editor, TextDocument& doc ) = 0; + + virtual void onDocumentSelectionChange( UICodeEditor* editor, TextDocument& doc ) = 0; + + 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, + const std::vector& colorSchemes = {}, + const std::string& initColorScheme = "" ); + + virtual ~UICodeEditorSplitter(); + + virtual bool tryTabClose( UICodeEditor* editor ); + + void closeEditorTab( UICodeEditor* editor ); + + void splitEditor( const SplitDirection& direction, UICodeEditor* editor ); + + void switchToTab( Int32 index ); + + UITabWidget* findPreviousSplit( UICodeEditor* editor ); + + void switchPreviousSplit( UICodeEditor* editor ); + + UITabWidget* findNextSplit( UICodeEditor* editor ); + + void switchNextSplit( UICodeEditor* editor ); + + void setCurrentEditor( UICodeEditor* editor ); + + UITabWidget* tabWidgetFromEditor( UICodeEditor* editor ); + + UISplitter* splitterFromEditor( UICodeEditor* editor ); + + std::pair createCodeEditorInTabWidget( UITabWidget* tabWidget ); + + UICodeEditor* createCodeEditor(); + + void focusSomeEditor( Node* searchFrom = nullptr ); + + bool loadFileFromPath( const std::string& path, UICodeEditor* codeEditor = nullptr ); + + void loadFileFromPathInNewTab( const std::string& path ); + + void removeUnusedTab( UITabWidget* tabWidget ); + + UITabWidget* createEditorWithTabWidget( Node* parent ); + + void applyColorScheme( const SyntaxColorScheme& colorScheme ); + + void forEachEditor( std::function run ); + + void zoomIn(); + + void zoomOut(); + + void zoomReset(); + + void closeSplitter( UISplitter* splitter ); + + void addRemainingTabWidgets( Node* widget ); + + void closeTabWidgets( UISplitter* splitter ); + + UICodeEditor* getCurEditor() const; + + const std::string& getCurrentColorScheme() const; + + void setColorScheme( const std::string& name ); + + const std::map& getColorSchemes() const; + + bool editorExists( UICodeEditor* editor ) const; + + protected: + UISceneNode* mUISceneNode{nullptr}; + UICodeEditor* mCurEditor{nullptr}; + std::map mColorSchemes; + std::string mCurrentColorScheme; + std::vector mTabWidgets; + Client* mClient; + + UICodeEditorSplitter( UICodeEditorSplitter::Client* client, UISceneNode* sceneNode, + const std::vector& colorSchemes, + const std::string& initColorScheme ); + + virtual void onTabClosed( const TabEvent* tabEvent ); +}; + +}}} // namespace EE::UI::Tools + +#endif // EE_UI_TOOLS_UICODEEDITORSPLITTER_HPP diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 71baeac10..f5ad3b8cb 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -275,6 +275,14 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void setShowWhitespaces( const bool& showWhitespaces ); + const String& getHighlightWord() const; + + void setHighlightWord( const String& highlightWord ); + + const TextRange& getHighlightTextRange() const; + + void setHighlightTextRange( const TextRange& highlightSelection ); + protected: struct LastXOffset { TextPosition position; @@ -325,6 +333,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Float mLongestLineWidth{0}; Time mFindLongestLineWidthUpdateFrequency; Clock mLongestLineWidthLastUpdate; + String mHighlightWord; + TextRange mHighlightTextRange; UICodeEditor( const std::string& elementTag, const bool& autoRegisterBaseCommands = true, const bool& autoRegisterBaseKeybindings = true ); @@ -425,14 +435,18 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { virtual void drawSelectionMatch( const std::pair& lineRange, const Vector2f& startScroll, const Float& lineHeight ); + virtual void drawWordMatch( const String& text, const std::pair& lineRange, + const Vector2f& startScroll, const Float& lineHeight ); + virtual void drawLineText( const Int64& index, Vector2f position, const Float& fontSize, const Float& lineHeight ); virtual void drawWhitespaces( const std::pair& lineRange, const Vector2f& startScroll, const Float& lineHeight ); - virtual void drawSelection( const std::pair& lineRange, const Vector2f& startScroll, - const Float& lineHeight ); + virtual void drawTextRange( const TextRange& range, const std::pair& lineRange, + const Vector2f& startScroll, const Float& lineHeight, + const Color& backgrundColor ); virtual void drawLineNumbers( const std::pair& lineRange, const Vector2f& startScroll, const Vector2f& screenStart, const Float& lineHeight, diff --git a/projects/linux/ee.files b/projects/linux/ee.files index 3a2b16c44..04846f253 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -332,6 +332,7 @@ ../../include/eepp/ui/keyboardshortcut.hpp ../../include/eepp/ui/marginmove/scale.hpp ../../include/eepp/ui/tools/textureatlaseditor.hpp +../../include/eepp/ui/tools/uicodeeditorsplitter.hpp ../../include/eepp/ui/tools/uicolorpicker.hpp ../../include/eepp/ui/uibackgrounddrawable.hpp ../../include/eepp/ui/uiborderdrawable.hpp @@ -788,6 +789,7 @@ ../../src/eepp/ui/tools/textureatlasnew.hpp ../../src/eepp/ui/tools/textureatlastextureregioneditor.cpp ../../src/eepp/ui/tools/textureatlastextureregioneditor.hpp +../../src/eepp/ui/tools/uicodeeditorsplitter.cpp ../../src/eepp/ui/tools/uicolorpicker.cpp ../../src/eepp/ui/uibackgrounddrawable.cpp ../../src/eepp/ui/uiborderdrawable.cpp @@ -972,6 +974,8 @@ ../../src/thirdparty/SOIL2/src/SOIL2/stbi_pvr.h ../../src/tools/codeeditor/codeeditor.cpp ../../src/tools/codeeditor/codeeditor.hpp +../../src/tools/codeeditor/uicodeeditorsplitter.cpp +../../src/tools/codeeditor/uicodeeditorsplitter.hpp ../../src/tools/mapeditor/mapeditor.cpp ../../src/tools/textureatlaseditor/textureatlaseditor.cpp ../../src/tools/texturepacker/texturepacker.cpp diff --git a/projects/linux/ee.includes b/projects/linux/ee.includes index bbcd0ea96..dd9c5ab1f 100644 --- a/projects/linux/ee.includes +++ b/projects/linux/ee.includes @@ -6,8 +6,3 @@ ../../src/thirdparty/libvorbis/include /usr/include/freetype2/ ../../src/thirdparty/mbedtls/include -../../docs/articles -../../include/eepp/ui/doc -../../src/eepp/ui/doc -../../include/eepp/ui -../../src/eepp/ui diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 58dcb4bfb..7939d72e4 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -259,6 +259,11 @@ String& String::toUpper() { return *this; } +String::StringBaseType String::lastChar() const { + return mString.empty() ? std::numeric_limits::max() + : mString[mString.size() - 1]; +} + std::vector String::stringToUint8( const std::string& str ) { return std::vector( str.begin(), str.end() ); } diff --git a/src/eepp/system/inifile.cpp b/src/eepp/system/inifile.cpp index 0acfa1cc5..c9d69948d 100644 --- a/src/eepp/system/inifile.cpp +++ b/src/eepp/system/inifile.cpp @@ -445,6 +445,24 @@ bool IniFile::deleteHeaderComment( unsigned commentID ) { return false; } +std::map IniFile::getKeyMap( const unsigned& keyID ) const { + std::map map; + if ( keyID < mKeys.size() ) { + for ( size_t i = 0; i < mKeys[keyID].names.size(); i++ ) { + map[mKeys[keyID].names[i]] = mKeys[keyID].values[i]; + } + return map; + } + return {}; +} + +std::map IniFile::getKeyMap( const std::string& keyname ) const { + long keyID = findKey( keyname ); + if ( keyID != noID ) + return getKeyMap( keyID ); + return {}; +} + unsigned IniFile::getNumKeyComments( unsigned const keyID ) const { if ( keyID < mKeys.size() ) return (unsigned int)mKeys[keyID].comments.size(); diff --git a/src/eepp/ui/doc/syntaxcolorscheme.cpp b/src/eepp/ui/doc/syntaxcolorscheme.cpp index 62c7b6611..e251ff6f4 100644 --- a/src/eepp/ui/doc/syntaxcolorscheme.cpp +++ b/src/eepp/ui/doc/syntaxcolorscheme.cpp @@ -8,6 +8,8 @@ #include #include +using namespace EE::Graphics; + namespace EE { namespace UI { namespace Doc { // Color schemes are compatible with the lite (https://github.com/rxi/lite) color schemes. @@ -17,9 +19,10 @@ namespace EE { namespace UI { namespace Doc { // "line_break_column" (the right margin line column color) // "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) SyntaxColorScheme SyntaxColorScheme::getDefault() { - return {"lite-theme", + return {"lite", { {"normal", Color( "#e1e1e6" )}, {"symbol", Color( "#e1e1e6" )}, @@ -31,7 +34,7 @@ SyntaxColorScheme SyntaxColorScheme::getDefault() { {"string", Color( "#f7c95c" )}, {"operator", Color( "#93DDFA" )}, {"function", Color( "#93DDFA" )}, - {"link", {Color( "#93DDFA" ), Color::Transparent, Graphics::Text::Underlined}}, + {"link", {Color( "#93DDFA" ), Color::Transparent, Text::Underlined}}, }, { {"background", Color( "#2e2e32" )}, @@ -47,6 +50,7 @@ SyntaxColorScheme SyntaxColorScheme::getDefault() { {"line_break_column", Color( "#54575b99" )}, {"matching_bracket", Color( "#FFFFFF33" )}, {"matching_selection", Color( "#FFFFFF33" )}, + {"matching_search", Color( "#181b1e" )}, }}; } @@ -79,15 +83,15 @@ std::vector SyntaxColorScheme::loadFromStream( IOStream& stre } } else { if ( "bold" == val ) - style.style |= Graphics::Text::Bold; + style.style |= Text::Bold; else if ( "italic" == val ) - style.style |= Graphics::Text::Italic; + style.style |= Text::Italic; else if ( "underline" == val || "underlined" == val ) - style.style |= Graphics::Text::Underlined; + style.style |= Text::Underlined; else if ( "strikethrough" == val ) - style.style |= Graphics::Text::StrikeThrough; + style.style |= Text::StrikeThrough; else if ( "shadow" == val ) - style.style |= Graphics::Text::Shadow; + style.style |= Text::Shadow; } if ( refColorScheme.mSyntaxColors.find( valueName ) != diff --git a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp index 4abb1e52d..88561a68a 100644 --- a/src/eepp/ui/doc/syntaxdefinitionmanager.cpp +++ b/src/eepp/ui/doc/syntaxdefinitionmanager.cpp @@ -880,15 +880,19 @@ SyntaxDefinitionManager::SyntaxDefinitionManager() { add( {"Config File", {"%.ini$", "%.conf$", "%.desktop$", "%.service$", "%.cfg$", "Doxyfile"}, { - {{"#[%da-fA-F]+"}, "literal"}, - {{"#.-\n"}, "comment"}, + {{"%s?#%x+"}, "string"}, + {{"[%a_][%w-+_%s%p]*%f[=]"}, "keyword"}, + {{"^#.-\n"}, "comment"}, + {{"%s#.-\n"}, "comment"}, {{"\"", "\"", "\\"}, "string"}, {{"'", "'", "\\"}, "string"}, - {{"%[", "%]"}, "keyword2"}, - {{"[%a][%w_-]*%s*%f[=]"}, "keyword"}, + {{"^%[.-%]"}, "keyword2"}, + {{"%s%[.-%]"}, "keyword2"}, {{"="}, "operator"}, {{"https?://%S+"}, "link"}, - }} ); + }, + {}, + "#"} ); // Makefile add( {"Makefile", diff --git a/src/eepp/ui/doc/syntaxtokenizer.cpp b/src/eepp/ui/doc/syntaxtokenizer.cpp index d0370a581..78b7477e5 100644 --- a/src/eepp/ui/doc/syntaxtokenizer.cpp +++ b/src/eepp/ui/doc/syntaxtokenizer.cpp @@ -95,7 +95,8 @@ std::pair, int> SyntaxTokenizer::tokenize( const Syntax pattern.patterns[0][0] == '^' ? pattern.patterns[0] : "^" + pattern.patterns[0] ); LuaPatternMatcher words( patternStr ); int start, end = 0; - if ( words.find( text, start, end, i ) ) { + if ( words.find( text, start, end, i ) && start != end ) { + eeASSERT( start != end ); std::string patternText( text.substr( start, end - start ) ); std::string type = syntax.getSymbol( patternText ); pushToken( tokens, type.empty() ? pattern.type : type, patternText ); diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 168277948..b652407e0 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -309,13 +309,15 @@ bool TextDocument::save( IOStream& stream ) { notifyTextChanged(); } if ( i == lastLine ) { - if ( !text.empty() && text[text.size() - 1] == '\n' && !mForceNewLineAtEndOfFile ) { + if ( !text.empty() && text[text.size() - 1] == '\n' ) { // Last \n is added by the document but it's not part of the document. text.pop_back(); if ( text.empty() ) continue; - } else if ( !text.empty() && text[text.size()] != '\n' && mForceNewLineAtEndOfFile ) { - text += mLineEnding == LineEnding::CRLF ? "\n\r" : "\n"; + } + if ( mForceNewLineAtEndOfFile && !text.empty() && text[text.size() - 1] != '\n' ) { + text += "\n"; + textInput( "\n" ); } } if ( mLineEnding == LineEnding::CRLF ) { @@ -678,6 +680,10 @@ TextPosition TextDocument::endOfDoc() const { return TextPosition( mLines.size() - 1, mLines[mLines.size() - 1].size() - 1 ); } +TextRange TextDocument::getDocRange() const { + return {startOfDoc(), endOfDoc()}; +} + void TextDocument::deleteTo( int offset ) { TextPosition cursorPos = getSelection( true ).start(); if ( hasSelection() ) { @@ -725,12 +731,14 @@ void TextDocument::textInput( const String& text ) { void TextDocument::registerClient( Client* client ) { mClients.insert( client ); + if ( mActiveClient == nullptr ) + setActiveClient( client ); } void TextDocument::unregisterClient( Client* client ) { mClients.erase( client ); if ( mActiveClient == client ) - mActiveClient = nullptr; + setActiveClient( nullptr ); } void TextDocument::moveToPreviousChar() { @@ -1043,6 +1051,10 @@ void TextDocument::print() const { printf( "%s", mLines[i].toUtf8().c_str() ); } +TextRange TextDocument::sanitizeRange( const TextRange& range ) { + return {sanitizePosition( range.start() ), sanitizePosition( range.end() )}; +} + TextPosition TextDocument::sanitizePosition( const TextPosition& position ) const { Int64 line = eeclamp( position.line(), 0UL, mLines.size() - 1 ); Int64 col = @@ -1105,18 +1117,36 @@ void TextDocument::setCommand( const std::string& command, TextDocument::Documen mCommands[command] = func; } -TextPosition TextDocument::find( String text, TextPosition from, const bool& caseSensitive ) { +TextPosition TextDocument::find( String text, TextPosition from, const bool& caseSensitive, + TextRange restrictRange ) { + if ( text.empty() ) + return TextPosition(); from = sanitizePosition( from ); + + TextPosition to = endOfDoc(); + if ( restrictRange.isValid() ) { + restrictRange = sanitizeRange( restrictRange.normalized() ); + to = restrictRange.end(); + if ( from < restrictRange.start() || from > restrictRange.end() ) + return TextPosition(); + } + if ( !caseSensitive ) text.toLower(); - for ( size_t i = from.line(); i < linesCount(); i++ ) { + + for ( Int64 i = from.line(); i <= to.line(); i++ ) { size_t col; - if ( (Int64)i == from.line() ) { + if ( i == from.line() ) { col = caseSensitive ? line( i ).getText().substr( from.column() ).find( text ) : String::toLower( line( i ).getText() ).substr( from.column() ).find( text ); if ( String::StringType::npos != col ) col += from.column(); + } else if ( i == to.line() && to != endOfDoc() ) { + col = + caseSensitive + ? line( i ).getText().substr( 0, to.column() ).find( text ) + : String::toLower( line( i ).getText() ).substr( 0, to.column() ).find( text ); } else { col = caseSensitive ? line( i ).getText().find( text ) : String::toLower( line( i ).getText() ).find( text ); @@ -1128,17 +1158,35 @@ TextPosition TextDocument::find( String text, TextPosition from, const bool& cas return TextPosition(); } -TextPosition TextDocument::findLast( String text, TextPosition from, const bool& caseSensitive ) { +TextPosition TextDocument::findLast( String text, TextPosition from, const bool& caseSensitive, + TextRange restrictRange ) { + if ( text.empty() ) + return TextPosition(); from = sanitizePosition( from ); + + TextPosition to = startOfDoc(); + if ( restrictRange.isValid() ) { + restrictRange = sanitizeRange( restrictRange.normalized() ); + to = restrictRange.start(); + if ( from < restrictRange.start() || from > restrictRange.end() ) + return TextPosition(); + } + if ( !caseSensitive ) text.toLower(); - for ( Int64 i = from.line(); i >= 0; i-- ) { + for ( Int64 i = from.line(); i >= to.line(); i-- ) { size_t col; - if ( (Int64)i == from.line() ) { + if ( i == from.line() ) { col = caseSensitive ? line( i ).getText().substr( 0, from.column() ).rfind( text ) : String::toLower( line( i ).getText() ) .substr( 0, from.column() ) .rfind( text ); + } else if ( i == to.line() ) { + col = caseSensitive + ? line( i ).getText().substr( to.column() ).rfind( text ) + : String::toLower( line( i ).getText() ).substr( to.column() ).rfind( text ); + if ( String::StringType::npos != col ) + col += to.column(); } else { col = caseSensitive ? line( i ).getText().rfind( text ) : String::toLower( line( i ).getText() ).rfind( text ); @@ -1159,8 +1207,8 @@ TextPosition TextDocument::replaceSelection( const String& replace ) { } TextPosition TextDocument::replace( String search, const String& replace, TextPosition from, - const bool& caseSensitive ) { - TextPosition start( find( search, from, caseSensitive ) ); + const bool& caseSensitive, TextRange restrictRange ) { + TextPosition start( find( search, from, caseSensitive, restrictRange ) ); if ( start.isValid() ) { TextPosition end = positionOffset( start, search.size() ); if ( end.isValid() ) { @@ -1344,5 +1392,3 @@ void TextDocument::initializeCommands() { TextDocument::Client::~Client() {} }}} // namespace EE::UI::Doc - - diff --git a/src/eepp/ui/keyboardshortcut.cpp b/src/eepp/ui/keyboardshortcut.cpp index 74f78bbaf..28553c514 100644 --- a/src/eepp/ui/keyboardshortcut.cpp +++ b/src/eepp/ui/keyboardshortcut.cpp @@ -83,6 +83,13 @@ KeyBindings::Shortcut KeyBindings::getShortcutFromString( const std::string& key Shortcut shortcut; Uint32 mod = 0; auto keysSplit = String::split( keys, '+' ); + if ( keys.find( "keypad +" ) != std::string::npos ) + mod = 0; + if ( keysSplit.size() == 1 && getKeyMod( keysSplit[0] ) && keys.find( "++" ) ) + keysSplit.emplace_back( "+" ); + if ( keysSplit.size() == 2 && getKeyMod( keysSplit[0] ) && + keys.find( " +" ) != std::string::npos ) + keysSplit[1] += "+"; for ( auto& part : keysSplit ) { if ( ( mod = getKeyMod( part ) ) ) { shortcut.mod |= mod; @@ -112,4 +119,30 @@ std::string KeyBindings::getCommandFromKeyBind( const KeyBindings::Shortcut& key return ""; } +void KeyBindings::reset() { + mShortcuts.clear(); +} + +const ShortcutMap& KeyBindings::getShortcutMap() const { + return mShortcuts; +} + +std::string KeyBindings::getShortcutString( KeyBindings::Shortcut shortcut ) { + std::vector mods; + std::string keyname( String::toLower( mInput->getKeyName( shortcut.key ) ) ); + if ( shortcut.mod & KEYMOD_CTRL ) + mods.emplace_back( "ctrl" ); + if ( shortcut.mod & KEYMOD_SHIFT ) + mods.emplace_back( "shift" ); + if ( shortcut.mod & KEYMOD_LALT ) + mods.emplace_back( "alt" ); + if ( shortcut.mod & KEYMOD_RALT ) + mods.emplace_back( "altgr" ); + if ( shortcut.mod & KEYMOD_META ) + mods.emplace_back( "meta" ); + if ( mods.empty() ) + return keyname; + return String::join( mods, '+' ) + "+" + keyname; +} + }} // namespace EE::UI diff --git a/src/eepp/ui/tools/textureatlastextureregioneditor.cpp b/src/eepp/ui/tools/textureatlastextureregioneditor.cpp index 1755bc9f3..3485c4c08 100644 --- a/src/eepp/ui/tools/textureatlastextureregioneditor.cpp +++ b/src/eepp/ui/tools/textureatlastextureregioneditor.cpp @@ -28,7 +28,7 @@ TextureAtlasTextureRegionEditor::TextureAtlasTextureRegionEditor( TextureAtlasEd mDrag->center(); mDragPos = mDrag->getPixelsPosition(); - mDrag->addEventListener( Event::OnPositionChange, [this]( const Event* event ) { + mDrag->addEventListener( Event::OnPositionChange, [this]( const Event* ) { if ( NULL != mGfx->getTextureRegion() ) { Vector2f Diff = -( mDragPos - mDrag->getPixelsPosition() ); diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp new file mode 100644 index 000000000..066b4eec6 --- /dev/null +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -0,0 +1,640 @@ +#include +#include +#include +#include + +using namespace EE::System; + +namespace EE { namespace UI { namespace Tools { + +UICodeEditorSplitter* UICodeEditorSplitter::New( UICodeEditorSplitter::Client* client, + UISceneNode* sceneNode, + const std::vector& colorSchemes, + const std::string& initColorScheme ) { + return eeNew( UICodeEditorSplitter, ( client, sceneNode, colorSchemes, initColorScheme ) ); +} + +UICodeEditorSplitter::UICodeEditorSplitter( UICodeEditorSplitter::Client* client, + UISceneNode* sceneNode, + const std::vector& colorSchemes, + const std::string& initColorScheme ) : + mUISceneNode( sceneNode ), mClient( client ) { + if ( !colorSchemes.empty() ) { + for ( auto& colorScheme : colorSchemes ) + mColorSchemes.insert( std::make_pair( colorScheme.getName(), colorScheme ) ); + mCurrentColorScheme = mColorSchemes.find( initColorScheme ) != mColorSchemes.end() + ? initColorScheme + : colorSchemes[0].getName(); + } else { + mColorSchemes["default"] = SyntaxColorScheme::getDefault(); + mCurrentColorScheme = "default"; + } +} + +UICodeEditorSplitter::~UICodeEditorSplitter() {} + +UITabWidget* UICodeEditorSplitter::tabWidgetFromEditor( UICodeEditor* editor ) { + if ( editor ) + return ( (UITab*)editor->getData() )->getTabWidget(); + return nullptr; +} + +UISplitter* UICodeEditorSplitter::splitterFromEditor( UICodeEditor* editor ) { + if ( editor && editor->getParent()->getParent()->getParent()->isType( UI_TYPE_SPLITTER ) ) + return editor->getParent()->getParent()->getParent()->asType(); + return nullptr; +} + +UICodeEditor* UICodeEditorSplitter::createCodeEditor() { + UICodeEditor* codeEditor = UICodeEditor::NewOpt( false, true ); + TextDocument& doc = codeEditor->getDocument(); + const CodeEditorConfig& editorConfig = mClient->getCodeEditorConfig(); + codeEditor->setFontSize( editorConfig.fontSize ); + 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 ); + 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 ); + + /* global commands */ + doc.setCommand( "move-to-previous-line", [&] { + if ( mCurEditor ) + mCurEditor->moveToPreviousLine(); + } ); + doc.setCommand( "move-to-next-line", [&] { + if ( mCurEditor ) + mCurEditor->moveToNextLine(); + } ); + doc.setCommand( "select-to-previous-line", [&] { + if ( mCurEditor ) + mCurEditor->selectToPreviousLine(); + } ); + doc.setCommand( "select-to-next-line", [&] { + if ( mCurEditor ) + mCurEditor->selectToNextLine(); + } ); + doc.setCommand( "move-scroll-up", [&] { + if ( mCurEditor ) + mCurEditor->moveScrollUp(); + } ); + doc.setCommand( "move-scroll-down", [&] { + if ( mCurEditor ) + mCurEditor->moveScrollDown(); + } ); + doc.setCommand( "indent", [&] { + if ( mCurEditor ) + mCurEditor->indent(); + } ); + doc.setCommand( "unindent", [&] { + if ( mCurEditor ) + mCurEditor->unindent(); + } ); + doc.setCommand( "copy", [&] { + if ( mCurEditor ) + mCurEditor->copy(); + } ); + doc.setCommand( "cut", [&] { + if ( mCurEditor ) + mCurEditor->cut(); + } ); + doc.setCommand( "paste", [&] { + if ( mCurEditor ) + mCurEditor->paste(); + } ); + doc.setCommand( "font-size-grow", [&] { zoomIn(); } ); + doc.setCommand( "font-size-shrink", [&] { zoomOut(); } ); + doc.setCommand( "font-size-reset", [&] { zoomReset(); } ); + doc.setCommand( "lock", [&] { + if ( mCurEditor ) { + mCurEditor->setLocked( true ); + mClient->onDocumentStateChanged( mCurEditor, mCurEditor->getDocument() ); + } + } ); + doc.setCommand( "unlock", [&] { + if ( mCurEditor ) { + mCurEditor->setLocked( false ); + mClient->onDocumentStateChanged( mCurEditor, mCurEditor->getDocument() ); + } + } ); + doc.setCommand( "lock-toggle", [&] { + if ( mCurEditor ) { + mCurEditor->setLocked( !mCurEditor->isLocked() ); + mClient->onDocumentStateChanged( mCurEditor, mCurEditor->getDocument() ); + } + } ); + codeEditor->addUnlockedCommand( "copy" ); + codeEditor->addUnlockedCommand( "select-all" ); + /* global commands */ + + doc.setCommand( "switch-to-previous-colorscheme", [&] { + auto it = mColorSchemes.find( mCurrentColorScheme ); + auto prev = std::prev( it, 1 ); + if ( prev != mColorSchemes.end() ) { + setColorScheme( prev->first ); + } else { + setColorScheme( mColorSchemes.rbegin()->first ); + } + } ); + doc.setCommand( "switch-to-next-colorscheme", [&] { + auto it = mColorSchemes.find( mCurrentColorScheme ); + if ( ++it != mColorSchemes.end() ) + mCurrentColorScheme = it->first; + else + mCurrentColorScheme = mColorSchemes.begin()->first; + applyColorScheme( mColorSchemes[mCurrentColorScheme] ); + } ); + doc.setCommand( "switch-to-previous-split", [&] { switchPreviousSplit( mCurEditor ); } ); + doc.setCommand( "switch-to-next-split", [&] { switchNextSplit( mCurEditor ); } ); + doc.setCommand( "close-doc", [&] { tryTabClose( mCurEditor ); } ); + doc.setCommand( "create-new", [&] { + auto d = createCodeEditorInTabWidget( tabWidgetFromEditor( mCurEditor ) ); + d.first->getTabWidget()->setTabSelected( d.first ); + } ); + doc.setCommand( "next-doc", [&] { + UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); + if ( tabWidget && tabWidget->getTabCount() > 1 ) { + UITab* tab = (UITab*)mCurEditor->getData(); + Uint32 tabIndex = tabWidget->getTabIndex( tab ); + switchToTab( ( tabIndex + 1 ) % tabWidget->getTabCount() ); + } + } ); + doc.setCommand( "previous-doc", [&] { + UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); + if ( tabWidget && tabWidget->getTabCount() > 1 ) { + UITab* tab = (UITab*)mCurEditor->getData(); + Uint32 tabIndex = tabWidget->getTabIndex( tab ); + Int32 newTabIndex = (Int32)tabIndex - 1; + switchToTab( newTabIndex < 0 ? tabWidget->getTabCount() - newTabIndex : newTabIndex ); + } + } ); + for ( int i = 1; i <= 10; i++ ) + doc.setCommand( String::format( "switch-to-tab-%d", i ), [&, i] { switchToTab( i - 1 ); } ); + doc.setCommand( "switch-to-first-tab", [&] { + UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); + if ( tabWidget && tabWidget->getTabCount() ) { + switchToTab( 0 ); + } + } ); + doc.setCommand( "switch-to-last-tab", [&] { + UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); + if ( tabWidget && tabWidget->getTabCount() ) { + switchToTab( tabWidget->getTabCount() - 1 ); + } + } ); + doc.setCommand( "split-right", [&] { splitEditor( SplitDirection::Right, mCurEditor ); } ); + doc.setCommand( "split-bottom", [&] { splitEditor( SplitDirection::Bottom, mCurEditor ); } ); + doc.setCommand( "split-left", [&] { splitEditor( SplitDirection::Left, mCurEditor ); } ); + doc.setCommand( "split-top", [&] { splitEditor( SplitDirection::Top, mCurEditor ); } ); + doc.setCommand( "split-swap", [&] { + if ( UISplitter* splitter = splitterFromEditor( mCurEditor ) ) + splitter->swap(); + } ); + codeEditor->addEventListener( Event::OnFocus, [&]( const Event* event ) { + setCurrentEditor( event->getNode()->asType() ); + } ); + codeEditor->addEventListener( Event::OnTextChanged, [&]( const Event* event ) { + mClient->onDocumentModified( event->getNode()->asType(), + event->getNode()->asType()->getDocument() ); + } ); + codeEditor->addEventListener( Event::OnSelectionChanged, [&]( const Event* event ) { + mClient->onDocumentSelectionChange( + event->getNode()->asType(), + event->getNode()->asType()->getDocument() ); + } ); + codeEditor->addKeyBindingString( "ctrl+s", "save-doc", false ); + codeEditor->addKeyBindingString( "ctrl+l", "lock-toggle", true ); + codeEditor->addKeyBindingString( "ctrl+t", "create-new", true ); + codeEditor->addKeyBindingString( "ctrl+w", "close-doc", true ); + codeEditor->addKeyBindingString( "ctrl+tab", "next-doc", true ); + codeEditor->addKeyBindingString( "ctrl+shift+tab", "previous-doc", true ); + codeEditor->addKeyBindingString( "alt+shift+j", "split-left", true ); + codeEditor->addKeyBindingString( "alt+shift+l", "split-right", true ); + codeEditor->addKeyBindingString( "alt+shift+i", "split-top", true ); + codeEditor->addKeyBindingString( "alt+shift+k", "split-bottom", true ); + codeEditor->addKeyBindingString( "alt+shift+s", "split-swap", true ); + codeEditor->addKeyBindingString( "ctrl+alt+j", "switch-to-previous-split", true ); + codeEditor->addKeyBindingString( "ctrl+alt+l", "switch-to-next-split", true ); + codeEditor->addKeyBindingString( "ctrl+alt+n", "switch-to-previous-colorscheme", true ); + codeEditor->addKeyBindingString( "ctrl+alt+m", "switch-to-next-colorscheme", true ); + for ( int i = 1; i <= 10; i++ ) { + codeEditor->addKeyBindingString( String::format( "ctrl+%d", i ), + String::format( "switch-to-tab-%d", i ), true ); + codeEditor->addKeyBindingString( String::format( "alt+%d", i ), + String::format( "switch-to-tab-%d", i ), true ); + } + + if ( nullptr == mCurEditor ) + setCurrentEditor( codeEditor ); + + mClient->onCodeEditorCreated( codeEditor, doc ); + + return codeEditor; +} + +const std::string& UICodeEditorSplitter::getCurrentColorScheme() const { + return mCurrentColorScheme; +} + +void UICodeEditorSplitter::setColorScheme( const std::string& name ) { + if ( name != mCurrentColorScheme ) { + mCurrentColorScheme = name; + applyColorScheme( mColorSchemes[mCurrentColorScheme] ); + } +} + +const std::map& UICodeEditorSplitter::getColorSchemes() const { + return mColorSchemes; +} + +bool UICodeEditorSplitter::editorExists( UICodeEditor* editor ) const { + for ( auto tabWidget : mTabWidgets ) { + for ( size_t i = 0; i < tabWidget->getTabCount(); i++ ) { + if ( editor == tabWidget->getTab( i )->getOwnedWidget() ) { + return true; + } + } + } + return false; +} + +bool UICodeEditorSplitter::loadFileFromPath( const std::string& path, UICodeEditor* codeEditor ) { + if ( FileSystem::isDirectory( path ) ) + return false; + if ( nullptr == codeEditor ) + codeEditor = mCurEditor; + codeEditor->setColorScheme( mColorSchemes[mCurrentColorScheme] ); + bool ret = codeEditor->loadFromFile( path ); + mClient->onDocumentLoaded( codeEditor, path ); + return ret; +} + +void UICodeEditorSplitter::loadFileFromPathInNewTab( const std::string& path ) { + auto d = createCodeEditorInTabWidget( tabWidgetFromEditor( mCurEditor ) ); + UITabWidget* tabWidget = d.first->getTabWidget(); + UITab* addedTab = d.first; + loadFileFromPath( path, d.second ); + tabWidget->setTabSelected( addedTab ); +} + +void UICodeEditorSplitter::setCurrentEditor( UICodeEditor* editor ) { + bool isNew = mCurEditor != editor; + mCurEditor = editor; + if ( isNew ) + mClient->onCodeEditorFocusChange( editor ); + if ( editor ) + mClient->onDocumentStateChanged( editor, editor->getDocument() ); +} + +std::pair +UICodeEditorSplitter::createCodeEditorInTabWidget( UITabWidget* tabWidget ) { + if ( nullptr == tabWidget ) + return std::make_pair( (UITab*)nullptr, (UICodeEditor*)nullptr ); + UICodeEditor* editor = createCodeEditor(); + editor->addEventListener( Event::OnDocumentChanged, [&]( const Event* event ) { + mClient->onDocumentStateChanged( event->getNode()->asType(), + event->getNode()->asType()->getDocument() ); + } ); + UITab* tab = tabWidget->add( editor->getDocument().getFilename(), editor ); + editor->setData( (UintPtr)tab ); + return std::make_pair( tab, editor ); +} + +void UICodeEditorSplitter::removeUnusedTab( UITabWidget* tabWidget ) { + if ( tabWidget && tabWidget->getTabCount() == 2 && + tabWidget->getTab( 0 ) + ->getOwnedWidget() + ->asType() + ->getDocument() + .isEmpty() ) { + tabWidget->removeTab( (Uint32)0 ); + } +} + +UITabWidget* UICodeEditorSplitter::createEditorWithTabWidget( Node* parent ) { + UICodeEditor* prevCurEditor = mCurEditor; + UITabWidget* tabWidget = UITabWidget::New(); + tabWidget->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); + tabWidget->setParent( parent ); + tabWidget->setTabsClosable( true ); + tabWidget->setHideTabBarOnSingleTab( true ); + tabWidget->setAllowRearrangeTabs( true ); + tabWidget->setAllowDragAndDropTabs( true ); + tabWidget->addEventListener( Event::OnTabSelected, [&]( const Event* event ) { + UITabWidget* tabWidget = event->getNode()->asType(); + setCurrentEditor( tabWidget->getTabSelected()->getOwnedWidget()->asType() ); + } ); + tabWidget->setTabTryCloseCallback( [&]( UITab* tab ) -> bool { + tryTabClose( tab->getOwnedWidget()->asType() ); + return false; + } ); + tabWidget->addEventListener( Event::OnTabClosed, [&]( const Event* event ) { + onTabClosed( static_cast( event ) ); + } ); + auto editorData = createCodeEditorInTabWidget( tabWidget ); + // Open same document in the new split + if ( prevCurEditor && prevCurEditor != editorData.second && + !prevCurEditor->getDocument().isEmpty() ) + editorData.second->setDocument( prevCurEditor->getDocumentRef() ); + mTabWidgets.push_back( tabWidget ); + return tabWidget; +} + +void UICodeEditorSplitter::applyColorScheme( const SyntaxColorScheme& colorScheme ) { + forEachEditor( + [colorScheme]( UICodeEditor* editor ) { editor->setColorScheme( colorScheme ); } ); + mClient->onColorSchemeChanged( mCurrentColorScheme ); +} + +void UICodeEditorSplitter::forEachEditor( std::function run ) { + for ( auto tabWidget : mTabWidgets ) + for ( size_t i = 0; i < tabWidget->getTabCount(); i++ ) + run( tabWidget->getTab( i )->getOwnedWidget()->asType() ); +} + +void UICodeEditorSplitter::zoomIn() { + forEachEditor( []( UICodeEditor* editor ) { editor->fontSizeGrow(); } ); +} + +void UICodeEditorSplitter::zoomOut() { + forEachEditor( []( UICodeEditor* editor ) { editor->fontSizeShrink(); } ); +} + +void UICodeEditorSplitter::zoomReset() { + forEachEditor( []( UICodeEditor* editor ) { editor->fontSizeReset(); } ); +} + +bool UICodeEditorSplitter::tryTabClose( UICodeEditor* editor ) { + if ( nullptr != editor && editor->isDirty() ) { + UIMessageBox* msgBox = + UIMessageBox::New( UIMessageBox::OK_CANCEL, + "Do you really want to close this tab?\nAll changes will be lost." ); + msgBox->addEventListener( Event::MsgBoxConfirmClick, + [&, editor]( const Event* ) { closeEditorTab( editor ); } ); + msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { + msgBox = nullptr; + if ( mCurEditor ) + mCurEditor->setFocus(); + } ); + msgBox->setTitle( "Close Tab?" ); + msgBox->center(); + msgBox->show(); + return false; + } else { + closeEditorTab( editor ); + return true; + } +} + +void UICodeEditorSplitter::closeEditorTab( UICodeEditor* editor ) { + if ( editor ) { + UITabWidget* tabWidget = tabWidgetFromEditor( editor ); + if ( tabWidget ) { + if ( !( editor->getDocument().isEmpty() && + !tabWidget->getParent()->isType( UI_TYPE_SPLITTER ) && + tabWidget->getTabCount() == 1 ) ) { + tabWidget->removeTab( (UITab*)editor->getData() ); + } + } + } +} + +void UICodeEditorSplitter::splitEditor( const SplitDirection& direction, UICodeEditor* editor ) { + if ( !editor ) + return; + UIOrientation orientation = + direction == SplitDirection::Left || direction == SplitDirection::Right + ? UIOrientation::Horizontal + : UIOrientation::Vertical; + UITabWidget* tabWidget = tabWidgetFromEditor( editor ); + if ( !tabWidget ) + return; + Node* parent = tabWidget->getParent(); + UISplitter* parentSplitter = nullptr; + bool wasFirst = true; + + if ( parent->isType( UI_TYPE_SPLITTER ) ) { + parentSplitter = parent->asType(); + wasFirst = parentSplitter->getFirstWidget() == tabWidget; + if ( !parentSplitter->isFull() ) { + parentSplitter->setOrientation( orientation ); + createEditorWithTabWidget( parentSplitter ); + if ( direction == SplitDirection::Left || direction == SplitDirection::Top ) + parentSplitter->swap(); + return; + } + } + + UISplitter* splitter = UISplitter::New(); + splitter->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); + splitter->setOrientation( orientation ); + tabWidget->detach(); + splitter->setParent( parent ); + tabWidget->setParent( splitter ); + createEditorWithTabWidget( splitter ); + if ( direction == SplitDirection::Left || direction == SplitDirection::Top ) + splitter->swap(); + + if ( parentSplitter ) { + if ( wasFirst && parentSplitter->getFirstWidget() != splitter ) { + parentSplitter->swap(); + } else if ( !wasFirst && parentSplitter->getLastWidget() != splitter ) { + parentSplitter->swap(); + } + } +} + +void UICodeEditorSplitter::switchToTab( Int32 index ) { + UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); + if ( tabWidget ) { + tabWidget->setTabSelected( eeclamp( index, 0, tabWidget->getTabCount() - 1 ) ); + } +} + +UITabWidget* UICodeEditorSplitter::findPreviousSplit( UICodeEditor* editor ) { + if ( !editor ) + return nullptr; + UISplitter* splitter = splitterFromEditor( editor ); + if ( !splitter ) + return nullptr; + UITabWidget* tabWidget = tabWidgetFromEditor( editor ); + if ( tabWidget ) { + auto it = std::find( mTabWidgets.rbegin(), mTabWidgets.rend(), tabWidget ); + if ( it != mTabWidgets.rend() && ++it != mTabWidgets.rend() ) { + return *it; + } + } + return nullptr; +} + +void UICodeEditorSplitter::switchPreviousSplit( UICodeEditor* editor ) { + UITabWidget* tabWidget = findPreviousSplit( editor ); + if ( tabWidget && tabWidget->getTabSelected() && + tabWidget->getTabSelected()->getOwnedWidget() ) { + tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); + } else { + tabWidget = findNextSplit( editor ); + if ( tabWidget && tabWidget->getTabSelected() && + tabWidget->getTabSelected()->getOwnedWidget() ) { + tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); + } + } +} + +UITabWidget* UICodeEditorSplitter::findNextSplit( UICodeEditor* editor ) { + if ( !editor ) + return nullptr; + UISplitter* splitter = splitterFromEditor( editor ); + if ( !splitter ) + return nullptr; + UITabWidget* tabWidget = tabWidgetFromEditor( editor ); + if ( tabWidget ) { + auto it = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget ); + if ( it != mTabWidgets.end() && ++it != mTabWidgets.end() ) { + return *it; + } + } + return nullptr; +} + +void UICodeEditorSplitter::switchNextSplit( UICodeEditor* editor ) { + UITabWidget* tabWidget = findNextSplit( editor ); + if ( tabWidget && tabWidget->getTabSelected() && + tabWidget->getTabSelected()->getOwnedWidget() ) { + tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); + } else { + tabWidget = findPreviousSplit( editor ); + if ( tabWidget && tabWidget->getTabSelected() && + tabWidget->getTabSelected()->getOwnedWidget() ) { + tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); + } + } +} + +void UICodeEditorSplitter::focusSomeEditor( Node* searchFrom ) { + UICodeEditor* editor = + searchFrom ? searchFrom->findByType( UI_TYPE_CODEEDITOR ) + : mUISceneNode->getRoot()->findByType( UI_TYPE_CODEEDITOR ); + if ( searchFrom && !editor ) + editor = mUISceneNode->getRoot()->findByType( UI_TYPE_CODEEDITOR ); + if ( editor && tabWidgetFromEditor( editor ) && !tabWidgetFromEditor( editor )->isClosing() ) { + UITabWidget* tabW = tabWidgetFromEditor( editor ); + if ( tabW && tabW->getTabCount() > 0 ) { + tabW->setTabSelected( tabW->getTabSelected() ); + } + } else { + UITabWidget* tabW = mUISceneNode->getRoot()->findByType( UI_TYPE_TABWIDGET ); + if ( tabW && tabW->getTabCount() > 0 ) { + tabW->setTabSelected( tabW->getTabSelected() ); + } + } +} + +void UICodeEditorSplitter::closeTabWidgets( UISplitter* splitter ) { + Node* node = splitter->getFirstChild(); + while ( node ) { + if ( node->isType( UI_TYPE_TABWIDGET ) ) { + auto it = + std::find( mTabWidgets.begin(), mTabWidgets.end(), node->asType() ); + if ( it != mTabWidgets.end() ) { + mTabWidgets.erase( it ); + } + } else if ( node->isType( UI_TYPE_SPLITTER ) ) { + closeTabWidgets( node->asType() ); + } + node = node->getNextNode(); + } +} + +UICodeEditor* UICodeEditorSplitter::getCurEditor() const { + return mCurEditor; +} + +void UICodeEditorSplitter::addRemainingTabWidgets( Node* widget ) { + if ( widget->isType( UI_TYPE_TABWIDGET ) ) { + if ( std::find( mTabWidgets.begin(), mTabWidgets.end(), widget->asType() ) == + mTabWidgets.end() ) { + mTabWidgets.push_back( widget->asType() ); + } + } else if ( widget->isType( UI_TYPE_SPLITTER ) ) { + UISplitter* splitter = widget->asType(); + addRemainingTabWidgets( splitter->getFirstWidget() ); + addRemainingTabWidgets( splitter->getLastWidget() ); + } +} + +void UICodeEditorSplitter::closeSplitter( UISplitter* splitter ) { + splitter->setParent( mUISceneNode->getRoot() ); + splitter->setVisible( false ); + splitter->setEnabled( false ); + splitter->close(); + closeTabWidgets( splitter ); +} + +void UICodeEditorSplitter::onTabClosed( const TabEvent* tabEvent ) { + UICodeEditor* editor = mCurEditor; + UITabWidget* tabWidget = tabEvent->getTab()->getTabWidget(); + if ( tabWidget->getTabCount() == 0 ) { + UISplitter* splitter = splitterFromEditor( editor ); + if ( splitter ) { + if ( splitter->isFull() ) { + tabWidget->close(); + auto itWidget = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget ); + if ( itWidget != mTabWidgets.end() ) { + mTabWidgets.erase( itWidget ); + } + + // Remove splitter if it's redundant + Node* parent = splitter->getParent(); + if ( parent->isType( UI_TYPE_SPLITTER ) ) { + UISplitter* parentSplitter = parent->asType(); + Node* remainingNode = tabWidget == splitter->getFirstWidget() + ? splitter->getLastWidget() + : splitter->getFirstWidget(); + bool wasFirst = parentSplitter->getFirstWidget() == splitter; + remainingNode->detach(); + closeSplitter( splitter ); + remainingNode->setParent( parentSplitter ); + addRemainingTabWidgets( remainingNode ); + if ( wasFirst ) + parentSplitter->swap(); + focusSomeEditor( parentSplitter ); + } else { + // Then this is the main splitter + Node* remainingNode = tabWidget == splitter->getFirstWidget() + ? splitter->getLastWidget() + : splitter->getFirstWidget(); + closeSplitter( splitter ); + eeASSERT( parent->getChildCount() == 0 ); + remainingNode->setParent( parent ); + addRemainingTabWidgets( remainingNode ); + focusSomeEditor( nullptr ); + } + if ( tabEvent->getTab()->getOwnedWidget() == mCurEditor ) + setCurrentEditor( nullptr ); + return; + } + } + auto d = createCodeEditorInTabWidget( tabWidget ); + d.first->getTabWidget()->setTabSelected( d.first ); + } else { + tabWidget->setTabSelected( eemin( tabWidget->getTabCount() - 1, tabEvent->getTabIndex() ) ); + } + if ( tabEvent->getTab()->getOwnedWidget() == mCurEditor ) + setCurrentEditor( nullptr ); +} + +}}} // namespace EE::UI::Tools diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 894e74ac1..2065c99c3 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -152,13 +152,22 @@ void UICodeEditor::draw() { drawMatchingBrackets( startScroll, lineHeight ); } - if ( mHighlightSelectionMatch && mDoc->hasSelection() && - mDoc->getSelection().start().line() == mDoc->getSelection().end().line() ) { + if ( mDoc->hasSelection() ) { + drawTextRange( mDoc->getSelection( true ), lineRange, startScroll, lineHeight, + mFontStyleConfig.getFontSelectionBackColor() ); + } + + if ( mHighlightTextRange.isValid() && mHighlightTextRange.hasSelection() ) { + drawTextRange( mHighlightTextRange, lineRange, startScroll, lineHeight, + mFontStyleConfig.getFontSelectionBackColor() ); + } + + if ( mHighlightSelectionMatch && mDoc->hasSelection() && mDoc->getSelection().inSameLine() ) { drawSelectionMatch( lineRange, startScroll, lineHeight ); } - if ( mDoc->hasSelection() ) { - drawSelection( lineRange, startScroll, lineHeight ); + if ( !mHighlightWord.empty() ) { + drawWordMatch( mHighlightWord, lineRange, startScroll, lineHeight ); } // Draw tab marker @@ -590,11 +599,13 @@ Sizef UICodeEditor::getMaxScroll() const { } Uint32 UICodeEditor::onMouseDown( const Vector2i& position, const Uint32& flags ) { - if ( isTextSelectionEnabled() && !getUISceneNode()->getEventDispatcher()->isNodeDragging() && - NULL != mFont && !mMouseDown && ( flags & EE_BUTTON_LMASK ) ) { + if ( isTextSelectionEnabled() && !getEventDispatcher()->isNodeDragging() && NULL != mFont && + !mMouseDown && getEventDispatcher()->getMouseDownNode() == this && + ( flags & EE_BUTTON_LMASK ) ) { mMouseDown = true; Input* input = getUISceneNode()->getWindow()->getInput(); input->captureMouse( true ); + setFocus(); if ( input->isShiftPressed() ) { mDoc->selectTo( resolveScreenPosition( position.asFloat() ) ); } else { @@ -793,6 +804,7 @@ void UICodeEditor::updateEditor() { void UICodeEditor::onDocumentTextChanged() { invalidateDraw(); + checkMatchingBrackets(); sendCommonEvent( Event::OnTextChanged ); invalidateLongestLineWidth(); } @@ -1237,9 +1249,9 @@ const SyntaxDefinition& UICodeEditor::getSyntaxDefinition() const { void UICodeEditor::checkMatchingBrackets() { if ( mHighlightMatchingBracket ) { + const std::vector open{'{', '(', '['}; + const std::vector close{'}', ')', ']'}; mMatchingBrackets = TextRange(); - std::vector open{'{', '(', '['}; - std::vector close{'}', ')', ']'}; TextPosition pos = mDoc->getSelection().start(); TextDocumentLine& line = mDoc->line( pos.line() ); auto isOpenIt = std::find( open.begin(), open.end(), line[pos.column()] ); @@ -1406,7 +1418,32 @@ const bool& UICodeEditor::getShowWhitespaces() const { } void UICodeEditor::setShowWhitespaces( const bool& showWhitespaces ) { - mShowWhitespaces = showWhitespaces; + if ( mShowWhitespaces != showWhitespaces ) { + mShowWhitespaces = showWhitespaces; + invalidateDraw(); + } +} + +const String& UICodeEditor::getHighlightWord() const { + return mHighlightWord; +} + +void UICodeEditor::setHighlightWord( const String& highlightWord ) { + if ( mHighlightWord != highlightWord ) { + mHighlightWord = highlightWord; + invalidateDraw(); + } +} + +const TextRange& UICodeEditor::getHighlightTextRange() const { + return mHighlightTextRange; +} + +void UICodeEditor::setHighlightTextRange( const TextRange& highlightSelection ) { + if ( highlightSelection != mHighlightTextRange ) { + mHighlightTextRange = highlightSelection; + invalidateDraw(); + } } const Time& UICodeEditor::getFindLongestLineWidthUpdateFrequency() const { @@ -1449,15 +1486,18 @@ void UICodeEditor::drawSelectionMatch( const std::pair& lineRange, const Vector2f& startScroll, const Float& lineHeight ) { if ( !mDoc->hasSelection() ) return; - - Primitives primitives; - primitives.setForceDraw( false ); - primitives.setColor( Color( mSelectionMatchColor ).blendAlpha( mAlpha ) ); - TextRange selection = mDoc->getSelection( true ); const String& selectionLine = mDoc->line( selection.start().line() ).getText(); String text( selectionLine.substr( selection.start().column(), selection.end().column() - selection.start().column() ) ); + drawWordMatch( text, lineRange, startScroll, lineHeight ); +} + +void UICodeEditor::drawWordMatch( const String& text, const std::pair& lineRange, + const Vector2f& startScroll, const Float& lineHeight ) { + Primitives primitives; + primitives.setForceDraw( false ); + primitives.setColor( Color( mSelectionMatchColor ).blendAlpha( mAlpha ) ); for ( auto ln = lineRange.first; ln <= lineRange.second; ln++ ) { const String& line = mDoc->line( ln ).getText(); @@ -1512,34 +1552,32 @@ void UICodeEditor::drawLineText( const Int64& index, Vector2f position, const Fl } } -void UICodeEditor::drawSelection( const std::pair& lineRange, const Vector2f& startScroll, - const Float& lineHeight ) { +void UICodeEditor::drawTextRange( const TextRange& range, const std::pair& lineRange, + const Vector2f& startScroll, const Float& lineHeight, + const Color& backgrundColor ) { Primitives primitives; primitives.setForceDraw( false ); - primitives.setColor( - Color( mFontStyleConfig.getFontSelectionBackColor() ).blendAlpha( mAlpha ) ); + primitives.setColor( Color( backgrundColor ).blendAlpha( mAlpha ) ); - TextRange selection = mDoc->getSelection( true ); - - int startLine = eemax( lineRange.first, selection.start().line() ); - int endLine = eemin( lineRange.second, selection.end().line() ); + int startLine = eemax( lineRange.first, range.start().line() ); + int endLine = eemin( lineRange.second, range.end().line() ); for ( auto ln = startLine; ln <= endLine; ln++ ) { const String& line = mDoc->line( ln ).getText(); Rectf selRect; selRect.Top = startScroll.y + ln * lineHeight; selRect.Bottom = selRect.Top + lineHeight; - if ( selection.start().line() == ln ) { - selRect.Left = startScroll.x + getXOffsetCol( {ln, selection.start().column()} ); - if ( selection.end().line() == ln ) { - selRect.Right = startScroll.x + getXOffsetCol( {ln, selection.end().column()} ); + if ( range.start().line() == ln ) { + selRect.Left = startScroll.x + getXOffsetCol( {ln, range.start().column()} ); + if ( range.end().line() == ln ) { + selRect.Right = startScroll.x + getXOffsetCol( {ln, range.end().column()} ); } else { selRect.Right = startScroll.x + getXOffsetCol( {ln, static_cast( line.length() )} ); } - } else if ( selection.end().line() == ln ) { + } else if ( range.end().line() == ln ) { selRect.Left = startScroll.x + getXOffsetCol( {ln, 0} ); - selRect.Right = startScroll.x + getXOffsetCol( {ln, selection.end().column()} ); + selRect.Right = startScroll.x + getXOffsetCol( {ln, range.end().column()} ); } else { selRect.Left = startScroll.x + getXOffsetCol( {ln, 0} ); selRect.Right = diff --git a/src/eepp/ui/uitab.cpp b/src/eepp/ui/uitab.cpp index 1d3f6a50d..9f76d1fe5 100644 --- a/src/eepp/ui/uitab.cpp +++ b/src/eepp/ui/uitab.cpp @@ -187,8 +187,6 @@ UIPushButton* UITab::setText( const String& text ) { updateTab(); tTabW->orderTabs(); - - tTabW->setTabSelected( tTabW->getTabSelected() ); } } return this; diff --git a/src/tools/codeeditor/codeeditor.cpp b/src/tools/codeeditor/codeeditor.cpp index 9f47cd91f..aed2557ff 100644 --- a/src/tools/codeeditor/codeeditor.cpp +++ b/src/tools/codeeditor/codeeditor.cpp @@ -8,7 +8,8 @@ void appLoop() { } bool App::onCloseRequestCallback( EE::Window::Window* ) { - if ( nullptr != mCurEditor && mCurEditor->isDirty() ) { + if ( nullptr != mEditorSplitter->getCurEditor() && + mEditorSplitter->getCurEditor()->isDirty() ) { UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::OK_CANCEL, "Do you really want to close the code editor?\nAll changes will be lost." ); @@ -24,401 +25,15 @@ bool App::onCloseRequestCallback( EE::Window::Window* ) { } } -bool App::tryTabClose( UICodeEditor* editor ) { - if ( nullptr != editor && editor->isDirty() ) { - UIMessageBox* msgBox = - UIMessageBox::New( UIMessageBox::OK_CANCEL, - "Do you really want to close this tab?\nAll changes will be lost." ); - msgBox->addEventListener( Event::MsgBoxConfirmClick, - [&, editor]( const Event* ) { closeEditorTab( editor ); } ); - msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { - msgBox = nullptr; - if ( mCurEditor ) - mCurEditor->setFocus(); - } ); - msgBox->setTitle( "Close Tab?" ); - msgBox->center(); - msgBox->show(); - return false; - } else { - closeEditorTab( editor ); - return true; - } -} - -void App::closeEditorTab( UICodeEditor* editor ) { - if ( editor ) { - UITabWidget* tabWidget = tabWidgetFromEditor( editor ); - if ( tabWidget ) { - if ( !( editor->getDocument().isEmpty() && - !tabWidget->getParent()->isType( UI_TYPE_SPLITTER ) && - tabWidget->getTabCount() == 1 ) ) { - tabWidget->removeTab( (UITab*)editor->getData() ); - } - } - } -} - -void App::splitEditor( const SplitDirection& direction, UICodeEditor* editor ) { - if ( !editor ) - return; - UIOrientation orientation = - direction == SplitDirection::Left || direction == SplitDirection::Right - ? UIOrientation::Horizontal - : UIOrientation::Vertical; - UITabWidget* tabWidget = tabWidgetFromEditor( editor ); - if ( !tabWidget ) - return; - Node* parent = tabWidget->getParent(); - UISplitter* parentSplitter = nullptr; - bool wasFirst = true; - - if ( parent->isType( UI_TYPE_SPLITTER ) ) { - parentSplitter = parent->asType(); - wasFirst = parentSplitter->getFirstWidget() == tabWidget; - if ( !parentSplitter->isFull() ) { - parentSplitter->setOrientation( orientation ); - createEditorWithTabWidget( parentSplitter ); - if ( direction == SplitDirection::Left || direction == SplitDirection::Top ) - parentSplitter->swap(); - return; - } - } - - UISplitter* splitter = UISplitter::New(); - splitter->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); - splitter->setOrientation( orientation ); - tabWidget->detach(); - splitter->setParent( parent ); - tabWidget->setParent( splitter ); - createEditorWithTabWidget( splitter ); - if ( direction == SplitDirection::Left || direction == SplitDirection::Top ) - splitter->swap(); - - if ( parentSplitter ) { - if ( wasFirst && parentSplitter->getFirstWidget() != splitter ) { - parentSplitter->swap(); - } else if ( !wasFirst && parentSplitter->getLastWidget() != splitter ) { - parentSplitter->swap(); - } - } -} - -void App::switchToTab( Int32 index ) { - UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); - if ( tabWidget ) { - tabWidget->setTabSelected( eeclamp( index, 0, tabWidget->getTabCount() - 1 ) ); - } -} - -UITabWidget* App::findPreviousSplit( UICodeEditor* editor ) { - if ( !editor ) - return nullptr; - UISplitter* splitter = splitterFromEditor( editor ); - if ( !splitter ) - return nullptr; - UITabWidget* tabWidget = tabWidgetFromEditor( editor ); - if ( tabWidget ) { - auto it = std::find( mTabWidgets.rbegin(), mTabWidgets.rend(), tabWidget ); - if ( it != mTabWidgets.rend() && ++it != mTabWidgets.rend() ) { - return *it; - } - } - return nullptr; -} - -void App::switchPreviousSplit( UICodeEditor* editor ) { - UITabWidget* tabWidget = findPreviousSplit( editor ); - if ( tabWidget && tabWidget->getTabSelected() && - tabWidget->getTabSelected()->getOwnedWidget() ) { - tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); - } else { - tabWidget = findNextSplit( editor ); - if ( tabWidget && tabWidget->getTabSelected() && - tabWidget->getTabSelected()->getOwnedWidget() ) { - tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); - } - } -} - -UITabWidget* App::findNextSplit( UICodeEditor* editor ) { - if ( !editor ) - return nullptr; - UISplitter* splitter = splitterFromEditor( editor ); - if ( !splitter ) - return nullptr; - UITabWidget* tabWidget = tabWidgetFromEditor( editor ); - if ( tabWidget ) { - auto it = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget ); - if ( it != mTabWidgets.end() && ++it != mTabWidgets.end() ) { - return *it; - } - } - return nullptr; -} - -void App::switchNextSplit( UICodeEditor* editor ) { - UITabWidget* tabWidget = findNextSplit( editor ); - if ( tabWidget && tabWidget->getTabSelected() && - tabWidget->getTabSelected()->getOwnedWidget() ) { - tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); - } else { - tabWidget = findPreviousSplit( editor ); - if ( tabWidget && tabWidget->getTabSelected() && - tabWidget->getTabSelected()->getOwnedWidget() ) { - tabWidget->getTabSelected()->getOwnedWidget()->setFocus(); - } - } -} - -void App::applyColorScheme( const SyntaxColorScheme& colorScheme ) { - for ( UITabWidget* tabWidget : mTabWidgets ) { - for ( size_t i = 0; i < tabWidget->getTabCount(); i++ ) { - tabWidget->getTab( i )->getOwnedWidget()->asType()->setColorScheme( - colorScheme ); - } - } - updateColorSchemeMenu(); -} - void App::saveDoc() { - if ( mCurEditor->getDocument().hasFilepath() ) { - if ( mCurEditor->save() ) + if ( mEditorSplitter->getCurEditor()->getDocument().hasFilepath() ) { + if ( mEditorSplitter->getCurEditor()->save() ) updateEditorState(); } else { saveFileDialog(); } } -void App::forEachEditor( std::function run ) { - for ( auto tabWidget : mTabWidgets ) - for ( size_t i = 0; i < tabWidget->getTabCount(); i++ ) - run( tabWidget->getTab( i )->getOwnedWidget()->asType() ); -} - -void App::zoomIn() { - forEachEditor( []( UICodeEditor* editor ) { editor->fontSizeGrow(); } ); -} - -void App::zoomOut() { - forEachEditor( []( UICodeEditor* editor ) { editor->fontSizeShrink(); } ); -} - -void App::zoomReset() { - forEachEditor( []( UICodeEditor* editor ) { editor->fontSizeReset(); } ); -} - -UICodeEditor* App::createCodeEditor() { - UICodeEditor* codeEditor = UICodeEditor::NewOpt( false, true ); - TextDocument& doc = codeEditor->getDocument(); - codeEditor->setFontSize( mConfig.editor.fontSize ); - codeEditor->setEnableColorPickerOnSelection( true ); - codeEditor->setColorScheme( mColorSchemes[mCurrentColorScheme] ); - codeEditor->setShowLineNumber( mConfig.editor.showLineNumbers ); - codeEditor->setShowWhitespaces( mConfig.editor.showWhiteSpaces ); - codeEditor->setHighlightMatchingBracket( mConfig.editor.highlightMatchingBracket ); - codeEditor->setHorizontalScrollBarEnabled( mConfig.editor.horizontalScrollbar ); - codeEditor->setHighlightCurrentLine( mConfig.editor.highlightCurrentLine ); - codeEditor->setTabWidth( mConfig.editor.tabWidth ); - codeEditor->setLineBreakingColumn( mConfig.editor.lineBreakingColumn ); - doc.setAutoDetectIndentType( mConfig.editor.autoDetectIndentType ); - doc.setLineEnding( mConfig.editor.windowsLineEndings ? TextDocument::LineEnding::CRLF - : TextDocument::LineEnding::LF ); - doc.setTrimTrailingWhitespaces( mConfig.editor.trimTrailingWhitespaces ); - doc.setIndentType( mConfig.editor.indentSpaces ? TextDocument::IndentType::IndentSpaces - : TextDocument::IndentType::IndentTabs ); - doc.setForceNewLineAtEndOfFile( mConfig.editor.forceNewLineAtEndOfFile ); - doc.setIndentWidth( mConfig.editor.indentWidth ); - doc.setBOM( mConfig.editor.writeUnicodeBOM ); - - /* global commands */ - doc.setCommand( "move-to-previous-line", [&] { - if ( mCurEditor ) - mCurEditor->moveToPreviousLine(); - } ); - doc.setCommand( "move-to-next-line", [&] { - if ( mCurEditor ) - mCurEditor->moveToNextLine(); - } ); - doc.setCommand( "select-to-previous-line", [&] { - if ( mCurEditor ) - mCurEditor->selectToPreviousLine(); - } ); - doc.setCommand( "select-to-next-line", [&] { - if ( mCurEditor ) - mCurEditor->selectToNextLine(); - } ); - doc.setCommand( "move-scroll-up", [&] { - if ( mCurEditor ) - mCurEditor->moveScrollUp(); - } ); - doc.setCommand( "move-scroll-down", [&] { - if ( mCurEditor ) - mCurEditor->moveScrollDown(); - } ); - doc.setCommand( "indent", [&] { - if ( mCurEditor ) - mCurEditor->indent(); - } ); - doc.setCommand( "unindent", [&] { - if ( mCurEditor ) - mCurEditor->unindent(); - } ); - doc.setCommand( "copy", [&] { - if ( mCurEditor ) - mCurEditor->copy(); - } ); - doc.setCommand( "cut", [&] { - if ( mCurEditor ) - mCurEditor->cut(); - } ); - doc.setCommand( "paste", [&] { - if ( mCurEditor ) - mCurEditor->paste(); - } ); - doc.setCommand( "font-size-grow", [&] { zoomIn(); } ); - doc.setCommand( "font-size-shrink", [&] { zoomOut(); } ); - doc.setCommand( "font-size-reset", [&] { zoomReset(); } ); - doc.setCommand( "lock", [&] { - if ( mCurEditor ) { - mCurEditor->setLocked( true ); - updateDocumentMenu(); - } - } ); - doc.setCommand( "unlock", [&] { - if ( mCurEditor ) { - mCurEditor->setLocked( false ); - updateDocumentMenu(); - } - } ); - doc.setCommand( "lock-toggle", [&] { - if ( mCurEditor ) { - mCurEditor->setLocked( !mCurEditor->isLocked() ); - updateDocumentMenu(); - } - } ); - codeEditor->addUnlockedCommand( "copy" ); - codeEditor->addUnlockedCommand( "select-all" ); - /* global commands */ - - doc.setCommand( "switch-to-previous-colorscheme", [&] { - auto it = mColorSchemes.find( mCurrentColorScheme ); - auto prev = std::prev( it, 1 ); - if ( prev != mColorSchemes.end() ) { - setColorScheme( prev->first ); - } else { - setColorScheme( mColorSchemes.rbegin()->first ); - } - } ); - doc.setCommand( "switch-to-next-colorscheme", [&] { - auto it = mColorSchemes.find( mCurrentColorScheme ); - if ( ++it != mColorSchemes.end() ) - mCurrentColorScheme = it->first; - else - mCurrentColorScheme = mColorSchemes.begin()->first; - applyColorScheme( mColorSchemes[mCurrentColorScheme] ); - } ); - doc.setCommand( "switch-to-previous-split", [&] { switchPreviousSplit( mCurEditor ); } ); - doc.setCommand( "switch-to-next-split", [&] { switchNextSplit( mCurEditor ); } ); - doc.setCommand( "save-doc", [&] { saveDoc(); } ); - doc.setCommand( "save-as-doc", [&] { saveFileDialog(); } ); - doc.setCommand( "find-replace", [&] { showFindView(); } ); - doc.setCommand( "repeat-find", [&] { - findNextText( "", mSearchBarLayout->find( "case_sensitive" )->isChecked() ); - } ); - doc.setCommand( "close-app", [&] { closeApp(); } ); - doc.setCommand( "fullscreen-toggle", [&]() { - mWindow->toggleFullscreen(); - mViewMenu->find( "fullscreen-mode" ) - ->asType() - ->setActive( !mWindow->isWindowed() ); - } ); - doc.setCommand( "open-file", [&] { openFileDialog(); } ); - doc.setCommand( "console-toggle", [&] { - mConsole->toggle(); - bool lock = mConsole->isActive(); - forEachEditor( [lock]( UICodeEditor* editor ) { editor->setLocked( lock ); } ); - } ); - doc.setCommand( "close-doc", [&] { tryTabClose( mCurEditor ); } ); - doc.setCommand( "create-new", [&] { - auto d = createCodeEditorInTabWidget( tabWidgetFromEditor( mCurEditor ) ); - d.first->getTabWidget()->setTabSelected( d.first ); - } ); - doc.setCommand( "next-doc", [&] { - UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); - if ( tabWidget && tabWidget->getTabCount() > 1 ) { - UITab* tab = (UITab*)mCurEditor->getData(); - Uint32 tabIndex = tabWidget->getTabIndex( tab ); - switchToTab( ( tabIndex + 1 ) % tabWidget->getTabCount() ); - } - } ); - doc.setCommand( "previous-doc", [&] { - UITabWidget* tabWidget = tabWidgetFromEditor( mCurEditor ); - if ( tabWidget && tabWidget->getTabCount() > 1 ) { - UITab* tab = (UITab*)mCurEditor->getData(); - Uint32 tabIndex = tabWidget->getTabIndex( tab ); - Int32 newTabIndex = (Int32)tabIndex - 1; - switchToTab( newTabIndex < 0 ? tabWidget->getTabCount() - newTabIndex : newTabIndex ); - } - } ); - for ( int i = 1; i <= 10; i++ ) - doc.setCommand( String::format( "switch-to-tab-%d", i ), [&, i] { switchToTab( i - 1 ); } ); - doc.setCommand( "split-right", [&] { splitEditor( SplitDirection::Right, mCurEditor ); } ); - doc.setCommand( "split-bottom", [&] { splitEditor( SplitDirection::Bottom, mCurEditor ); } ); - doc.setCommand( "split-left", [&] { splitEditor( SplitDirection::Left, mCurEditor ); } ); - doc.setCommand( "split-top", [&] { splitEditor( SplitDirection::Top, mCurEditor ); } ); - doc.setCommand( "split-swap", [&] { - if ( UISplitter* splitter = splitterFromEditor( mCurEditor ) ) - splitter->swap(); - } ); - - codeEditor->addEventListener( Event::OnFocus, [&]( const Event* event ) { - setCurrentEditor( event->getNode()->asType() ); - } ); - codeEditor->addEventListener( Event::OnTextChanged, [&]( const Event* event ) { - updateEditorTitle( event->getNode()->asType() ); - } ); - codeEditor->addEventListener( Event::OnSelectionChanged, [&]( const Event* event ) { - updateEditorTitle( event->getNode()->asType() ); - } ); - - codeEditor->addKeyBindingString( "f2", "open-file", true ); - codeEditor->addKeyBindingString( "f3", "repeat-find", false ); - codeEditor->addKeyBindingString( "f12", "console-toggle", true ); - codeEditor->addKeyBindingString( "alt+return", "fullscreen-toggle", true ); - codeEditor->addKeyBindingString( "alt+keypad enter", "fullscreen-toggle", true ); - codeEditor->addKeyBindingString( "ctrl+s", "save-doc", false ); - codeEditor->addKeyBindingString( "ctrl+f", "find-replace", false ); - codeEditor->addKeyBindingString( "ctrl+q", "close-app", true ); - codeEditor->addKeyBindingString( "ctrl+o", "open-file", true ); - codeEditor->addKeyBindingString( "ctrl+l", "lock-toggle", true ); - codeEditor->addKeyBindingString( "ctrl+t", "create-new", true ); - codeEditor->addKeyBindingString( "ctrl+w", "close-doc", true ); - codeEditor->addKeyBindingString( "ctrl+tab", "next-doc", true ); - codeEditor->addKeyBindingString( "ctrl+shift+tab", "previous-doc", true ); - codeEditor->addKeyBindingString( "alt+shift+j", "split-left", true ); - codeEditor->addKeyBindingString( "alt+shift+l", "split-right", true ); - codeEditor->addKeyBindingString( "alt+shift+i", "split-top", true ); - codeEditor->addKeyBindingString( "alt+shift+k", "split-bottom", true ); - codeEditor->addKeyBindingString( "alt+shift+s", "split-swap", true ); - codeEditor->addKeyBindingString( "ctrl+alt+j", "switch-to-previous-split", true ); - codeEditor->addKeyBindingString( "ctrl+alt+l", "switch-to-next-split", true ); - codeEditor->addKeyBindingString( "ctrl+alt+n", "switch-to-previous-colorscheme", true ); - codeEditor->addKeyBindingString( "ctrl+alt+m", "switch-to-next-colorscheme", true ); - - for ( int i = 1; i <= 10; i++ ) { - codeEditor->addKeyBindingString( String::format( "ctrl+%d", i ), - String::format( "switch-to-tab-%d", i ), true ); - codeEditor->addKeyBindingString( String::format( "alt+%d", i ), - String::format( "switch-to-tab-%d", i ), true ); - } - - if ( nullptr == mCurEditor ) { - setCurrentEditor( codeEditor ); - } - return codeEditor; -} - std::string App::titleFromEditor( UICodeEditor* editor ) { std::string title( editor->getDocument().getFilename() ); return editor->getDocument().isDirty() ? title + "*" : title; @@ -433,215 +48,23 @@ void App::updateEditorTitle( UICodeEditor* editor ) { setAppTitle( title ); } -void App::focusSomeEditor( Node* searchFrom ) { - UICodeEditor* editor = - searchFrom ? searchFrom->findByType( UI_TYPE_CODEEDITOR ) - : mUISceneNode->getRoot()->findByType( UI_TYPE_CODEEDITOR ); - if ( searchFrom && !editor ) - editor = mUISceneNode->getRoot()->findByType( UI_TYPE_CODEEDITOR ); - if ( editor && tabWidgetFromEditor( editor ) && !tabWidgetFromEditor( editor )->isClosing() ) { - UITabWidget* tabW = tabWidgetFromEditor( editor ); - if ( tabW && tabW->getTabCount() > 0 ) { - tabW->setTabSelected( tabW->getTabSelected() ); - } - } else { - UITabWidget* tabW = mUISceneNode->getRoot()->findByType( UI_TYPE_TABWIDGET ); - if ( tabW && tabW->getTabCount() > 0 ) { - tabW->setTabSelected( tabW->getTabSelected() ); - } - } -} - -void App::closeTabWidgets( UISplitter* splitter ) { - Node* node = splitter->getFirstChild(); - while ( node ) { - if ( node->isType( UI_TYPE_TABWIDGET ) ) { - auto it = - std::find( mTabWidgets.begin(), mTabWidgets.end(), node->asType() ); - if ( it != mTabWidgets.end() ) { - mTabWidgets.erase( it ); - } - } else if ( node->isType( UI_TYPE_SPLITTER ) ) { - closeTabWidgets( node->asType() ); - } - node = node->getNextNode(); - } -} - -void App::addRemainingTabWidgets( Node* widget ) { - if ( widget->isType( UI_TYPE_TABWIDGET ) ) { - if ( std::find( mTabWidgets.begin(), mTabWidgets.end(), widget->asType() ) == - mTabWidgets.end() ) { - mTabWidgets.push_back( widget->asType() ); - } - } else if ( widget->isType( UI_TYPE_SPLITTER ) ) { - UISplitter* splitter = widget->asType(); - addRemainingTabWidgets( splitter->getFirstWidget() ); - addRemainingTabWidgets( splitter->getLastWidget() ); - } -} - -void App::closeSplitter( UISplitter* splitter ) { - splitter->setParent( mUISceneNode->getRoot() ); - splitter->setVisible( false ); - splitter->setEnabled( false ); - splitter->close(); - closeTabWidgets( splitter ); -} - -void App::onTabClosed( const TabEvent* tabEvent ) { - UICodeEditor* editor = mCurEditor; - if ( tabEvent->getTab()->getOwnedWidget() == mCurEditor ) { - setCurrentEditor( nullptr ); - } - UITabWidget* tabWidget = tabEvent->getTab()->getTabWidget(); - if ( tabWidget->getTabCount() == 0 ) { - UISplitter* splitter = splitterFromEditor( editor ); - if ( splitter ) { - if ( splitter->isFull() ) { - tabWidget->close(); - auto itWidget = std::find( mTabWidgets.begin(), mTabWidgets.end(), tabWidget ); - if ( itWidget != mTabWidgets.end() ) { - mTabWidgets.erase( itWidget ); - } - - // Remove splitter if it's redundant - Node* parent = splitter->getParent(); - if ( parent->isType( UI_TYPE_SPLITTER ) ) { - UISplitter* parentSplitter = parent->asType(); - Node* remainingNode = tabWidget == splitter->getFirstWidget() - ? splitter->getLastWidget() - : splitter->getFirstWidget(); - bool wasFirst = parentSplitter->getFirstWidget() == splitter; - remainingNode->detach(); - closeSplitter( splitter ); - remainingNode->setParent( parentSplitter ); - addRemainingTabWidgets( remainingNode ); - if ( wasFirst ) - parentSplitter->swap(); - focusSomeEditor( parentSplitter ); - } else { - // Then this is the main splitter - Node* remainingNode = tabWidget == splitter->getFirstWidget() - ? splitter->getLastWidget() - : splitter->getFirstWidget(); - closeSplitter( splitter ); - eeASSERT( parent->getChildCount() == 0 ); - remainingNode->setParent( parent ); - addRemainingTabWidgets( remainingNode ); - focusSomeEditor( nullptr ); - } - return; - } - } - auto d = createCodeEditorInTabWidget( tabWidget ); - d.first->getTabWidget()->setTabSelected( d.first ); - } else { - tabWidget->setTabSelected( eemin( tabWidget->getTabCount() - 1, tabEvent->getTabIndex() ) ); - } -} - -std::pair App::createCodeEditorInTabWidget( UITabWidget* tabWidget ) { - if ( nullptr == tabWidget ) - return std::make_pair( (UITab*)nullptr, (UICodeEditor*)nullptr ); - UICodeEditor* editor = createCodeEditor(); - editor->addEventListener( Event::OnDocumentChanged, [&]( const Event* event ) { - updateEditorTitle( event->getNode()->asType() ); - } ); - UITab* tab = tabWidget->add( editor->getDocument().getFilename(), editor ); - editor->setData( (UintPtr)tab ); - return std::make_pair( tab, editor ); -} - -void App::removeUnusedTab( UITabWidget* tabWidget ) { - if ( tabWidget && tabWidget->getTabCount() == 2 && - tabWidget->getTab( 0 ) - ->getOwnedWidget() - ->asType() - ->getDocument() - .isEmpty() ) { - tabWidget->removeTab( (Uint32)0 ); - } -} - -UITabWidget* App::createEditorWithTabWidget( Node* parent ) { - UICodeEditor* prevCurEditor = mCurEditor; - UITabWidget* tabWidget = UITabWidget::New(); - tabWidget->setLayoutSizePolicy( SizePolicy::MatchParent, SizePolicy::MatchParent ); - tabWidget->setParent( parent ); - tabWidget->setTabsClosable( true ); - tabWidget->setHideTabBarOnSingleTab( true ); - tabWidget->setAllowRearrangeTabs( true ); - tabWidget->setAllowDragAndDropTabs( true ); - tabWidget->addEventListener( Event::OnTabSelected, [&]( const Event* event ) { - UITabWidget* tabWidget = event->getNode()->asType(); - setCurrentEditor( tabWidget->getTabSelected()->getOwnedWidget()->asType() ); - } ); - tabWidget->setTabTryCloseCallback( [&]( UITab* tab ) -> bool { - tryTabClose( tab->getOwnedWidget()->asType() ); - return false; - } ); - tabWidget->addEventListener( Event::OnTabClosed, [&]( const Event* event ) { - onTabClosed( static_cast( event ) ); - } ); - auto editorData = createCodeEditorInTabWidget( tabWidget ); - // Open same document in the new split - if ( prevCurEditor && prevCurEditor != editorData.second && - !prevCurEditor->getDocument().isEmpty() ) - editorData.second->setDocument( prevCurEditor->getDocumentRef() ); - mTabWidgets.push_back( tabWidget ); - return tabWidget; -} - -UITabWidget* App::tabWidgetFromEditor( UICodeEditor* editor ) { - if ( editor ) - return ( (UITab*)editor->getData() )->getTabWidget(); - return nullptr; -} - -UISplitter* App::splitterFromEditor( UICodeEditor* editor ) { - if ( editor && editor->getParent()->getParent()->getParent()->isType( UI_TYPE_SPLITTER ) ) - return editor->getParent()->getParent()->getParent()->asType(); - return nullptr; -} - void App::setAppTitle( const std::string& title ) { mWindow->setTitle( mWindowTitle + String( title.empty() ? "" : " - " + title ) ); } -bool App::loadFileFromPath( const std::string& path, UICodeEditor* codeEditor ) { - if ( FileSystem::isDirectory( path ) ) - return false; - if ( nullptr == codeEditor ) - codeEditor = mCurEditor; - codeEditor->setColorScheme( mColorSchemes[mCurrentColorScheme] ); - bool ret = codeEditor->loadFromFile( path ); - updateEditorTitle( codeEditor ); - if ( codeEditor == mCurEditor ) - updateCurrentFiletype(); - removeUnusedTab( tabWidgetFromEditor( codeEditor ) ); +void App::onDocumentModified( UICodeEditor* editor, TextDocument& ) { + bool isDirty = editor->getDocument().isDirty(); + bool wasDirty = + !mWindow->getTitle().empty() && mWindow->getTitle()[mWindow->getTitle().size() - 1] == '*'; - auto found = std::find( mRecentFiles.begin(), mRecentFiles.end(), path ); - if ( found != mRecentFiles.end() ) - mRecentFiles.erase( found ); - mRecentFiles.insert( mRecentFiles.begin(), path ); - if ( mRecentFiles.size() > 10 ) - mRecentFiles.resize( 10 ); - updateRecentFiles(); - return ret; -} + if ( isDirty != wasDirty ) + setAppTitle( titleFromEditor( editor ) ); -void App::loadFileFromPathInNewTab( const std::string& path ) { - auto d = createCodeEditorInTabWidget( tabWidgetFromEditor( mCurEditor ) ); - UITabWidget* tabWidget = d.first->getTabWidget(); - UITab* addedTab = d.first; - loadFileFromPath( path, d.second ); - tabWidget->setTabSelected( addedTab ); -} + const String::StringBaseType& tabDirty = + ( (UITab*)editor->getData() )->getText().lastChar() == '*'; -void App::setCurrentEditor( UICodeEditor* editor ) { - mCurEditor = editor; - updateEditorState(); + if ( isDirty != tabDirty ) + updateEditorTitle( editor ); } void App::openFileDialog() { @@ -650,11 +73,12 @@ void App::openFileDialog() { dialog->setTitle( "Open File" ); dialog->setCloseShortcut( KEY_ESCAPE ); dialog->addEventListener( Event::OpenFile, [&]( const Event* event ) { - loadFileFromPathInNewTab( event->getNode()->asType()->getFullPath() ); + mEditorSplitter->loadFileFromPathInNewTab( + event->getNode()->asType()->getFullPath() ); } ); dialog->addEventListener( Event::OnWindowClose, [&]( const Event* ) { - if ( mCurEditor && !SceneManager::instance()->isShootingDown() ) - mCurEditor->setFocus(); + if ( mEditorSplitter->getCurEditor() && !SceneManager::instance()->isShootingDown() ) + mEditorSplitter->getCurEditor()->setFocus(); } ); dialog->center(); dialog->show(); @@ -666,16 +90,17 @@ void App::saveFileDialog() { dialog->setWinFlags( UI_WIN_DEFAULT_FLAGS | UI_WIN_MAXIMIZE_BUTTON | UI_WIN_MODAL ); dialog->setTitle( "Save File As" ); dialog->setCloseShortcut( KEY_ESCAPE ); - std::string filename( mCurEditor->getDocument().getFilename() ); - if ( FileSystem::fileExtension( mCurEditor->getDocument().getFilename() ).empty() ) - filename += mCurEditor->getSyntaxDefinition().getFileExtension(); + std::string filename( mEditorSplitter->getCurEditor()->getDocument().getFilename() ); + if ( FileSystem::fileExtension( mEditorSplitter->getCurEditor()->getDocument().getFilename() ) + .empty() ) + filename += mEditorSplitter->getCurEditor()->getSyntaxDefinition().getFileExtension(); dialog->setFileName( filename ); dialog->addEventListener( Event::SaveFile, [&]( const Event* event ) { - if ( mCurEditor ) { + if ( mEditorSplitter->getCurEditor() ) { std::string path( event->getNode()->asType()->getFullPath() ); if ( !path.empty() && !FileSystem::isDirectory( path ) && FileSystem::fileCanWrite( FileSystem::fileRemoveFileName( path ) ) ) { - if ( mCurEditor->getDocument().save( path ) ) { + if ( mEditorSplitter->getCurEditor()->getDocument().save( path ) ) { updateEditorState(); } else { UIMessageBox* msg = @@ -692,106 +117,133 @@ void App::saveFileDialog() { } } ); dialog->addEventListener( Event::OnWindowClose, [&]( const Event* ) { - if ( mCurEditor && !SceneManager::instance()->isShootingDown() ) - mCurEditor->setFocus(); + if ( mEditorSplitter->getCurEditor() && !SceneManager::instance()->isShootingDown() ) + mEditorSplitter->getCurEditor()->setFocus(); } ); dialog->center(); dialog->show(); } -void App::findPrevText( String text, const bool& caseSensitive ) { - if ( text.empty() ) - text = mLastSearch; - if ( !mCurEditor || text.empty() ) +void App::findPrevText( SearchState& search ) { + if ( search.text.empty() ) + search.text = mLastSearch; + if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || search.text.empty() ) return; - mLastSearch = text; - TextDocument& doc = mCurEditor->getDocument(); - TextPosition found = doc.findLast( text, doc.getSelection( true ).start(), caseSensitive ); + + search.editor->getDocument().setActiveClient( search.editor ); + mLastSearch = search.text; + TextDocument& doc = search.editor->getDocument(); + TextRange range = doc.getDocRange(); + TextPosition from = doc.getSelection( true ).start(); + if ( search.range.isValid() ) { + range = doc.sanitizeRange( search.range ).normalized(); + from = from < range.start() ? range.start() : from; + } + + TextPosition found = doc.findLast( search.text, from, search.caseSensitive, search.range ); if ( found.isValid() ) { - doc.setSelection( {doc.positionOffset( found, text.size() ), found} ); + doc.setSelection( {doc.positionOffset( found, search.text.size() ), found} ); } else { - found = doc.findLast( text, doc.endOfDoc() ); + found = doc.findLast( search.text, range.end() ); if ( found.isValid() ) { - doc.setSelection( {doc.positionOffset( found, text.size() ), found} ); + doc.setSelection( {doc.positionOffset( found, search.text.size() ), found} ); } } } -void App::findNextText( String text, const bool& caseSensitive ) { - if ( text.empty() ) - text = mLastSearch; - if ( !mCurEditor || text.empty() ) +void App::findNextText( SearchState& search ) { + if ( search.text.empty() ) + search.text = mLastSearch; + if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || search.text.empty() ) return; - mLastSearch = text; - TextDocument& doc = mCurEditor->getDocument(); - TextPosition found = doc.find( text, doc.getSelection( true ).end(), caseSensitive ); + + search.editor->getDocument().setActiveClient( search.editor ); + mLastSearch = search.text; + TextDocument& doc = search.editor->getDocument(); + TextRange range = doc.getDocRange(); + TextPosition from = doc.getSelection( true ).end(); + if ( search.range.isValid() ) { + range = doc.sanitizeRange( search.range ).normalized(); + from = from < range.start() ? range.start() : from; + } + + TextPosition found = doc.find( search.text, from, search.caseSensitive, range ); if ( found.isValid() ) { - doc.setSelection( {doc.positionOffset( found, text.size() ), found} ); + doc.setSelection( {doc.positionOffset( found, search.text.size() ), found} ); } else { - found = doc.find( text, {0, 0} ); + found = doc.find( search.text, range.start(), search.caseSensitive, range ); if ( found.isValid() ) { - doc.setSelection( {doc.positionOffset( found, text.size() ), found} ); + doc.setSelection( {doc.positionOffset( found, search.text.size() ), found} ); } } } -void App::replaceSelection( const String& replacement ) { - if ( !mCurEditor || !mCurEditor->getDocument().hasSelection() ) +void App::replaceSelection( SearchState& search, const String& replacement ) { + if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) || + !search.editor->getDocument().hasSelection() ) return; - mCurEditor->getDocument().replaceSelection( replacement ); + search.editor->getDocument().setActiveClient( search.editor ); + search.editor->getDocument().replaceSelection( replacement ); } -void App::replaceAll( String find, const String& replace, const bool& caseSensitive ) { - if ( !mCurEditor ) +void App::replaceAll( SearchState& search, const String& replace ) { + if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) ) return; - if ( find.empty() ) - find = mLastSearch; - if ( !mCurEditor || find.empty() ) + if ( search.text.empty() ) + search.text = mLastSearch; + if ( search.text.empty() ) return; - mLastSearch = find; - TextDocument& doc = mCurEditor->getDocument(); + + search.editor->getDocument().setActiveClient( search.editor ); + mLastSearch = search.text; + TextDocument& doc = search.editor->getDocument(); TextPosition found; TextPosition startedPosition = doc.getSelection().start(); - doc.setSelection( doc.startOfDoc() ); + TextPosition from = doc.startOfDoc(); + if ( search.range.isValid() ) + from = search.range.normalized().start(); do { - found = doc.find( find, doc.getSelection( true ).end(), caseSensitive ); + found = doc.find( search.text, from, search.caseSensitive, search.range ); if ( found.isValid() ) { - doc.setSelection( {doc.positionOffset( found, find.size() ), found} ); - doc.replaceSelection( replace ); + doc.setSelection( {doc.positionOffset( found, search.text.size() ), found} ); + from = doc.replaceSelection( replace ); } } while ( found.isValid() ); doc.setSelection( startedPosition ); } -void App::findAndReplace( String find, String replace, const bool& caseSensitive ) { - if ( find.empty() ) - find = mLastSearch; - if ( !mCurEditor || find.empty() ) +void App::findAndReplace( SearchState& search, const String& replace ) { + if ( !search.editor || !mEditorSplitter->editorExists( search.editor ) ) return; - mLastSearch = find; - TextDocument& doc = mCurEditor->getDocument(); - if ( doc.hasSelection() && doc.getSelectedText() == find ) { - replaceSelection( replace ); + if ( search.text.empty() ) + search.text = mLastSearch; + if ( search.text.empty() ) + return; + search.editor->getDocument().setActiveClient( search.editor ); + mLastSearch = search.text; + TextDocument& doc = search.editor->getDocument(); + if ( doc.hasSelection() && doc.getSelectedText() == search.text ) { + replaceSelection( search, replace ); } else { - findNextText( find, caseSensitive ); + findNextText( search ); } } void App::runCommand( const std::string& command ) { - if ( mCurEditor ) - mCurEditor->getDocument().execute( command ); + if ( mEditorSplitter->getCurEditor() ) + mEditorSplitter->getCurEditor()->getDocument().execute( command ); } void App::loadConfig() { - std::string path( Sys::getConfigPath( "ecode" ) ); - if ( !FileSystem::fileExists( path ) ) - FileSystem::makeDir( path ); - FileSystem::dirPathAddSlashAtEnd( path ); - mIni.loadFromFile( path + "config.cfg" ); - mIniState.loadFromFile( path + "prev_state.cfg" ); + mConfigPath = Sys::getConfigPath( "ecode" ); + if ( !FileSystem::fileExists( mConfigPath ) ) + FileSystem::makeDir( mConfigPath ); + FileSystem::dirPathAddSlashAtEnd( mConfigPath ); + mIni.loadFromFile( mConfigPath + "config.cfg" ); + mIniState.loadFromFile( mConfigPath + "state.cfg" ); std::string recent = mIniState.getValue( "files", "recentfiles", "" ); mRecentFiles = String::split( recent, ';' ); - mCurrentColorScheme = mConfig.editor.colorScheme = + mInitColorScheme = mConfig.editor.colorScheme = mIni.getValue( "editor", "colorscheme", "lite" ); mConfig.editor.fontSize = mIni.getValueF( "editor", "font_size", 11 ); mConfig.window.size.setWidth( @@ -821,10 +273,12 @@ void App::loadConfig() { mConfig.editor.tabWidth = eemax( 2, mIni.getValueI( "editor", "tab_width", 4 ) ); mConfig.editor.lineBreakingColumn = eemax( 0, mIni.getValueI( "editor", "line_breaking_column", 100 ) ); + mConfig.editor.highlightSelectionMatch = + mIni.getValueB( "editor", "highlight_selection_match", true ); } void App::saveConfig() { - mConfig.editor.colorScheme = mCurrentColorScheme; + mConfig.editor.colorScheme = mEditorSplitter->getCurrentColorScheme(); mConfig.window.size = mWindow->getLastWindowedSize(); mConfig.window.maximized = mWindow->isMaximized(); mIni.setValue( "editor", "colorscheme", mConfig.editor.colorScheme ); @@ -851,6 +305,7 @@ void App::saveConfig() { mIni.setValueB( "editor", "windows_line_endings", mConfig.editor.windowsLineEndings ); mIni.setValueI( "editor", "tab_width", mConfig.editor.tabWidth ); mIni.setValueI( "editor", "line_breaking_column", mConfig.editor.lineBreakingColumn ); + mIni.setValueB( "editor", "highlight_selection_match", mConfig.editor.highlightSelectionMatch ); mIni.writeFile(); mIniState.writeFile(); } @@ -871,41 +326,45 @@ void App::initSearchBar() { UITextInput* findInput = mSearchBarLayout->find( "search_find" ); UITextInput* replaceInput = mSearchBarLayout->find( "search_replace" ); UICheckBox* caseSensitiveChk = mSearchBarLayout->find( "case_sensitive" ); - findInput->addEventListener( Event::OnTextChanged, [&, findInput, - caseSensitiveChk]( const Event* ) { - if ( mCurEditor ) { - if ( !findInput->getText().empty() ) { - mCurEditor->getDocument().setSelection( mCurEditor->getDocument().startOfDoc() ); - findNextText( findInput->getText(), caseSensitiveChk->isChecked() ); + findInput->addEventListener( Event::OnTextChanged, [&, findInput]( const Event* ) { + if ( mSearchState.editor && mEditorSplitter->editorExists( mSearchState.editor ) ) { + mSearchState.text = findInput->getText(); + mSearchState.editor->setHighlightWord( mSearchState.text ); + if ( !mSearchState.text.empty() ) { + mSearchState.editor->getDocument().setSelection( {0, 0} ); + findNextText( mSearchState ); } else { - mCurEditor->getDocument().setSelection( - mCurEditor->getDocument().getSelection().start() ); + mSearchState.editor->getDocument().setSelection( + mSearchState.editor->getDocument().getSelection().start() ); } } } ); mSearchBarLayout->addCommand( "close-searchbar", [&] { mSearchBarLayout->setEnabled( false )->setVisible( false ); - mCurEditor->setFocus(); + mEditorSplitter->getCurEditor()->setFocus(); + if ( mSearchState.editor ) { + if ( mEditorSplitter->editorExists( mSearchState.editor ) ) { + mSearchState.editor->setHighlightWord( "" ); + mSearchState.editor->setHighlightTextRange( TextRange() ); + } + mSearchState.reset(); + } } ); - mSearchBarLayout->addCommand( "repeat-find", [this, findInput, caseSensitiveChk] { - findNextText( findInput->getText(), caseSensitiveChk->isChecked() ); + mSearchBarLayout->addCommand( "repeat-find", [this] { findNextText( mSearchState ); } ); + mSearchBarLayout->addCommand( "replace-all", [this, replaceInput] { + replaceAll( mSearchState, replaceInput->getText() ); + replaceInput->setFocus(); } ); - mSearchBarLayout->addCommand( "replace-all", [this, findInput, replaceInput, caseSensitiveChk] { - replaceAll( findInput->getText(), replaceInput->getText(), caseSensitiveChk->isChecked() ); - } ); - mSearchBarLayout->addCommand( "find-and-replace", - [this, findInput, replaceInput, caseSensitiveChk] { - findAndReplace( findInput->getText(), replaceInput->getText(), - caseSensitiveChk->isChecked() ); - } ); - mSearchBarLayout->addCommand( "find-prev", [this, findInput, caseSensitiveChk] { - findPrevText( findInput->getText(), caseSensitiveChk->isChecked() ); + mSearchBarLayout->addCommand( "find-and-replace", [this, replaceInput] { + findAndReplace( mSearchState, replaceInput->getText() ); } ); + mSearchBarLayout->addCommand( "find-prev", [this] { findPrevText( mSearchState ); } ); mSearchBarLayout->addCommand( "replace-selection", [this, replaceInput] { - replaceSelection( replaceInput->getText() ); + replaceSelection( mSearchState, replaceInput->getText() ); } ); - mSearchBarLayout->addCommand( "change-case", [caseSensitiveChk] { + mSearchBarLayout->addCommand( "change-case", [&, caseSensitiveChk] { caseSensitiveChk->setChecked( !caseSensitiveChk->isChecked() ); + mSearchState.caseSensitive = caseSensitiveChk->isChecked(); } ); mSearchBarLayout->getKeyBindings().addKeybindsString( { {"f3", "repeat-find"}, @@ -927,19 +386,38 @@ void App::initSearchBar() { } void App::showFindView() { - if ( !mCurEditor ) + UICodeEditor* editor = mEditorSplitter->getCurEditor(); + if ( !editor ) return; + + mSearchState.editor = editor; + mSearchState.range = TextRange(); + mSearchState.caseSensitive = + mSearchBarLayout->find( "case_sensitive" )->isChecked(); mSearchBarLayout->setEnabled( true )->setVisible( true ); + UITextInput* findInput = mSearchBarLayout->find( "search_find" ); findInput->setFocus(); - String text = mCurEditor->getDocument().getSelectedText(); - if ( !text.empty() ) { - findInput->setText( text ); - findInput->getDocument().selectAll(); - } else if ( !findInput->getText().empty() ) { - findInput->getDocument().selectAll(); + + const TextDocument& doc = editor->getDocument(); + + if ( doc.getSelection().hasSelection() && doc.getSelection().inSameLine() ) { + String text = doc.getSelectedText(); + if ( !text.empty() ) { + findInput->setText( text ); + findInput->getDocument().selectAll(); + } else if ( !findInput->getText().empty() ) { + findInput->getDocument().selectAll(); + } + } else if ( doc.getSelection().hasSelection() ) { + mSearchState.range = doc.getSelection( true ); + if ( !findInput->getText().empty() ) + findInput->getDocument().selectAll(); } - mCurEditor->getDocument().setActiveClient( mCurEditor ); + mSearchState.text = findInput->getText(); + editor->setHighlightTextRange( mSearchState.range ); + editor->setHighlightWord( mSearchState.text ); + editor->getDocument().setActiveClient( editor ); } void App::closeApp() { @@ -982,24 +460,25 @@ void App::mainLoop() { void App::onFileDropped( String file ) { Vector2f mousePos( mUISceneNode->getEventDispatcher()->getMousePosf() ); Node* node = mUISceneNode->overFind( mousePos ); - UICodeEditor* codeEditor = mCurEditor; + UICodeEditor* codeEditor = mEditorSplitter->getCurEditor(); if ( !node ) node = codeEditor; if ( node && node->isType( UI_TYPE_CODEEDITOR ) ) { codeEditor = node->asType(); if ( !codeEditor->getDocument().isEmpty() ) { - auto d = createCodeEditorInTabWidget( tabWidgetFromEditor( codeEditor ) ); + auto d = mEditorSplitter->createCodeEditorInTabWidget( + mEditorSplitter->tabWidgetFromEditor( codeEditor ) ); codeEditor = d.second; d.first->getTabWidget()->setTabSelected( d.first ); } } - loadFileFromPath( file, codeEditor ); + mEditorSplitter->loadFileFromPath( file, codeEditor ); } void App::onTextDropped( String text ) { Vector2f mousePos( mUISceneNode->getEventDispatcher()->getMousePosf() ); Node* node = mUISceneNode->overFind( mousePos ); - UICodeEditor* codeEditor = mCurEditor; + UICodeEditor* codeEditor = mEditorSplitter->getCurEditor(); if ( node && node->isType( UI_TYPE_CODEEDITOR ) ) codeEditor = node->asType(); if ( codeEditor && !text.empty() ) { @@ -1011,6 +490,7 @@ void App::onTextDropped( String text ) { App::~App() { saveConfig(); + eeSAFE_DELETE( mEditorSplitter ); eeSAFE_DELETE( mConsole ); } @@ -1035,7 +515,7 @@ void App::updateRecentFiles() { if ( txt != "Clear Menu" ) { std::string path( txt.toUtf8() ); if ( FileSystem::fileExists( path ) && !FileSystem::isDirectory( path ) ) { - loadFileFromPathInNewTab( path ); + mEditorSplitter->loadFileFromPathInNewTab( path ); } } else { mRecentFiles.clear(); @@ -1053,6 +533,8 @@ UIMenu* App::createViewMenu() { ->setActive( mConfig.editor.highlightMatchingBracket ); mViewMenu->addCheckBox( "Highlight Current Line" ) ->setActive( mConfig.editor.highlightCurrentLine ); + mViewMenu->addCheckBox( "Highlight Selection Match" ) + ->setActive( mConfig.editor.highlightSelectionMatch ); mViewMenu->addCheckBox( "Enable Horizontal ScrollBar" ) ->setActive( mConfig.editor.horizontalScrollbar ); mViewMenu->addSeparator(); @@ -1078,27 +560,32 @@ UIMenu* App::createViewMenu() { UIMenuItem* item = event->getNode()->asType(); if ( item->getText() == "Show Line Numbers" ) { mConfig.editor.showLineNumbers = item->asType()->isActive(); - forEachEditor( [&]( UICodeEditor* editor ) { + mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { editor->setShowLineNumber( mConfig.editor.showLineNumbers ); } ); } else if ( item->getText() == "Show White Space" ) { mConfig.editor.showWhiteSpaces = item->asType()->isActive(); - forEachEditor( [&]( UICodeEditor* editor ) { + mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { editor->setShowWhitespaces( mConfig.editor.showWhiteSpaces ); } ); } else if ( item->getText() == "Highlight Matching Bracket" ) { mConfig.editor.highlightMatchingBracket = item->asType()->isActive(); - forEachEditor( [&]( UICodeEditor* editor ) { + mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { editor->setHighlightMatchingBracket( mConfig.editor.highlightMatchingBracket ); } ); } else if ( item->getText() == "Highlight Current Line" ) { mConfig.editor.highlightCurrentLine = item->asType()->isActive(); - forEachEditor( [&]( UICodeEditor* editor ) { + mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { editor->setHighlightCurrentLine( mConfig.editor.highlightCurrentLine ); } ); + } else if ( item->getText() == "Highlight Selection Match" ) { + mConfig.editor.highlightSelectionMatch = item->asType()->isActive(); + mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { + editor->setHighlightSelectionMatch( mConfig.editor.highlightSelectionMatch ); + } ); } else if ( item->getText() == "Enable Horizontal ScrollBar" ) { mConfig.editor.horizontalScrollbar = item->asType()->isActive(); - forEachEditor( [&]( UICodeEditor* editor ) { + mEditorSplitter->forEachEditor( [&]( UICodeEditor* editor ) { editor->setHorizontalScrollBarEnabled( mConfig.editor.horizontalScrollbar ); } ); } else if ( item->getText() == "Editor Font Size" ) { @@ -1114,12 +601,13 @@ UIMenu* App::createViewMenu() { Float val; if ( String::fromString( val, msgBox->getTextInput()->getText() ) ) { mConfig.editor.fontSize = val; - forEachEditor( [val]( UICodeEditor* editor ) { editor->setFontSize( val ); } ); + mEditorSplitter->forEachEditor( + [val]( UICodeEditor* editor ) { editor->setFontSize( val ); } ); } } ); msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { - if ( mCurEditor ) - mCurEditor->setFocus(); + if ( mEditorSplitter->getCurEditor() ) + mEditorSplitter->getCurEditor()->setFocus(); } ); } else if ( item->getText() == "UI Font Size" ) { UIMessageBox* msgBox = UIMessageBox::New( UIMessageBox::INPUT, @@ -1138,8 +626,8 @@ UIMenu* App::createViewMenu() { } } ); msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { - if ( mCurEditor ) - mCurEditor->setFocus(); + if ( mEditorSplitter->getCurEditor() ) + mEditorSplitter->getCurEditor()->setFocus(); } ); } else if ( item->getText() == "Line Breaking Column" ) { UIMessageBox* msgBox = @@ -1154,21 +642,21 @@ UIMenu* App::createViewMenu() { int val; if ( String::fromString( val, msgBox->getTextInput()->getText() ) && val >= 0 ) { mConfig.editor.lineBreakingColumn = val; - forEachEditor( + mEditorSplitter->forEachEditor( [val]( UICodeEditor* editor ) { editor->setLineBreakingColumn( val ); } ); msgBox->closeWindow(); } } ); msgBox->addEventListener( Event::OnClose, [&]( const Event* ) { - if ( mCurEditor ) - mCurEditor->setFocus(); + if ( mEditorSplitter->getCurEditor() ) + mEditorSplitter->getCurEditor()->setFocus(); } ); } else if ( "Zoom In" == item->getText() ) { - zoomIn(); + mEditorSplitter->zoomIn(); } else if ( "Zoom Out" == item->getText() ) { - zoomOut(); + mEditorSplitter->zoomOut(); } else if ( "Zoom Reset" == item->getText() ) { - zoomReset(); + mEditorSplitter->zoomReset(); } else if ( "Full Screen Mode" == item->getText() ) { runCommand( "fullscreen-toggle" ); } else { @@ -1220,11 +708,11 @@ UIMenu* App::createDocumentMenu() { mDocMenu->addSubMenu( "Indentation Type", nullptr, tabTypeMenu )->setId( "indent_type" ); tabTypeMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { const String& text = event->getNode()->asType()->getId(); - if ( mCurEditor ) { + if ( mEditorSplitter->getCurEditor() ) { TextDocument::IndentType indentType = text == "tabs" ? TextDocument::IndentType::IndentTabs : TextDocument::IndentType::IndentSpaces; - mCurEditor->getDocument().setIndentType( indentType ); + mEditorSplitter->getCurEditor()->getDocument().setIndentType( indentType ); mConfig.editor.indentSpaces = indentType == TextDocument::IndentType::IndentSpaces; } } ); @@ -1233,14 +721,16 @@ UIMenu* App::createDocumentMenu() { for ( size_t w = 2; w <= 12; w++ ) indentWidthMenu ->addRadioButton( String::toString( w ), - mCurEditor && mCurEditor->getDocument().getIndentWidth() == w ) + mEditorSplitter->getCurEditor() && + mEditorSplitter->getCurEditor()->getDocument().getIndentWidth() == + w ) ->setId( String::format( "indent_width_%zu", w ) ) ->setData( w ); mDocMenu->addSubMenu( "Indent Width", nullptr, indentWidthMenu )->setId( "indent_width" ); indentWidthMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { - if ( mCurEditor ) { + if ( mEditorSplitter->getCurEditor() ) { int width = event->getNode()->getData(); - mCurEditor->getDocument().setIndentWidth( width ); + mEditorSplitter->getCurEditor()->getDocument().setIndentWidth( width ); mConfig.editor.indentWidth = width; } } ); @@ -1248,14 +738,16 @@ UIMenu* App::createDocumentMenu() { UIPopUpMenu* tabWidthMenu = UIPopUpMenu::New(); for ( size_t w = 2; w <= 12; w++ ) tabWidthMenu - ->addRadioButton( String::toString( w ), mCurEditor && mCurEditor->getTabWidth() == w ) + ->addRadioButton( String::toString( w ), + mEditorSplitter->getCurEditor() && + mEditorSplitter->getCurEditor()->getTabWidth() == w ) ->setId( String::format( "tab_width_%zu", w ) ) ->setData( w ); mDocMenu->addSubMenu( "Tab Width", nullptr, tabWidthMenu )->setId( "tab_width" ); tabWidthMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { - if ( mCurEditor ) { + if ( mEditorSplitter->getCurEditor() ) { int width = event->getNode()->getData(); - mCurEditor->setTabWidth( width ); + mEditorSplitter->getCurEditor()->setTabWidth( width ); mConfig.editor.tabWidth = width; } } ); @@ -1268,10 +760,10 @@ UIMenu* App::createDocumentMenu() { mDocMenu->addSubMenu( "Line Endings", nullptr, lineEndingsMenu )->setId( "line_endings" ); lineEndingsMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { bool winLe = event->getNode()->asType()->getId() == "windows"; - if ( mCurEditor ) { + if ( mEditorSplitter->getCurEditor() ) { mConfig.editor.windowsLineEndings = winLe; - mCurEditor->getDocument().setLineEnding( winLe ? TextDocument::LineEnding::CRLF - : TextDocument::LineEnding::LF ); + mEditorSplitter->getCurEditor()->getDocument().setLineEnding( + winLe ? TextDocument::LineEnding::CRLF : TextDocument::LineEnding::LF ); } } ); @@ -1289,11 +781,12 @@ UIMenu* App::createDocumentMenu() { ->setId( "write_bom" ); mDocMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { - if ( !mCurEditor || event->getNode()->isType( UI_TYPE_MENU_SEPARATOR ) || + if ( !mEditorSplitter->getCurEditor() || + event->getNode()->isType( UI_TYPE_MENU_SEPARATOR ) || event->getNode()->isType( UI_TYPE_MENUSUBMENU ) ) return; const String& id = event->getNode()->getId(); - TextDocument& doc = mCurEditor->getDocument(); + TextDocument& doc = mEditorSplitter->getCurEditor()->getDocument(); if ( event->getNode()->isType( UI_TYPE_MENUCHECKBOX ) ) { UIMenuCheckBox* item = event->getNode()->asType(); @@ -1310,7 +803,7 @@ UIMenu* App::createDocumentMenu() { doc.setBOM( item->isActive() ); mConfig.editor.writeUnicodeBOM = item->isActive(); } else if ( "read_only" == id ) { - mCurEditor->setLocked( item->isActive() ); + mEditorSplitter->getCurEditor()->setLocked( item->isActive() ); } } } ); @@ -1318,22 +811,15 @@ UIMenu* App::createDocumentMenu() { } void App::updateDocumentMenu() { - if ( !mCurEditor ) + if ( !mEditorSplitter->getCurEditor() ) return; - const TextDocument& doc = mCurEditor->getDocument(); + const TextDocument& doc = mEditorSplitter->getCurEditor()->getDocument(); mDocMenu->find( "auto_indent" ) ->asType() ->setActive( doc.getAutoDetectIndentType() ); - mDocMenu->find( "indent_type" ) - ->asType() - ->getSubMenu() - ->find( doc.getIndentType() == TextDocument::IndentType::IndentTabs ? "tabs" : "spaces" ) - ->asType() - ->setActive( true ); - mDocMenu->find( "indent_width" ) ->asType() ->getSubMenu() @@ -1341,10 +827,17 @@ void App::updateDocumentMenu() { ->asType() ->setActive( true ); + mDocMenu->find( "indent_type" ) + ->asType() + ->getSubMenu() + ->find( doc.getIndentType() == TextDocument::IndentType::IndentTabs ? "tabs" : "spaces" ) + ->asType() + ->setActive( true ); + mDocMenu->find( "tab_width" ) ->asType() ->getSubMenu() - ->find( String::format( "tab_width_%d", mCurEditor->getTabWidth() ) ) + ->find( String::format( "tab_width_%d", mEditorSplitter->getCurEditor()->getTabWidth() ) ) ->asType() ->setActive( true ); @@ -1365,7 +858,127 @@ void App::updateDocumentMenu() { ->asType() ->setActive( true ); - mDocMenu->find( "read_only" )->asType()->setActive( mCurEditor->isLocked() ); + mDocMenu->find( "read_only" ) + ->asType() + ->setActive( mEditorSplitter->getCurEditor()->isLocked() ); +} + +void App::onDocumentStateChanged( UICodeEditor*, TextDocument& ) { + updateEditorState(); +} + +void App::onDocumentSelectionChange( UICodeEditor* editor, TextDocument& doc ) { + onDocumentModified( editor, doc ); +} + +void App::onCodeEditorFocusChange( UICodeEditor* editor ) { + if ( mSearchState.editor && mSearchState.editor != editor ) { + String word; + /*if ( mEditorSplitter->editorExists( mSearchState.editor ) )*/ { + word = mSearchState.editor->getHighlightWord(); + mSearchState.editor->setHighlightWord( "" ); + mSearchState.editor->setHighlightTextRange( TextRange() ); + mSearchState.text = ""; + mSearchState.range = TextRange(); + } + if ( editor ) { + mSearchState.editor = editor; + mSearchState.editor->setHighlightWord( word ); + mSearchState.range = TextRange(); + } + } +} + +void App::onColorSchemeChanged( const std::string& ) { + updateColorSchemeMenu(); +} + +void App::onDocumentLoaded( UICodeEditor* codeEditor, const std::string& path ) { + updateEditorTitle( codeEditor ); + if ( codeEditor == mEditorSplitter->getCurEditor() ) + updateCurrentFiletype(); + mEditorSplitter->removeUnusedTab( mEditorSplitter->tabWidgetFromEditor( codeEditor ) ); + auto found = std::find( mRecentFiles.begin(), mRecentFiles.end(), path ); + if ( found != mRecentFiles.end() ) + mRecentFiles.erase( found ); + mRecentFiles.insert( mRecentFiles.begin(), path ); + if ( mRecentFiles.size() > 10 ) + mRecentFiles.resize( 10 ); + updateRecentFiles(); +} + +const UICodeEditorSplitter::CodeEditorConfig& App::getCodeEditorConfig() const { + return mConfig.editor; +} + +void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { + editor->addKeyBindingString( "alt+return", "fullscreen-toggle", true ); + editor->addKeyBindingString( "alt+keypad enter", "fullscreen-toggle", true ); + editor->addKeyBindingString( "f2", "open-file", true ); + editor->addKeyBindingString( "f3", "repeat-find", false ); + editor->addKeyBindingString( "f12", "console-toggle", true ); + editor->addKeyBindingString( "ctrl+f", "find-replace", false ); + editor->addKeyBindingString( "ctrl+q", "close-app", true ); + editor->addKeyBindingString( "ctrl+o", "open-file", true ); + doc.setCommand( "save-doc", [&] { saveDoc(); } ); + doc.setCommand( "save-as-doc", [&] { saveFileDialog(); } ); + doc.setCommand( "find-replace", [&] { showFindView(); } ); + doc.setCommand( "repeat-find", [&] { findNextText( mSearchState ); } ); + doc.setCommand( "close-app", [&] { closeApp(); } ); + doc.setCommand( "fullscreen-toggle", [&]() { + mWindow->toggleFullscreen(); + mViewMenu->find( "fullscreen-mode" ) + ->asType() + ->setActive( !mWindow->isWindowed() ); + } ); + doc.setCommand( "open-file", [&] { openFileDialog(); } ); + doc.setCommand( "console-toggle", [&] { + mConsole->toggle(); + bool lock = mConsole->isActive(); + mEditorSplitter->forEachEditor( + [lock]( UICodeEditor* editor ) { editor->setLocked( lock ); } ); + } ); + doc.setCommand( "lock", [&] { + if ( mEditorSplitter->getCurEditor() ) { + mEditorSplitter->getCurEditor()->setLocked( true ); + updateDocumentMenu(); + } + } ); + doc.setCommand( "unlock", [&] { + if ( mEditorSplitter->getCurEditor() ) { + mEditorSplitter->getCurEditor()->setLocked( false ); + updateDocumentMenu(); + } + } ); + doc.setCommand( "lock-toggle", [&] { + if ( mEditorSplitter->getCurEditor() ) { + mEditorSplitter->getCurEditor()->setLocked( + !mEditorSplitter->getCurEditor()->isLocked() ); + updateDocumentMenu(); + } + } ); + + if ( mDefKeybindings.empty() ) { + std::string bindingsPath = mConfigPath + "keybindings.cfg"; + IniFile ini( bindingsPath ); + if ( FileSystem::fileExists( bindingsPath ) ) { + mDefKeybindings = ini.getKeyMap( "keybindings" ); + } else { + const ShortcutMap& map = editor->getKeyBindings().getShortcutMap(); + for ( auto it : map ) { + KeyBindings::Shortcut shortcut( it.first ); + ini.setValue( "keybindings", editor->getKeyBindings().getShortcutString( shortcut ), + it.second ); + } + ini.writeFile(); + mDefKeybindings = ini.getKeyMap( "keybindings" ); + } + } + + if ( !mDefKeybindings.empty() ) { + editor->getKeyBindings().reset(); + editor->getKeyBindings().addKeybindsString( mDefKeybindings ); + } } void App::createSettingsMenu() { @@ -1415,30 +1028,23 @@ void App::createSettingsMenu() { updateRecentFiles(); } -void App::setColorScheme( const std::string& name ) { - if ( name != mCurrentColorScheme ) { - mCurrentColorScheme = name; - applyColorScheme( mColorSchemes[mCurrentColorScheme] ); - } -} - void App::updateColorSchemeMenu() { for ( size_t i = 0; i < mColorSchemeMenu->getCount(); i++ ) { UIMenuRadioButton* menuItem = mColorSchemeMenu->getItem( i )->asType(); - menuItem->setActive( mCurrentColorScheme == menuItem->getText() ); + menuItem->setActive( mEditorSplitter->getCurrentColorScheme() == menuItem->getText() ); } } UIMenu* App::createColorSchemeMenu() { mColorSchemeMenu = UIPopUpMenu::New(); - for ( auto& colorScheme : mColorSchemes ) { - mColorSchemeMenu->addRadioButton( colorScheme.first, - mCurrentColorScheme == colorScheme.first ); + for ( auto& colorScheme : mEditorSplitter->getColorSchemes() ) { + mColorSchemeMenu->addRadioButton( + colorScheme.first, mEditorSplitter->getCurrentColorScheme() == colorScheme.first ); } mColorSchemeMenu->addEventListener( Event::OnItemClicked, [&]( const Event* event ) { UIMenuItem* item = event->getNode()->asType(); const String& name = item->getText(); - setColorScheme( name ); + mEditorSplitter->setColorScheme( name ); } ); return mColorSchemeMenu; } @@ -1449,13 +1055,16 @@ UIMenu* App::createFiletypeMenu() { auto names = dM->getLanguageNames(); for ( auto& name : names ) { mFiletypeMenu->addRadioButton( - name, mCurEditor && mCurEditor->getSyntaxDefinition().getLanguageName() == name ); + name, + mEditorSplitter->getCurEditor() && + mEditorSplitter->getCurEditor()->getSyntaxDefinition().getLanguageName() == name ); } mFiletypeMenu->addEventListener( Event::OnItemClicked, [&, dM]( const Event* event ) { UIMenuItem* item = event->getNode()->asType(); const String& name = item->getText(); - if ( mCurEditor ) { - mCurEditor->setSyntaxDefinition( dM->getStyleByLanguageName( name ) ); + if ( mEditorSplitter->getCurEditor() ) { + mEditorSplitter->getCurEditor()->setSyntaxDefinition( + dM->getStyleByLanguageName( name ) ); updateCurrentFiletype(); } } ); @@ -1463,9 +1072,9 @@ UIMenu* App::createFiletypeMenu() { } void App::updateCurrentFiletype() { - if ( !mCurEditor ) + if ( !mEditorSplitter->getCurEditor() ) return; - std::string curLang( mCurEditor->getSyntaxDefinition().getLanguageName() ); + std::string curLang( mEditorSplitter->getCurEditor()->getSyntaxDefinition().getLanguageName() ); for ( size_t i = 0; i < mFiletypeMenu->getCount(); i++ ) { UIMenuRadioButton* menuItem = mFiletypeMenu->getItem( i )->asType(); std::string itemLang( menuItem->getText() ); @@ -1474,8 +1083,8 @@ void App::updateCurrentFiletype() { } void App::updateEditorState() { - if ( mCurEditor ) { - updateEditorTitle( mCurEditor ); + if ( mEditorSplitter->getCurEditor() ) { + updateEditorTitle( mEditorSplitter->getCurEditor() ); updateCurrentFiletype(); updateDocumentMenu(); } @@ -1548,20 +1157,6 @@ void App::init( const std::string& file, const Float& pidelDensity ) { ->setDefaultFontSize( mConfig.ui.fontSize ) ->add( theme ); - auto colorSchemes = - SyntaxColorScheme::loadFromFile( resPath + "assets/colorschemes/colorschemes.conf" ); - if ( !colorSchemes.empty() ) { - for ( auto& colorScheme : colorSchemes ) - mColorSchemes[colorScheme.getName()] = colorScheme; - mCurrentColorScheme = - mColorSchemes.find( mConfig.editor.colorScheme ) != mColorSchemes.end() - ? mConfig.editor.colorScheme - : colorSchemes[0].getName(); - } else { - mColorSchemes["default"] = SyntaxColorScheme::getDefault(); - mCurrentColorScheme = "default"; - } - mUISceneNode->getRoot()->addClass( "appbackground" ); const std::string baseUI = R"xml( @@ -1676,14 +1271,19 @@ void App::init( const std::string& file, const Float& pidelDensity ) { addIcon( "fullscreen", 0xed9c, 12 ); mUISceneNode->getUIIconThemeManager()->setCurrentTheme( iconTheme ); + + mEditorSplitter = UICodeEditorSplitter::New( + this, mUISceneNode, + SyntaxColorScheme::loadFromFile( resPath + "assets/colorschemes/colorschemes.conf" ), + mInitColorScheme ); + initSearchBar(); createSettingsMenu(); - createEditorWithTabWidget( mBaseLayout ); - if ( !file.empty() ) { - loadFileFromPath( file ); - } + mEditorSplitter->createEditorWithTabWidget( mBaseLayout ); + if ( !file.empty() && FileSystem::fileExists( file ) ) + mEditorSplitter->loadFileFromPath( FileSystem::getRealPath( file ) ); mConsole = eeNew( Console, ( fontMono, true, true, 1024 * 1000, 0, mWindow ) ); diff --git a/src/tools/codeeditor/codeeditor.hpp b/src/tools/codeeditor/codeeditor.hpp index af4e2e087..b42b5e204 100644 --- a/src/tools/codeeditor/codeeditor.hpp +++ b/src/tools/codeeditor/codeeditor.hpp @@ -35,39 +35,36 @@ class UISearchBar : public UILinearLayout { } }; -class App { +struct UIConfig { + Float fontSize{11}; +}; + +struct WindowConfig { + Float pixelDensity{0}; + Sizei size{1280, 720}; + bool maximized{false}; +}; + +struct AppConfig { + WindowConfig window; + UICodeEditorSplitter::CodeEditorConfig editor; + UIConfig ui; +}; + +struct SearchState { + UICodeEditor* editor{nullptr}; + String text; + TextRange range = TextRange(); + bool caseSensitive{false}; + void reset() { + editor = nullptr; + range = TextRange(); + text = ""; + } +}; + +class App : public UICodeEditorSplitter::Client { public: - struct Config { - struct { - Float pixelDensity{0}; - Sizei size{1280, 720}; - bool maximized{false}; - } window; - struct { - std::string colorScheme{"lite"}; - Float fontSize{11}; - 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}; - int indentWidth{4}; - int tabWidth{4}; - int lineBreakingColumn{100}; - } editor; - struct { - Float fontSize{11}; - } ui; - }; - - enum class SplitDirection { Left, Right, Top, Bottom }; - ~App(); void init( const std::string& file, const Float& pidelDensity ); @@ -80,9 +77,9 @@ class App { void saveFileDialog(); - void findPrevText( String text = "", const bool& caseSensitive = true ); + void findPrevText( SearchState& search ); - void findNextText( String text = "", const bool& caseSensitive = true ); + void findNextText( SearchState& search ); void closeApp(); @@ -90,37 +87,11 @@ class App { void showFindView(); - UICodeEditor* createCodeEditor(); + void replaceSelection( SearchState& search, const String& replacement ); - UITabWidget* tabWidgetFromEditor( UICodeEditor* editor ); + void replaceAll( SearchState& search, const String& replace ); - UISplitter* splitterFromEditor( UICodeEditor* editor ); - - std::pair createCodeEditorInTabWidget( UITabWidget* tabWidget ); - - UITabWidget* createEditorWithTabWidget( Node* parent ); - - void splitEditor( const SplitDirection& direction, UICodeEditor* editor ); - - void focusSomeEditor( Node* searchFrom = nullptr ); - - void switchToTab( Int32 index ); - - UITabWidget* findPreviousSplit( UICodeEditor* editor ); - - void switchPreviousSplit( UICodeEditor* editor ); - - UITabWidget* findNextSplit( UICodeEditor* editor ); - - void switchNextSplit( UICodeEditor* editor ); - - void applyColorScheme( const SyntaxColorScheme& colorScheme ); - - void replaceSelection( const String& replacement ); - - void replaceAll( String find, const String& replace, const bool& caseSensitive ); - - void findAndReplace( String find, String replace, const bool& caseSensitive ); + void findAndReplace( SearchState& search, const String& replace ); void runCommand( const std::string& command ); @@ -128,22 +99,14 @@ class App { void saveConfig(); - void loadFileFromPathInNewTab( const std::string& path ); - - void setCurrentEditor( UICodeEditor* editor ); - protected: EE::Window::Window* mWindow{nullptr}; UISceneNode* mUISceneNode{nullptr}; - UICodeEditor* mCurEditor{nullptr}; Console* mConsole{nullptr}; std::string mWindowTitle{"ecode"}; String mLastSearch; UILayout* mBaseLayout{nullptr}; UISearchBar* mSearchBarLayout{nullptr}; - std::vector mTabWidgets; - std::map mColorSchemes; - std::string mCurrentColorScheme; UIPopUpMenu* mSettingsMenu{nullptr}; UITextView* mSettingsButton{nullptr}; UIPopUpMenu* mColorSchemeMenu{nullptr}; @@ -151,9 +114,14 @@ class App { IniFile mIni; IniFile mIniState; std::vector mRecentFiles; - Config mConfig; + AppConfig mConfig; UIPopUpMenu* mDocMenu{nullptr}; UIPopUpMenu* mViewMenu{nullptr}; + UICodeEditorSplitter* mEditorSplitter{nullptr}; + std::string mInitColorScheme; + std::map mDefKeybindings; + std::string mConfigPath; + SearchState mSearchState; void onFileDropped( String file ); @@ -167,14 +135,6 @@ class App { bool onCloseRequestCallback( EE::Window::Window* ); - void closeEditorTab( UICodeEditor* editor ); - - void onTabClosed( const TabEvent* tabEvent ); - - void closeSplitter( UISplitter* splitter ); - - void closeTabWidgets( UISplitter* splitter ); - void initSearchBar(); void addRemainingTabWidgets( Node* widget ); @@ -185,8 +145,6 @@ class App { void updateColorSchemeMenu(); - void setColorScheme( const std::string& name ); - UIMenu* createFiletypeMenu(); void updateCurrentFiletype(); @@ -195,12 +153,8 @@ class App { void saveDoc(); - void removeUnusedTab( UITabWidget* tabWidget ); - void updateRecentFiles(); - void forEachEditor( std::function run ); - UIMenu* createViewMenu(); UIMenu* createEditMenu(); @@ -211,11 +165,21 @@ class App { void updateDocumentMenu(); - void zoomIn(); + void onDocumentStateChanged( UICodeEditor*, TextDocument& ); - void zoomOut(); + void onDocumentModified( UICodeEditor* editor, TextDocument& ); - void zoomReset(); + void onColorSchemeChanged( const std::string& ); + + void onDocumentLoaded( UICodeEditor* codeEditor, const std::string& path ); + + const UICodeEditorSplitter::CodeEditorConfig& getCodeEditorConfig() const; + + void onCodeEditorCreated( UICodeEditor*, TextDocument& doc ); + + void onDocumentSelectionChange( UICodeEditor* editor, TextDocument& ); + + void onCodeEditorFocusChange( UICodeEditor* editor ); }; #endif // EE_TOOLS_CODEEDITOR_HPP