diff --git a/bin/assets/colorschemes/colorschemes.conf b/bin/assets/colorschemes/colorschemes.conf index 30db30db9..90a9b48d6 100644 --- a/bin/assets/colorschemes/colorschemes.conf +++ b/bin/assets/colorschemes/colorschemes.conf @@ -186,6 +186,8 @@ selection = #b7dce8 line_number = #d0d0d0 line_number2 = #808080 line_highlight = #f2f2f2 +suggestion = #404040,#e8e8e8 +suggestion_selected = #404040,#f7f7f7 normal = #181818 symbol = #181818 @@ -391,6 +393,9 @@ minimap_highlight = #FF00FFFF error = #990000FF; warning = #999900FF; notice = #8abdff +suggestion = #1d1f27,#e1e1e6 +suggestion_selected = #222533,#ffffff +suggestion_scrollbar = #3daee9 [solarized light] background = #fdf6e3 @@ -401,6 +406,9 @@ selection = #eee8d5 line_number = #93a1a1 line_number2 = #002b36 line_highlight = #eee8d5 +suggestion = #657b83,#f7f1de +suggestion_selected = #657b83,#eee8d5 +suggestion_scrollbar = #002b36 normal = #657b83 symbol = #657b83 @@ -583,6 +591,9 @@ minimap_highlight = #FF00FFFF error = #990000FF; warning = #999900FF; notice = #8abdff +suggestion = #1d1f27,#e1e1e6 +suggestion_selected = #222533,#f7f9f9 +suggestion_scrollbar = #ff5971 [solarobj] background = #fdf6e3 @@ -613,6 +624,10 @@ error = #990000FF; warning = #999900FF; notice = #8abdff +suggestion = #3e3c37,#ebe4d2 +suggestion_selected = #3e3c37,#fff8e4 +suggestion_scrollbar = #6c71c4 + [catppuccin macchiato] background = #24273a text = #cad3f5 @@ -669,8 +684,8 @@ line_break_column = #54575b99 matching_bracket = #dbdbdb matching_selection = #ffff00 matching_search = #0000f0 -suggestion = #e1e1e6,#1d1f27 -suggestion_selected = #ffffff,#222533 +suggestion = #1d1f27,#e1e1e6 +suggestion_selected = #222533,#ffffff suggestion_scrollbar = #3daee9 error = #ff3030 warning = yellow diff --git a/bin/assets/plugins/debugger.json b/bin/assets/plugins/debugger.json index fff95bdc2..e4d25cc95 100644 --- a/bin/assets/plugins/debugger.json +++ b/bin/assets/plugins/debugger.json @@ -20,32 +20,6 @@ "env": "${env}" } }, - { - "name": "Launch binary in Terminal", - "request": "launch", - "arguments": { - "program": "${file}", - "args": "${args}", - "cwd": "${cwd}", - "env": "${env}", - "runInTerminal": true - } - }, - { - "name": "Attach to Name", - "request": "attach", - "arguments": { - "program": "${file}" - } - }, - { - "name": "Attach to Name (wait)", - "request": "attach", - "arguments": { - "program": "${file}", - "waitFor": true - } - }, { "name": "Attach to PID", "request": "attach", @@ -98,15 +72,17 @@ } }, { - "name": "Attach to Name", + "name": "Attach to binary", "request": "attach", + "runTarget": true, "arguments": { "program": "${file}" } }, { - "name": "Attach to Name (wait)", + "name": "Attach to binary (wait)", "request": "attach", + "runTarget": true, "arguments": { "program": "${file}", "waitFor": true @@ -182,7 +158,7 @@ { "name": "debugpy", "url": "https://github.com/microsoft/debugpy", - "type": "python", + "type": "debugpy", "languages": [ "python" ], "run": { "command": "python3", @@ -209,7 +185,7 @@ } }, { - "name": "Attach", + "name": "Attach to PID", "command_arguments": ["--pid", "${command:pickProcess}"], "request": "attach", "arguments": { @@ -258,8 +234,7 @@ "request": "launch", "arguments": { "program": "${file}", - "args": "${args}", - "stopOnEntry": true + "args": "${args}" } } ] diff --git a/bin/assets/plugins/lspclient.json b/bin/assets/plugins/lspclient.json index 5a96f9dca..7864e827d 100644 --- a/bin/assets/plugins/lspclient.json +++ b/bin/assets/plugins/lspclient.json @@ -103,7 +103,8 @@ "name": "zls", "url": "https://github.com/zigtools/zls", "command": "zls", - "file_patterns": ["%.zig$"] + "file_patterns": ["%.zig$"], + "extra_trigger_chars": [ "," ] }, { "language": "odin", diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index 54720c8ac..462b82b34 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -302,7 +302,9 @@ ListView::cell { } ListBox:hover, -ListView:hover { +ListView:hover, +ListBox:focus, +ListView:focus { border-color: var(--primary); } @@ -383,8 +385,8 @@ SpinBox::input { } SpinBox::input, -SpinBox::input::hover, -SpinBox::input::focus { +SpinBox::input:hover, +SpinBox::input:focus { border-width: 0dp; border-color: transparent; } diff --git a/include/eepp/core/string.hpp b/include/eepp/core/string.hpp index b38317de7..feb1e36e7 100644 --- a/include/eepp/core/string.hpp +++ b/include/eepp/core/string.hpp @@ -437,6 +437,9 @@ class EE_API String { /** @return The number of codepoints of the utf8 string. */ static size_t utf8Length( const std::string_view& utf8String ); + /** Converts a character position from an utf8 string to a code point position */ + static size_t utf8ToCodepointPosition( const std::string_view& utf8Text, size_t utf8Pos ); + /** @return The next character in a utf8 null terminated string */ static Uint32 utf8Next( char*& utf8String ); diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index abd3e0842..91bee978e 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -91,15 +91,20 @@ class EE_API TextDocument { virtual void onDocumentClosed( TextDocument* ) = 0; virtual void onDocumentDirtyOnFileSystem( TextDocument* ) = 0; virtual void onDocumentMoved( TextDocument* ) = 0; + virtual void onDocumentReset( TextDocument* ) = 0; + virtual void onDocumentReloaded( TextDocument* doc ) { onDocumentClosed( doc ); onDocumentLoaded( doc ); } - virtual void onDocumentReset( TextDocument* ) = 0; + virtual void onDocumentSyntaxDefinitionChange( const SyntaxDefinition& ) {} + virtual void onDocumentLineMove( const Int64& /*fromLine*/, const Int64& /*toLine*/, const Int64& /*numLines*/ ) {} + virtual TextRange getVisibleRange() const { return {}; }; + virtual void onFoldRegionsUpdated( size_t /*oldCount*/, size_t /*newCount*/ ) {} }; diff --git a/include/eepp/ui/doc/textposition.hpp b/include/eepp/ui/doc/textposition.hpp index 94bccf785..bb93e4efa 100644 --- a/include/eepp/ui/doc/textposition.hpp +++ b/include/eepp/ui/doc/textposition.hpp @@ -81,6 +81,10 @@ class EE_API TextPosition { return {}; } + Int64 distance( const TextPosition& other ) const { + return std::abs( mLine - other.mLine ) + std::abs( mColumn - other.mColumn ); + } + private: Int64 mLine{ 0xffffffff }; Int64 mColumn{ 0xffffffff }; diff --git a/include/eepp/ui/doc/textrange.hpp b/include/eepp/ui/doc/textrange.hpp index c302f0873..eb6b2cd29 100644 --- a/include/eepp/ui/doc/textrange.hpp +++ b/include/eepp/ui/doc/textrange.hpp @@ -114,6 +114,14 @@ class EE_API TextRange { bool isNormalized() const { return mStart <= mEnd; } + static TextRange convertToLineColumn( const String::View& text, Int64 startOffset, + Int64 endOffset ); + + static TextRange convertToLineColumn( const std::string_view& text, Int64 startOffset, + Int64 endOffset ); + + Int64 minimumDistance(const TextRange& other) const; + private: TextPosition mStart; TextPosition mEnd; @@ -121,6 +129,10 @@ class EE_API TextRange { TextPosition normalizedStart() const { return mStart < mEnd ? mStart : mEnd; } TextPosition normalizedEnd() const { return mStart < mEnd ? mEnd : mStart; } + + template + static TextRange convertToLineColumn( const StringType& text, Int64 startOffset, + Int64 endOffset ); }; class EE_API TextRanges : public std::vector { diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index ff9021cc8..fbc681e84 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -693,7 +693,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Vector2f getScreenScroll() const; - Float getViewportWidth( const bool& forceVScroll = false ) const; + Float getViewportWidth( bool forceVScroll = false, bool includeMinimap = false ) const; Float getTopAreaWidth() const; @@ -779,6 +779,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Float getPluginsGutterSpace() const; + void setEnableFlashCursor( bool enable ) { mEnableFlashCursor = enable; } + + bool isEnabledFlashCursor() { return mEnableFlashCursor; } + protected: struct LastXOffset { TextPosition position{ 0, 0 }; @@ -826,10 +830,11 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { bool mFoldsAlwaysVisible{ false }; bool mFoldsVisible{ false }; bool mFoldsIsFirst{ true }; + bool mEnableFlashCursor{ false }; + Uint32 mTabWidth; std::atomic mHighlightWordProcessing{ false }; TextRange mLinkPosition; String mLink; - Uint32 mTabWidth; Vector2f mScroll; Float mMouseWheelScroll; Float mFontSize; @@ -854,6 +859,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { Color mMinimapHoverColor; Color mMinimapSelectionColor; Color mMinimapHighlightColor; + Color mPreviewColor; SyntaxColorScheme mColorScheme; UIScrollBar* mVScrollBar; UIScrollBar* mHScrollBar; @@ -872,7 +878,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { TextRanges mHighlightWordCache; Mutex mHighlightWordCacheMutex; TextRange mHighlightTextRange; - Color mPreviewColor; TextRange mPreviewColorRange; std::vector mPlugins; UILoader* mLoader{ nullptr }; @@ -903,6 +908,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { String::HashType mTagFoldRange{ 0 }; Uint32 mTabIndentCharacter{ 187 /*'ยป'*/ }; CharacterAlignment mTabIndentAlignment{ CharacterAlignment::Center }; + Uint32 mModDownCount{ 0 }; + Clock mModDownClock; UICodeEditor( const std::string& elementTag, const bool& autoRegisterBaseCommands = true, const bool& autoRegisterBaseKeybindings = true ); @@ -1104,6 +1111,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client { void refreshTag(); bool isNotMonospace() const; + + void flashCursor(); }; }} // namespace EE::UI diff --git a/src/eepp/core/string.cpp b/src/eepp/core/string.cpp index 9a30db70c..dff88fee1 100644 --- a/src/eepp/core/string.cpp +++ b/src/eepp/core/string.cpp @@ -1261,18 +1261,18 @@ bool String::isAscii() const { size_t len = mString.size(); size_t i = 0; -// #ifdef EE_STD_SIMD -// using simd_type = simd::native_simd; -// constexpr size_t simd_size = simd_type::size(); -// const simd_type ascii_limit = 127; -// for ( ; i + simd_size - 1 < len; i += simd_size ) { -// simd_type chunk; -// chunk.copy_from( &data[i], simd::element_aligned ); -// auto mask = chunk > ascii_limit; -// if ( simd::any_of( mask ) ) -// return false; -// } -// #endif + // #ifdef EE_STD_SIMD + // using simd_type = simd::native_simd; + // constexpr size_t simd_size = simd_type::size(); + // const simd_type ascii_limit = 127; + // for ( ; i + simd_size - 1 < len; i += simd_size ) { + // simd_type chunk; + // chunk.copy_from( &data[i], simd::element_aligned ); + // auto mask = chunk > ascii_limit; + // if ( simd::any_of( mask ) ) + // return false; + // } + // #endif for ( ; i < len; ++i ) if ( data[i] > 127 ) @@ -1590,6 +1590,44 @@ Uint32 String::utf8Next( char*& utf8String ) { return utf8::unchecked::next( utf8String ); } +size_t String::utf8ToCodepointPosition( const std::string_view& utf8Text, size_t utf8Pos ) { + size_t codepointPos = 0; + size_t bytePos = 0; + + while ( bytePos < utf8Pos && bytePos < utf8Text.length() ) { + unsigned char c = utf8Text[bytePos]; + + // Skip continuation bytes (10xxxxxx) + if ( ( c & 0xC0 ) == 0x80 ) { + bytePos++; + continue; + } + + // Count this as one codepoint + codepointPos++; + + // Move to the next character + if ( c < 0x80 ) + bytePos += 1; // ASCII + else if ( c < 0xE0 ) + bytePos += 2; // 2-byte sequence + else if ( c < 0xF0 ) + bytePos += 3; // 3-byte sequence + else + bytePos += 4; // 4-byte sequence + + // Handle potential truncated sequences + if ( bytePos > utf8Text.length() ) + break; + } + + // If we're in the middle of a character, return the previous complete character position + if ( utf8Pos > bytePos ) + codepointPos--; + + return codepointPos; +} + String::operator std::string() const { return toUtf8(); } diff --git a/src/eepp/ui/doc/documentview.cpp b/src/eepp/ui/doc/documentview.cpp index 704b3074e..4124a4181 100644 --- a/src/eepp/ui/doc/documentview.cpp +++ b/src/eepp/ui/doc/documentview.cpp @@ -324,26 +324,67 @@ DocumentView::VisibleLineRange DocumentView::getVisibleLineRange( const TextPosi info.range = mDoc->getLineRange( pos.line() ); return info; } + Int64 fromIdx = static_cast( toVisibleIndex( pos.line() ) ); Int64 toIdx = static_cast( toVisibleIndex( pos.line(), true ) ); - // TODO: Implement binary search + DocumentView::VisibleLineRange info; - for ( Int64 i = fromIdx; i < toIdx; i++ ) { - Int64 fromCol = mVisibleLines[i].column(); - Int64 toCol = i + 1 <= toIdx - ? mVisibleLines[i + 1].column() - ( allowVisualLineEnd ? 0 : 1 ) + + // If we have no visible lines in range, return early + if ( fromIdx >= toIdx ) { + info.visibleIndex = static_cast( toIdx ); + if ( info.visibleIndex != VisibleIndex::invalid ) { + info.range = { { pos.line(), mVisibleLines[toIdx].column() }, + mDoc->endOfLine( { pos.line(), 0ll } ) }; + } + return info; + } + + // Binary search implementation + Int64 left = fromIdx; + Int64 right = toIdx - 1; // Subtract 1 since we need to access [i+1] in the loop + + while ( left <= right ) { + Int64 mid = left + ( right - left ) / 2; + + Int64 fromCol = mVisibleLines[mid].column(); + Int64 toCol = mid + 1 <= toIdx + ? mVisibleLines[mid + 1].column() - ( allowVisualLineEnd ? 0 : 1 ) : mDoc->line( pos.line() ).size(); + if ( pos.column() >= fromCol && pos.column() <= toCol ) { - info.visibleIndex = static_cast( i ); + // If it's between the limits we must check if it fits into the previous one + if ( allowVisualLineEnd && pos.column() == fromCol && mid - 1 >= 0 ) { + Int64 fromCol = mVisibleLines[mid - 1].column(); + Int64 toCol = mid <= toIdx + ? mVisibleLines[mid].column() - ( allowVisualLineEnd ? 0 : 1 ) + : mDoc->line( pos.line() ).size(); + + info.visibleIndex = static_cast( mid - 1 ); + info.range = { { pos.line(), fromCol }, { pos.line(), toCol } }; + return info; + } + + // Found the correct range + info.visibleIndex = static_cast( mid ); info.range = { { pos.line(), fromCol }, { pos.line(), toCol } }; return info; } + + if ( pos.column() < fromCol ) { + right = mid - 1; + } else { + left = mid + 1; + } } + + // If we didn't find an exact match, return the last possible range eeASSERT( toIdx >= 0 ); info.visibleIndex = static_cast( toIdx ); - if ( info.visibleIndex != VisibleIndex::invalid ) + if ( info.visibleIndex != VisibleIndex::invalid ) { info.range = { { pos.line(), mVisibleLines[toIdx].column() }, mDoc->endOfLine( { pos.line(), 0ll } ) }; + } return info; } diff --git a/src/eepp/ui/doc/languages/markdown.cpp b/src/eepp/ui/doc/languages/markdown.cpp index 854728fcc..0f859b3ab 100644 --- a/src/eepp/ui/doc/languages/markdown.cpp +++ b/src/eepp/ui/doc/languages/markdown.cpp @@ -40,6 +40,8 @@ void addMarkdown() { "?[%w_.~!*:@&+$/?%%#=-]*)%)" }, { "keyword", "function", "link" } }, { { "!?%[([^%]].-)%]%((%#+[%w-]*)%)" }, { "keyword", "function", "link" } }, + { { "!?%[([^%]].-)%]%(([%w_.~!*:@&+$/?%%#=-]*)%)" }, + { "keyword", "function", "string" } }, { { "https?://[%w_.~!*:@&+$/?%%#-]-%w[-.%w]*%.%w%w%w?%w?:?%d*/?[%w_.~!*:@&+$/" "?%%#=-]*" }, "link" }, diff --git a/src/eepp/ui/doc/syntaxtokenizer.cpp b/src/eepp/ui/doc/syntaxtokenizer.cpp index 327e9e7ab..907807400 100644 --- a/src/eepp/ui/doc/syntaxtokenizer.cpp +++ b/src/eepp/ui/doc/syntaxtokenizer.cpp @@ -197,6 +197,7 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax PatternMatcher::Range matches[12]; int start, end; + const std::string_view textv{ text }; size_t numMatches; size_t i = startIndex; SyntaxState retState = state; @@ -228,7 +229,7 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax ( range.first == -1 || rangeSubsyntax.first < range.first ) ) { if ( !skipSubSyntaxSeparator ) { pushToken( tokens, curState.subsyntaxInfo->types[0], - text.substr( i, rangeSubsyntax.second - i ) ); + textv.substr( i, rangeSubsyntax.second - i ) ); } popSubsyntax( curState, retState, syntax ); i = rangeSubsyntax.second; @@ -239,16 +240,16 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax if ( !skip ) { if ( range.first != -1 ) { if ( range.second > range.first && pattern.types.size() >= 3 ) { - pushToken( tokens, pattern.types[0], text.substr( i, range.first - i ) ); + pushToken( tokens, pattern.types[0], textv.substr( i, range.first - i ) ); pushToken( tokens, pattern.types[pattern.types.size() - 1], - text.substr( range.first, range.second - range.first ) ); + textv.substr( range.first, range.second - range.first ) ); } else { - pushToken( tokens, pattern.types[0], text.substr( i, range.second - i ) ); + pushToken( tokens, pattern.types[0], textv.substr( i, range.second - i ) ); } setSubsyntaxPatternIdx( curState, retState, SYNTAX_TOKENIZER_STATE_NONE ); i = range.second; } else { - pushToken( tokens, pattern.types[0], text.substr( i ) ); + pushToken( tokens, pattern.types[0], textv.substr( i ) ); break; } } @@ -264,7 +265,7 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax if ( rangeSubsyntax.first != -1 ) { if ( !skipSubSyntaxSeparator ) { pushToken( tokens, curState.subsyntaxInfo->types[0], - text.substr( i, rangeSubsyntax.second - i ) ); + textv.substr( i, rangeSubsyntax.second - i ) ); } popSubsyntax( curState, retState, syntax ); i = rangeSubsyntax.second; @@ -292,7 +293,7 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax int patternMatchStart = matches[0].start; int patternMatchEnd = matches[0].end; std::string patternFullText( - text.substr( patternMatchStart, patternMatchEnd - patternMatchStart ) ); + textv.substr( patternMatchStart, patternMatchEnd - patternMatchStart ) ); auto patternType = pattern.types[0]; int lastStart = patternMatchStart; int lastEnd = patternMatchEnd; @@ -315,13 +316,13 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax if ( curMatch == 1 && start > lastStart ) { pushToken( tokens, patternType, - text.substr( patternMatchStart, start - patternMatchStart ) ); + textv.substr( patternMatchStart, start - patternMatchStart ) ); } else if ( start > lastEnd ) { pushToken( tokens, patternType, - text.substr( lastEnd, start - lastEnd ) ); + textv.substr( lastEnd, start - lastEnd ) ); } - patternText = text.substr( start, end - start ); + patternText = textv.substr( start, end - start ); SyntaxStyleType type = curState.currentSyntax->getSymbol( patternText ); if ( !skipSubSyntaxSeparator || !pattern.hasSyntax() ) { pushToken( tokens, @@ -344,7 +345,7 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax if ( curMatch == numMatches - 1 && end < patternMatchEnd ) { pushToken( tokens, patternType, - text.substr( end, patternMatchEnd - end ) ); + textv.substr( end, patternMatchEnd - end ) ); i = patternMatchEnd; } @@ -367,7 +368,7 @@ _tokenize( const SyntaxDefinition& syntax, const std::string& text, const Syntax String::utf8Next( strEnd ); end = start + ( strEnd - strStart ); } - patternText = text.substr( start, end - start ); + patternText = textv.substr( start, end - start ); SyntaxStyleType type = curState.currentSyntax->getSymbol( patternText ); if ( !skipSubSyntaxSeparator || !pattern.hasSyntax() ) { pushToken( tokens, diff --git a/src/eepp/ui/doc/textrange.cpp b/src/eepp/ui/doc/textrange.cpp index 1506bef67..486c678e7 100644 --- a/src/eepp/ui/doc/textrange.cpp +++ b/src/eepp/ui/doc/textrange.cpp @@ -1,5 +1,6 @@ #include #include +#include namespace EE { namespace UI { namespace Doc { @@ -101,6 +102,87 @@ TextRange TextRange::fromString( const std::string& range ) { return {}; } +template +TextRange TextRange::convertToLineColumn( const StringType& text, Int64 startOffset, + Int64 endOffset ) { + if ( startOffset < 0 || endOffset < 0 || startOffset > static_cast( text.length() ) || + endOffset > static_cast( text.length() ) || startOffset > endOffset ) { + return {}; + } + + TextRange result; + Int64 currentLine = 0; + Int64 currentCol = 0; + Int64 currentPos = 0; + bool foundStart = false; + bool foundEnd = false; + size_t len = text.length(); + + for ( size_t i = 0; i < len; i++ ) { + if ( currentPos == startOffset ) { + result.setStart( { currentLine, currentCol } ); + foundStart = true; + } + + if ( currentPos == endOffset ) { + result.setEnd( { currentLine, currentCol } ); + foundEnd = true; + } + + if ( foundStart && foundEnd ) + break; + + if ( i == text.length() ) + break; + + if ( text[i] == '\n' ) { + currentLine++; + currentCol = 0; + } else { + currentCol++; + } + + currentPos++; + } + + if ( currentPos == startOffset ) + result.setStart( { currentLine, currentCol } ); + + if ( currentPos == endOffset ) + result.setEnd( { currentLine, currentCol } ); + + return result; +} + +TextRange TextRange::convertToLineColumn( const String::View& text, Int64 startOffset, + Int64 endOffset ) { + return convertToLineColumn( text, startOffset, endOffset ); +} + +TextRange TextRange::convertToLineColumn( const std::string_view& text, Int64 startOffset, + Int64 endOffset ) { + return convertToLineColumn( text, startOffset, endOffset ); +} + +Int64 TextRange::minimumDistance( const TextRange& other ) const { + if ( intersects( other ) ) + return 0; + + auto minDist = std::numeric_limits::max(); + + const TextPosition corners1[] = { mStart, mEnd }; + const TextPosition corners2[] = { other.start(), other.end() }; + + for ( const auto& c1 : corners1 ) { + for ( const auto& c2 : corners2 ) { + auto dist = c1.distance( c2 ); + minDist = std::min( minDist, dist ); + } + } + + return minDist; +} + TextRanges::TextRanges() {} TextRanges::TextRanges( const std::vector& ranges ) : std::vector( ranges ) {} diff --git a/src/eepp/ui/tools/uicodeeditorsplitter.cpp b/src/eepp/ui/tools/uicodeeditorsplitter.cpp index bb7f79d95..bcfb5ed29 100644 --- a/src/eepp/ui/tools/uicodeeditorsplitter.cpp +++ b/src/eepp/ui/tools/uicodeeditorsplitter.cpp @@ -244,24 +244,24 @@ UICodeEditor* UICodeEditorSplitter::createCodeEditor() { registerSplitterCommands( doc ); /* Splitter commands */ - editor->addEventListener( Event::OnFocus, [this]( const Event* event ) { + editor->on( Event::OnFocus, [this]( const Event* event ) { setCurrentWidget( event->getNode()->asType() ); } ); - editor->addEventListener( Event::OnTextChanged, [this]( const Event* event ) { + editor->on( Event::OnTextChanged, [this]( const Event* event ) { mClient->onDocumentModified( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); - editor->addEventListener( Event::OnSelectionChanged, [this]( const Event* event ) { + editor->on( Event::OnSelectionChanged, [this]( const Event* event ) { mClient->onDocumentSelectionChange( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); - editor->addEventListener( Event::OnCursorPosChange, [this]( const Event* event ) { + editor->on( Event::OnCursorPosChange, [this]( const Event* event ) { mClient->onDocumentCursorPosChange( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); - editor->addEventListener( Event::OnDocumentUndoRedo, [this]( const Event* event ) { + editor->on( Event::OnDocumentUndoRedo, [this]( const Event* event ) { mClient->onDocumentUndoRedo( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); @@ -526,7 +526,7 @@ UICodeEditorSplitter::createCodeEditorInTabWidget( UITabWidget* tabWidget ) { return std::make_pair( (UITab*)nullptr, (UICodeEditor*)nullptr ); UICodeEditor* editor = createCodeEditor(); mAboutToAddEditor = editor; - editor->addEventListener( Event::OnDocumentChanged, [this]( const Event* event ) { + editor->on( Event::OnDocumentChanged, [this]( const Event* event ) { mClient->onDocumentStateChanged( event->getNode()->asType(), event->getNode()->asType()->getDocument() ); } ); @@ -568,10 +568,10 @@ UICodeEditorSplitter::createWidgetInTabWidget( UITabWidget* tabWidget, UIWidget* return std::make_pair( (UITab*)nullptr, (UIWidget*)nullptr ); UITab* tab = tabWidget->add( tabName, widget ); widget->setData( (UintPtr)tab ); - widget->addEventListener( Event::OnFocus, [this]( const Event* event ) { + widget->on( Event::OnFocus, [this]( const Event* event ) { setCurrentWidget( event->getNode()->asType() ); } ); - widget->addEventListener( Event::OnTitleChange, [this]( const Event* event ) { + widget->on( Event::OnTitleChange, [this]( const Event* event ) { const TextEvent* tevent = static_cast( event ); UIWidget* widget = event->getNode()->asType(); UITabWidget* tabWidget = tabWidgetFromWidget( widget ); @@ -644,7 +644,7 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) { }, mVisualSplitEdgePercent ); } - tabWidget->addEventListener( Event::OnTabSelected, [this]( const Event* event ) { + tabWidget->on( Event::OnTabSelected, [this]( const Event* event ) { UITabWidget* tabWidget = event->getNode()->asType(); if ( tabWidget->getTabSelected()->getOwnedWidget()->isType( UI_TYPE_CODEEDITOR ) ) { setCurrentEditor( @@ -661,7 +661,7 @@ UITabWidget* UICodeEditorSplitter::createTabWidget( Node* parent ) { } return true; } ); - tabWidget->addEventListener( Event::OnTabClosed, [this]( const Event* event ) { + tabWidget->on( Event::OnTabClosed, [this]( const Event* event ) { onTabClosed( static_cast( event ) ); } ); if ( mOnTabWidgetCreateCb ) @@ -1007,18 +1007,17 @@ bool UICodeEditorSplitter::tryTabClose( UIWidget* widget, "\"%s\" will be lost." ) .toUtf8(), editor->getDocument().getFilename() ) ); - mTryCloseMsgBox->addEventListener( Event::OnConfirm, - [this, editor, focusTabBehavior]( const Event* ) { - closeTab( editor, focusTabBehavior ); - } ); - mTryCloseMsgBox->addEventListener( Event::OnClose, - [this, onMsgBoxCloseCb]( const Event* ) { - mTryCloseMsgBox = nullptr; - if ( mCurEditor ) - mCurEditor->setFocus(); - if ( onMsgBoxCloseCb ) - onMsgBoxCloseCb(); - } ); + mTryCloseMsgBox->on( Event::OnConfirm, + [this, editor, focusTabBehavior]( const Event* ) { + closeTab( editor, focusTabBehavior ); + } ); + mTryCloseMsgBox->on( Event::OnClose, [this, onMsgBoxCloseCb]( const Event* ) { + mTryCloseMsgBox = nullptr; + if ( mCurEditor ) + mCurEditor->setFocus(); + if ( onMsgBoxCloseCb ) + onMsgBoxCloseCb(); + } ); mTryCloseMsgBox->setTitle( widget->getUISceneNode()->i18n( "ask_close_tab", "Close Tab?" ) ); mTryCloseMsgBox->center(); diff --git a/src/eepp/ui/uicheckbox.cpp b/src/eepp/ui/uicheckbox.cpp index 556ee327b..f3d909ce8 100644 --- a/src/eepp/ui/uicheckbox.cpp +++ b/src/eepp/ui/uicheckbox.cpp @@ -289,18 +289,20 @@ bool UICheckBox::applyProperty( const StyleSheetProperty& attribute ) { return true; } -Uint32 UICheckBox::onKeyDown( const KeyEvent& Event ) { - UITextView::onKeyDown( Event ); +Uint32 UICheckBox::onKeyDown( const KeyEvent& event ) { + UITextView::onKeyDown( event ); - if ( Event.getKeyCode() == KEY_SPACE ) { + if ( event.getKeyCode() == KEY_SPACE ) { if ( Sys::getTicks() - mLastTick > 250 ) { mLastTick = Sys::getTicks(); setChecked( !mChecked ); + + return 1; } } - return 1; + return UITextView::onKeyDown( event ); } void UICheckBox::onAlphaChange() { diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 2d5ef6d27..3d19ebf97 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -128,9 +131,9 @@ UICodeEditor::UICodeEditor( const std::string& elementTag, const bool& autoRegis mLineNumberPaddingLeft( PixelDensity::dpToPx( 6 ) ), mLineNumberPaddingRight( PixelDensity::dpToPx( 6 ) ), mFoldRegionWidth( PixelDensity::dpToPx( 12 ) ), + mPreviewColor( Color::Transparent ), mKeyBindings( getUISceneNode()->getWindow()->getInput() ), - mFindLongestLineWidthUpdateFrequency( Seconds( 1 ) ), - mPreviewColor( Color::Transparent ) { + mFindLongestLineWidthUpdateFrequency( Seconds( 1 ) ) { mFlags |= UI_TAB_STOP | UI_OWNS_CHILDS_POSITION | UI_SCROLLABLE; setTextSelection( true ); setColorScheme( SyntaxColorScheme::getDefault() ); @@ -693,10 +696,10 @@ void UICodeEditor::disableEditorFeatures() { mUseDefaultStyle = true; } -Float UICodeEditor::getViewportWidth( const bool& forceVScroll ) const { +Float UICodeEditor::getViewportWidth( bool forceVScroll, bool includeMinimap ) const { Float vScrollWidth = mVScrollBar->isVisible() || forceVScroll ? mVScrollBar->getPixelsSize().getWidth() : 0.f; - if ( mMinimapEnabled ) + if ( mMinimapEnabled && !includeMinimap ) vScrollWidth += getMinimapWidth(); Float viewWidth = eefloor( mSize.getWidth() - mPaddingPx.Left - mPaddingPx.Right - getGutterWidth() - vScrollWidth ); @@ -1107,6 +1110,28 @@ Uint32 UICodeEditor::onTextEditing( const TextEditingEvent& event ) { return 1; } +void UICodeEditor::flashCursor() { + Vector2f screenStart( getScreenStart() ); + Vector2f start( screenStart.x + getGutterWidth(), screenStart.y + getPluginsTopSpace() ); + Vector2f startScroll( start - mScroll ); + auto offset = getTextPositionOffset( mDoc->getSelection().start(), getLineHeight() ); + Vector2f cursorPos( startScroll.x + offset.x - getFontHeight() * 0.5f, + startScroll.y + offset.y ); + UIWidget* widget = UIWidget::New(); + widget->setBorderColor( Color( mCaretColor ).blendAlpha( 100 ).blendAlpha( mAlpha ) ); + widget->setPixelsPosition( cursorPos.floor() ); + widget->setBorderWidth( PixelDensity::dpToPx( 2 ) ); + widget->setPixelsSize( Sizef( getFontHeight(), getFontHeight() ) ); + widget->setEnabled( false ); + + Float scale = eemax( getUISceneNode()->getPixelsSize().getWidth() / getFontHeight(), + getUISceneNode()->getPixelsSize().getHeight() / getFontHeight() ); + + widget->runAction( Actions::Sequence::New( + Actions::Scale::New( { scale, scale }, { 1, 1 }, Milliseconds( 250 ), Ease::Linear ), + Actions::Close::New() ) ); +} + Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { if ( getUISceneNode()->getWindow()->getIME().isEditing() ) return 0; @@ -1129,6 +1154,23 @@ Uint32 UICodeEditor::onKeyDown( const KeyEvent& event ) { return 1; } } + + if ( isEnabledFlashCursor() && event.getSanitizedMod() == KeyMod::getDefaultModifier() ) { + if ( mModDownCount == 0 ) + mModDownClock.restart(); + + if ( mModDownClock.getElapsedTime() < Milliseconds( 250 ) ) { + mModDownCount++; + if ( mModDownCount == 5 ) { + mModDownCount = 0; + flashCursor(); + } + } else + mModDownCount = 0; + + mModDownClock.restart(); + } + return 0; } @@ -4221,6 +4263,7 @@ void UICodeEditor::registerCommands() { mDoc->setCommand( "copy-file-path-and-position", [this] { copyFilePath( true ); } ); mDoc->setCommand( "find-replace", [this] { showFindReplace(); } ); mDoc->setCommand( "open-context-menu", [this] { createContextMenu(); } ); + mDoc->setCommand( "flash-cursor", [this] { flashCursor(); } ); mUnlockedCmd.insert( { "copy", "select-all", "open-containing-folder", "copy-containing-folder-path", "copy-file-path", "copy-file-path-and-position", "open-context-menu", "find-replace" } ); diff --git a/src/eepp/ui/uilistbox.cpp b/src/eepp/ui/uilistbox.cpp index 829c60c40..3074ef2a6 100644 --- a/src/eepp/ui/uilistbox.cpp +++ b/src/eepp/ui/uilistbox.cpp @@ -890,7 +890,7 @@ void UIListBox::selectNext() { } Uint32 UIListBox::onKeyDown( const KeyEvent& event ) { - UINode::onKeyDown( event ); + Uint32 ret = UINode::onKeyDown( event ); if ( mFlags & UI_MULTI_SELECT ) return 0; @@ -961,7 +961,7 @@ Uint32 UIListBox::onKeyDown( const KeyEvent& event ) { itemKeyEvent( event ); - return 1; + return ret; } Uint32 UIListBox::onMessage( const NodeMessage* Msg ) { diff --git a/src/eepp/ui/uipushbutton.cpp b/src/eepp/ui/uipushbutton.cpp index 5ed5b8765..5ab438f18 100644 --- a/src/eepp/ui/uipushbutton.cpp +++ b/src/eepp/ui/uipushbutton.cpp @@ -449,15 +449,15 @@ void UIPushButton::onAlignChange() { mTextBox->setVerticalAlign( getVerticalAlign() ); } -Uint32 UIPushButton::onKeyDown( const KeyEvent& Event ) { - if ( Event.getKeyCode() == KEY_RETURN ) { +Uint32 UIPushButton::onKeyDown( const KeyEvent& event ) { + if ( event.getKeyCode() == KEY_RETURN || event.getKeyCode() == Window::KEY_KP_ENTER ) { NodeMessage Msg( this, NodeMessage::MouseClick, EE_BUTTON_LMASK ); messagePost( &Msg ); onMouseClick( Vector2i( 0, 0 ), EE_BUTTON_LMASK ); pushState( UIState::StatePressed ); } - return UIWidget::onKeyDown( Event ); + return UIWidget::onKeyDown( event ); } Uint32 UIPushButton::onKeyUp( const KeyEvent& Event ) { diff --git a/src/eepp/ui/uitextview.cpp b/src/eepp/ui/uitextview.cpp index c611c44c4..62d45743a 100644 --- a/src/eepp/ui/uitextview.cpp +++ b/src/eepp/ui/uitextview.cpp @@ -990,7 +990,7 @@ Uint32 UIAnchor::onKeyDown( const KeyEvent& event ) { } } - return 0; + return UIWidget::onKeyDown( event ); } std::string UIAnchor::getPropertyString( const PropertyDefinition* propertyDef, diff --git a/src/eepp/ui/uiwindow.cpp b/src/eepp/ui/uiwindow.cpp index 16bf4d5b0..7e41f7d72 100644 --- a/src/eepp/ui/uiwindow.cpp +++ b/src/eepp/ui/uiwindow.cpp @@ -1783,7 +1783,7 @@ Uint32 UIWindow::onKeyDown( const KeyEvent& event ) { std::string cmd = mKeyBindings.getCommandFromKeyBind( { event.getKeyCode(), event.getMod() } ); if ( !cmd.empty() ) { executeKeyBindingCommand( cmd ); - return 0; + return 1; } return UIWidget::onKeyDown( event ); } diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp index d47bc7ba8..aae3bf473 100644 --- a/src/tools/ecode/appconfig.cpp +++ b/src/tools/ecode/appconfig.cpp @@ -182,6 +182,7 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath, editor.tabIndentAlignment = characterAlignmentFromString( ini.getValue( "editor", "tab_indent_alignment", characterAlignmentToString( CharacterAlignment::Center ) ) ); + editor.flashCursor = ini.getValueB( "editor", "flash_cursor", true ); searchBarConfig.caseSensitive = ini.getValueB( "search_bar", "case_sensitive", false ); searchBarConfig.luaPattern = ini.getValueB( "search_bar", "lua_pattern", false ); @@ -328,6 +329,7 @@ void AppConfig::save( const std::vector& recentFiles, ini.setValue( "editor", "tab_indent_character", editor.tabIndentCharacter ); ini.setValue( "editor", "tab_indent_alignment", characterAlignmentToString( editor.tabIndentAlignment ) ); + ini.setValueB( "editor", "flash_cursor", editor.flashCursor ); ini.setValueB( "search_bar", "case_sensitive", searchBarConfig.caseSensitive ); ini.setValueB( "search_bar", "lua_pattern", searchBarConfig.luaPattern ); diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp index 8874badc5..2e5f7a264 100644 --- a/src/tools/ecode/appconfig.hpp +++ b/src/tools/ecode/appconfig.hpp @@ -82,6 +82,7 @@ struct CodeEditorConfig { bool autoReloadOnDiskChange{ false }; bool codeFoldingEnabled{ true }; bool codeFoldingAlwaysVisible{ false }; + bool flashCursor{ false }; LineWrapMode wrapMode{ LineWrapMode::NoWrap }; LineWrapType wrapType{ LineWrapType::Viewport }; bool wrapKeepIndentation{ true }; diff --git a/src/tools/ecode/docsearchcontroller.cpp b/src/tools/ecode/docsearchcontroller.cpp index 6f9891cd7..efd7bad1e 100644 --- a/src/tools/ecode/docsearchcontroller.cpp +++ b/src/tools/ecode/docsearchcontroller.cpp @@ -188,6 +188,8 @@ void DocSearchController::initSearchBar( void DocSearchController::showFindView() { mApp->hideLocateBar(); mApp->hideGlobalSearchBar(); + mApp->getStatusBar()->hideAllElements(); + if ( !mSplitter->curEditorExistsAndFocused() ) return; diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp index cb7a9ceb9..9383b83d1 100644 --- a/src/tools/ecode/ecode.cpp +++ b/src/tools/ecode/ecode.cpp @@ -2363,6 +2363,7 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) { editor->setLineWrapType( config.wrapType ); editor->setFoldDrawable( findIcon( "chevron-down", PixelDensity::dpToPxI( 12 ) ) ); editor->setFoldedDrawable( findIcon( "chevron-right", PixelDensity::dpToPxI( 12 ) ) ); + editor->setEnableFlashCursor( config.flashCursor ); doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() ); doc.setAutoCloseBracketsPairs( makeAutoClosePairs( mConfig.editor.autoCloseBrackets ) ); diff --git a/src/tools/ecode/globalsearchcontroller.cpp b/src/tools/ecode/globalsearchcontroller.cpp index 3748af411..ec4df2786 100644 --- a/src/tools/ecode/globalsearchcontroller.cpp +++ b/src/tools/ecode/globalsearchcontroller.cpp @@ -456,6 +456,7 @@ void GlobalSearchController::showGlobalSearch( bool searchReplace ) { mApp->hideStatusTerminal(); mApp->hideStatusBuildOutput(); mApp->hideStatusAppOutput(); + mApp->getStatusBar()->hideAllElements(); bool wasReplaceTree = mGlobalSearchTreeReplace == mGlobalSearchTree; mGlobalSearchTree = searchReplace ? mGlobalSearchTreeReplace : mGlobalSearchTreeSearch; diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp index 1dc5bc162..33e6c95d9 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.cpp @@ -357,10 +357,9 @@ bool AutoCompletePlugin::onKeyDown( UICodeEditor* editor, const KeyEvent& event } } else if ( mShortcuts["autocomplete-next-signature-help"] == eventShortcut ) { if ( mSignatureHelp.signatures.size() > 1 ) { - mSignatureHelpSelected = - mSignatureHelpSelected == (int)mSignatureHelp.signatures.size() - 1 - ? mSignatureHelp.signatures.size() - 1 - : 0; + mSignatureHelpSelected = mSignatureHelpSelected <= 0 + ? mSignatureHelp.signatures.size() + : mSignatureHelpSelected; --mSignatureHelpSelected; mSignatureHelpSelected = mSignatureHelpSelected % mSignatureHelp.signatures.size(); editor->invalidateDraw(); @@ -818,9 +817,68 @@ AutoCompletePlugin::processSignatureHelp( const LSPSignatureHelp& signatureHelp } if ( !editor ) return {}; - editor->runOnMainThread( [this, editor, signatureHelp] { + + // Convert the LSP Signature Help into our own object: + // We will convert the UTF-8 label to UTF-32, then we will remove any new lines and extra spaces + // This guarantees that we always display a single line signature help + SignatureHelp signatures; + signatures.activeSignature = signatureHelp.activeSignature; + signatures.activeParameter = signatureHelp.activeParameter; + signatures.signatures.reserve( signatureHelp.signatures.size() ); + + TextDocument doc; + for ( const auto& sig : signatureHelp.signatures ) { + String initialLabel( sig.label ); + SignatureInformation nsig; + nsig.documentation = sig.documentation; + + doc.reset(); + doc.textInput( initialLabel ); + std::vector parameters; + parameters.reserve( sig.parameters.size() ); + + for ( size_t i = 0; i < sig.parameters.size(); i++ ) { + auto start = String::utf8ToCodepointPosition( sig.label, sig.parameters[i].start ); + auto end = String::utf8ToCodepointPosition( sig.label, sig.parameters[i].end ); + auto sel = TextRange::convertToLineColumn( initialLabel.view(), start, end ); + + if ( i == 0 ) + doc.setSelection( i, sel ); + else + doc.addSelection( sel ); + + parameters.emplace_back( doc.getSelectedText( i ) ); + } + + auto selections( doc.getSelections() ); + nsig.parameters.reserve( selections.size() ); + + if ( 0 != doc.replaceAll( "\n", "" ) ) { + while ( 0 != doc.replaceAll( " ", " " ) ) + ; + + nsig.label = doc.line( 0 ).getTextWithoutNewLine(); + + for ( const auto& param : parameters ) { + auto res = doc.find( param ); + if ( res.isValid() ) + nsig.parameters.emplace_back( res.result ); + } + } else { + nsig.label = std::move( initialLabel ); + + if ( !sig.parameters.empty() ) { + for ( auto& sel : selections ) + nsig.parameters.emplace_back( sel ); + } + } + + signatures.signatures.emplace_back( nsig ); + } + + editor->runOnMainThread( [this, editor, signatures = std::move( signatures )] { mSignatureHelpVisible = true; - mSignatureHelp = signatureHelp; + mSignatureHelp = signatures; if ( mSignatureHelp.signatures.empty() ) resetSignatureHelp(); editor->invalidateDraw(); @@ -942,7 +1000,7 @@ void AutoCompletePlugin::drawSignatureHelp( UICodeEditor* editor, const Vector2f primitives.setColor( Color( selectedStyle.background ).blendAlpha( editor->getAlpha() ) ); String str; if ( mSignatureHelp.signatures.size() > 1 ) { - str = String::format( "%s (%d of %zu)", curSig.label.c_str(), + str = String::format( "%s (%d of %zu)", curSig.label.toUtf8(), mSignatureHelpSelected == -1 ? 1 : mSignatureHelpSelected + 1, mSignatureHelp.signatures.size() ); } else { @@ -962,38 +1020,42 @@ void AutoCompletePlugin::drawSignatureHelp( UICodeEditor* editor, const Vector2f } bool hasParams = !curSig.parameters.empty(); - LSPParameterInformation curParam = + TextRange curParam = hasParams ? curSig.parameters[mSignatureHelp.activeParameter % curSig.parameters.size()] - : LSPParameterInformation{ -1, -1 }; + : TextRange{}; Rectf curParamRect; if ( hasParams ) { curParamRect = Rectf( { { boxRect.getPosition().x + mBoxPadding.Left + - curParam.start * editor->getGlyphWidth(), + curParam.start().column() * editor->getGlyphWidth(), boxRect.getPosition().y }, - { ( curParam.end - curParam.start ) * editor->getGlyphWidth(), mRowHeight } } ); + { ( curParam.end().column() - curParam.start().column() ) * editor->getGlyphWidth(), + mRowHeight } } ); if ( !editor->getScreenRect().contains( Rectf{ { curParamRect.getPosition().x + - ( curParam.end - curParam.start ) * editor->getGlyphWidth(), + ( curParam.end().column() - curParam.start().column() ) * + editor->getGlyphWidth(), curParamRect.getPosition().y }, curParamRect.getSize() } ) ) { auto offset = editor->getTextPositionOffset( mSignatureHelpPosition ); - pos = { static_cast( startScroll.x - curParam.start * editor->getGlyphWidth() + + pos = { static_cast( startScroll.x - + curParam.start().column() * editor->getGlyphWidth() + offset.x ), static_cast( startScroll.y + offset.y + vdiff ) }; boxRect.setPosition( pos ); curParamRect.setPosition( { boxRect.getPosition().x + mBoxPadding.Left + - curParam.start * editor->getGlyphWidth(), + curParam.start().column() * editor->getGlyphWidth(), boxRect.getPosition().y } ); } } primitives.drawRoundedRectangle( boxRect, 0.f, Vector2f::One, 6 ); - if ( hasParams && curParam.end - curParam.start > 0 && curParam.end < (int)str.size() ) { + if ( hasParams && curParam.end() != curParam.start() && + curParam.end().column() < (int)str.size() ) { primitives.setColor( matchingSelection.color ); primitives.drawRoundedRectangle( curParamRect, 0.f, Vector2f::One, 6 ); } @@ -1112,10 +1174,13 @@ void AutoCompletePlugin::postDraw( UICodeEditor* editor, const Vector2f& startSc LSPCompletionItemHelper::toIconString( suggestion.kind ), PixelDensity::dpToPxI( 12 ) ); if ( icon ) { + Color iconColor( icon->getColor() ); + icon->setColor( mSuggestionIndex == (int)i ? selectedStyle.color : normalStyle.color ); Vector2f padding( eefloor( ( iconSpace.getWidth() - icon->getSize().getWidth() ) * 0.5f ), eefloor( ( iconSpace.getHeight() - icon->getSize().getHeight() ) * 0.5f ) ); icon->draw( { cursorPos.x + padding.x, cursorPos.y + mRowHeight * count + padding.y } ); + icon->setColor( iconColor ); } if ( mSuggestionIndex == (int)i && !suggestion.documentation.value.empty() ) { diff --git a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp index 6bbc86e4c..423b9048c 100644 --- a/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp +++ b/src/tools/ecode/plugins/autocomplete/autocompleteplugin.hpp @@ -63,7 +63,7 @@ class AutoCompletePlugin : public Plugin { "Auto complete shows the completion popup as you type, so you can fill " "in long words by typing only a few characters.", AutoCompletePlugin::New, - { 0, 2, 6 }, + { 0, 2, 7 }, AutoCompletePlugin::NewSync }; } @@ -144,7 +144,20 @@ class AutoCompletePlugin : public Plugin { Int32 mSuggestionsStartIndex{ 0 }; std::unordered_map mCapabilities; Mutex mCapabilitiesMutex; - LSPSignatureHelp mSignatureHelp; + + struct SignatureInformation { + String label; + LSPMarkupContent documentation; + std::vector parameters; + }; + + struct SignatureHelp { + std::vector signatures; + int activeSignature{ 0 }; + int activeParameter{ 0 }; + }; + + SignatureHelp mSignatureHelp; TextPosition mSignatureHelpPosition; Int32 mSignatureHelpSelected{ -1 }; Mutex mHandlesMutex; diff --git a/src/tools/ecode/plugins/debugger/config.hpp b/src/tools/ecode/plugins/debugger/config.hpp index 4ca08c3d1..6efafa8be 100644 --- a/src/tools/ecode/plugins/debugger/config.hpp +++ b/src/tools/ecode/plugins/debugger/config.hpp @@ -48,6 +48,7 @@ struct ProtocolSettings { bool redirectStderr{ false }; bool redirectStdout{ false }; bool supportsSourceRequest{ true }; + bool runTarget{ false }; std::string launchRequestType{ "launch" }; json launchArgs; std::string locale{ "en-US" }; diff --git a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp index c30a43b00..713b04a93 100644 --- a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp +++ b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.cpp @@ -38,9 +38,10 @@ void DebuggerClientDap::makeRequest( const std::string_view& command, std::string cmd = jsonCmd.dump(); std::string msg( String::format( "Content-Length: %zu\r\n\r\n%s", cmd.size(), cmd ) ); - Log::instance()->writel( mDebug ? LogLevel::Info : LogLevel::Debug, - "DebuggerClientDap::makeRequest:" ); - Log::instance()->writel( mDebug ? LogLevel::Info : LogLevel::Debug, msg ); + if ( mDebug ) { + Log::debug( "DebuggerClientDap::makeRequest:" ); + Log::debug( msg ); + } mBus->write( msg.data(), msg.size() ); @@ -59,9 +60,10 @@ void DebuggerClientDap::makeResponse( int reqSeq, bool success, const std::strin std::string cmd = jsonCmd.dump(); std::string msg( String::format( "Content-Length: %zu\r\n\r\n%s", cmd.size(), cmd ) ); - Log::instance()->writel( mDebug ? LogLevel::Info : LogLevel::Debug, - "DebuggerClientDap::makeResponse:" ); - Log::instance()->writel( mDebug ? LogLevel::Info : LogLevel::Debug, msg ); + if ( mDebug ) { + Log::debug( "DebuggerClientDap::makeResponse:" ); + Log::debug( msg ); + } mBus->write( msg.data(), msg.size() ); } @@ -71,10 +73,14 @@ bool DebuggerClientDap::isServerConnected() const { ( mBus->state() == Bus::State::Running ); } -bool DebuggerClientDap::supportsTerminate() const { +bool DebuggerClientDap::supportsTerminateRequest() const { return mAdapterCapabilities.supportsTerminateRequest; } +bool DebuggerClientDap::supportsTerminateDebuggee() const { + return mAdapterCapabilities.supportTerminateDebuggee; +} + bool DebuggerClientDap::start() { bool started = mBus->start(); if ( started ) @@ -147,6 +153,13 @@ void DebuggerClientDap::requestLaunchCommand() { setState( State::Failed ); } } ); + + if ( mProtocol.runTarget && runTargetCb ) { + if ( !mProtocol.launchArgs.contains( "waitFor" ) ) + runTargetCb(); + else + mWaitingToAttach = true; + } } void DebuggerClientDap::requestInitialize() { @@ -347,6 +360,11 @@ void DebuggerClientDap::processEventOutput( const nlohmann::json& body ) { Output output( body ); for ( auto listener : mListeners ) listener->outputProduced( output ); + + if ( mWaitingToAttach && mProtocol.runTarget && runTargetCb ) { + runTargetCb(); + mWaitingToAttach = false; + } } void DebuggerClientDap::processEventProcess( const nlohmann::json& body ) { @@ -528,8 +546,11 @@ bool DebuggerClientDap::terminate( bool restart ) { return true; } -bool DebuggerClientDap::disconnect( bool restart ) { +bool DebuggerClientDap::disconnect( bool terminateDebuggee, bool restart ) { nlohmann::json arguments; + if ( mAdapterCapabilities.supportTerminateDebuggee && terminateDebuggee ) + arguments["terminateDebuggee"] = true; + if ( restart ) arguments["restart"] = true; diff --git a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.hpp b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.hpp index 8e4fe44aa..1738f6ffb 100644 --- a/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.hpp +++ b/src/tools/ecode/plugins/debugger/dap/debuggerclientdap.hpp @@ -22,6 +22,8 @@ class DebuggerClientDap : public DebuggerClient { std::function doneFn )> runInTerminalCb; + std::function runTargetCb; + DebuggerClientDap( const ProtocolSettings& protocolSettings, std::unique_ptr&& bus ); virtual ~DebuggerClientDap(); @@ -42,7 +44,7 @@ class DebuggerClientDap : public DebuggerClient { bool terminate( bool restart ) override; - bool disconnect( bool restart = false ) override; + bool disconnect( bool terminateDebuggee, bool restart = false ) override; bool threads() override; @@ -63,7 +65,9 @@ class DebuggerClientDap : public DebuggerClient { bool isServerConnected() const override; - bool supportsTerminate() const override; + bool supportsTerminateRequest() const override; + + bool supportsTerminateDebuggee() const override; bool setBreakpoints( const std::string& path, const std::vector& breakpoints, @@ -85,6 +89,8 @@ class DebuggerClientDap : public DebuggerClient { bool started() const override; + void setSilent( bool silent ) override { mDebug = !silent; } + protected: std::unique_ptr mBus; UnorderedMap> mBreakpoints; diff --git a/src/tools/ecode/plugins/debugger/debuggerclient.hpp b/src/tools/ecode/plugins/debugger/debuggerclient.hpp index ea3641f39..2a44c1bab 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclient.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerclient.hpp @@ -71,7 +71,7 @@ class DebuggerClient { virtual bool terminate( bool restart ) = 0; - virtual bool disconnect( bool restart = false ) = 0; + virtual bool disconnect( bool terminateDebuggee, bool restart = false ) = 0; virtual bool threads() = 0; @@ -93,7 +93,9 @@ class DebuggerClient { virtual bool isServerConnected() const = 0; - virtual bool supportsTerminate() const = 0; + virtual bool supportsTerminateRequest() const = 0; + + virtual bool supportsTerminateDebuggee() const = 0; virtual bool setBreakpoints( const std::string& path, const std::vector& breakpoints, @@ -113,6 +115,8 @@ class DebuggerClient { virtual bool configurationDone() = 0; + virtual void setSilent( bool silent ) = 0; + void addListener( Listener* listener ); void removeListener( Listener* listener ); @@ -125,6 +129,7 @@ class DebuggerClient { State mState{ State::None }; bool mLaunched{ false }; bool mConfigured{ false }; + bool mWaitingToAttach{ false }; std::vector mListeners; void checkRunning(); diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp index 6f03ee497..7f821dfe6 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.cpp @@ -192,10 +192,6 @@ void DebuggerClientListener::sendBreakpoints() { Lock l( mPlugin->mBreakpointsMutex ); for ( const auto& fileBps : mPlugin->mBreakpoints ) { std::string path( fileBps.first ); - if ( isRemote() ) { - FileSystem::filePathRemoveBasePath( mPlugin->mProjectPath, path ); - path = "/" + path; - } mClient->setBreakpoints( path, fromSet( fileBps.second ) ); } } @@ -314,7 +310,9 @@ void DebuggerClientListener::outputProduced( const Output& output ) { } } -void DebuggerClientListener::debuggingProcess( const ProcessInfo& ) {} +void DebuggerClientListener::debuggingProcess( const ProcessInfo& info ) { + mProcessInfo = info; +} void DebuggerClientListener::errorResponse( const std::string& command, const std::string& summary, const std::optional& /*message*/ ) { diff --git a/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp b/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp index aaf382f03..589d58e40 100644 --- a/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerclientlistener.hpp @@ -71,6 +71,8 @@ class DebuggerClientListener : public DebuggerClient::Listener { void sendBreakpoints(); + const ProcessInfo& getProcessInfo() const { return mProcessInfo; } + protected: DebuggerClient* mClient{ nullptr }; DebuggerPlugin* mPlugin{ nullptr }; @@ -84,6 +86,7 @@ class DebuggerClientListener : public DebuggerClient::Listener { std::shared_ptr mStackModel; std::shared_ptr mVariablesHolder; std::unordered_map mScopeRef; + ProcessInfo mProcessInfo; StatusDebuggerController* getStatusDebuggerController() const; diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp index 760b1e79c..bd32ecf63 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.cpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.cpp @@ -1,5 +1,4 @@ #include "../../notificationcenter.hpp" -#include "../../projectbuild.hpp" #include "../../terminalmanager.hpp" #include "../../uistatusbar.hpp" #include "../../widgetcommandexecuter.hpp" @@ -55,6 +54,46 @@ static constexpr auto KEY_FILEDIRNAME = "${fileDirname}"; static constexpr auto KEY_RANDPORT = "${randPort}"; static constexpr auto KEY_PID = "${pid}"; +static constexpr auto KEY_USER_HOME = "${userHome}"; +static constexpr auto KEY_WORKSPACEFOLDER_BASENAME = "${workspaceFolderBasename}"; +static constexpr auto KEY_FILE_WORKSPACEFOLDER = "${fileWorkspaceFolder}"; +static constexpr auto KEY_RELATIVE_FILE = "${relativeFile}"; +static constexpr auto KEY_RELATIVE_FILE_DIRNAME = "${relativeFileDirname}"; +static constexpr auto KEY_FILE_BASENAME = "${fileBasename}"; +static constexpr auto KEY_FILE_BASENAME_NOEXTENSION = "${fileBasenameNoExtension}"; +static constexpr auto KEY_FILE_EXTNAME = "${fileExtname}"; +static constexpr auto KEY_FILE_DIRNAME_BASENAME = "${fileDirnameBasename}"; +static constexpr auto KEY_LINE_NUMBER = "${lineNumber}"; +static constexpr auto KEY_SELECTED_TEXT = "${selectedText}"; +static constexpr auto KEY_EXEC_PATH = "${execPath}"; +static constexpr auto KEY_DEFAULT_BUILD_TASK = "${defaultBuildTask}"; +static constexpr auto KEY_PATH_SEPARATOR = "${pathSeparator}"; +static constexpr auto KEY_PATH_SEPARATOR_ABBR = "${/}"; + +/* + +- **${userHome}** - the path of the user's home folder +- **${workspaceFolder}** - the path of the folder opened in VS Code +- **${workspaceFolderBasename}** - the name of the folder opened in VS Code without any slashes (/) +- **${file}** - the current opened file +- **${fileWorkspaceFolder}** - the current opened file's workspace folder +- **${relativeFile}** - the current opened file relative to `workspaceFolder` +- **${relativeFileDirname}** - the current opened file's dirname relative to `workspaceFolder` +- **${fileBasename}** - the current opened file's basename +- **${fileBasenameNoExtension}** - the current opened file's basename with no file extension +- **${fileExtname}** - the current opened file's extension +- **${fileDirname}** - the current opened file's folder path +- **${fileDirnameBasename}** - the current opened file's folder name +- **${cwd}** - the task runner's current working directory upon the startup of VS Code +- **${lineNumber}** - the current selected line number in the active file +- **${selectedText}** - the current selected text in the active file +- **${execPath}** - the path to the running VS Code executable +- **${defaultBuildTask}** - the name of the default build task +- **${pathSeparator}** - the character used by the operating system to separate components in file +paths +- **${/}** - shorthand for **${pathSeparator}** + +*/ Plugin* DebuggerPlugin::New( PluginManager* pluginManager ) { return eeNew( DebuggerPlugin, ( pluginManager, false ) ); } @@ -80,6 +119,12 @@ DebuggerPlugin::~DebuggerPlugin() { waitUntilLoaded(); mShuttingDown = true; + { + Lock l( mClientsMutex ); + for ( const auto& client : mClients ) + client.first->unregisterClient( client.second.get() ); + } + if ( mSidePanel && mTab ) { if ( Engine::isRunninMainThread() ) mSidePanel->removeTab( mTab ); @@ -330,6 +375,7 @@ static std::initializer_list DebuggerCommandList = { "debugger-breakpoint-enable-toggle", "debugger-start", "debugger-stop", + "debugger-start-stop", "debugger-step-over", "debugger-step-into", "debugger-step-out", @@ -367,6 +413,11 @@ void DebuggerPlugin::loadDAPConfig( const std::string& path, bool updateConfigFi mFetchGlobals = config.value( "fetch_globals", false ); else if ( updateConfigFile ) config["fetch_globals"] = mFetchGlobals; + + if ( config.contains( "silent" ) ) + mSilence = config.value( "silent", true ); + else if ( updateConfigFile ) + config["silent"] = mSilence; } if ( j.contains( "dap" ) ) { @@ -407,6 +458,7 @@ void DebuggerPlugin::loadDAPConfig( const std::string& path, bool updateConfigFi dapConfig.name = config.value( "name", "" ); if ( !dapConfig.name.empty() ) { dapConfig.request = config.value( "request", REQUEST_TYPE_LAUNCH ); + dapConfig.runTarget = config.value( "runTarget", false ); dapConfig.args = config["arguments"]; } if ( config.contains( "command_arguments" ) ) { @@ -448,7 +500,7 @@ void DebuggerPlugin::loadDAPConfig( const std::string& path, bool updateConfigFi } if ( mKeyBindings.empty() ) { - mKeyBindings["debugger-start"] = "mod+f5"; + mKeyBindings["debugger-start-stop"] = "mod+f5"; mKeyBindings["debugger-continue-interrupt"] = "f5"; mKeyBindings["debugger-breakpoint-toggle"] = "f9"; mKeyBindings["debugger-breakpoint-enable-toggle"] = "mod+f9"; @@ -660,6 +712,15 @@ void DebuggerPlugin::buildSidePanelTab() { border-top-right-radius: var(--button-radius); border-bottom-right-radius: var(--button-radius); } + #panel_debugger_help { + color: var(--primary); + text-decoration: none; + text-align: center; + margin-top: 32dp; + } + #panel_debugger_help:hover { + text-decoration: underline; + } @@ -675,6 +736,7 @@ void DebuggerPlugin::buildSidePanelTab() { + )html"; @@ -1019,23 +1081,66 @@ void DebuggerPlugin::updateDebuggerConfigurationList() { } } +void DebuggerPlugin::replaceInVal( std::string& val, + const std::optional& runConfig, + ProjectBuild* buildConfig, int randomPort ) { + + if ( runConfig ) { + String::replaceAll( val, KEY_FILE, runConfig->cmd ); + String::replaceAll( val, KEY_CWD, runConfig->workingDir ); + String::replaceAll( val, KEY_FILEDIRNAME, runConfig->workingDir ); + std::string fileRelativePath( runConfig->cmd ); + FileSystem::filePathRemoveBasePath( mProjectPath, fileRelativePath ); + String::replaceAll( val, KEY_RELATIVE_FILE, fileRelativePath ); + std::string fileDirName( FileSystem::fileRemoveFileName( fileRelativePath ) ); + FileSystem::dirRemoveSlashAtEnd( fileDirName ); + String::replaceAll( val, KEY_RELATIVE_FILE_DIRNAME, fileDirName ); + String::replaceAll( val, KEY_FILE_BASENAME, + FileSystem::fileNameFromPath( fileRelativePath ) ); + String::replaceAll( + val, KEY_FILE_BASENAME_NOEXTENSION, + FileSystem::fileRemoveExtension( FileSystem::fileNameFromPath( fileRelativePath ) ) ); + String::replaceAll( val, KEY_FILE_EXTNAME, FileSystem::fileExtension( fileRelativePath ) ); + String::replaceAll( val, KEY_FILE_DIRNAME_BASENAME, + FileSystem::fileNameFromPath( fileDirName ) ); + } + + if ( buildConfig ) { + String::replaceAll( val, KEY_DEFAULT_BUILD_TASK, buildConfig->getName() ); + } + + String::replaceAll( val, KEY_WORKSPACEFOLDER, mProjectPath ); + String::replaceAll( val, KEY_USER_HOME, Sys::getUserDirectory() ); + String::replaceAll( val, KEY_WORKSPACEFOLDER_BASENAME, + FileSystem::fileNameFromPath( mProjectPath ) ); + String::replaceAll( val, KEY_FILE_WORKSPACEFOLDER, mProjectPath ); + String::replaceAll( val, KEY_EXEC_PATH, Sys::getProcessFilePath() ); + String::replaceAll( val, KEY_PATH_SEPARATOR, FileSystem::getOSSlash() ); + String::replaceAll( val, KEY_PATH_SEPARATOR_ABBR, FileSystem::getOSSlash() ); + + auto* editor = getPluginContext()->getSplitter()->getCurEditor(); + if ( getPluginContext()->getSplitter()->getCurEditor() ) { + String::replaceAll( + val, KEY_LINE_NUMBER, + String::toString( editor->getDocument().getSelection().start().line() ) ); + String::replaceAll( val, KEY_SELECTED_TEXT, + editor->getDocument().getSelectedText().toUtf8() ); + } + + if ( String::contains( val, KEY_RANDPORT ) ) + String::replaceAll( val, KEY_RANDPORT, String::toString( randomPort ) ); +} + std::vector DebuggerPlugin::replaceKeyInString( std::string val, int randomPort, const std::unordered_map& solvedInputs ) { auto pbm = getPluginContext()->getProjectBuildManager(); auto runConfig = pbm ? pbm->getCurrentRunConfig() : std::optional{}; + auto buildConfig = pbm ? pbm->getCurrentBuild() : nullptr; - const auto replaceVal = [this, &runConfig, &solvedInputs, randomPort]( std::string& val ) { - if ( runConfig ) { - String::replaceAll( val, KEY_FILE, runConfig->cmd ); - String::replaceAll( val, KEY_CWD, runConfig->workingDir ); - String::replaceAll( val, KEY_FILEDIRNAME, runConfig->workingDir ); - } - - String::replaceAll( val, KEY_WORKSPACEFOLDER, mProjectPath ); - - if ( String::contains( val, KEY_RANDPORT ) ) - String::replaceAll( val, KEY_RANDPORT, String::toString( randomPort ) ); + const auto replaceVal = [this, &runConfig, &buildConfig, &solvedInputs, + randomPort]( std::string& val ) { + replaceInVal( val, runConfig, buildConfig, randomPort ); LuaPattern::Range matches[2]; if ( inputPtrn.matches( val, matches ) ) { @@ -1077,18 +1182,9 @@ void DebuggerPlugin::replaceKeysInJson( auto runConfig = pbm ? pbm->getCurrentRunConfig() : std::optional{}; auto buildConfig = pbm ? pbm->getCurrentBuild() : nullptr; - const auto replaceVal = [this, &solvedInputs, &runConfig, + const auto replaceVal = [this, &solvedInputs, &runConfig, &buildConfig, randomPort]( nlohmann::json& j, std::string& val ) -> bool { - if ( runConfig ) { - String::replaceAll( val, KEY_FILE, runConfig->cmd ); - String::replaceAll( val, KEY_CWD, runConfig->workingDir ); - String::replaceAll( val, KEY_FILEDIRNAME, runConfig->workingDir ); - } - - String::replaceAll( val, KEY_WORKSPACEFOLDER, mProjectPath ); - - if ( String::contains( val, KEY_RANDPORT ) ) - String::replaceAll( val, KEY_RANDPORT, String::toString( randomPort ) ); + replaceInVal( val, runConfig, buildConfig, randomPort ); LuaPattern::Range matches[2]; if ( inputPtrn.matches( val, matches ) ) { @@ -1269,6 +1365,13 @@ void DebuggerPlugin::onRegisterDocument( TextDocument* doc ) { doc->setCommand( "debugger-stop", [this] { exitDebugger( true ); } ); + doc->setCommand( "debugger-start-stop", [this] { + if ( mDebugger ) + exitDebugger( true ); + else + runCurrentConfig(); + } ); + doc->setCommand( "debugger-breakpoint-toggle", [doc, this] { if ( setBreakpoint( doc, doc->getSelection().start().line() + 1 ) ) getUISceneNode()->getRoot()->invalidateDraw(); @@ -1303,6 +1406,16 @@ void DebuggerPlugin::onRegisterDocument( TextDocument* doc ) { if ( mTab ) mTab->setTabSelected(); } ); + + Lock l( mClientsMutex ); + mClients[doc] = std::make_unique( this, doc ); + doc->registerClient( mClients[doc].get() ); +} + +void DebuggerPlugin::onUnregisterDocument( TextDocument* doc ) { + Lock l( mClientsMutex ); + doc->unregisterClient( mClients[doc].get() ); + mClients.erase( doc ); } void DebuggerPlugin::onRegisterEditor( UICodeEditor* editor ) { @@ -1422,6 +1535,23 @@ void DebuggerPlugin::drawLineNumbersBefore( UICodeEditor* editor, } } +void DebuggerPlugin::drawBeforeLineText( UICodeEditor* editor, const Int64& index, + Vector2f position, const Float& /*fontSize*/, + const Float& lineHeight ) { + if ( !mDebugger || !mListener || !mListener->isStopped() || !mListener->getCurrentScopePos() || + editor->getDocument().getFilePath() != mListener->getCurrentScopePos()->first || + mListener->getCurrentScopePos()->second - 1 != index || + !editor->getDocumentView().isLineVisible( index ) ) + return; + + Primitives p; + Color color( editor->getColorScheme().getEditorSyntaxStyle( "warning"_sst ).color ); + Color blendedColor( Color( color, 20 ).blendAlpha( editor->getAlpha() ) ); + p.setColor( blendedColor ); + p.drawRectangle( + Rectf( position, Sizef( editor->getViewportWidth( false, true ), lineHeight ) ) ); +} + bool DebuggerPlugin::setBreakpoint( const std::string& doc, Uint32 lineNumber ) { Lock l( mBreakpointsMutex ); @@ -1690,6 +1820,7 @@ void DebuggerPlugin::prepareAndRun( DapTool debugger, DapConfig config, int randomPort = Math::randi( 44000, 45000 ); ProtocolSettings protocolSettings; protocolSettings.launchRequestType = config.request; + protocolSettings.runTarget = config.runTarget; auto args = config.args; replaceKeysInJson( args, randomPort, solvedInputs ); protocolSettings.launchArgs = args; @@ -1954,6 +2085,7 @@ void DebuggerPlugin::run( const std::string& debugger, ProtocolSettings&& protoc mListener = std::make_unique( mDebugger.get(), this ); mListener->setIsRemote( isRemote ); mDebugger->addListener( mListener.get() ); + mDebugger->setSilent( mSilence ); DebuggerClientDap* dap = static_cast( mDebugger.get() ); dap->runInTerminalCb = [this]( bool isIntegrated, std::string cmd, @@ -1980,6 +2112,11 @@ void DebuggerPlugin::run( const std::string& debugger, ProtocolSettings&& protoc } ); }; + dap->runTargetCb = [this] { + getUISceneNode()->runOnMainThread( + [this] { getPluginContext()->runCommand( "project-run-executable" ); } ); + }; + mDebugger->start(); } @@ -1988,7 +2125,7 @@ void DebuggerPlugin::exitDebugger( bool requestDisconnect ) { mDebugger->removeListener( mListener.get() ); if ( requestDisconnect && mDebugger ) - mDebugger->disconnect( false ); + mDebugger->disconnect( true, false ); if ( mDebugger || mListener ) { mThreadPool->run( [this] { @@ -2146,7 +2283,6 @@ void DebuggerPlugin::displayTooltip( UICodeEditor* editor, const std::string& ex } bool DebuggerPlugin::onMouseMove( UICodeEditor* editor, const Vector2i& position, const Uint32& ) { - if ( !mDebugger || !mListener || !mDebugger->isServerConnected() || mDebuggingState != StatusDebuggerController::State::Paused ) { return false; @@ -2203,7 +2339,38 @@ bool DebuggerPlugin::onMouseMove( UICodeEditor* editor, const Vector2i& position }, mHoverDelay, getMouseMoveHash( editor ) ); editor->updateMouseCursor( position.asFloat() ); - return true; + return false; +} + +void DebuggerPlugin::onDocumentLineMove( TextDocument* doc, const Int64& fromLine, + const Int64& toLine, const Int64& numLines ) { + Lock l( mBreakpointsMutex ); + + auto& breakpoints = mBreakpoints[doc->getFilePath()]; + + if ( breakpoints.empty() ) + return; + + std::vector bpToModify; + + for ( auto& bp : breakpoints ) { + // bp line numbers start at 1 + if ( bp.line - 1 >= fromLine ) { + bpToModify.push_back( bp ); + break; + } + } + + for ( const auto& bp : bpToModify ) + breakpoints.erase( bp ); + + for ( auto&& bp : bpToModify ) { + bp.line += numLines; + breakpoints.insert( std::move( bp ) ); + } + + if ( mBreakpointsModel ) + mBreakpointsModel->move( doc->getFilePath(), fromLine, toLine, numLines ); } } // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp index 8a724c405..16c903874 100644 --- a/src/tools/ecode/plugins/debugger/debuggerplugin.hpp +++ b/src/tools/ecode/plugins/debugger/debuggerplugin.hpp @@ -1,5 +1,6 @@ #pragma once +#include "../../projectbuild.hpp" #include "../plugin.hpp" #include "../pluginmanager.hpp" #include "config.hpp" @@ -21,6 +22,7 @@ struct DapConfig { std::string request; std::vector cmdArgs; nlohmann::json args; + bool runTarget{ false }; }; struct DapTool { @@ -77,6 +79,8 @@ class DebuggerPlugin : public PluginBase { void initStatusDebuggerController(); + bool isSilent() const { return mSilence; } + protected: friend class DebuggerClientListener; @@ -84,6 +88,7 @@ class DebuggerPlugin : public PluginBase { bool mFetchRegisters{ false }; bool mFetchGlobals{ false }; bool mChangingBreakpoint{ false }; + bool mSilence{ true }; std::string mProjectPath; std::vector mDaps; @@ -129,6 +134,37 @@ class DebuggerPlugin : public PluginBase { std::string mCurDebugger; std::string mCurConfiguration; + class DebuggerPluginClient : public TextDocument::Client { + public: + explicit DebuggerPluginClient( DebuggerPlugin* parent, TextDocument* doc ) : + mDoc( doc ), mParent( parent ) {} + + virtual void onDocumentTextChanged( const DocumentContentChange& ) {} + virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& ) {} + virtual void onDocumentCursorChange( const TextPosition& ) {} + virtual void onDocumentSelectionChange( const TextRange& ) {} + virtual void onDocumentLineCountChange( const size_t&, const size_t& ) {} + virtual void onDocumentLineChanged( const Int64& ) {} + virtual void onDocumentSaved( TextDocument* ) {} + virtual void onDocumentClosed( TextDocument* doc ) { onDocumentReset( doc ); } + virtual void onDocumentDirtyOnFileSystem( TextDocument* ) {} + virtual void onDocumentMoved( TextDocument* ) {} + virtual void onDocumentReset( TextDocument* ) {} + + virtual void onDocumentLineMove( const Int64& fromLine, const Int64& toLine, + const Int64& numLines ) { + mParent->onDocumentLineMove( mDoc, fromLine, toLine, numLines ); + } + + protected: + TextDocument* mDoc{ nullptr }; + DebuggerPlugin* mParent{ nullptr }; + }; + + using ClientsMap = std::unordered_map>; + ClientsMap mClients; + Mutex mClientsMutex; + DebuggerPlugin( PluginManager* pluginManager, bool sync ); void load( PluginManager* pluginManager ); @@ -176,6 +212,9 @@ class DebuggerPlugin : public PluginBase { const Float& lineHeight, const Float& lineNumberWidth, const int& lineNumberDigits, const Float& fontSize ) override; + void drawBeforeLineText( UICodeEditor* editor, const Int64& index, Vector2f position, + const Float& /*fontSize*/, const Float& lineHeight ) override; + bool setBreakpoint( UICodeEditor* editor, Uint32 lineNumber ); bool setBreakpoint( TextDocument* doc, Uint32 lineNumber ); @@ -240,6 +279,13 @@ class DebuggerPlugin : public PluginBase { bool resume( int threadId, bool singleThread = false ); + virtual void onUnregisterDocument( TextDocument* doc ) override; + + void onDocumentLineMove( TextDocument* doc, const Int64& fromLine, const Int64& toLine, + const Int64& numLines ); + + void replaceInVal( std::string& val, const std::optional& runConfig, + ProjectBuild* buildConfig, int randomPort ); }; } // namespace ecode diff --git a/src/tools/ecode/plugins/debugger/models/breakpointsmodel.cpp b/src/tools/ecode/plugins/debugger/models/breakpointsmodel.cpp index 1d5e7c2b5..4fcb97063 100644 --- a/src/tools/ecode/plugins/debugger/models/breakpointsmodel.cpp +++ b/src/tools/ecode/plugins/debugger/models/breakpointsmodel.cpp @@ -106,6 +106,24 @@ void BreakpointsModel::enable( const std::string& filePath, } } +void BreakpointsModel::move( const std::string& doc, Int64 fromLine, Int64 toLine, + Int64 numLines ) { + Lock l( mResourceLock ); + bool modified = false; + for ( auto& [dc, bp] : mBreakpoints ) { + if ( dc != doc ) + continue; + + if ( bp.line - 1 >= fromLine ) { + bp.line += numLines; + modified = true; + } + } + + if ( modified ) + invalidate( UpdateFlag::DontInvalidateIndexes ); +} + const std::pair& BreakpointsModel::get( ModelIndex index ) { static std::pair EMPTY = {}; if ( !index.isValid() || index.row() >= static_cast( mBreakpoints.size() ) ) diff --git a/src/tools/ecode/plugins/debugger/models/breakpointsmodel.hpp b/src/tools/ecode/plugins/debugger/models/breakpointsmodel.hpp index 3d4cbbc07..514ed3875 100644 --- a/src/tools/ecode/plugins/debugger/models/breakpointsmodel.hpp +++ b/src/tools/ecode/plugins/debugger/models/breakpointsmodel.hpp @@ -38,6 +38,8 @@ class BreakpointsModel : public Model { void enable( const std::string& filePath, const SourceBreakpointStateful& breakpoint, bool enable ); + void move( const std::string& doc, Int64 fromLine, Int64 toLine, Int64 numLines ); + const std::pair& get( ModelIndex index ); protected: std::vector> mBreakpoints; diff --git a/src/tools/ecode/plugins/debugger/models/threadsmodel.cpp b/src/tools/ecode/plugins/debugger/models/threadsmodel.cpp index 5c9776b3e..74a444c55 100644 --- a/src/tools/ecode/plugins/debugger/models/threadsmodel.cpp +++ b/src/tools/ecode/plugins/debugger/models/threadsmodel.cpp @@ -24,8 +24,11 @@ std::string ThreadsModel::columnName( const size_t& colIdx ) const { Variant ThreadsModel::data( const ModelIndex& modelIndex, ModelRole role ) const { if ( role == ModelRole::Display && modelIndex.column() == Columns::ID ) { - return Variant( String::format( "#%d (%s)", mThreads[modelIndex.row()].id, - mThreads[modelIndex.row()].name.c_str() ) ); + if ( mThreads[modelIndex.row()].name.empty() ) + return Variant( String::format( "#%d", mThreads[modelIndex.row()].id ) ); + else + return Variant( String::format( "#%d (%s)", mThreads[modelIndex.row()].id, + mThreads[modelIndex.row()].name.c_str() ) ); } else if ( role == ModelRole::Icon && modelIndex.column() == Columns::ID && mThreads[modelIndex.row()].id == mCurrentThreadId ) { static UIIcon* circleFilled = mSceneNode->findIcon( "circle-filled" ); diff --git a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp index 0f55ee0bf..14648c505 100644 --- a/src/tools/ecode/plugins/lsp/lspclientplugin.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientplugin.cpp @@ -1,5 +1,5 @@ -#include "lspclientplugin.hpp" #include "../../version.hpp" +#include "lspclientplugin.hpp" #include #include #include @@ -1284,6 +1284,7 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std lsp.host = tlsp.host; lsp.port = tlsp.port; lsp.initializationOptions = tlsp.initializationOptions; + lsp.extraTriggerChars = tlsp.extraTriggerChars; lsp.usesLSP = use; if ( obj.contains( "share_process" ) && obj["share_process"].is_boolean() ) { lsp.shareProcessWithOtherDefinition = obj["share_process"].get(); @@ -1320,13 +1321,22 @@ void LSPClientPlugin::loadLSPConfig( std::vector& lsps, const std auto& fp = obj["file_patterns"]; for ( auto& pattern : fp ) - lsp.filePatterns.push_back( pattern.get() ); + if ( pattern.is_string() ) + lsp.filePatterns.push_back( pattern.get() ); if ( obj.contains( "rootIndicationFileNames" ) ) { lsp.rootIndicationFileNames.clear(); auto& fnms = obj["rootIndicationFileNames"]; for ( auto& fn : fnms ) - lsp.rootIndicationFileNames.push_back( fn ); + if ( fn.is_string() ) + lsp.rootIndicationFileNames.push_back( fn ); + } + + if ( obj.contains( "extra_trigger_chars" ) && obj["extra_trigger_chars"].is_array() ) { + for ( auto& jch : obj["extra_trigger_chars"] ) { + if ( jch.is_string() ) + lsp.extraTriggerChars.push_back( jch ); + } } sanitizeCommand( lsp.command, mManager->getWorkspaceFolder() ); diff --git a/src/tools/ecode/plugins/lsp/lspclientserver.cpp b/src/tools/ecode/plugins/lsp/lspclientserver.cpp index b2727fa93..636202ffd 100644 --- a/src/tools/ecode/plugins/lsp/lspclientserver.cpp +++ b/src/tools/ecode/plugins/lsp/lspclientserver.cpp @@ -1,5 +1,5 @@ -#include "lspclientserver.hpp" #include "lspclientplugin.hpp" +#include "lspclientserver.hpp" #include "lspclientservermanager.hpp" #include #include @@ -504,9 +504,11 @@ static LSPSymbolInformationList parseDocumentSymbols( const json& result, bool i for ( const auto& r : ret ) rret.push_back( LSPSymbolInformationTmp::fromTmp( r ) ); - if ( isSilent ) + if ( !isSilent ) { Log::debug( "LSPClientServer - parseDocumentSymbols took: %.2fms", clock.getElapsedTimeAndReset().asMilliseconds() ); + } + return rret; } @@ -1201,6 +1203,10 @@ void LSPClientServer::initialize() { try { #endif fromJson( mCapabilities, resp["capabilities"] ); + + for ( const auto& ch : mLSP.extraTriggerChars ) + if ( !ch.empty() ) + mCapabilities.signatureHelpProvider.triggerCharacters.push_back( ch[0] ); #ifndef EE_DEBUG } catch ( const json::exception& e ) { Log::warning( diff --git a/src/tools/ecode/plugins/lsp/lspdefinition.hpp b/src/tools/ecode/plugins/lsp/lspdefinition.hpp index 8f31bb8fc..727c53a75 100644 --- a/src/tools/ecode/plugins/lsp/lspdefinition.hpp +++ b/src/tools/ecode/plugins/lsp/lspdefinition.hpp @@ -30,6 +30,7 @@ struct LSPDefinition { std::string usesLSP; bool shareProcessWithOtherDefinition{ false }; bool disabled{ false }; + std::vector extraTriggerChars; bool commandAvailable() const { auto cmdp( String::split( command, ' ' ) ); diff --git a/src/tools/ecode/plugins/lsp/lspprotocol.hpp b/src/tools/ecode/plugins/lsp/lspprotocol.hpp index 1ad011a3d..731693ce2 100644 --- a/src/tools/ecode/plugins/lsp/lspprotocol.hpp +++ b/src/tools/ecode/plugins/lsp/lspprotocol.hpp @@ -637,7 +637,7 @@ struct LSPPreviousResultId { using LSPPreviousResultIds = std::vector; static constexpr auto LSPDocumentDiagnosticReportKindFull = "full"; -static constexpr auto LSPDocumentDiagnosticReportKindUnchanged= "unchanged"; +static constexpr auto LSPDocumentDiagnosticReportKindUnchanged = "unchanged"; struct LSPFullDocumentDiagnosticReport { URI uri; diff --git a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp index 11977ba37..9d8c346e6 100644 --- a/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp +++ b/src/tools/ecode/plugins/xmltools/xmltoolsplugin.hpp @@ -73,15 +73,15 @@ class XMLToolsPlugin : public PluginBase { mDoc( doc ), mParent( parent ) {} virtual void onDocumentTextChanged( const DocumentContentChange& ); - virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& ) {}; - virtual void onDocumentCursorChange( const TextPosition& ) {}; - virtual void onDocumentInterestingCursorChange( const TextPosition& ) {}; + virtual void onDocumentUndoRedo( const TextDocument::UndoRedo& ) {} + virtual void onDocumentCursorChange( const TextPosition& ) {} + virtual void onDocumentInterestingCursorChange( const TextPosition& ) {} virtual void onDocumentSelectionChange( const TextRange& ); - virtual void onDocumentLineCountChange( const size_t&, const size_t& ) {}; - virtual void onDocumentLineChanged( const Int64& ) {}; - virtual void onDocumentSaved( TextDocument* ) {}; - virtual void onDocumentClosed( TextDocument* doc ) { onDocumentReset( doc ); }; - virtual void onDocumentDirtyOnFileSystem( TextDocument* ) {}; + virtual void onDocumentLineCountChange( const size_t&, const size_t& ) {} + virtual void onDocumentLineChanged( const Int64& ) {} + virtual void onDocumentSaved( TextDocument* ) {} + virtual void onDocumentClosed( TextDocument* doc ) { onDocumentReset( doc ); } + virtual void onDocumentDirtyOnFileSystem( TextDocument* ) {} virtual void onDocumentMoved( TextDocument* ) {}; virtual void onDocumentReset( TextDocument* ) { mSelections.clear(); } diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp index b016df3c6..1bd1d89f2 100644 --- a/src/tools/ecode/settingsmenu.cpp +++ b/src/tools/ecode/settingsmenu.cpp @@ -658,6 +658,17 @@ UIMenu* SettingsMenu::createDocumentMenu() { "before exiting the program." ) ) ->setId( "session_snapshot" ); + mGlobalMenu + ->addCheckBox( i18n( "allow_flash_cursor", "Allow Flashing Cursor" ), + mApp->getConfig().editor.flashCursor ) + ->setTooltipText( i18n( + "allow_flash_cursor_desc", + "When enabled, pressing the default modifier key 5 times within 1.5 seconds will\n" + "trigger a visual effect that highlights the current cursor position. A large,\n" + "transparent rectangle will briefly animate, shrinking down to the cursor, making it\n" + "easier to locate when it's hard to see." ) ) + ->setId( "allow_flash_cursor" ); + mGlobalMenu->addSeparator(); mGlobalMenu->add( i18n( "line_breaking_column", "Line Breaking Column" ) ) @@ -716,6 +727,8 @@ UIMenu* SettingsMenu::createDocumentMenu() { mApp->getConfig().editor.autoReloadOnDiskChange = item->isActive(); } else if ( "session_snapshot" == id ) { mApp->getConfig().workspace.sessionSnapshot = item->isActive(); + } else if ( "allow_flash_cursor" == id ) { + mApp->getConfig().editor.flashCursor = item->isActive(); } } else if ( "line_breaking_column" == id ) { mApp->getSettingsActions()->setLineBreakingColumn(); diff --git a/src/tools/ecode/uistatusbar.cpp b/src/tools/ecode/uistatusbar.cpp index 062a7ddf6..e16dcab3e 100644 --- a/src/tools/ecode/uistatusbar.cpp +++ b/src/tools/ecode/uistatusbar.cpp @@ -1,9 +1,9 @@ -#include "uistatusbar.hpp" #include "globalsearchcontroller.hpp" #include "plugins/plugincontextprovider.hpp" #include "statusappoutputcontroller.hpp" #include "statusbuildoutputcontroller.hpp" #include "statusterminalcontroller.hpp" +#include "uistatusbar.hpp" #include "universallocator.hpp" #include #include @@ -213,4 +213,9 @@ void UIStatusBar::removeStatusBarElement( const std::string& id ) { } } +void UIStatusBar::hideAllElements() { + for ( auto& [_, el] : mElements ) + el.second->hide(); +} + } // namespace ecode diff --git a/src/tools/ecode/uistatusbar.hpp b/src/tools/ecode/uistatusbar.hpp index 7b03b322a..8f584f3af 100644 --- a/src/tools/ecode/uistatusbar.hpp +++ b/src/tools/ecode/uistatusbar.hpp @@ -53,6 +53,8 @@ class UIStatusBar : public UILinearLayout, public WidgetCommandExecuter { std::shared_ptr getStatusBarElement( const std::string& id ) const; + void hideAllElements(); + protected: UnorderedMap>> mElements; diff --git a/src/tools/ecode/universallocator.cpp b/src/tools/ecode/universallocator.cpp index 5697d28cc..1d258a168 100644 --- a/src/tools/ecode/universallocator.cpp +++ b/src/tools/ecode/universallocator.cpp @@ -630,6 +630,7 @@ void UniversalLocator::showBar() { mApp->hideStatusTerminal(); mApp->hideStatusBuildOutput(); mApp->hideStatusAppOutput(); + mApp->getStatusBar()->hideAllElements(); mLocateBarLayout->setVisible( true ); mLocateInput->setFocus(); diff --git a/src/tools/ecode/version.hpp b/src/tools/ecode/version.hpp index 6cbba1ebc..15045c6e0 100644 --- a/src/tools/ecode/version.hpp +++ b/src/tools/ecode/version.hpp @@ -7,8 +7,8 @@ using namespace EE; #define ECODE_MAJOR_VERSION 0 -#define ECODE_MINOR_VERSION 6 -#define ECODE_PATCH_LEVEL 5 +#define ECODE_MINOR_VERSION 7 +#define ECODE_PATCH_LEVEL 0 /* ECODE_COMMIT_NUMBER 9999 is used for official releases, nightly builds (pre-releases) will * contain the number of commits after the last official release */