mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-22 13:01:05 +03:00
feat(ecode): add Git diff gutter and minimap indicators
Compute document line changes asynchronously against the repository baseline and render added, modified, and removed markers in the editor gutter and minimap. Keep diff indicators disabled by default to avoid unexpected background work. Add settings for enabling them and configuring the debounce delay, with translations for all supported locales. Handle fully added files, stale asynchronous results, document lifetime, repository changes, and unsaved edits. Add focused Git diff and document serialization regression coverage. Vendor a reduced simdutf build and use it to accelerate String UTF-8 and UTF-32 conversion and codepoint counting. Preserve efficient scalar and cached-ASCII paths while reusing output storage for document snapshots. Add simdutf to the Premake 4, Premake 5, and Android builds. Fix UIConsole wheel and keyboard scrolling, and repair its text cache by hashing log entries before moving their strings. Initialize cache hashes and add regression coverage for scrolling and moved log entries. Preserve debugger response data when notifying multiple listeners by copying for all but the final listener. Remove an evaluation-order hazard when capturing and submitting URIs for asynchronous document loading. Refs SpartanJ/ecode#955.
This commit is contained in:
@@ -1235,6 +1235,10 @@ Für sichtbare Änderung ecode neu starten.</string>
|
|||||||
<string name="discordrpc_language_icons_desc">Display an icon for the active document language.</string>
|
<string name="discordrpc_language_icons_desc">Display an icon for the active document language.</string>
|
||||||
<string name="formatter_auto_format_on_save">Format on Save</string>
|
<string name="formatter_auto_format_on_save">Format on Save</string>
|
||||||
<string name="formatter_auto_format_on_save_desc">Automatically format supported documents when saving.</string>
|
<string name="formatter_auto_format_on_save_desc">Automatically format supported documents when saving.</string>
|
||||||
|
<string name="git_diff_gutter">Show Git Diff Gutter</string>
|
||||||
|
<string name="git_diff_gutter_desc">Show added, modified and deleted line indicators in the editor gutter.</string>
|
||||||
|
<string name="git_diff_gutter_debounce_delay">Git Diff Gutter Update Delay</string>
|
||||||
|
<string name="git_diff_gutter_debounce_delay_desc">How long to wait after editing before updating Git diff indicators.</string>
|
||||||
<string name="git_filetree_highlight_changes">Highlight File Tree Changes</string>
|
<string name="git_filetree_highlight_changes">Highlight File Tree Changes</string>
|
||||||
<string name="git_filetree_highlight_changes_desc">Highlight files with Git changes in the file tree.</string>
|
<string name="git_filetree_highlight_changes_desc">Highlight files with Git changes in the file tree.</string>
|
||||||
<string name="git_filetree_highlight_style_color">File Tree Highlight Color</string>
|
<string name="git_filetree_highlight_style_color">File Tree Highlight Color</string>
|
||||||
|
|||||||
@@ -1220,6 +1220,10 @@ Restart ecode to see the changes.</string>
|
|||||||
<string name="discordrpc_language_icons_desc">Display an icon for the active document language.</string>
|
<string name="discordrpc_language_icons_desc">Display an icon for the active document language.</string>
|
||||||
<string name="formatter_auto_format_on_save">Format on Save</string>
|
<string name="formatter_auto_format_on_save">Format on Save</string>
|
||||||
<string name="formatter_auto_format_on_save_desc">Automatically format supported documents when saving.</string>
|
<string name="formatter_auto_format_on_save_desc">Automatically format supported documents when saving.</string>
|
||||||
|
<string name="git_diff_gutter">Show Git Diff Gutter</string>
|
||||||
|
<string name="git_diff_gutter_desc">Show added, modified and deleted line indicators in the editor gutter.</string>
|
||||||
|
<string name="git_diff_gutter_debounce_delay">Git Diff Gutter Update Delay</string>
|
||||||
|
<string name="git_diff_gutter_debounce_delay_desc">How long to wait after editing before updating Git diff indicators.</string>
|
||||||
<string name="git_filetree_highlight_changes">Highlight File Tree Changes</string>
|
<string name="git_filetree_highlight_changes">Highlight File Tree Changes</string>
|
||||||
<string name="git_filetree_highlight_changes_desc">Highlight files with Git changes in the file tree.</string>
|
<string name="git_filetree_highlight_changes_desc">Highlight files with Git changes in the file tree.</string>
|
||||||
<string name="git_filetree_highlight_style_color">File Tree Highlight Color</string>
|
<string name="git_filetree_highlight_style_color">File Tree Highlight Color</string>
|
||||||
|
|||||||
@@ -1219,6 +1219,10 @@ Redémarrer ecode pour voir les changements.</string>
|
|||||||
<string name="discordrpc_language_icons_desc">Display an icon for the active document language.</string>
|
<string name="discordrpc_language_icons_desc">Display an icon for the active document language.</string>
|
||||||
<string name="formatter_auto_format_on_save">Format on Save</string>
|
<string name="formatter_auto_format_on_save">Format on Save</string>
|
||||||
<string name="formatter_auto_format_on_save_desc">Automatically format supported documents when saving.</string>
|
<string name="formatter_auto_format_on_save_desc">Automatically format supported documents when saving.</string>
|
||||||
|
<string name="git_diff_gutter">Show Git Diff Gutter</string>
|
||||||
|
<string name="git_diff_gutter_desc">Show added, modified and deleted line indicators in the editor gutter.</string>
|
||||||
|
<string name="git_diff_gutter_debounce_delay">Git Diff Gutter Update Delay</string>
|
||||||
|
<string name="git_diff_gutter_debounce_delay_desc">How long to wait after editing before updating Git diff indicators.</string>
|
||||||
<string name="git_filetree_highlight_changes">Highlight File Tree Changes</string>
|
<string name="git_filetree_highlight_changes">Highlight File Tree Changes</string>
|
||||||
<string name="git_filetree_highlight_changes_desc">Highlight files with Git changes in the file tree.</string>
|
<string name="git_filetree_highlight_changes_desc">Highlight files with Git changes in the file tree.</string>
|
||||||
<string name="git_filetree_highlight_style_color">File Tree Highlight Color</string>
|
<string name="git_filetree_highlight_style_color">File Tree Highlight Color</string>
|
||||||
|
|||||||
@@ -1024,6 +1024,10 @@ file in the directory tree.</string>
|
|||||||
<string name="discordrpc_language_icons_desc">Display an icon for the active document language.</string>
|
<string name="discordrpc_language_icons_desc">Display an icon for the active document language.</string>
|
||||||
<string name="formatter_auto_format_on_save">Format on Save</string>
|
<string name="formatter_auto_format_on_save">Format on Save</string>
|
||||||
<string name="formatter_auto_format_on_save_desc">Automatically format supported documents when saving.</string>
|
<string name="formatter_auto_format_on_save_desc">Automatically format supported documents when saving.</string>
|
||||||
|
<string name="git_diff_gutter">Show Git Diff Gutter</string>
|
||||||
|
<string name="git_diff_gutter_desc">Show added, modified and deleted line indicators in the editor gutter.</string>
|
||||||
|
<string name="git_diff_gutter_debounce_delay">Git Diff Gutter Update Delay</string>
|
||||||
|
<string name="git_diff_gutter_debounce_delay_desc">How long to wait after editing before updating Git diff indicators.</string>
|
||||||
<string name="git_filetree_highlight_changes">Highlight File Tree Changes</string>
|
<string name="git_filetree_highlight_changes">Highlight File Tree Changes</string>
|
||||||
<string name="git_filetree_highlight_changes_desc">Highlight files with Git changes in the file tree.</string>
|
<string name="git_filetree_highlight_changes_desc">Highlight files with Git changes in the file tree.</string>
|
||||||
<string name="git_filetree_highlight_style_color">File Tree Highlight Color</string>
|
<string name="git_filetree_highlight_style_color">File Tree Highlight Color</string>
|
||||||
|
|||||||
@@ -805,6 +805,12 @@ class EE_API String {
|
|||||||
/** Convert the string to UTF-8, reusing the output buffer capacity. */
|
/** Convert the string to UTF-8, reusing the output buffer capacity. */
|
||||||
void toUtf8( std::string& output ) const;
|
void toUtf8( std::string& output ) const;
|
||||||
|
|
||||||
|
/** Return the UTF-8 size of a UTF-32 string. Text hints can avoid rescanning known ASCII. */
|
||||||
|
static std::size_t utf8EncodedLength( View string, Uint32 textHints = 0 );
|
||||||
|
|
||||||
|
/** Append a UTF-32 string as UTF-8. Text hints can enable faster known-safe paths. */
|
||||||
|
static void appendUtf8( View string, std::string& output, Uint32 textHints = 0 );
|
||||||
|
|
||||||
/** Convert the string to a UTF-16 string */
|
/** Convert the string to a UTF-16 string */
|
||||||
std::basic_string<char16_t> toUtf16() const;
|
std::basic_string<char16_t> toUtf16() const;
|
||||||
|
|
||||||
@@ -1393,8 +1399,8 @@ struct TextHints {
|
|||||||
ContextualAlternates = 1 << 4,
|
ContextualAlternates = 1 << 4,
|
||||||
ContextualLigatures = 1 << 5,
|
ContextualLigatures = 1 << 5,
|
||||||
DiscretionaryLigatures = 1 << 6,
|
DiscretionaryLigatures = 1 << 6,
|
||||||
OpenTypeFeatures =
|
OpenTypeFeatures = StandardLigatures | ContextualAlternates | ContextualLigatures |
|
||||||
StandardLigatures | ContextualAlternates | ContextualLigatures | DiscretionaryLigatures,
|
DiscretionaryLigatures,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -770,6 +770,9 @@ class EE_API TextDocument {
|
|||||||
|
|
||||||
String toString();
|
String toString();
|
||||||
|
|
||||||
|
/** Convert the document to UTF-8, reusing the output buffer capacity. */
|
||||||
|
void toUtf8String( std::string& stream );
|
||||||
|
|
||||||
std::string toUtf8String();
|
std::string toUtf8String();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ class EE_API UIConsole : public UIWidget,
|
|||||||
protected:
|
protected:
|
||||||
struct TextCache {
|
struct TextCache {
|
||||||
Text text;
|
Text text;
|
||||||
String::HashType hash;
|
String::HashType hash{ 0 };
|
||||||
};
|
};
|
||||||
struct CommandLogCache {
|
struct CommandLogCache {
|
||||||
String log;
|
String log;
|
||||||
@@ -237,6 +237,8 @@ class EE_API UIConsole : public UIWidget,
|
|||||||
|
|
||||||
virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
|
virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
|
||||||
|
|
||||||
|
virtual Uint32 onMouseWheel( const Vector2f& offset, bool flipped );
|
||||||
|
|
||||||
virtual Uint32 onFocus( NodeFocusReason reason );
|
virtual Uint32 onFocus( NodeFocusReason reason );
|
||||||
|
|
||||||
virtual Uint32 onFocusLoss();
|
virtual Uint32 onFocusLoss();
|
||||||
@@ -287,6 +289,8 @@ class EE_API UIConsole : public UIWidget,
|
|||||||
|
|
||||||
void paste();
|
void paste();
|
||||||
|
|
||||||
|
bool scrollByLines( Int32 lines );
|
||||||
|
|
||||||
void createDefaultCommands();
|
void createDefaultCommands();
|
||||||
|
|
||||||
/** Internal Callback for default command ( cmdlist ) */
|
/** Internal Callback for default command ( cmdlist ) */
|
||||||
|
|||||||
10
premake4.lua
10
premake4.lua
@@ -797,6 +797,7 @@ function add_static_links()
|
|||||||
end
|
end
|
||||||
|
|
||||||
links { "SOIL2-static",
|
links { "SOIL2-static",
|
||||||
|
"simdutf-static",
|
||||||
"libzip-static",
|
"libzip-static",
|
||||||
"jpeg-compressor-static",
|
"jpeg-compressor-static",
|
||||||
"zlib-static",
|
"zlib-static",
|
||||||
@@ -1228,6 +1229,14 @@ solution "eepp"
|
|||||||
files { "src/thirdparty/pugixml/*.cpp" }
|
files { "src/thirdparty/pugixml/*.cpp" }
|
||||||
build_base_cpp_configuration( "pugixml" )
|
build_base_cpp_configuration( "pugixml" )
|
||||||
|
|
||||||
|
project "simdutf-static"
|
||||||
|
kind "StaticLib"
|
||||||
|
language "C++"
|
||||||
|
set_targetdir("libs/" .. os.get_real() .. "/thirdparty/")
|
||||||
|
files { "src/thirdparty/simdutf/simdutf.cpp" }
|
||||||
|
includedirs { "src/thirdparty/simdutf" }
|
||||||
|
build_base_cpp_configuration( "simdutf" )
|
||||||
|
|
||||||
project "zlib-static"
|
project "zlib-static"
|
||||||
kind "StaticLib"
|
kind "StaticLib"
|
||||||
language "C"
|
language "C"
|
||||||
@@ -2023,6 +2032,7 @@ solution "eepp"
|
|||||||
"src/tools/ecode/jsonhelper.cpp",
|
"src/tools/ecode/jsonhelper.cpp",
|
||||||
"src/tools/ecode/projectdirectorytree.cpp",
|
"src/tools/ecode/projectdirectorytree.cpp",
|
||||||
"src/tools/ecode/plugins/git/git.cpp",
|
"src/tools/ecode/plugins/git/git.cpp",
|
||||||
|
"src/tools/ecode/plugins/git/gitdiff.cpp",
|
||||||
"src/tools/ecode/plugins/autocomplete/snippetparser.cpp",
|
"src/tools/ecode/plugins/autocomplete/snippetparser.cpp",
|
||||||
"src/tools/ecode/plugins/autocomplete/usersnippetstore.cpp" }
|
"src/tools/ecode/plugins/autocomplete/usersnippetstore.cpp" }
|
||||||
eepp_module_backward_add( false )
|
eepp_module_backward_add( false )
|
||||||
|
|||||||
10
premake5.lua
10
premake5.lua
@@ -757,6 +757,7 @@ function add_static_links()
|
|||||||
end
|
end
|
||||||
|
|
||||||
links { "SOIL2-static",
|
links { "SOIL2-static",
|
||||||
|
"simdutf-static",
|
||||||
"chipmunk-static",
|
"chipmunk-static",
|
||||||
"libzip-static",
|
"libzip-static",
|
||||||
"jpeg-compressor-static",
|
"jpeg-compressor-static",
|
||||||
@@ -1266,6 +1267,14 @@ workspace "eepp"
|
|||||||
build_base_cpp_configuration( "pugixml" )
|
build_base_cpp_configuration( "pugixml" )
|
||||||
target_dir_thirdparty()
|
target_dir_thirdparty()
|
||||||
|
|
||||||
|
project "simdutf-static"
|
||||||
|
kind "StaticLib"
|
||||||
|
language "C++"
|
||||||
|
files { "src/thirdparty/simdutf/simdutf.cpp" }
|
||||||
|
includedirs { "src/thirdparty/simdutf" }
|
||||||
|
build_base_cpp_configuration( "simdutf" )
|
||||||
|
target_dir_thirdparty()
|
||||||
|
|
||||||
project "zlib-static"
|
project "zlib-static"
|
||||||
kind "StaticLib"
|
kind "StaticLib"
|
||||||
language "C"
|
language "C"
|
||||||
@@ -2038,6 +2047,7 @@ workspace "eepp"
|
|||||||
"src/tools/ecode/jsonhelper.cpp",
|
"src/tools/ecode/jsonhelper.cpp",
|
||||||
"src/tools/ecode/projectdirectorytree.cpp",
|
"src/tools/ecode/projectdirectorytree.cpp",
|
||||||
"src/tools/ecode/plugins/git/git.cpp",
|
"src/tools/ecode/plugins/git/git.cpp",
|
||||||
|
"src/tools/ecode/plugins/git/gitdiff.cpp",
|
||||||
"src/tools/ecode/plugins/autocomplete/snippetparser.cpp",
|
"src/tools/ecode/plugins/autocomplete/snippetparser.cpp",
|
||||||
"src/tools/ecode/plugins/autocomplete/usersnippetstore.cpp" }
|
"src/tools/ecode/plugins/autocomplete/usersnippetstore.cpp" }
|
||||||
filter { "system:not windows", "system:not haiku" }
|
filter { "system:not windows", "system:not haiku" }
|
||||||
|
|||||||
@@ -113,13 +113,29 @@ LOCAL_C_INCLUDES := $(EEPP_C_INCLUDES)
|
|||||||
|
|
||||||
LOCAL_SRC_FILES := $(foreach F, $(CODE_SRCS), $(addprefix $(dir $(F)),$(notdir $(wildcard $(LOCAL_PATH)/$(F)))))
|
LOCAL_SRC_FILES := $(foreach F, $(CODE_SRCS), $(addprefix $(dir $(F)),$(notdir $(wildcard $(LOCAL_PATH)/$(F)))))
|
||||||
|
|
||||||
LOCAL_STATIC_LIBRARIES := freetype libpng libwebp md4c pcre2 oniguruma harfbuzz sheenbidi gumbo-parser brotli
|
LOCAL_STATIC_LIBRARIES := simdutf freetype libpng libwebp md4c pcre2 oniguruma harfbuzz sheenbidi gumbo-parser brotli
|
||||||
|
|
||||||
LOCAL_SHARED_LIBRARIES := SDL2
|
LOCAL_SHARED_LIBRARIES := SDL2
|
||||||
|
|
||||||
include $(BUILD_STATIC_LIBRARY)
|
include $(BUILD_STATIC_LIBRARY)
|
||||||
#*************** EEPP ***************
|
#*************** EEPP ***************
|
||||||
|
|
||||||
|
#*************** SIMDUTF ***************
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_PATH := $(EEPP_THIRD_PARTY_PATH)/simdutf
|
||||||
|
|
||||||
|
LOCAL_MODULE := simdutf
|
||||||
|
|
||||||
|
LOCAL_CPPFLAGS := -std=c++20
|
||||||
|
|
||||||
|
LOCAL_C_INCLUDES := $(LOCAL_PATH)
|
||||||
|
|
||||||
|
LOCAL_SRC_FILES := simdutf.cpp
|
||||||
|
|
||||||
|
include $(BUILD_STATIC_LIBRARY)
|
||||||
|
#*************** SIMDUTF ***************
|
||||||
|
|
||||||
#*************** CHIPMUNK ***************
|
#*************** CHIPMUNK ***************
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <thirdparty/fast_float/include/fast_float/fast_float.h>
|
#include <thirdparty/fast_float/include/fast_float/fast_float.h>
|
||||||
#define FTS_FUZZY_MATCH_IMPLEMENTATION
|
#define FTS_FUZZY_MATCH_IMPLEMENTATION
|
||||||
#include <thirdparty/fts_fuzzy_match/fts_fuzzy_match.h>
|
#include <thirdparty/fts_fuzzy_match/fts_fuzzy_match.h>
|
||||||
|
#include <thirdparty/simdutf/simdutf.h>
|
||||||
#include <thirdparty/utf8cpp/utf8.h>
|
#include <thirdparty/utf8cpp/utf8.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -54,6 +55,41 @@
|
|||||||
|
|
||||||
namespace EE {
|
namespace EE {
|
||||||
|
|
||||||
|
static constexpr std::size_t SimdUtfConversionThreshold = 32;
|
||||||
|
|
||||||
|
static bool hasUtf8Bom( std::string_view string ) {
|
||||||
|
return string.size() >= 3 && static_cast<Uint8>( string[0] ) == 0xEF &&
|
||||||
|
static_cast<Uint8>( string[1] ) == 0xBB && static_cast<Uint8>( string[2] ) == 0xBF;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decodeUtf8( std::string_view input, String::StringType& output, bool skipBom ) {
|
||||||
|
if ( skipBom && hasUtf8Bom( input ) )
|
||||||
|
input.remove_prefix( 3 );
|
||||||
|
|
||||||
|
output.clear();
|
||||||
|
if ( input.empty() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( input.size() < SimdUtfConversionThreshold ) {
|
||||||
|
output.reserve( input.size() + 1 );
|
||||||
|
Utf8::toUtf32( input.begin(), input.end(), std::back_inserter( output ) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTF-32 cannot contain more code units than the UTF-8 input contains bytes.
|
||||||
|
output.resize( input.size() );
|
||||||
|
const std::size_t written =
|
||||||
|
simdutf::convert_utf8_to_utf32( input.data(), input.size(), output.data() );
|
||||||
|
if ( written != 0 ) {
|
||||||
|
output.resize( written );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve the existing permissive behavior for malformed UTF-8.
|
||||||
|
output.clear();
|
||||||
|
Utf8::toUtf32( input.begin(), input.end(), std::back_inserter( output ) );
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T> static bool _fromString( T& t, std::string_view s, int base = 10 ) {
|
template <typename T> static bool _fromString( T& t, std::string_view s, int base = 10 ) {
|
||||||
const char* begin = s.data();
|
const char* begin = s.data();
|
||||||
const char* end = s.data() + s.size();
|
const char* end = s.data() + s.size();
|
||||||
@@ -1985,70 +2021,27 @@ String::String( StringBaseType utf32Char ) {
|
|||||||
String::String( size_t count, StringBaseType utf32Char ) : mString( count, utf32Char ) {}
|
String::String( size_t count, StringBaseType utf32Char ) : mString( count, utf32Char ) {}
|
||||||
|
|
||||||
String::String( const char* utf8String ) {
|
String::String( const char* utf8String ) {
|
||||||
if ( utf8String ) {
|
if ( utf8String )
|
||||||
std::size_t length = strlen( utf8String );
|
decodeUtf8( utf8String, mString, false );
|
||||||
|
|
||||||
if ( length > 0 ) {
|
|
||||||
mString.reserve( length + 1 );
|
|
||||||
|
|
||||||
Utf8::toUtf32( utf8String, utf8String + length, std::back_inserter( mString ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String( const char* utf8String, const size_t& utf8StringSize ) {
|
String::String( const char* utf8String, const size_t& utf8StringSize ) {
|
||||||
if ( utf8String && utf8StringSize > 0 ) {
|
if ( utf8String )
|
||||||
mString.reserve( utf8StringSize + 1 );
|
decodeUtf8( std::string_view{ utf8String, utf8StringSize }, mString, true );
|
||||||
|
|
||||||
int skip = 0;
|
|
||||||
// Skip BOM
|
|
||||||
if ( utf8StringSize >= 3 && (char)0xef == utf8String[0] && (char)0xbb == utf8String[1] &&
|
|
||||||
(char)0xbf == utf8String[2] ) {
|
|
||||||
skip = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utf8::toUtf32( utf8String + skip, utf8String + utf8StringSize,
|
|
||||||
std::back_inserter( mString ) );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String( const std::string& utf8String ) {
|
String::String( const std::string& utf8String ) {
|
||||||
mString.reserve( utf8String.length() + 1 );
|
decodeUtf8( utf8String, mString, true );
|
||||||
|
|
||||||
int skip = 0;
|
|
||||||
// Skip BOM
|
|
||||||
if ( utf8String.size() >= 3 && (char)0xef == utf8String[0] && (char)0xbb == utf8String[1] &&
|
|
||||||
(char)0xbf == utf8String[2] ) {
|
|
||||||
skip = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utf8::toUtf32( utf8String.begin() + skip, utf8String.end(), std::back_inserter( mString ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String( const std::basic_string<char8_t>& utf8String ) {
|
String::String( const std::basic_string<char8_t>& utf8String ) {
|
||||||
mString.reserve( utf8String.length() + 1 );
|
decodeUtf8(
|
||||||
|
std::string_view{ reinterpret_cast<const char*>( utf8String.data() ), utf8String.size() },
|
||||||
int skip = 0;
|
mString, true );
|
||||||
// Skip BOM
|
|
||||||
if ( utf8String.size() >= 3 && (char8_t)0xef == utf8String[0] &&
|
|
||||||
(char8_t)0xbb == utf8String[1] && (char8_t)0xbf == utf8String[2] ) {
|
|
||||||
skip = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utf8::toUtf32( utf8String.begin() + skip, utf8String.end(), std::back_inserter( mString ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String( const std::string_view& utf8String ) {
|
String::String( const std::string_view& utf8String ) {
|
||||||
mString.reserve( utf8String.length() + 1 );
|
decodeUtf8( utf8String, mString, true );
|
||||||
|
|
||||||
int skip = 0;
|
|
||||||
// Skip BOM
|
|
||||||
if ( utf8String.size() >= 3 && (char)0xef == utf8String[0] && (char)0xbb == utf8String[1] &&
|
|
||||||
(char)0xbf == utf8String[2] ) {
|
|
||||||
skip = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
Utf8::toUtf32( utf8String.begin() + skip, utf8String.end(), std::back_inserter( mString ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef EE_NO_WIDECHAR
|
#ifndef EE_NO_WIDECHAR
|
||||||
@@ -2112,37 +2105,11 @@ String String::fromLatin1( const char* str, const size_t& stringSize ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String String::fromUtf8( const std::string& utf8String ) {
|
String String::fromUtf8( const std::string& utf8String ) {
|
||||||
String::StringType utf32;
|
return String( utf8String );
|
||||||
|
|
||||||
// Skip BOM
|
|
||||||
int skip = 0;
|
|
||||||
if ( utf8String.size() >= 3 && (char)0xef == utf8String[0] && (char)0xbb == utf8String[1] &&
|
|
||||||
(char)0xbf == utf8String[2] ) {
|
|
||||||
skip = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
utf32.reserve( utf8String.length() + 1 );
|
|
||||||
|
|
||||||
Utf8::toUtf32( utf8String.begin() + skip, utf8String.end(), std::back_inserter( utf32 ) );
|
|
||||||
|
|
||||||
return String( utf32 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String String::fromUtf8( const std::string_view& utf8String ) {
|
String String::fromUtf8( const std::string_view& utf8String ) {
|
||||||
String::StringType utf32;
|
return String( utf8String );
|
||||||
|
|
||||||
// Skip BOM
|
|
||||||
int skip = 0;
|
|
||||||
if ( utf8String.size() >= 3 && (char)0xef == utf8String[0] && (char)0xbb == utf8String[1] &&
|
|
||||||
(char)0xbf == utf8String[2] ) {
|
|
||||||
skip = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
utf32.reserve( utf8String.length() + 1 );
|
|
||||||
|
|
||||||
Utf8::toUtf32( utf8String.begin() + skip, utf8String.end(), std::back_inserter( utf32 ) );
|
|
||||||
|
|
||||||
return String( utf32 );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define iscont( p ) ( ( *( p ) & 0xC0 ) == 0x80 )
|
#define iscont( p ) ( ( *( p ) & 0xC0 ) == 0x80 )
|
||||||
@@ -2160,12 +2127,18 @@ static inline size_t utf8_length( const char* s, const char* e ) {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline size_t utf8LengthImpl( const char* data, std::size_t size ) {
|
||||||
|
if ( size >= SimdUtfConversionThreshold )
|
||||||
|
return simdutf::count_utf8( data, size );
|
||||||
|
return utf8_length( data, data + size );
|
||||||
|
}
|
||||||
|
|
||||||
size_t String::utf8Length( const std::string& utf8String ) {
|
size_t String::utf8Length( const std::string& utf8String ) {
|
||||||
return utf8_length( utf8String.c_str(), utf8String.c_str() + utf8String.length() );
|
return utf8LengthImpl( utf8String.data(), utf8String.size() );
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t String::utf8Length( const std::string_view& utf8String ) {
|
size_t String::utf8Length( const std::string_view& utf8String ) {
|
||||||
return utf8_length( utf8String.data(), utf8String.data() + utf8String.length() );
|
return utf8LengthImpl( utf8String.data(), utf8String.size() );
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint32 String::utf8Next( char*& utf8String ) {
|
Uint32 String::utf8Next( char*& utf8String ) {
|
||||||
@@ -2235,8 +2208,44 @@ std::string String::toUtf8() const {
|
|||||||
|
|
||||||
void String::toUtf8( std::string& output ) const {
|
void String::toUtf8( std::string& output ) const {
|
||||||
output.clear();
|
output.clear();
|
||||||
output.reserve( mString.length() + 1 );
|
appendUtf8( mString, output );
|
||||||
Utf32::toUtf8( mString.begin(), mString.end(), std::back_inserter( output ) );
|
}
|
||||||
|
|
||||||
|
std::size_t String::utf8EncodedLength( View string, Uint32 textHints ) {
|
||||||
|
if ( textHints & TextHints::AllAscii )
|
||||||
|
return string.size();
|
||||||
|
return simdutf::utf8_length_from_utf32( string.data(), string.size() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::appendUtf8( View string, std::string& output, Uint32 textHints ) {
|
||||||
|
static constexpr std::size_t SimdThreshold = 32;
|
||||||
|
if ( textHints & TextHints::AllAscii ) {
|
||||||
|
const std::size_t initialSize = output.size();
|
||||||
|
output.resize( initialSize + string.size() );
|
||||||
|
char* destination = output.data() + initialSize;
|
||||||
|
for ( std::size_t i = 0; i < string.size(); ++i )
|
||||||
|
destination[i] = static_cast<char>( string[i] );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( string.size() < SimdThreshold ) {
|
||||||
|
output.reserve( output.size() + string.size() );
|
||||||
|
Utf32::toUtf8( string.begin(), string.end(), std::back_inserter( output ) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t initialSize = output.size();
|
||||||
|
const std::size_t outputSize = utf8EncodedLength( string, textHints );
|
||||||
|
output.resize( initialSize + outputSize );
|
||||||
|
char* destination = output.data() + initialSize;
|
||||||
|
const std::size_t written =
|
||||||
|
simdutf::convert_utf32_to_utf8( string.data(), string.size(), destination );
|
||||||
|
if ( written == outputSize )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Preserve conversion for malformed UTF-32 without penalizing valid text.
|
||||||
|
output.resize( initialSize );
|
||||||
|
Utf32::toUtf8( string.begin(), string.end(), std::back_inserter( output ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
std::basic_string<char16_t> String::toUtf16() const {
|
std::basic_string<char16_t> String::toUtf16() const {
|
||||||
@@ -2437,12 +2446,7 @@ String& String::assign( const char* s ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String& String::assignUtf8( std::string_view utf8String ) {
|
String& String::assignUtf8( std::string_view utf8String ) {
|
||||||
if ( utf8String.empty() ) {
|
decodeUtf8( utf8String, mString, false );
|
||||||
mString.clear();
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
mString.resize( utf8Length( utf8String ) );
|
|
||||||
toUtf32( utf8String, mString.data(), mString.size() );
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -958,8 +958,8 @@ bool TextDocument::loadAsyncFromURL( const std::string& url,
|
|||||||
mLoadingAsync = true;
|
mLoadingAsync = true;
|
||||||
|
|
||||||
Http::getAsync(
|
Http::getAsync(
|
||||||
[this, onLoaded = std::move( onLoaded ),
|
[this, onLoaded = std::move( onLoaded ), uri]( const Http&, Http::Request&,
|
||||||
uri = std::move( uri )]( const Http&, Http::Request&, Http::Response& response ) {
|
Http::Response& response ) {
|
||||||
if ( response.getStatus() <= Http::Response::Ok ) {
|
if ( response.getStatus() <= Http::Response::Ok ) {
|
||||||
std::string path( URI::getTempPathFromURI( uri ) );
|
std::string path( URI::getTempPathFromURI( uri ) );
|
||||||
FileSystem::fileWrite( path, (const Uint8*)response.getBody().c_str(),
|
FileSystem::fileWrite( path, (const Uint8*)response.getBody().c_str(),
|
||||||
@@ -1462,22 +1462,23 @@ String TextDocument::toString() {
|
|||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TextDocument::toUtf8String() {
|
void TextDocument::toUtf8String( std::string& stream ) {
|
||||||
Lock l( mLinesMutex );
|
Lock l( mLinesMutex );
|
||||||
Lock l2( *mDocumentMutex );
|
Lock l2( *mDocumentMutex );
|
||||||
std::string stream;
|
std::size_t utf8Size = 0;
|
||||||
std::size_t totalCodepoints = 0;
|
|
||||||
for ( const auto& line : mLines )
|
for ( const auto& line : mLines )
|
||||||
totalCodepoints += line.size();
|
utf8Size += String::utf8EncodedLength( line.getText().getString(), line.getTextHints() );
|
||||||
|
|
||||||
// Heuristic reserve: Codepoints + 25% to account for UTF-8 expansion
|
stream.clear();
|
||||||
stream.reserve( totalCodepoints + ( totalCodepoints >> 2 ) );
|
stream.reserve( utf8Size );
|
||||||
|
|
||||||
for ( const auto& line : mLines ) {
|
for ( const auto& line : mLines )
|
||||||
const String& text = line.getText();
|
String::appendUtf8( line.getText().getString(), stream, line.getTextHints() );
|
||||||
// Low-level conversion directly into the stream buffer
|
}
|
||||||
Utf32::toUtf8( text.begin(), text.end(), std::back_inserter( stream ) );
|
|
||||||
}
|
std::string TextDocument::toUtf8String() {
|
||||||
|
std::string stream;
|
||||||
|
toUtf8String( stream );
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -477,7 +477,8 @@ void UIConsole::privPushText( String&& str ) {
|
|||||||
String::replaceAll( str, "\r", "" );
|
String::replaceAll( str, "\r", "" );
|
||||||
if ( str.empty() )
|
if ( str.empty() )
|
||||||
return;
|
return;
|
||||||
mCmdLog.push_back( { std::move( str ), String::hash( str ) } );
|
const String::HashType hash = String::hash( str );
|
||||||
|
mCmdLog.push_back( { std::move( str ), hash } );
|
||||||
if ( mVisible )
|
if ( mVisible )
|
||||||
invalidateDraw();
|
invalidateDraw();
|
||||||
if ( mCmdLog.size() >= mMaxLogLines )
|
if ( mCmdLog.size() >= mMaxLogLines )
|
||||||
@@ -939,15 +940,11 @@ Uint32 UIConsole::onKeyDown( const KeyEvent& event ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( event.getMod() & KEYMOD_SHIFT ) {
|
if ( event.getMod() & KEYMOD_SHIFT ) {
|
||||||
if ( event.getKeyCode() == KEY_UP && mCon.min - mCon.modif > 0 ) {
|
if ( event.getKeyCode() == KEY_UP && scrollByLines( 1 ) ) {
|
||||||
mCon.modif++;
|
|
||||||
invalidateDraw();
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( event.getKeyCode() == KEY_DOWN && mCon.modif > 0 ) {
|
if ( event.getKeyCode() == KEY_DOWN && scrollByLines( -1 ) ) {
|
||||||
mCon.modif--;
|
|
||||||
invalidateDraw();
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -971,20 +968,12 @@ Uint32 UIConsole::onKeyDown( const KeyEvent& event ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( event.getKeyCode() == KEY_PAGEUP ) {
|
if ( event.getKeyCode() == KEY_PAGEUP ) {
|
||||||
if ( mCon.min - mCon.modif - linesOnScreen() / 2 > 0 )
|
scrollByLines( eemax( 1, linesOnScreen() / 2 ) );
|
||||||
mCon.modif += linesOnScreen() / 2;
|
|
||||||
else
|
|
||||||
mCon.modif = mCon.min;
|
|
||||||
invalidateDraw();
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( event.getKeyCode() == KEY_PAGEDOWN ) {
|
if ( event.getKeyCode() == KEY_PAGEDOWN ) {
|
||||||
if ( mCon.modif - linesOnScreen() / 2 > 0 )
|
scrollByLines( -eemax( 1, linesOnScreen() / 2 ) );
|
||||||
mCon.modif -= linesOnScreen() / 2;
|
|
||||||
else
|
|
||||||
mCon.modif = 0;
|
|
||||||
invalidateDraw();
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1221,19 +1210,7 @@ Uint32 UIConsole::onMouseDoubleClick( const Vector2i& position, const Uint32& fl
|
|||||||
}
|
}
|
||||||
|
|
||||||
Uint32 UIConsole::onMouseUp( const Vector2i& position, const Uint32& flags ) {
|
Uint32 UIConsole::onMouseUp( const Vector2i& position, const Uint32& flags ) {
|
||||||
if ( flags == EE_BUTTON_WUMASK ) {
|
if ( flags & EE_BUTTON_LMASK ) {
|
||||||
if ( mCon.min - mCon.modif - 6 > 0 ) {
|
|
||||||
mCon.modif += 6;
|
|
||||||
} else {
|
|
||||||
mCon.modif = mCon.min;
|
|
||||||
}
|
|
||||||
} else if ( flags == EE_BUTTON_WDMASK ) {
|
|
||||||
if ( mCon.modif - 6 > 0 ) {
|
|
||||||
mCon.modif -= 6;
|
|
||||||
} else {
|
|
||||||
mCon.modif = 0;
|
|
||||||
}
|
|
||||||
} else if ( flags & EE_BUTTON_LMASK ) {
|
|
||||||
if ( mMouseDown ) {
|
if ( mMouseDown ) {
|
||||||
mMouseDown = false;
|
mMouseDown = false;
|
||||||
getInput()->captureMouse( false );
|
getInput()->captureMouse( false );
|
||||||
@@ -1244,6 +1221,21 @@ Uint32 UIConsole::onMouseUp( const Vector2i& position, const Uint32& flags ) {
|
|||||||
return UIWidget::onMouseUp( position, flags );
|
return UIWidget::onMouseUp( position, flags );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Uint32 UIConsole::onMouseWheel( const Vector2f& offset, bool ) {
|
||||||
|
if ( offset.y == 0.f )
|
||||||
|
return 0;
|
||||||
|
return scrollByLines( offset.y > 0.f ? 6 : -6 ) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UIConsole::scrollByLines( Int32 lines ) {
|
||||||
|
const Int32 previousOffset = mCon.modif;
|
||||||
|
mCon.modif = eeclamp( mCon.modif + lines, 0, mCon.min );
|
||||||
|
if ( previousOffset == mCon.modif )
|
||||||
|
return false;
|
||||||
|
invalidateDraw();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void UIConsole::onDocumentTextChanged( const DocumentContentChange& ) {
|
void UIConsole::onDocumentTextChanged( const DocumentContentChange& ) {
|
||||||
resetCursor();
|
resetCursor();
|
||||||
|
|
||||||
|
|||||||
68
src/tests/unit_tests/gitdiff_tests.cpp
Normal file
68
src/tests/unit_tests/gitdiff_tests.cpp
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#include "utest.h"
|
||||||
|
|
||||||
|
#include "../../tools/ecode/plugins/git/gitdiff.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace ecode;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
std::string decorationSignature( const ComputedGitLineDiff& diff ) {
|
||||||
|
std::string signature;
|
||||||
|
signature.reserve( diff.lines.size() * 2 );
|
||||||
|
for ( const auto& line : diff.lines ) {
|
||||||
|
switch ( line.change ) {
|
||||||
|
case GitLineChange::None:
|
||||||
|
signature += 'n';
|
||||||
|
break;
|
||||||
|
case GitLineChange::Added:
|
||||||
|
signature += 'a';
|
||||||
|
break;
|
||||||
|
case GitLineChange::Modified:
|
||||||
|
signature += 'm';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ( line.deletedBefore )
|
||||||
|
signature += '^';
|
||||||
|
}
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool matchesDiff( std::string_view baseline, std::string_view current,
|
||||||
|
std::string_view expectedSignature, bool expectedDeletedAtEOF = false ) {
|
||||||
|
const auto diff = computeGitLineDiff( baseline, current );
|
||||||
|
const auto actualSignature = decorationSignature( diff );
|
||||||
|
return actualSignature == expectedSignature && diff.deletedAtEOF == expectedDeletedAtEOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
UTEST( GitDiff, ClassifiesLineChangesAndDeletionBoundaries ) {
|
||||||
|
EXPECT_TRUE( matchesDiff( "", "", "" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB", "A\nB", "nn" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nC", "A\nB\nC", "nan" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nD", "A\nB\nC\nD", "naan" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB\nC", "A\nC", "nn^" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB\nC\nD", "A\nD", "nn^" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB", "B", "n^" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB", "A", "n", true ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB", "", "n", true ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB\nC", "A\nX\nC", "nmn" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB\nC\nD", "A\nX\nY\nD", "nmmn" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB", "A\nX\nY\nB", "naan" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB\nC\nD\nE", "A\nX\nE", "nm^n" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB\nC\nD\nE", "A\nX\nC\nY\nE", "nmnmn" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "", "A\nB", "aa" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "hello", "hello\n", "na" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "hello\n", "hello", "n", true ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB", "X\nB", "mn" ) );
|
||||||
|
EXPECT_TRUE( matchesDiff( "A\nB", "A\nX", "nm" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( GitDiff, NormalizesDocumentLineEndingsWithoutReallocation ) {
|
||||||
|
std::string text = "A\r\nB\rC\n";
|
||||||
|
const char* storage = text.data();
|
||||||
|
normalizeGitDiffText( text );
|
||||||
|
EXPECT_TRUE( text == "A\nB\nC\n" );
|
||||||
|
EXPECT_TRUE( text.data() == storage );
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "utest.hpp"
|
#include "utest.hpp"
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <eepp/core/string.hpp>
|
#include <eepp/core/string.hpp>
|
||||||
|
#include <eepp/core/utf.hpp>
|
||||||
#include <eepp/system/filesystem.hpp>
|
#include <eepp/system/filesystem.hpp>
|
||||||
#include <eepp/system/sys.hpp>
|
#include <eepp/system/sys.hpp>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
@@ -317,6 +318,58 @@ UTEST( String, reusableFormattingAndUtf8Assignment ) {
|
|||||||
EXPECT_EQ( utf8Storage, reusableUtf8.data() );
|
EXPECT_EQ( utf8Storage, reusableUtf8.data() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UTEST( String, acceleratedUtf8Conversion ) {
|
||||||
|
const std::string ascii( 256, 'a' );
|
||||||
|
const String asciiText( ascii );
|
||||||
|
EXPECT_STDSTREQ( ascii, asciiText.toUtf8() );
|
||||||
|
EXPECT_EQ( ascii.size(),
|
||||||
|
String::utf8EncodedLength( asciiText.getString(), TextHints::AllAscii ) );
|
||||||
|
std::string asciiOutput;
|
||||||
|
String::appendUtf8( asciiText.getString(), asciiOutput, TextHints::AllAscii );
|
||||||
|
EXPECT_STDSTREQ( ascii, asciiOutput );
|
||||||
|
|
||||||
|
const std::string unicode = std::string( 64, 'a' ) + "áβ中🙂" + std::string( 64, 'z' );
|
||||||
|
const String unicodeText = String::fromUtf8( unicode );
|
||||||
|
EXPECT_STDSTREQ( unicode, unicodeText.toUtf8() );
|
||||||
|
EXPECT_EQ( unicode.size(), String::utf8EncodedLength( unicodeText.getString() ) );
|
||||||
|
|
||||||
|
std::string appended = "prefix:";
|
||||||
|
String::appendUtf8( unicodeText.getString(), appended );
|
||||||
|
EXPECT_STDSTREQ( "prefix:" + unicode, appended );
|
||||||
|
|
||||||
|
String::StringType malformed( 64, U'a' );
|
||||||
|
malformed.push_back( static_cast<char32_t>( 0x110000 ) );
|
||||||
|
malformed.append( 64, U'z' );
|
||||||
|
const String malformedText( malformed );
|
||||||
|
EXPECT_STDSTREQ( std::string( 64, 'a' ) + std::string( 64, 'z' ), malformedText.toUtf8() );
|
||||||
|
}
|
||||||
|
|
||||||
|
UTEST( String, acceleratedUtf8Decoding ) {
|
||||||
|
const std::string ascii( 256, 'a' );
|
||||||
|
const String asciiText( ascii );
|
||||||
|
EXPECT_EQ( ascii.size(), asciiText.size() );
|
||||||
|
EXPECT_STDSTREQ( ascii, asciiText.toUtf8() );
|
||||||
|
|
||||||
|
const std::string unicode = std::string( 64, 'a' ) + "áβ中🙂" + std::string( 64, 'z' );
|
||||||
|
String reused;
|
||||||
|
reused.assignUtf8( unicode );
|
||||||
|
EXPECT_STDSTREQ( unicode, reused.toUtf8() );
|
||||||
|
EXPECT_EQ( reused.size(), String::utf8Length( unicode ) );
|
||||||
|
|
||||||
|
const std::string withBom = "\xEF\xBB\xBF" + ascii;
|
||||||
|
EXPECT_STDSTREQ( ascii, String( withBom ).toUtf8() );
|
||||||
|
reused.assignUtf8( withBom );
|
||||||
|
ASSERT_TRUE( !reused.empty() );
|
||||||
|
EXPECT_EQ( static_cast<Uint32>( 0xFEFF ), static_cast<Uint32>( reused.front() ) );
|
||||||
|
|
||||||
|
std::string malformed( 64, 'a' );
|
||||||
|
malformed.append( "\xF0\x28\x8C\x28", 4 );
|
||||||
|
malformed.append( 64, 'z' );
|
||||||
|
String::StringType expected;
|
||||||
|
Utf8::toUtf32( malformed.begin(), malformed.end(), std::back_inserter( expected ) );
|
||||||
|
EXPECT_TRUE( String( malformed ).getString() == expected );
|
||||||
|
}
|
||||||
|
|
||||||
UTEST( String, byteStringEscapeAndUnescape ) {
|
UTEST( String, byteStringEscapeAndUnescape ) {
|
||||||
const std::string raw = "line one\r\nline two\t\a\b\f\v";
|
const std::string raw = "line one\r\nline two\t\a\b\f\v";
|
||||||
const std::string escaped = String::escape( raw );
|
const std::string escaped = String::escape( raw );
|
||||||
|
|||||||
@@ -77,6 +77,19 @@ UTEST( TextDocument, insertSingleLineAtDifferentPositions ) {
|
|||||||
EXPECT_EQ( 1u, doc.linesCount() );
|
EXPECT_EQ( 1u, doc.linesCount() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UTEST( TextDocument, toUtf8StringReusesOutputStorage ) {
|
||||||
|
TextDocument doc;
|
||||||
|
doc.insert( 0, { 0, 0 }, "alpha\nbeta" );
|
||||||
|
|
||||||
|
std::string output( 4096, 'x' );
|
||||||
|
output.reserve( 8192 );
|
||||||
|
const size_t capacity = output.capacity();
|
||||||
|
doc.toUtf8String( output );
|
||||||
|
|
||||||
|
EXPECT_TRUE( output == doc.toUtf8String() );
|
||||||
|
EXPECT_EQ( capacity, output.capacity() );
|
||||||
|
}
|
||||||
|
|
||||||
UTEST( TextDocument, insertNewLinesIntoEmptyDocument ) {
|
UTEST( TextDocument, insertNewLinesIntoEmptyDocument ) {
|
||||||
{
|
{
|
||||||
TextDocument doc;
|
TextDocument doc;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#include "utest.h"
|
#include "utest.h"
|
||||||
#include <eepp/system/filesystem.hpp>
|
|
||||||
#include <eepp/scene/scenemanager.hpp>
|
#include <eepp/scene/scenemanager.hpp>
|
||||||
|
#include <eepp/system/filesystem.hpp>
|
||||||
#include <eepp/ui/uiapplication.hpp>
|
#include <eepp/ui/uiapplication.hpp>
|
||||||
#include <eepp/ui/uicodeeditor.hpp>
|
#include <eepp/ui/uicodeeditor.hpp>
|
||||||
|
#include <eepp/ui/uiconsole.hpp>
|
||||||
#include <eepp/ui/uiscenenode.hpp>
|
#include <eepp/ui/uiscenenode.hpp>
|
||||||
#include <eepp/ui/uiscrollbar.hpp>
|
#include <eepp/ui/uiscrollbar.hpp>
|
||||||
#include <eepp/ui/uiscrollview.hpp>
|
#include <eepp/ui/uiscrollview.hpp>
|
||||||
@@ -54,6 +55,25 @@ class WheelOverrideWidget : public UIWidget {
|
|||||||
int calls{ 0 };
|
int calls{ 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ScrollingTestConsole : public UIConsole {
|
||||||
|
public:
|
||||||
|
ScrollingTestConsole() : UIConsole( nullptr, false, false, 128 ) {}
|
||||||
|
|
||||||
|
using UIConsole::onKeyDown;
|
||||||
|
using UIConsole::onMouseWheel;
|
||||||
|
|
||||||
|
void setScrollRange( Int32 maximum ) {
|
||||||
|
mCon.min = maximum;
|
||||||
|
mCon.modif = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Int32 getScrollOffset() const { return mCon.modif; }
|
||||||
|
|
||||||
|
void pushText( String text ) { privPushText( std::move( text ) ); }
|
||||||
|
|
||||||
|
String::HashType getLastLogHash() const { return mCmdLog.back().hash; }
|
||||||
|
};
|
||||||
|
|
||||||
UTEST( UIScrolling, SceneSmoothScrollingDefaultsCanBeInheritedSnapshottedAndAppliedNow ) {
|
UTEST( UIScrolling, SceneSmoothScrollingDefaultsCanBeInheritedSnapshottedAndAppliedNow ) {
|
||||||
UIApplication app(
|
UIApplication app(
|
||||||
WindowSettings{ 320, 240, "eepp - scrolling test" },
|
WindowSettings{ 320, 240, "eepp - scrolling test" },
|
||||||
@@ -126,6 +146,33 @@ UTEST( UIScrolling, CodeEditorKeepsLegacyWheelStepButSoftensFractionalMomentumTa
|
|||||||
eeDelete( editor );
|
eeDelete( editor );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UTEST( UIScrolling, ConsoleHandlesWheelAndKeyboardScrolling ) {
|
||||||
|
UIApplication app(
|
||||||
|
WindowSettings{ 320, 240, "eepp - console scrolling test" },
|
||||||
|
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
|
||||||
|
auto* console = eeNew( ScrollingTestConsole, () );
|
||||||
|
console->setParent( app.getUI()->getRoot() );
|
||||||
|
console->setScrollRange( 20 );
|
||||||
|
const String logLine( "console cache hash regression" );
|
||||||
|
console->pushText( logLine );
|
||||||
|
EXPECT_EQ( String::hash( logLine ), console->getLastLogHash() );
|
||||||
|
|
||||||
|
EXPECT_EQ( 1u, console->onMouseWheel( { 0.f, 1.f }, false ) );
|
||||||
|
EXPECT_EQ( 6, console->getScrollOffset() );
|
||||||
|
EXPECT_EQ( 1u, console->onMouseWheel( { 0.f, -1.f }, false ) );
|
||||||
|
EXPECT_EQ( 0, console->getScrollOffset() );
|
||||||
|
|
||||||
|
KeyEvent pageUp( console, Event::KeyDown, KEY_PAGEUP, SCANCODE_PAGEUP, 0, KEYMOD_SHIFT );
|
||||||
|
EXPECT_EQ( 1u, console->onKeyDown( pageUp ) );
|
||||||
|
EXPECT_GT( console->getScrollOffset(), 0 );
|
||||||
|
|
||||||
|
KeyEvent pageDown( console, Event::KeyDown, KEY_PAGEDOWN, SCANCODE_PAGEDOWN, 0, KEYMOD_SHIFT );
|
||||||
|
EXPECT_EQ( 1u, console->onKeyDown( pageDown ) );
|
||||||
|
EXPECT_EQ( 0, console->getScrollOffset() );
|
||||||
|
|
||||||
|
eeDelete( console );
|
||||||
|
}
|
||||||
|
|
||||||
UTEST( UIScrolling, WheelEventRebuildsHoverStateAndBubblesWhenEditorCannotScroll ) {
|
UTEST( UIScrolling, WheelEventRebuildsHoverStateAndBubblesWhenEditorCannotScroll ) {
|
||||||
UIApplication app(
|
UIApplication app(
|
||||||
WindowSettings{ 320, 240, "eepp - wheel bubbling test" },
|
WindowSettings{ 320, 240, "eepp - wheel bubbling test" },
|
||||||
@@ -211,7 +258,8 @@ UTEST( UIScrolling, MouseWheelListenerConsumesBeforeScrollableAncestor ) {
|
|||||||
++wheelEvents;
|
++wheelEvents;
|
||||||
receivedOffset = event->asMouseWheelEvent()->getOffset();
|
receivedOffset = event->asMouseWheelEvent()->getOffset();
|
||||||
} );
|
} );
|
||||||
app.getWindow()->getInput()->setMousePos( child->convertToWorldSpace( { 20.f, 20.f } ).asInt() );
|
app.getWindow()->getInput()->setMousePos(
|
||||||
|
child->convertToWorldSpace( { 20.f, 20.f } ).asInt() );
|
||||||
|
|
||||||
InputEvent event{};
|
InputEvent event{};
|
||||||
event.Type = InputEvent::MouseWheel;
|
event.Type = InputEvent::MouseWheel;
|
||||||
|
|||||||
18
src/thirdparty/simdutf/LICENSE
vendored
Normal file
18
src/thirdparty/simdutf/LICENSE
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
Copyright 2021 The simdutf authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
27993
src/thirdparty/simdutf/simdutf.cpp
vendored
Normal file
27993
src/thirdparty/simdutf/simdutf.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
6190
src/thirdparty/simdutf/simdutf.h
vendored
Normal file
6190
src/thirdparty/simdutf/simdutf.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,16 @@ inline DebuggerClientDap::ResponseHandler makeResponseHandler(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Notify>
|
||||||
|
void notifyAllWithValue( const std::vector<DebuggerClient::Listener*>& listeners, T value,
|
||||||
|
Notify&& notify ) {
|
||||||
|
if ( listeners.empty() )
|
||||||
|
return;
|
||||||
|
for ( std::size_t i = 0; i + 1 < listeners.size(); ++i )
|
||||||
|
notify( listeners[i], T( value ) );
|
||||||
|
notify( listeners.back(), std::move( value ) );
|
||||||
|
}
|
||||||
|
|
||||||
DebuggerClientDap::DebuggerClientDap( const ProtocolSettings& protocolSettings,
|
DebuggerClientDap::DebuggerClientDap( const ProtocolSettings& protocolSettings,
|
||||||
std::unique_ptr<Bus>&& bus ) :
|
std::unique_ptr<Bus>&& bus ) :
|
||||||
mProtocol( protocolSettings ) {
|
mProtocol( protocolSettings ) {
|
||||||
@@ -784,11 +794,15 @@ bool DebuggerClientDap::threads() {
|
|||||||
if ( response.success && response.body.contains( DAP_THREADS ) ) {
|
if ( response.success && response.body.contains( DAP_THREADS ) ) {
|
||||||
std::vector<DapThread> threads(
|
std::vector<DapThread> threads(
|
||||||
DapThread::parseList( response.body[DAP_THREADS] ) );
|
DapThread::parseList( response.body[DAP_THREADS] ) );
|
||||||
for ( auto listener : mListeners )
|
notifyAllWithValue( mListeners, std::move( threads ),
|
||||||
listener->threads( std::move( threads ), mCurrentSessionId );
|
[this]( auto* listener, auto&& value ) {
|
||||||
|
listener->threads( std::move( value ), mCurrentSessionId );
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
for ( auto listener : mListeners )
|
notifyAllWithValue( mListeners, std::vector<DapThread>{},
|
||||||
listener->threads( {}, mCurrentSessionId );
|
[this]( auto* listener, auto&& value ) {
|
||||||
|
listener->threads( std::move( value ), mCurrentSessionId );
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mCurrentSessionId );
|
mCurrentSessionId );
|
||||||
@@ -805,14 +819,16 @@ bool DebuggerClientDap::stackTrace( int threadId, int startFrame, int levels ) {
|
|||||||
const int threadId = request.value( DAP_THREAD_ID, 1 );
|
const int threadId = request.value( DAP_THREAD_ID, 1 );
|
||||||
if ( response.success ) {
|
if ( response.success ) {
|
||||||
StackTraceInfo stackTraceInfo( response.body );
|
StackTraceInfo stackTraceInfo( response.body );
|
||||||
for ( auto listener : mListeners )
|
notifyAllWithValue( mListeners, std::move( stackTraceInfo ),
|
||||||
listener->stackTrace( threadId, std::move( stackTraceInfo ),
|
[this, threadId]( auto* listener, auto&& value ) {
|
||||||
|
listener->stackTrace( threadId, std::move( value ),
|
||||||
mCurrentSessionId );
|
mCurrentSessionId );
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
StackTraceInfo stackTraceInfo;
|
notifyAllWithValue(
|
||||||
for ( auto listener : mListeners )
|
mListeners, StackTraceInfo{}, [this, threadId]( auto* listener, auto&& value ) {
|
||||||
listener->stackTrace( threadId, std::move( stackTraceInfo ),
|
listener->stackTrace( threadId, std::move( value ), mCurrentSessionId );
|
||||||
mCurrentSessionId );
|
} );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mCurrentSessionId );
|
mCurrentSessionId );
|
||||||
@@ -827,12 +843,17 @@ bool DebuggerClientDap::scopes( int frameId ) {
|
|||||||
const int frameId = request.value( DAP_FRAME_ID, 1 );
|
const int frameId = request.value( DAP_FRAME_ID, 1 );
|
||||||
if ( response.success && response.body.contains( DAP_SCOPES ) ) {
|
if ( response.success && response.body.contains( DAP_SCOPES ) ) {
|
||||||
auto scopes( Scope::parseList( response.body[DAP_SCOPES] ) );
|
auto scopes( Scope::parseList( response.body[DAP_SCOPES] ) );
|
||||||
for ( auto listener : mListeners )
|
notifyAllWithValue( mListeners, std::move( scopes ),
|
||||||
listener->scopes( frameId, std::move( scopes ), mCurrentSessionId );
|
[this, frameId]( auto* listener, auto&& value ) {
|
||||||
|
listener->scopes( frameId, std::move( value ),
|
||||||
|
mCurrentSessionId );
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
std::vector<Scope> scopes;
|
notifyAllWithValue( mListeners, std::vector<Scope>{},
|
||||||
for ( auto listener : mListeners )
|
[this, frameId]( auto* listener, auto&& value ) {
|
||||||
listener->scopes( frameId, std::move( scopes ), mCurrentSessionId );
|
listener->scopes( frameId, std::move( value ),
|
||||||
|
mCurrentSessionId );
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mCurrentSessionId );
|
mCurrentSessionId );
|
||||||
@@ -907,12 +928,15 @@ bool DebuggerClientDap::modules( int start, int count ) {
|
|||||||
[this]( const auto& response, const auto& ) {
|
[this]( const auto& response, const auto& ) {
|
||||||
if ( response.success ) {
|
if ( response.success ) {
|
||||||
ModulesInfo info( response.body );
|
ModulesInfo info( response.body );
|
||||||
for ( auto listener : mListeners )
|
notifyAllWithValue( mListeners, std::move( info ),
|
||||||
listener->modules( std::move( info ), mCurrentSessionId );
|
[this]( auto* listener, auto&& value ) {
|
||||||
|
listener->modules( std::move( value ), mCurrentSessionId );
|
||||||
|
} );
|
||||||
} else {
|
} else {
|
||||||
ModulesInfo info;
|
notifyAllWithValue( mListeners, ModulesInfo{},
|
||||||
for ( auto listener : mListeners )
|
[this]( auto* listener, auto&& value ) {
|
||||||
listener->modules( std::move( info ), mCurrentSessionId );
|
listener->modules( std::move( value ), mCurrentSessionId );
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mCurrentSessionId );
|
mCurrentSessionId );
|
||||||
|
|||||||
185
src/tools/ecode/plugins/git/gitdiff.cpp
Normal file
185
src/tools/ecode/plugins/git/gitdiff.cpp
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
#include "gitdiff.hpp"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <dtl/dtl.hpp>
|
||||||
|
|
||||||
|
namespace ecode {
|
||||||
|
|
||||||
|
namespace GitDiffDetail {
|
||||||
|
|
||||||
|
class LineSequence {
|
||||||
|
public:
|
||||||
|
using value_type = std::string_view;
|
||||||
|
using iterator = const value_type*;
|
||||||
|
using const_iterator = const value_type*;
|
||||||
|
|
||||||
|
LineSequence() = default;
|
||||||
|
|
||||||
|
LineSequence( const value_type* data, std::size_t size ) : mData( data ), mSize( size ) {}
|
||||||
|
|
||||||
|
LineSequence( const_iterator first, const_iterator last ) :
|
||||||
|
mData( first ), mSize( first == last ? 0 : static_cast<std::size_t>( last - first ) ) {}
|
||||||
|
|
||||||
|
const_iterator begin() const { return mData; }
|
||||||
|
|
||||||
|
const_iterator end() const { return mSize == 0 ? mData : mData + mSize; }
|
||||||
|
|
||||||
|
const value_type& operator[]( std::size_t index ) const { return mData[index]; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const value_type* mData{ nullptr };
|
||||||
|
std::size_t mSize{ 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
class LineDiffBuilder {
|
||||||
|
public:
|
||||||
|
explicit LineDiffBuilder( std::size_t currentLineCount ) {
|
||||||
|
mResult.lines.reserve( currentLineCount );
|
||||||
|
}
|
||||||
|
|
||||||
|
void add( int type ) {
|
||||||
|
switch ( type ) {
|
||||||
|
case dtl::SES_COMMON:
|
||||||
|
finishEditBlock();
|
||||||
|
mResult.lines.emplace_back(
|
||||||
|
GitLineDecoration{ GitLineChange::None, mDeletedBeforeNextLine } );
|
||||||
|
mDeletedBeforeNextLine = false;
|
||||||
|
++mCurrentIndex;
|
||||||
|
break;
|
||||||
|
case dtl::SES_ADD:
|
||||||
|
startEditBlock();
|
||||||
|
mResult.lines.emplace_back();
|
||||||
|
++mCurrentIndex;
|
||||||
|
++mAddedCount;
|
||||||
|
break;
|
||||||
|
case dtl::SES_DELETE:
|
||||||
|
startEditBlock();
|
||||||
|
++mDeletedCount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputedGitLineDiff finish() {
|
||||||
|
finishEditBlock();
|
||||||
|
mResult.deletedAtEOF = mDeletedBeforeNextLine;
|
||||||
|
|
||||||
|
// TextDocument exposes one logical line for an empty buffer. Keep one decoration slot so an
|
||||||
|
// entire-file deletion can still be rendered at that boundary.
|
||||||
|
if ( mResult.deletedAtEOF && mResult.lines.empty() )
|
||||||
|
mResult.lines.emplace_back();
|
||||||
|
return std::move( mResult );
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void startEditBlock() {
|
||||||
|
if ( !mInEditBlock ) {
|
||||||
|
mInEditBlock = true;
|
||||||
|
mBlockStart = mCurrentIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishEditBlock() {
|
||||||
|
if ( !mInEditBlock )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const std::size_t modifiedCount = std::min( mDeletedCount, mAddedCount );
|
||||||
|
for ( std::size_t i = 0; i < mAddedCount; ++i ) {
|
||||||
|
mResult.lines[mBlockStart + i].change =
|
||||||
|
i < modifiedCount ? GitLineChange::Modified : GitLineChange::Added;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( mDeletedCount > modifiedCount ) {
|
||||||
|
if ( mAddedCount != 0 )
|
||||||
|
mResult.lines[mBlockStart].deletedBefore = true;
|
||||||
|
else
|
||||||
|
mDeletedBeforeNextLine = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mInEditBlock = false;
|
||||||
|
mDeletedCount = 0;
|
||||||
|
mAddedCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputedGitLineDiff mResult;
|
||||||
|
std::size_t mCurrentIndex{ 0 };
|
||||||
|
std::size_t mBlockStart{ 0 };
|
||||||
|
std::size_t mDeletedCount{ 0 };
|
||||||
|
std::size_t mAddedCount{ 0 };
|
||||||
|
bool mInEditBlock{ false };
|
||||||
|
bool mDeletedBeforeNextLine{ false };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename SesElement, typename Builder> class StoreLineDiff {
|
||||||
|
public:
|
||||||
|
explicit StoreLineDiff( Builder& builder ) : mBuilder( builder ) {}
|
||||||
|
|
||||||
|
void operator()( const SesElement& entry ) { mBuilder.add( entry.second.type ); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Builder& mBuilder;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::size_t lineCount( std::string_view text ) {
|
||||||
|
if ( text.empty() )
|
||||||
|
return 0;
|
||||||
|
return 1 + static_cast<std::size_t>( std::count( text.begin(), text.end(), '\n' ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void appendLines( std::string_view text, std::vector<std::string_view>& lines ) {
|
||||||
|
if ( text.empty() )
|
||||||
|
return;
|
||||||
|
std::size_t start = 0;
|
||||||
|
while ( start <= text.size() ) {
|
||||||
|
const std::size_t end = text.find( '\n', start );
|
||||||
|
lines.emplace_back( text.substr( start, end == std::string_view::npos ? text.size() - start
|
||||||
|
: end - start ) );
|
||||||
|
if ( end == std::string_view::npos )
|
||||||
|
break;
|
||||||
|
start = end + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace GitDiffDetail
|
||||||
|
|
||||||
|
void normalizeGitDiffText( std::string& text ) {
|
||||||
|
const std::size_t firstCarriageReturn = text.find( '\r' );
|
||||||
|
if ( firstCarriageReturn == std::string::npos )
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::size_t write = firstCarriageReturn;
|
||||||
|
for ( std::size_t read = firstCarriageReturn; read < text.size(); ++read ) {
|
||||||
|
if ( text[read] != '\r' ) {
|
||||||
|
text[write++] = text[read];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
text[write++] = '\n';
|
||||||
|
if ( read + 1 < text.size() && text[read + 1] == '\n' )
|
||||||
|
++read;
|
||||||
|
}
|
||||||
|
text.resize( write );
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputedGitLineDiff computeGitLineDiff( std::string_view baseline, std::string_view current ) {
|
||||||
|
const std::size_t baselineLineCount = GitDiffDetail::lineCount( baseline );
|
||||||
|
const std::size_t currentLineCount = GitDiffDetail::lineCount( current );
|
||||||
|
std::vector<std::string_view> lineStorage;
|
||||||
|
lineStorage.reserve( baselineLineCount + currentLineCount );
|
||||||
|
GitDiffDetail::appendLines( baseline, lineStorage );
|
||||||
|
GitDiffDetail::appendLines( current, lineStorage );
|
||||||
|
|
||||||
|
static constexpr std::string_view emptyLine;
|
||||||
|
const std::string_view* lineData = lineStorage.empty() ? &emptyLine : lineStorage.data();
|
||||||
|
const GitDiffDetail::LineSequence baselineLines( lineData, baselineLineCount );
|
||||||
|
const GitDiffDetail::LineSequence currentLines(
|
||||||
|
baselineLineCount != 0 ? lineData + baselineLineCount : lineData, currentLineCount );
|
||||||
|
|
||||||
|
dtl::Diff<std::string_view, GitDiffDetail::LineSequence> diff( baselineLines, currentLines );
|
||||||
|
diff.compose();
|
||||||
|
GitDiffDetail::LineDiffBuilder builder( currentLineCount );
|
||||||
|
diff.storeSES<GitDiffDetail::LineDiffBuilder, GitDiffDetail::StoreLineDiff>( builder );
|
||||||
|
return builder.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ecode
|
||||||
29
src/tools/ecode/plugins/git/gitdiff.hpp
Normal file
29
src/tools/ecode/plugins/git/gitdiff.hpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef ECODE_GITDIFF_HPP
|
||||||
|
#define ECODE_GITDIFF_HPP
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ecode {
|
||||||
|
|
||||||
|
enum class GitLineChange : std::uint8_t { None, Added, Modified };
|
||||||
|
|
||||||
|
struct GitLineDecoration {
|
||||||
|
GitLineChange change{ GitLineChange::None };
|
||||||
|
bool deletedBefore{ false };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ComputedGitLineDiff {
|
||||||
|
std::vector<GitLineDecoration> lines;
|
||||||
|
bool deletedAtEOF{ false };
|
||||||
|
};
|
||||||
|
|
||||||
|
void normalizeGitDiffText( std::string& text );
|
||||||
|
|
||||||
|
ComputedGitLineDiff computeGitLineDiff( std::string_view baseline, std::string_view current );
|
||||||
|
|
||||||
|
} // namespace ecode
|
||||||
|
|
||||||
|
#endif // ECODE_GITDIFF_HPP
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
#include "githistorymodel.hpp"
|
#include "githistorymodel.hpp"
|
||||||
#include "githistorytreeview.hpp"
|
#include "githistorytreeview.hpp"
|
||||||
#include "gitstatusmodel.hpp"
|
#include "gitstatusmodel.hpp"
|
||||||
|
#include <eepp/graphics/globalbatchrenderer.hpp>
|
||||||
#include <eepp/graphics/image.hpp>
|
#include <eepp/graphics/image.hpp>
|
||||||
#include <eepp/graphics/primitives.hpp>
|
|
||||||
#include <eepp/scene/scenemanager.hpp>
|
#include <eepp/scene/scenemanager.hpp>
|
||||||
#include <eepp/system/base64.hpp>
|
#include <eepp/system/base64.hpp>
|
||||||
#include <eepp/system/filesystem.hpp>
|
#include <eepp/system/filesystem.hpp>
|
||||||
@@ -131,6 +131,19 @@ void GitPlugin::registerSettings( SettingsPage& page ) {
|
|||||||
i18n( "git_filetree_highlight_changes_desc",
|
i18n( "git_filetree_highlight_changes_desc",
|
||||||
"Highlight files with Git changes in the file tree." ),
|
"Highlight files with Git changes in the file tree." ),
|
||||||
true );
|
true );
|
||||||
|
page.addBool( "diff-gutter", "/config/diff_gutter",
|
||||||
|
i18n( "git_diff_gutter", "Show Git Diff Gutter" ),
|
||||||
|
i18n( "git_diff_gutter_desc",
|
||||||
|
"Show added, modified and deleted line indicators in the editor gutter." ),
|
||||||
|
DEFAULT_DIFF_GUTTER_ENABLED );
|
||||||
|
page.addText( "diff-gutter-debounce-delay", "/config/diff_gutter_debounce_delay",
|
||||||
|
i18n( "git_diff_gutter_debounce_delay", "Git Diff Gutter Update Delay" ),
|
||||||
|
i18n( "git_diff_gutter_debounce_delay_desc",
|
||||||
|
"How long to wait after editing before updating Git diff indicators." ),
|
||||||
|
mDiffGutterDebounceDelay.toString(), []( const std::string& text ) {
|
||||||
|
Time value;
|
||||||
|
return SettingsPage::parseNonNegativeSettingsTime( text, value );
|
||||||
|
} );
|
||||||
page.addText( "filetree-highlight-style-color", "/config/filetree_highlight_style_color",
|
page.addText( "filetree-highlight-style-color", "/config/filetree_highlight_style_color",
|
||||||
i18n( "git_filetree_highlight_style_color", "File Tree Highlight Color" ),
|
i18n( "git_filetree_highlight_style_color", "File Tree Highlight Color" ),
|
||||||
i18n( "git_filetree_highlight_style_color_desc",
|
i18n( "git_filetree_highlight_style_color_desc",
|
||||||
@@ -152,6 +165,34 @@ void GitPlugin::registerSettings( SettingsPage& page ) {
|
|||||||
|
|
||||||
static constexpr auto DEFAULT_HIGHLIGHT_COLOR = "var(--font-highlight)"sv;
|
static constexpr auto DEFAULT_HIGHLIGHT_COLOR = "var(--font-highlight)"sv;
|
||||||
static constexpr auto GIT_STATUS_UPDATE_TAG = String::hash( "git::status-update" );
|
static constexpr auto GIT_STATUS_UPDATE_TAG = String::hash( "git::status-update" );
|
||||||
|
static constexpr auto GIT_DIFF_BASELINE_DEBOUNCE_DELAY = Milliseconds( 100 );
|
||||||
|
static constexpr auto GIT_DIFF_BASELINE_UPDATE_TAG =
|
||||||
|
String::hash( "GitPlugin::diff-baseline-update" );
|
||||||
|
static constexpr Float GIT_DIFF_GUTTER_WIDTH_DP = 3;
|
||||||
|
|
||||||
|
static Action::UniqueID getDiffGutterDebounceTag( TextDocument* doc ) {
|
||||||
|
return hashCombine( String::hash( "GitPlugin::diff-gutter-" ),
|
||||||
|
reinterpret_cast<Action::UniqueID>( doc ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::shared_ptr<const std::string>& emptyGitDiffBaseline() {
|
||||||
|
static const auto baseline = std::make_shared<const std::string>();
|
||||||
|
return baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool gitHeadMetadataChanged( const FileInfo& file ) {
|
||||||
|
const std::string& path = file.getFilepath();
|
||||||
|
const std::string& name = file.getFileName();
|
||||||
|
if ( name == "HEAD" || name == "packed-refs" )
|
||||||
|
return true;
|
||||||
|
return path.find( "/refs/" ) != std::string::npos ||
|
||||||
|
path.find( "/reftable/" ) != std::string::npos
|
||||||
|
#if EE_PLATFORM == EE_PLATFORM_WIN
|
||||||
|
|| path.find( "\\refs\\" ) != std::string::npos ||
|
||||||
|
path.find( "\\reftable\\" ) != std::string::npos
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
static std::string writeGitBlobTempFile( const std::string& contents,
|
static std::string writeGitBlobTempFile( const std::string& contents,
|
||||||
const std::string& sourceFilePath ) {
|
const std::string& sourceFilePath ) {
|
||||||
@@ -258,11 +299,18 @@ void GitPlugin::unregisterEditors() {
|
|||||||
endModelStyler();
|
endModelStyler();
|
||||||
if ( getUISceneNode() )
|
if ( getUISceneNode() )
|
||||||
getUISceneNode()->removeActionsByTag( GIT_STATUS_UPDATE_TAG );
|
getUISceneNode()->removeActionsByTag( GIT_STATUS_UPDATE_TAG );
|
||||||
|
if ( mDiffGutterEnabled && getUISceneNode() )
|
||||||
|
getUISceneNode()->removeActionsByTag( GIT_DIFF_BASELINE_UPDATE_TAG );
|
||||||
if ( mStatusBar && mRepositionCbId ) {
|
if ( mStatusBar && mRepositionCbId ) {
|
||||||
mStatusBar->removeEventListener( mRepositionCbId );
|
mStatusBar->removeEventListener( mRepositionCbId );
|
||||||
mRepositionCbId = 0;
|
mRepositionCbId = 0;
|
||||||
}
|
}
|
||||||
|
if ( mDiffGutterEnabled && getUISceneNode() ) {
|
||||||
|
for ( const auto& [doc, _] : mDocumentDiffs )
|
||||||
|
getUISceneNode()->removeActionsByTag( getDiffGutterDebounceTag( doc ) );
|
||||||
|
}
|
||||||
PluginBase::unregisterEditors();
|
PluginBase::unregisterEditors();
|
||||||
|
mDocumentDiffs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GitPlugin::onSaveState( IniFile* state ) {
|
void GitPlugin::onSaveState( IniFile* state ) {
|
||||||
@@ -309,6 +357,11 @@ void GitPlugin::load( PluginManager* pluginManager ) {
|
|||||||
|
|
||||||
bool updateConfigFile = false;
|
bool updateConfigFile = false;
|
||||||
|
|
||||||
|
if ( !j.contains( "config" ) || !j["config"].is_object() ) {
|
||||||
|
j["config"] = json::object();
|
||||||
|
updateConfigFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
if ( j.contains( "config" ) ) {
|
if ( j.contains( "config" ) ) {
|
||||||
auto& config = j["config"];
|
auto& config = j["config"];
|
||||||
|
|
||||||
@@ -333,6 +386,22 @@ void GitPlugin::load( PluginManager* pluginManager ) {
|
|||||||
updateConfigFile = true;
|
updateConfigFile = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( config.contains( "diff_gutter" ) && config["diff_gutter"].is_boolean() )
|
||||||
|
mDiffGutterEnabled = config["diff_gutter"].get<bool>();
|
||||||
|
else {
|
||||||
|
mDiffGutterEnabled = DEFAULT_DIFF_GUTTER_ENABLED;
|
||||||
|
config["diff_gutter"] = mDiffGutterEnabled;
|
||||||
|
updateConfigFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( config.contains( "diff_gutter_debounce_delay" ) ) {
|
||||||
|
mDiffGutterDebounceDelay =
|
||||||
|
Time::fromString( config.value( "diff_gutter_debounce_delay", "750ms" ) );
|
||||||
|
} else {
|
||||||
|
config["diff_gutter_debounce_delay"] = mDiffGutterDebounceDelay.toString();
|
||||||
|
updateConfigFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
if ( config.contains( "filetree_highlight_style_color" ) ) {
|
if ( config.contains( "filetree_highlight_style_color" ) ) {
|
||||||
mHighlightStyleColor =
|
mHighlightStyleColor =
|
||||||
config.value( "filetree_highlight_style_color", DEFAULT_HIGHLIGHT_COLOR );
|
config.value( "filetree_highlight_style_color", DEFAULT_HIGHLIGHT_COLOR );
|
||||||
@@ -412,6 +481,8 @@ void GitPlugin::load( PluginManager* pluginManager ) {
|
|||||||
mReady = true;
|
mReady = true;
|
||||||
fireReadyCbs();
|
fireReadyCbs();
|
||||||
setReady( clock.getElapsedTime() );
|
setReady( clock.getElapsedTime() );
|
||||||
|
if ( mDiffGutterEnabled && getUISceneNode() )
|
||||||
|
mLifetime.weakHandle().run( []( GitPlugin* plugin ) { plugin->initializeDiffGutter(); } );
|
||||||
}
|
}
|
||||||
|
|
||||||
void GitPlugin::initModelStyler() {
|
void GitPlugin::initModelStyler() {
|
||||||
@@ -538,14 +609,13 @@ void GitPlugin::updateStatusBarSync() {
|
|||||||
? i18n( "git_operation_ready_commit", "%s · Ready to commit" )
|
? i18n( "git_operation_ready_commit", "%s · Ready to commit" )
|
||||||
: i18n( "git_operation_ready", "%s · Ready to continue" ) )
|
: i18n( "git_operation_ready", "%s · Ready to continue" ) )
|
||||||
.toUtf8(),
|
.toUtf8(),
|
||||||
operation.toUtf8().c_str() )
|
operation.toUtf8() )
|
||||||
: String::format(
|
: String::format(
|
||||||
( conflictSession->files.size() == 1
|
( conflictSession->files.size() == 1
|
||||||
? i18n( "git_operation_conflict", "%s · %d conflict" )
|
? i18n( "git_operation_conflict", "%s · %d conflict" )
|
||||||
: i18n( "git_operation_conflicts", "%s · %d conflicts" ) )
|
: i18n( "git_operation_conflicts", "%s · %d conflicts" ) )
|
||||||
.toUtf8(),
|
.toUtf8(),
|
||||||
operation.toUtf8().c_str(),
|
operation.toUtf8(), static_cast<int>( conflictSession->files.size() ) ) );
|
||||||
static_cast<int>( conflictSession->files.size() ) ) );
|
|
||||||
const bool canContinue = conflictSession->files.empty() &&
|
const bool canContinue = conflictSession->files.empty() &&
|
||||||
conflictSession->operation != Git::GitOperation::None &&
|
conflictSession->operation != Git::GitOperation::None &&
|
||||||
conflictSession->operation != Git::GitOperation::StashApply;
|
conflictSession->operation != Git::GitOperation::StashApply;
|
||||||
@@ -621,7 +691,7 @@ void GitPlugin::updateStatusBarSync() {
|
|||||||
Lock l( mGitStatusMutex );
|
Lock l( mGitStatusMutex );
|
||||||
text = mStatusBarDisplayModifications &&
|
text = mStatusBarDisplayModifications &&
|
||||||
( mGitStatus.totalInserts || mGitStatus.totalDeletions )
|
( mGitStatus.totalInserts || mGitStatus.totalDeletions )
|
||||||
? String::format( "%s (+%d / -%d)", gitBranch().c_str(), mGitStatus.totalInserts,
|
? String::format( "%s (+%d / -%d)", gitBranch(), mGitStatus.totalInserts,
|
||||||
mGitStatus.totalDeletions )
|
mGitStatus.totalDeletions )
|
||||||
: gitBranch();
|
: gitBranch();
|
||||||
}
|
}
|
||||||
@@ -737,6 +807,7 @@ void GitPlugin::updateStatus( bool force ) {
|
|||||||
|
|
||||||
lifetime.run(
|
lifetime.run(
|
||||||
[conflictStates = std::move( conflictStates )]( GitPlugin* plugin ) mutable {
|
[conflictStates = std::move( conflictStates )]( GitPlugin* plugin ) mutable {
|
||||||
|
plugin->resolveAddedDocumentDiffs();
|
||||||
const bool selectStatusPanel = plugin->updateConflictSessions( conflictStates );
|
const bool selectStatusPanel = plugin->updateConflictSessions( conflictStates );
|
||||||
plugin->updateStatusBarSync();
|
plugin->updateStatusBarSync();
|
||||||
if ( plugin->mHistoryLoaded && plugin->mHistoryModel ) {
|
if ( plugin->mHistoryLoaded && plugin->mHistoryModel ) {
|
||||||
@@ -795,6 +866,8 @@ PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) {
|
|||||||
Lock l( mRepoMutex );
|
Lock l( mRepoMutex );
|
||||||
mProjectPath = mRepoSelected = mGit->getProjectPath();
|
mProjectPath = mRepoSelected = mGit->getProjectPath();
|
||||||
}
|
}
|
||||||
|
if ( mDiffGutterEnabled )
|
||||||
|
invalidateAllDocumentDiffBaselines();
|
||||||
|
|
||||||
{
|
{
|
||||||
Lock l( mReposMutex );
|
Lock l( mReposMutex );
|
||||||
@@ -827,6 +900,8 @@ PluginRequestHandle GitPlugin::processMessage( const PluginMessage& msg ) {
|
|||||||
}
|
}
|
||||||
case ecode::PluginMessageType::UIThemeReloaded: {
|
case ecode::PluginMessageType::UIThemeReloaded: {
|
||||||
mStatusCustomTokenizer.reset();
|
mStatusCustomTokenizer.reset();
|
||||||
|
if ( mDiffGutterEnabled )
|
||||||
|
updateDiffGutterColors();
|
||||||
updateUINow( true );
|
updateUINow( true );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -853,6 +928,17 @@ void GitPlugin::onFileSystemEvent( const FileEvent& ev, const FileInfo& file ) {
|
|||||||
if ( inGitFolder && file.getExtension() == "lock" )
|
if ( inGitFolder && file.getExtension() == "lock" )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Ignore index, object and log churn: only reference metadata can change the HEAD baseline.
|
||||||
|
if ( mDiffGutterEnabled && inGitFolder && gitHeadMetadataChanged( file ) && getUISceneNode() ) {
|
||||||
|
const auto lifetime = mLifetime.weakHandle();
|
||||||
|
getUISceneNode()->debounce(
|
||||||
|
[lifetime] {
|
||||||
|
lifetime.run(
|
||||||
|
[]( GitPlugin* plugin ) { plugin->invalidateAllDocumentDiffBaselines(); } );
|
||||||
|
},
|
||||||
|
GIT_DIFF_BASELINE_DEBOUNCE_DELAY, GIT_DIFF_BASELINE_UPDATE_TAG );
|
||||||
|
}
|
||||||
|
|
||||||
updateUI();
|
updateUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,12 +962,11 @@ void GitPlugin::displayTooltip( UICodeEditor* editor, const Git::Blame& blame,
|
|||||||
|
|
||||||
String str( blame.error.empty()
|
String str( blame.error.empty()
|
||||||
? String::format( "%s: %s (%s)\n%s: %s (%s)\n%s: %s\n\n%s",
|
? String::format( "%s: %s (%s)\n%s: %s (%s)\n%s: %s\n\n%s",
|
||||||
i18n( "commit", "Commit" ).toUtf8().c_str(),
|
i18n( "commit", "Commit" ).toUtf8(), blame.commitHash,
|
||||||
blame.commitHash.c_str(), blame.commitShortHash.c_str(),
|
blame.commitShortHash, i18n( "author", "Author" ).toUtf8(),
|
||||||
i18n( "author", "Author" ).toUtf8().c_str(),
|
blame.author, blame.authorEmail,
|
||||||
blame.author.c_str(), blame.authorEmail.c_str(),
|
i18n( "date", "Date" ).toUtf8(), blame.date,
|
||||||
i18n( "date", "Date" ).toUtf8().c_str(), blame.date.c_str(),
|
blame.commitMessage )
|
||||||
blame.commitMessage.c_str() )
|
|
||||||
: blame.error );
|
: blame.error );
|
||||||
|
|
||||||
Text::hardWrapText( str, PixelDensity::dpToPx( 400 ), tooltip->getFontStyleConfig(),
|
Text::hardWrapText( str, PixelDensity::dpToPx( 400 ), tooltip->getFontStyleConfig(),
|
||||||
@@ -965,6 +1050,35 @@ void GitPlugin::onRegisterListeners( UICodeEditor* editor, std::vector<Uint32>&
|
|||||||
if ( mTooltipInfoShowing )
|
if ( mTooltipInfoShowing )
|
||||||
hideTooltip( editor );
|
hideTooltip( editor );
|
||||||
} ) );
|
} ) );
|
||||||
|
if ( !mDiffGutterEnabled )
|
||||||
|
return;
|
||||||
|
listeners.push_back( editor->on( Event::OnTextChanged, [this, editor]( const Event* ) {
|
||||||
|
if ( !editor->hasDocument() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
TextDocument* doc = editor->getDocumentRef().get();
|
||||||
|
auto it = mDocumentDiffs.find( doc );
|
||||||
|
if ( it == mDocumentDiffs.end() ) {
|
||||||
|
ensureDocumentDiff( doc );
|
||||||
|
it = mDocumentDiffs.find( doc );
|
||||||
|
if ( it == mDocumentDiffs.end() )
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
++it->second.generation;
|
||||||
|
if ( it->second.baselineState != GitBaselineState::Loaded || !getUISceneNode() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto lifetime = mLifetime.weakHandle();
|
||||||
|
getUISceneNode()->debounce(
|
||||||
|
[lifetime, doc] {
|
||||||
|
lifetime.run( [doc]( GitPlugin* plugin ) { plugin->scheduleDocumentDiff( doc ); } );
|
||||||
|
},
|
||||||
|
mDiffGutterDebounceDelay, getDiffGutterDebounceTag( doc ) );
|
||||||
|
} ) );
|
||||||
|
listeners.push_back( editor->on( Event::OnDocumentMoved, [this]( const Event* event ) {
|
||||||
|
const auto* docEvent = static_cast<const DocEvent*>( event );
|
||||||
|
ensureDocumentDiff( docEvent->getDoc() );
|
||||||
|
} ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
Color GitPlugin::getVarColor( const std::string& var ) {
|
Color GitPlugin::getVarColor( const std::string& var ) {
|
||||||
@@ -972,6 +1086,399 @@ Color GitPlugin::getVarColor( const std::string& var ) {
|
|||||||
getUISceneNode()->getRoot()->getUIStyle()->getVariable( var ).getValue() );
|
getUISceneNode()->getRoot()->getUIStyle()->getVariable( var ).getValue() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GitPlugin::updateDiffGutterColors() {
|
||||||
|
const auto resolve = [this]( const char* variable, Color fallback ) {
|
||||||
|
Color color = getVarColor( variable );
|
||||||
|
return color.a == 0 ? fallback : Color( color, 80 );
|
||||||
|
};
|
||||||
|
mDiffAddedColor = resolve( "--theme-success", Color( 0, 150, 32, 80 ) );
|
||||||
|
mDiffModifiedColor = resolve( "--theme-warning", Color( 220, 170, 0, 80 ) );
|
||||||
|
mDiffDeletedColor = resolve( "--theme-error", Color( 180, 0, 32, 80 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
const Color& GitPlugin::getDiffGutterColor( GitLineChange change, bool deleted ) const {
|
||||||
|
if ( deleted )
|
||||||
|
return mDiffDeletedColor;
|
||||||
|
return change == GitLineChange::Added ? mDiffAddedColor : mDiffModifiedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::initializeDiffGutter() {
|
||||||
|
if ( !mDiffGutterEnabled || !mGit || !mGitFound )
|
||||||
|
return;
|
||||||
|
updateDiffGutterColors();
|
||||||
|
|
||||||
|
for ( const auto& [editor, doc] : mEditorDocs ) {
|
||||||
|
if ( editor && editor->hasDocument() ) {
|
||||||
|
editor->registerGutterSpace( this, PixelDensity::dpToPxI( GIT_DIFF_GUTTER_WIDTH_DP ),
|
||||||
|
0 );
|
||||||
|
ensureDocumentDiff( doc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::ensureDocumentDiff( TextDocument* doc ) {
|
||||||
|
if ( !mDiffGutterEnabled || !mGit || !mGitFound || !doc )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( !doc->hasFilepath() || doc->getFilePath().empty() ) {
|
||||||
|
auto found = mDocumentDiffs.find( doc );
|
||||||
|
if ( found != mDocumentDiffs.end() ) {
|
||||||
|
if ( getUISceneNode() )
|
||||||
|
getUISceneNode()->removeActionsByTag( getDiffGutterDebounceTag( doc ) );
|
||||||
|
mDocumentDiffs.erase( found );
|
||||||
|
redrawDocumentDiff( doc );
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string& path = doc->getFilePath();
|
||||||
|
|
||||||
|
auto found = mDocumentDiffs.find( doc );
|
||||||
|
if ( found != mDocumentDiffs.end() && found->second.path == path ) {
|
||||||
|
if ( found->second.baselineState == GitBaselineState::Pending )
|
||||||
|
loadDocumentDiffBaseline( doc );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [it, inserted] = mDocumentDiffs.try_emplace( doc );
|
||||||
|
auto& state = it->second;
|
||||||
|
if ( inserted )
|
||||||
|
state.identity = ++mNextDocumentDiffIdentity;
|
||||||
|
else
|
||||||
|
resetDocumentDiff( doc, state );
|
||||||
|
state.path = path;
|
||||||
|
state.repoPath = mGit->repoPath( path );
|
||||||
|
|
||||||
|
if ( state.baselineState == GitBaselineState::Pending )
|
||||||
|
loadDocumentDiffBaseline( doc );
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::loadDocumentDiffBaseline( TextDocument* doc ) {
|
||||||
|
auto it = mDocumentDiffs.find( doc );
|
||||||
|
if ( it == mDocumentDiffs.end() || it->second.baselineState != GitBaselineState::Pending ||
|
||||||
|
!mGit || !mGitFound )
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& state = it->second;
|
||||||
|
state.baselineState = GitBaselineState::Loading;
|
||||||
|
const Uint64 baselineGeneration = ++state.baselineGeneration;
|
||||||
|
const Uint32 documentDiffIdentity = state.identity;
|
||||||
|
const auto git = mGit;
|
||||||
|
const auto lifetime = mLifetime.weakHandle();
|
||||||
|
mThreadPool->run( [git, path = state.path, repoPath = state.repoPath, doc, baselineGeneration,
|
||||||
|
documentDiffIdentity, lifetime]() mutable {
|
||||||
|
auto result = git->showFile( path, "HEAD", repoPath );
|
||||||
|
if ( result.success() )
|
||||||
|
normalizeGitDiffText( result.result );
|
||||||
|
lifetime.run( [doc, baselineGeneration, documentDiffIdentity,
|
||||||
|
result = std::move( result )]( GitPlugin* plugin ) mutable {
|
||||||
|
auto it = plugin->mDocumentDiffs.find( doc );
|
||||||
|
if ( it == plugin->mDocumentDiffs.end() ||
|
||||||
|
it->second.identity != documentDiffIdentity ||
|
||||||
|
it->second.baselineGeneration != baselineGeneration )
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& state = it->second;
|
||||||
|
const bool addedFile = result.fail() && plugin->isDocumentAddedInGit( state );
|
||||||
|
state.baselineState = result.success() || addedFile ? GitBaselineState::Loaded
|
||||||
|
: GitBaselineState::Unavailable;
|
||||||
|
state.baseline.reset();
|
||||||
|
state.lines.clear();
|
||||||
|
state.deletedAtEOF = false;
|
||||||
|
++state.generation;
|
||||||
|
if ( state.baselineState == GitBaselineState::Loaded ) {
|
||||||
|
state.baseline =
|
||||||
|
result.success()
|
||||||
|
? std::make_shared<const std::string>( std::move( result.result ) )
|
||||||
|
: emptyGitDiffBaseline();
|
||||||
|
plugin->scheduleDocumentDiff( doc );
|
||||||
|
} else {
|
||||||
|
plugin->redrawDocumentDiff( doc );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::scheduleDocumentDiff( TextDocument* doc ) {
|
||||||
|
if ( !mDiffGutterEnabled )
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto it = mDocumentDiffs.find( doc );
|
||||||
|
if ( it == mDocumentDiffs.end() || it->second.baselineState != GitBaselineState::Loaded ||
|
||||||
|
!it->second.baseline )
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& state = it->second;
|
||||||
|
if ( state.diffRunning ) {
|
||||||
|
state.diffPending = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.diffRunning = true;
|
||||||
|
state.diffPending = false;
|
||||||
|
const Uint64 generation = state.generation;
|
||||||
|
const Uint32 documentDiffIdentity = state.identity;
|
||||||
|
auto baseline = state.baseline;
|
||||||
|
std::string current = std::move( mDiffSnapshotBuffer );
|
||||||
|
doc->toUtf8String( current );
|
||||||
|
const auto lifetime = mLifetime.weakHandle();
|
||||||
|
mThreadPool->run( [baseline = std::move( baseline ), current = std::move( current ), doc,
|
||||||
|
generation, documentDiffIdentity, lifetime]() mutable {
|
||||||
|
ComputedGitLineDiff result;
|
||||||
|
if ( *baseline != current )
|
||||||
|
result = computeGitLineDiff( *baseline, current );
|
||||||
|
lifetime.run( [doc, generation, documentDiffIdentity, current = std::move( current ),
|
||||||
|
result = std::move( result )]( GitPlugin* plugin ) mutable {
|
||||||
|
if ( current.capacity() > plugin->mDiffSnapshotBuffer.capacity() )
|
||||||
|
plugin->mDiffSnapshotBuffer = std::move( current );
|
||||||
|
auto it = plugin->mDocumentDiffs.find( doc );
|
||||||
|
if ( it == plugin->mDocumentDiffs.end() || it->second.identity != documentDiffIdentity )
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto& state = it->second;
|
||||||
|
state.diffRunning = false;
|
||||||
|
if ( state.generation == generation &&
|
||||||
|
state.baselineState == GitBaselineState::Loaded ) {
|
||||||
|
state.lines = std::move( result.lines );
|
||||||
|
state.deletedAtEOF = result.deletedAtEOF;
|
||||||
|
plugin->redrawDocumentDiff( doc );
|
||||||
|
}
|
||||||
|
if ( state.diffPending && state.baselineState == GitBaselineState::Loaded ) {
|
||||||
|
state.diffPending = false;
|
||||||
|
plugin->scheduleDocumentDiff( doc );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GitPlugin::isDocumentAddedInGit( const GitDocumentDiff& state ) {
|
||||||
|
std::string relativePath = state.path;
|
||||||
|
{
|
||||||
|
Lock l( mRepoMutex );
|
||||||
|
if ( mProjectPath.empty() )
|
||||||
|
return false;
|
||||||
|
FileSystem::filePathRemoveBasePath( mProjectPath, relativePath );
|
||||||
|
}
|
||||||
|
|
||||||
|
Lock l( mGitStatusMutex );
|
||||||
|
for ( const auto& [_, files] : mGitStatus.files ) {
|
||||||
|
for ( const auto& file : files ) {
|
||||||
|
if ( file.file == relativePath &&
|
||||||
|
( file.report.status == Git::GitStatus::Index_Added ||
|
||||||
|
file.report.status == Git::GitStatus::WorkingTree_IntentToAdd ||
|
||||||
|
file.report.status == Git::GitStatus::Untracked ) )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::resolveAddedDocumentDiffs() {
|
||||||
|
if ( !mDiffGutterEnabled )
|
||||||
|
return;
|
||||||
|
for ( auto& [doc, state] : mDocumentDiffs ) {
|
||||||
|
if ( state.baselineState != GitBaselineState::Unavailable ||
|
||||||
|
!isDocumentAddedInGit( state ) )
|
||||||
|
continue;
|
||||||
|
state.baseline = emptyGitDiffBaseline();
|
||||||
|
state.baselineState = GitBaselineState::Loaded;
|
||||||
|
++state.generation;
|
||||||
|
scheduleDocumentDiff( doc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::resetDocumentDiff( TextDocument* doc, GitDocumentDiff& state ) {
|
||||||
|
if ( getUISceneNode() )
|
||||||
|
getUISceneNode()->removeActionsByTag( getDiffGutterDebounceTag( doc ) );
|
||||||
|
++state.baselineGeneration;
|
||||||
|
++state.generation;
|
||||||
|
state.baseline.reset();
|
||||||
|
state.lines.clear();
|
||||||
|
state.deletedAtEOF = false;
|
||||||
|
state.baselineState = GitBaselineState::Pending;
|
||||||
|
state.diffPending = false;
|
||||||
|
redrawDocumentDiff( doc );
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::invalidateAllDocumentDiffBaselines() {
|
||||||
|
if ( !mDiffGutterEnabled )
|
||||||
|
return;
|
||||||
|
for ( auto& [doc, state] : mDocumentDiffs ) {
|
||||||
|
state.repoPath = mGit ? mGit->repoPath( state.path ) : std::string{};
|
||||||
|
resetDocumentDiff( doc, state );
|
||||||
|
}
|
||||||
|
for ( const auto& [doc, _] : mDocumentDiffs )
|
||||||
|
loadDocumentDiffBaseline( doc );
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::redrawDocumentDiff( TextDocument* doc ) {
|
||||||
|
for ( const auto& [editor, editorDoc] : mEditorDocs ) {
|
||||||
|
if ( editorDoc == doc && editor )
|
||||||
|
editor->invalidateDraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::onDocumentLoaded( TextDocument* doc ) {
|
||||||
|
if ( mDiffGutterEnabled )
|
||||||
|
ensureDocumentDiff( doc );
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::onDocumentChanged( UICodeEditor* editor, TextDocument* oldDoc ) {
|
||||||
|
if ( !mDiffGutterEnabled )
|
||||||
|
return;
|
||||||
|
if ( oldDoc ) {
|
||||||
|
bool stillRegistered = false;
|
||||||
|
for ( const auto& [_, doc] : mEditorDocs ) {
|
||||||
|
if ( doc == oldDoc ) {
|
||||||
|
stillRegistered = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( !stillRegistered ) {
|
||||||
|
if ( getUISceneNode() )
|
||||||
|
getUISceneNode()->removeActionsByTag( getDiffGutterDebounceTag( oldDoc ) );
|
||||||
|
mDocumentDiffs.erase( oldDoc );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( editor && editor->hasDocument() )
|
||||||
|
ensureDocumentDiff( editor->getDocumentRef().get() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::onUnregisterDocument( TextDocument* doc ) {
|
||||||
|
if ( mDiffGutterEnabled ) {
|
||||||
|
if ( getUISceneNode() )
|
||||||
|
getUISceneNode()->removeActionsByTag( getDiffGutterDebounceTag( doc ) );
|
||||||
|
mDocumentDiffs.erase( doc );
|
||||||
|
}
|
||||||
|
PluginBase::onUnregisterDocument( doc );
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::onRegisterEditor( UICodeEditor* editor ) {
|
||||||
|
if ( mDiffGutterEnabled ) {
|
||||||
|
editor->registerGutterSpace( this, PixelDensity::dpToPxI( GIT_DIFF_GUTTER_WIDTH_DP ), 0 );
|
||||||
|
if ( editor->hasDocument() )
|
||||||
|
ensureDocumentDiff( editor->getDocumentRef().get() );
|
||||||
|
}
|
||||||
|
PluginBase::onRegisterEditor( editor );
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::onUnregisterEditor( UICodeEditor* editor ) {
|
||||||
|
if ( mDiffGutterEnabled ) {
|
||||||
|
editor->unregisterGutterSpace( this );
|
||||||
|
editor->invalidateDraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::drawGutter( UICodeEditor* editor, const Int64& index, const Vector2f& screenStart,
|
||||||
|
const Float& lineHeight, const Float& gutterWidth,
|
||||||
|
const Float& /*fontSize*/ ) {
|
||||||
|
if ( !mDiffGutterEnabled || !editor || !editor->hasDocument() || index < 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
TextDocument* doc = editor->getDocumentRef().get();
|
||||||
|
auto it = mDocumentDiffs.find( doc );
|
||||||
|
if ( it == mDocumentDiffs.end() || it->second.baselineState != GitBaselineState::Loaded )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto& state = it->second;
|
||||||
|
const size_t lineIndex = static_cast<size_t>( index );
|
||||||
|
if ( lineIndex >= state.lines.size() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const GitLineDecoration& decoration = state.lines[lineIndex];
|
||||||
|
const bool deletedAtEOF = state.deletedAtEOF && lineIndex + 1 == state.lines.size();
|
||||||
|
if ( decoration.change == GitLineChange::None && !decoration.deletedBefore && !deletedAtEOF )
|
||||||
|
return;
|
||||||
|
|
||||||
|
GlobalBatchRenderer* batchRenderer = GlobalBatchRenderer::instance();
|
||||||
|
batchRenderer->setTexture( nullptr );
|
||||||
|
batchRenderer->setBlendMode( BlendMode::Alpha() );
|
||||||
|
batchRenderer->quadsBegin();
|
||||||
|
if ( decoration.change != GitLineChange::None ) {
|
||||||
|
Color color = getDiffGutterColor( decoration.change );
|
||||||
|
color.blendAlpha( editor->getAlpha() );
|
||||||
|
batchRenderer->quadsSetColor( color );
|
||||||
|
batchRenderer->batchQuad( Rectf( screenStart, { gutterWidth, lineHeight } ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( decoration.deletedBefore || deletedAtEOF ) {
|
||||||
|
Color color( getDiffGutterColor( GitLineChange::None, true ), 255 );
|
||||||
|
color.blendAlpha( editor->getAlpha() );
|
||||||
|
batchRenderer->quadsSetColor( color );
|
||||||
|
const Float markerHeight = PixelDensity::dpToPxI( 2 );
|
||||||
|
const Float markerY = deletedAtEOF && !decoration.deletedBefore
|
||||||
|
? screenStart.y + lineHeight - markerHeight
|
||||||
|
: screenStart.y;
|
||||||
|
batchRenderer->batchQuad(
|
||||||
|
Rectf( { screenStart.x, markerY }, { gutterWidth, markerHeight } ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GitPlugin::minimapDrawBefore( UICodeEditor* editor, const DocumentLineRange& docLineRange,
|
||||||
|
const DocumentViewLineRange& docViewRange,
|
||||||
|
const Vector2f& linePos, const Vector2f& /*lineSize*/,
|
||||||
|
const Float& /*charWidth*/, const Float& gutterWidth,
|
||||||
|
const DrawTextRangesFn& /*drawTextRanges*/ ) {
|
||||||
|
if ( !mDiffGutterEnabled || !editor || !editor->hasDocument() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
TextDocument* doc = editor->getDocumentRef().get();
|
||||||
|
auto it = mDocumentDiffs.find( doc );
|
||||||
|
if ( it == mDocumentDiffs.end() || it->second.baselineState != GitBaselineState::Loaded )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Int64 firstVisibleIndex = static_cast<Int64>( docViewRange.first );
|
||||||
|
const Int64 lastVisibleIndex = static_cast<Int64>( docViewRange.second );
|
||||||
|
if ( firstVisibleIndex > lastVisibleIndex || docLineRange.first > docLineRange.second )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto& state = it->second;
|
||||||
|
const Float markerWidth =
|
||||||
|
eemin( gutterWidth, static_cast<Float>( PixelDensity::dpToPxI( 2 ) ) );
|
||||||
|
if ( markerWidth <= 0 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Float markerX = linePos.x + gutterWidth - markerWidth;
|
||||||
|
const Float lineSpacing = editor->getMinimapLineSpacing();
|
||||||
|
const Float boundaryHeight =
|
||||||
|
eemin( lineSpacing, static_cast<Float>( PixelDensity::dpToPxI( 1 ) ) );
|
||||||
|
GlobalBatchRenderer* batchRenderer = GlobalBatchRenderer::instance();
|
||||||
|
for ( Int64 visibleIndex = firstVisibleIndex; visibleIndex <= lastVisibleIndex;
|
||||||
|
++visibleIndex ) {
|
||||||
|
const Int64 line = editor->getDocumentView()
|
||||||
|
.getVisibleIndexPosition( static_cast<VisibleIndex>( visibleIndex ) )
|
||||||
|
.line();
|
||||||
|
if ( line < docLineRange.first || line > docLineRange.second || line < 0 )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const size_t lineIndex = static_cast<size_t>( line );
|
||||||
|
if ( lineIndex >= state.lines.size() )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const GitLineDecoration& decoration = state.lines[lineIndex];
|
||||||
|
const bool deletedAtEOF = state.deletedAtEOF && lineIndex + 1 == state.lines.size();
|
||||||
|
if ( decoration.change == GitLineChange::None && !decoration.deletedBefore &&
|
||||||
|
!deletedAtEOF )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const Float markerY = linePos.y + ( visibleIndex - firstVisibleIndex ) * lineSpacing;
|
||||||
|
if ( decoration.change != GitLineChange::None ) {
|
||||||
|
Color color = getDiffGutterColor( decoration.change );
|
||||||
|
color.blendAlpha( editor->getAlpha() );
|
||||||
|
batchRenderer->quadsSetColor( color );
|
||||||
|
batchRenderer->batchQuad( { { markerX, markerY }, { markerWidth, lineSpacing } } );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( decoration.deletedBefore || deletedAtEOF ) {
|
||||||
|
Color color( getDiffGutterColor( GitLineChange::None, true ), 255 );
|
||||||
|
color.blendAlpha( editor->getAlpha() );
|
||||||
|
batchRenderer->quadsSetColor( color );
|
||||||
|
const Float boundaryY = deletedAtEOF && !decoration.deletedBefore
|
||||||
|
? markerY + lineSpacing - boundaryHeight
|
||||||
|
: markerY;
|
||||||
|
batchRenderer->batchQuad( { { markerX, boundaryY }, { markerWidth, boundaryHeight } } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void GitPlugin::blame( UICodeEditor* editor ) {
|
void GitPlugin::blame( UICodeEditor* editor ) {
|
||||||
if ( !mGitFound ) {
|
if ( !mGitFound ) {
|
||||||
editor->setTooltipText(
|
editor->setTooltipText(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "../plugin.hpp"
|
#include "../plugin.hpp"
|
||||||
#include "../pluginmanager.hpp"
|
#include "../pluginmanager.hpp"
|
||||||
#include "git.hpp"
|
#include "git.hpp"
|
||||||
|
#include "gitdiff.hpp"
|
||||||
#include "githistorymodel.hpp"
|
#include "githistorymodel.hpp"
|
||||||
#include <eepp/scene/eventconnection.hpp>
|
#include <eepp/scene/eventconnection.hpp>
|
||||||
#include <eepp/scene/mainthreadlifetime.hpp>
|
#include <eepp/scene/mainthreadlifetime.hpp>
|
||||||
@@ -131,12 +132,15 @@ class GitPlugin : public PluginBase {
|
|||||||
std::string mHighlightStyleColor;
|
std::string mHighlightStyleColor;
|
||||||
|
|
||||||
Time mRefreshFreq{ Seconds( 5 ) };
|
Time mRefreshFreq{ Seconds( 5 ) };
|
||||||
|
Time mDiffGutterDebounceDelay{ Milliseconds( 750 ) };
|
||||||
bool mGitFound{ false };
|
bool mGitFound{ false };
|
||||||
bool mTooltipInfoShowing{ false };
|
bool mTooltipInfoShowing{ false };
|
||||||
bool mStatusBarDisplayBranch{ true };
|
bool mStatusBarDisplayBranch{ true };
|
||||||
bool mStatusBarDisplayModifications{ true };
|
bool mStatusBarDisplayModifications{ true };
|
||||||
bool mStatusRecurseSubmodules{ true };
|
bool mStatusRecurseSubmodules{ true };
|
||||||
bool mFileTreeHighlightChanges{ true };
|
bool mFileTreeHighlightChanges{ true };
|
||||||
|
static constexpr bool DEFAULT_DIFF_GUTTER_ENABLED = false;
|
||||||
|
bool mDiffGutterEnabled{ DEFAULT_DIFF_GUTTER_ENABLED };
|
||||||
bool mOldDontAutoHideOnMouseMove{ false };
|
bool mOldDontAutoHideOnMouseMove{ false };
|
||||||
bool mOldUsingCustomStyling{ false };
|
bool mOldUsingCustomStyling{ false };
|
||||||
bool mInitialized{ false };
|
bool mInitialized{ false };
|
||||||
@@ -276,6 +280,28 @@ class GitPlugin : public PluginBase {
|
|||||||
Uint32 mModelChangedId{ 0 };
|
Uint32 mModelChangedId{ 0 };
|
||||||
Uint32 mModelStylerId{ 0 };
|
Uint32 mModelStylerId{ 0 };
|
||||||
|
|
||||||
|
enum class GitBaselineState : Uint8 { Pending, Loading, Loaded, Unavailable };
|
||||||
|
|
||||||
|
struct GitDocumentDiff {
|
||||||
|
std::string path;
|
||||||
|
std::string repoPath;
|
||||||
|
std::shared_ptr<const std::string> baseline;
|
||||||
|
std::vector<GitLineDecoration> lines;
|
||||||
|
Uint64 generation{ 0 };
|
||||||
|
Uint64 baselineGeneration{ 0 };
|
||||||
|
GitBaselineState baselineState{ GitBaselineState::Pending };
|
||||||
|
bool deletedAtEOF{ false };
|
||||||
|
bool diffRunning{ false };
|
||||||
|
bool diffPending{ false };
|
||||||
|
Uint32 identity{ 0 };
|
||||||
|
};
|
||||||
|
UnorderedMap<TextDocument*, GitDocumentDiff> mDocumentDiffs;
|
||||||
|
std::string mDiffSnapshotBuffer;
|
||||||
|
Uint32 mNextDocumentDiffIdentity{ 0 };
|
||||||
|
Color mDiffAddedColor{ 0, 150, 32, 80 };
|
||||||
|
Color mDiffModifiedColor{ 220, 170, 0, 80 };
|
||||||
|
Color mDiffDeletedColor{ 180, 0, 32, 80 };
|
||||||
|
|
||||||
GitPlugin( PluginManager* pluginManager, bool sync );
|
GitPlugin( PluginManager* pluginManager, bool sync );
|
||||||
|
|
||||||
void load( PluginManager* pluginManager );
|
void load( PluginManager* pluginManager );
|
||||||
@@ -288,8 +314,50 @@ class GitPlugin : public PluginBase {
|
|||||||
|
|
||||||
void onRegisterListeners( UICodeEditor*, std::vector<Uint32>& listeners ) override;
|
void onRegisterListeners( UICodeEditor*, std::vector<Uint32>& listeners ) override;
|
||||||
|
|
||||||
|
void onDocumentLoaded( TextDocument* doc ) override;
|
||||||
|
|
||||||
|
void onDocumentChanged( UICodeEditor*, TextDocument* oldDoc ) override;
|
||||||
|
|
||||||
|
void onUnregisterDocument( TextDocument* doc ) override;
|
||||||
|
|
||||||
|
void onRegisterEditor( UICodeEditor* editor ) override;
|
||||||
|
|
||||||
|
void onUnregisterEditor( UICodeEditor* editor ) override;
|
||||||
|
|
||||||
|
void drawGutter( UICodeEditor* editor, const Int64& index, const Vector2f& screenStart,
|
||||||
|
const Float& lineHeight, const Float& gutterWidth,
|
||||||
|
const Float& fontSize ) override;
|
||||||
|
|
||||||
|
void minimapDrawBefore( UICodeEditor* editor, const DocumentLineRange& docLineRange,
|
||||||
|
const DocumentViewLineRange& docViewRange, const Vector2f& linePos,
|
||||||
|
const Vector2f& lineSize, const Float& charWidth,
|
||||||
|
const Float& gutterWidth,
|
||||||
|
const DrawTextRangesFn& drawTextRanges ) override;
|
||||||
|
|
||||||
Color getVarColor( const std::string& var );
|
Color getVarColor( const std::string& var );
|
||||||
|
|
||||||
|
void updateDiffGutterColors();
|
||||||
|
|
||||||
|
const Color& getDiffGutterColor( GitLineChange change, bool deleted = false ) const;
|
||||||
|
|
||||||
|
void initializeDiffGutter();
|
||||||
|
|
||||||
|
void ensureDocumentDiff( TextDocument* doc );
|
||||||
|
|
||||||
|
void loadDocumentDiffBaseline( TextDocument* doc );
|
||||||
|
|
||||||
|
void scheduleDocumentDiff( TextDocument* doc );
|
||||||
|
|
||||||
|
bool isDocumentAddedInGit( const GitDocumentDiff& state );
|
||||||
|
|
||||||
|
void resolveAddedDocumentDiffs();
|
||||||
|
|
||||||
|
void resetDocumentDiff( TextDocument* doc, GitDocumentDiff& state );
|
||||||
|
|
||||||
|
void invalidateAllDocumentDiffBaselines();
|
||||||
|
|
||||||
|
void redrawDocumentDiff( TextDocument* doc );
|
||||||
|
|
||||||
void blame( UICodeEditor* editor );
|
void blame( UICodeEditor* editor );
|
||||||
|
|
||||||
void checkout( Git::Branch branch );
|
void checkout( Git::Branch branch );
|
||||||
|
|||||||
Reference in New Issue
Block a user