mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-09-22 04:58:34 +03:00
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.
186 lines
5.2 KiB
C++
186 lines
5.2 KiB
C++
#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
|