Add a work-in-progress Git History view to the source-control panel.

Display bounded first-parent commit history with asynchronous loading, independent pagination, and lazily expandable merge commits. Propagate inherited exclusions through nested merges to avoid repeating already represented history.
Add custom compact history rows, loading/error/retry states, keyboard navigation, repository and history-generation guards, and lifecycle invalidation after history-changing operations.
Add a commit details tab with full commit metadata and message, changed-line totals, GitHub links when available, and a multi-file diff viewer with global collapse and unified/split controls.
Extend UIDiffView with multi-file view controls and register the required Codicons.
The Git History interface and visual design remain a work in progress.
Fix caret positioning inside shaped ligature clusters.
Add explicit closest-glyph and interpolated ligature caret-positioning modes while keeping interpolation as the default for general text geometry.
Use the original closest-glyph behavior for caret and IME positioning in UICodeEditor, UITextEdit, UITextInput, and UIConsole, preventing cursor positions from being synthesized inside ligature glyphs.
Add regression coverage for both positioning modes.
Optimize SyntaxTokenizer by returning tokenizer results in a SmallVector, avoiding heap allocations in common cases.
This commit is contained in:
Martín Lucas Golini
2026-08-28 14:43:05 -03:00
parent 66d22ea5bb
commit baaa0a62c0
34 changed files with 1839 additions and 79 deletions
+14 -9
View File
@@ -41,6 +41,8 @@ class EE_API Text {
Shadow = 1 << 4 ///< Draw a shadow below the text
};
enum class LigatureCaretMode : Uint8 { ClosestGlyph, Interpolate };
static inline bool canSkipShaping( Uint32 textDrawHints ) {
return Text::TextShaperOptimizations && !( textDrawHints & TextHints::OpenTypeFeatures ) &&
( textDrawHints & ( TextHints::AllLatin1 | TextHints::AllAscii ) ) != 0;
@@ -134,7 +136,8 @@ class EE_API Text {
std::size_t index, Font* font, const Uint32& fontSize, const String& string,
const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f,
std::optional<Float> tabOffset = {}, bool allowNewLine = true, Uint32 textHints = 0,
TextDirection direction = TextDirection::Unspecified, const Vector2f& initialOffset = {} );
TextDirection direction = TextDirection::Unspecified, const Vector2f& initialOffset = {},
LigatureCaretMode ligatureCaretMode = LigatureCaretMode::Interpolate );
static std::size_t
findLastCharPosWithinLength( Font* font, const Uint32& fontSize, const String& string,
@@ -239,7 +242,9 @@ class EE_API Text {
Float getOutlineThickness() const;
Vector2f findCharacterPos( std::size_t index ) const;
Vector2f
findCharacterPos( std::size_t index,
LigatureCaretMode ligatureCaretMode = LigatureCaretMode::Interpolate ) const;
/** @return The current text local bounds. */
Rectf getLocalBounds();
@@ -497,13 +502,13 @@ class EE_API Text {
/** Ensures visual line info is up to date. */
void ensureVisualLinesUpdate();
static Vector2f findCharacterPos( std::size_t index, Font* font, const Uint32& fontSize,
const String& string, const Uint32& style,
const Uint32& tabWidth, const Float& outlineThickness,
std::optional<Float> tabOffset, bool allowNewLine,
Uint32 textHints, TextDirection direction,
LineWrapMode lineWrapMode, Float maxWrapWidth,
const Vector2f& initialOffset = {} );
static Vector2f
findCharacterPos( std::size_t index, Font* font, const Uint32& fontSize, const String& string,
const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness,
std::optional<Float> tabOffset, bool allowNewLine, Uint32 textHints,
TextDirection direction, LineWrapMode lineWrapMode, Float maxWrapWidth,
const Vector2f& initialOffset = {},
LigatureCaretMode ligatureCaretMode = LigatureCaretMode::Interpolate );
static Int32 findCharacterFromPos( const Vector2i& pos, bool returnNearest, Font* font,
const Uint32& fontSize, const String& string,
+1 -1
View File
@@ -137,7 +137,7 @@ class EE_API UIAbstractView : public UIScrollableWidget {
virtual void onModelSelectionChange();
virtual void onModelIndexDeleted( const void* ) {}
virtual void onModelIndexDeleted( const void* internalData );
void modelUpdate( unsigned flags );
+5 -5
View File
@@ -10,13 +10,13 @@ namespace EE { namespace UI { namespace Doc {
struct EE_API TokenizedLine {
SyntaxState initState;
String::HashType hash{ 0 };
std::vector<SyntaxTokenPosition> tokens;
SmallVector<SyntaxTokenPosition, 4> tokens;
SyntaxState state;
Uint64 signature{ 0 };
void updateSignature();
static Uint64 calcSignature( const std::vector<SyntaxTokenPosition>& tokens );
static Uint64 calcSignature( const SmallVector<SyntaxTokenPosition, 4>& tokens );
};
class EE_API SyntaxHighlighter {
@@ -31,10 +31,10 @@ class EE_API SyntaxHighlighter {
void invalidate( Int64 lineIndex );
const std::vector<SyntaxTokenPosition>& getLine( const size_t& index,
bool mustTokenize = true );
const SmallVector<SyntaxTokenPosition, 4>& getLine( const size_t& index,
bool mustTokenize = true );
void copyLineToBuffer( const size_t& index, std::vector<SyntaxTokenPosition>& buffer,
void copyLineToBuffer( const size_t& index, SmallVector<SyntaxTokenPosition, 4>& buffer,
bool mustTokenize = true );
Int64 getFirstInvalidLine() const;
+3 -3
View File
@@ -83,16 +83,16 @@ struct SyntaxState {
class EE_API SyntaxTokenizer {
public:
static std::pair<std::vector<SyntaxToken>, SyntaxState>
static std::pair<SmallVector<SyntaxToken, 4>, SyntaxState>
tokenize( const SyntaxDefinition& syntax, const std::string& text, const SyntaxState& state,
const size_t& startIndex = 0, bool skipSubSyntaxSeparator = false );
static std::pair<std::vector<SyntaxTokenPosition>, SyntaxState>
static std::pair<SmallVector<SyntaxTokenPosition, 4>, SyntaxState>
tokenizePosition( const SyntaxDefinition& syntax, const std::string& text,
const SyntaxState& state, const size_t& startIndex = 0,
bool skipSubSyntaxSeparator = false );
static std::pair<std::vector<SyntaxTokenComplete>, SyntaxState>
static std::pair<SmallVector<SyntaxTokenComplete, 4>, SyntaxState>
tokenizeComplete( const SyntaxDefinition& syntax, const std::string& text,
const SyntaxState& state, const size_t& startIndex = 0,
bool skipSubSyntaxSeparator = false );
+10
View File
@@ -36,6 +36,12 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
static bool isMultiFileDiff( const std::string& diff );
static std::vector<UIDiffView*> multiFileDiffViews( UIScrollView* multiDiff );
static void setMultiFileViewMode( UIScrollView* multiDiff, ViewMode mode );
static void setMultiFileCollapsed( UIScrollView* multiDiff, bool collapsed );
virtual ~UIDiffView();
virtual Uint32 getType() const override;
@@ -84,6 +90,9 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
void setHeadersVisible( bool visible );
void setCollapsed( bool collapsed );
bool isCollapsed() const { return mCollapsed; }
bool areHeadersVisible() const { return mHeadersVisible; }
const String& getFileName() const { return mFileName; }
@@ -115,6 +124,7 @@ class EE_API UIDiffView : public UIWidget, public WidgetCommandExecuter {
bool mHeadersVisible{ false };
bool mIsImageDiff{ false };
bool mAutoDeleteOldTempImage{ false };
bool mCollapsed{ false };
std::shared_ptr<SyntaxDefinition> mSyntaxDef;
String mFileName;
std::string mImageDiffOldPath;
+5 -5
View File
@@ -559,10 +559,10 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
void unregisterPlugin( UICodeEditorPlugin* plugin );
virtual Vector2d getTextPositionOffset( const TextPosition& pos,
std::optional<Float> lineHeight = {},
bool allowVisualLineEnd = false,
bool visualizeNewLine = false ) const;
virtual Vector2d getTextPositionOffset(
const TextPosition& pos, std::optional<Float> lineHeight = {},
bool allowVisualLineEnd = false, bool visualizeNewLine = false,
Text::LigatureCaretMode ligatureCaretMode = Text::LigatureCaretMode::Interpolate ) const;
Vector2d getTextPositionOffsetSanitized( TextPosition pos,
std::optional<Float> lineHeight = {} ) const;
@@ -1042,7 +1042,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
String::HashType mTagFoldRange{ 0 };
Uint32 mTabIndentCharacter{ 187 /*'»'*/ };
CharacterAlignment mTabIndentAlignment{ CharacterAlignment::Center };
std::vector<SyntaxTokenPosition> mTokens;
SmallVector<SyntaxTokenPosition, 4> mTokens;
TextDirection mTextDirection{ TextDirection::LeftToRight };
UICodeEditor( const bool& autoRegisterBaseCommands = true,