Fix a couple of bugs related with use-after-free. Try to make some of the text document and syntax highlighter functionality more thread-safe.

This commit is contained in:
Martín Lucas Golini
2025-08-10 01:25:10 -03:00
parent 61c3e72289
commit cec73de2eb
26 changed files with 469 additions and 220 deletions
@@ -34,6 +34,9 @@ class EE_API SyntaxHighlighter {
const std::vector<SyntaxTokenPosition>& getLine( const size_t& index,
bool mustTokenize = true );
void copyLineToBuffer( const size_t& index, std::vector<SyntaxTokenPosition>& buffer,
bool mustTokenize = true );
Int64 getFirstInvalidLine() const;
Int64 getMaxWantedLine() const;
+24
View File
@@ -21,6 +21,7 @@
#include <eepp/ui/doc/textrange.hpp>
#include <eepp/ui/doc/textundostack.hpp>
#include <functional>
#include <shared_mutex>
#include <vector>
using namespace EE::System;
@@ -84,6 +85,8 @@ class EE_API TextDocument {
class EE_API Client {
public:
enum Type { Core, Auxiliary };
virtual ~Client();
virtual void onDocumentLoaded( TextDocument* ) {};
virtual void onDocumentTextChanged( const DocumentContentChange& ) = 0;
@@ -99,6 +102,7 @@ class EE_API TextDocument {
virtual void onDocumentDirtyOnFileSystem( TextDocument* ) = 0;
virtual void onDocumentMoved( TextDocument* ) = 0;
virtual void onDocumentReset( TextDocument* ) = 0;
virtual Client::Type getTextDocumentClientType() = 0;
virtual void onDocumentReloaded( TextDocument* doc ) {
onDocumentClosed( doc );
@@ -273,6 +277,22 @@ class EE_API TextDocument {
TextRange getLineRange( Int64 line ) const;
String getLineText( Int64 line ) const;
String getLineTextSubStr( Int64 line, std::size_t pos, std::size_t n ) const;
String::HashType getLineHash( Int64 line ) const;
String getLineTextWithoutNewLine( Int64 line ) const;
std::size_t getLineLength( Int64 ) const;
void getLineTextToBuffer( Int64 line, String& buffer ) const;
std::string getLineTextUtf8( Int64 line ) const;
void getLineTextToBufferUtf8( Int64 line, std::string& buffer ) const;
void deleteTo( const size_t& cursorIdx, TextPosition position );
void deleteTo( const size_t& cursorIdx, int offset );
@@ -297,6 +317,8 @@ class EE_API TextDocument {
void unregisterClient( Client* client );
std::size_t clientOfTypeCount( Client::Type type );
void moveToPreviousChar();
void moveToNextChar();
@@ -690,6 +712,8 @@ class EE_API TextDocument {
TextRanges mSelection;
UnorderedSet<Client*> mClients;
Mutex mClientsMutex;
mutable Mutex mLinesMutex;
mutable std::shared_ptr<std::shared_mutex> mDocumentMutex;
TextFormat::Encoding mEncoding{ TextFormat::Encoding::UTF8 };
TextFormat::LineEnding mLineEnding{ TextFormat::LineEnding::LF };
std::atomic<bool> mLoading{ false };
+78 -37
View File
@@ -2,74 +2,115 @@
#define EE_UI_DOC_TEXTDOCUMENTLINE_HPP
#include <eepp/core/string.hpp>
#include <memory>
#include <mutex>
#include <shared_mutex>
namespace EE { namespace UI { namespace Doc {
class EE_API TextDocumentLine {
public:
TextDocumentLine( const String& text ) : mText( text ) { updateState(); }
TextDocumentLine( const String& text, std::shared_ptr<std::shared_mutex> docMutex ) :
mText( text ), mDocMutex( docMutex ) {
updateState();
}
~TextDocumentLine() {
if ( mDocMutex ) {
// Wait for any readers to finish before destruction
std::unique_lock<std::shared_mutex> lock( *mDocMutex );
}
}
void setText( String&& text ) {
mText = std::move( text );
updateState();
if ( mDocMutex ) {
std::unique_lock<std::shared_mutex> lock( *mDocMutex );
mText = std::move( text );
updateState();
} else {
mText = std::move( text );
updateState();
}
}
void setText( const String& text ) {
mText = text;
updateState();
const String& getText() const {
if ( mDocMutex ) {
std::shared_lock<std::shared_mutex> lock( *mDocMutex );
return mText;
}
return mText;
}
const String& getText() const { return mText; }
String getTextWithoutNewLine() const {
if ( mDocMutex ) {
std::shared_lock<std::shared_mutex> lock( *mDocMutex );
return mText.substr( 0, mText.size() - 1 );
}
return mText.substr( 0, mText.size() - 1 );
}
String getTextWithoutNewLine() const { return mText.substr( 0, mText.size() - 1 ); }
void operator=( const std::string& right ) { setText( right ); }
String::StringBaseType operator[]( std::size_t index ) const { return mText[index]; }
void insertChar( const unsigned int& pos, const String::StringBaseType& tchar ) {
mText.insert( mText.begin() + pos, tchar );
updateState();
String::StringBaseType operator[]( std::size_t index ) const {
if ( mDocMutex ) {
std::shared_lock<std::shared_mutex> lock( *mDocMutex );
return mText[index];
}
return mText[index];
}
void append( const String& text ) {
mText.append( text );
updateState();
}
void append( const String::StringBaseType& code ) {
mText.append( code );
updateState();
if ( mDocMutex ) {
std::unique_lock<std::shared_mutex> lock( *mDocMutex );
mText.append( text );
updateState();
} else {
mText.append( text );
updateState();
}
}
String substr( std::size_t pos = 0, std::size_t n = String::StringType::npos ) const {
if ( mDocMutex ) {
std::shared_lock<std::shared_mutex> lock( *mDocMutex );
return mText.substr( pos, n );
}
return mText.substr( pos, n );
}
String::Iterator insert( String::Iterator p, const String::StringBaseType& c ) {
auto it = mText.insert( p, c );
updateState();
return it;
bool empty() const {
if ( mDocMutex ) {
std::shared_lock<std::shared_mutex> lock( *mDocMutex );
return mText.empty();
}
return mText.empty();
}
bool empty() const { return mText.empty(); }
size_t size() const {
if ( mDocMutex ) {
std::shared_lock<std::shared_mutex> lock( *mDocMutex );
return mText.size();
}
return mText.size();
}
size_t size() const { return mText.size(); }
String::HashType getHash() const { return mHash; }
size_t length() const { return mText.length(); }
bool isAscii() const {
return ( mFlags & TextHints::AllAscii ) != 0;
}
const String::HashType& getHash() const { return mHash; }
std::string toUtf8() const { return mText.toUtf8(); }
bool isAscii() const { return ( mFlags & TextHints::AllAscii ) != 0; }
Uint32 getTextHints() const { return mFlags; }
Uint32 getTextHints() const {
if ( mDocMutex ) {
std::shared_lock<std::shared_mutex> lock( *mDocMutex );
return mFlags;
}
return mFlags;
}
protected:
String mText;
String::HashType mHash;
Uint32 mFlags{ 0 };
std::shared_ptr<std::shared_mutex> mDocMutex;
void updateState() {
mHash = mText.getHash();
@@ -363,6 +363,8 @@ class EE_API UICodeEditorSplitter {
UITabWidget* splitTabWidget( SplitDirection, UITabWidget* );
std::shared_ptr<TextDocument> getTextDocumentRef( TextDocument* doc );
protected:
UISceneNode* mUISceneNode{ nullptr };
std::shared_ptr<ThreadPool> mThreadPool;
+3 -5
View File
@@ -779,10 +779,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
Float getPluginsGutterSpace() const;
void setEnableFlashCursor( bool enable ) { mEnableFlashCursor = enable; }
bool isEnabledFlashCursor() { return mEnableFlashCursor; }
void setCursorVisible( bool visible );
bool isCursorVisible() const;
@@ -802,6 +798,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
bool usesTabStops() { return mTabStops; }
Client::Type getTextDocumentClientType() { return TextDocument::Client::Core; }
protected:
struct LastXOffset {
TextPosition position{ 0, 0 };
@@ -845,7 +843,6 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
bool mFoldsAlwaysVisible{ false };
bool mFoldsVisible{ false };
bool mFoldsIsFirst{ true };
bool mEnableFlashCursor{ false };
bool mDisableCursorBlinkingAfterAMinuteOfInactivity{ true };
bool mAllowSelectingTextFromGutter{ true };
bool mTabStops{ false };
@@ -931,6 +928,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;
UICodeEditor( const std::string& elementTag, const bool& autoRegisterBaseCommands = true,
const bool& autoRegisterBaseKeybindings = true );
+2
View File
@@ -145,6 +145,8 @@ class EE_API UIConsole : public UIWidget,
TextDocument& getDoc();
Client::Type getTextDocumentClientType() { return TextDocument::Client::Core; }
protected:
struct TextCache {
Text text;
+2
View File
@@ -123,6 +123,8 @@ class EE_API UITextInput : public UITextView, public TextDocument::Client {
void setSelectAllDocOnTabNavigate( bool selectAllDocOnTabNavigate );
Client::Type getTextDocumentClientType() { return TextDocument::Client::Core; }
protected:
TextDocument mDoc;
Float mWaitCursorTime;
+1 -1
View File
@@ -1161,7 +1161,7 @@ Glyph FontTrueType::loadGlyphByIndex( Uint32 index, unsigned int characterSize,
page.texture->update( pixelPtr, w, h, x, y );
if ( scale < 1.f )
eeFree( pixelPtr );
eeSAFE_DELETE_ARRAY( pixelPtr );
}
// Delete the FT glyph
+3 -3
View File
@@ -363,7 +363,7 @@ DocumentView::VisibleLineRange DocumentView::getVisibleLineRange( const TextPosi
Int64 fromCol = mVisibleLines[mid].column();
Int64 toCol = mid + 1 <= toIdx
? mVisibleLines[mid + 1].column() - ( allowVisualLineEnd ? 0 : 1 )
: mDoc->line( pos.line() ).size();
: mDoc->getLineLength( pos.line() );
if ( pos.column() >= fromCol && pos.column() <= toCol ) {
// If it's between the limits we must check if it fits into the previous one
@@ -371,7 +371,7 @@ DocumentView::VisibleLineRange DocumentView::getVisibleLineRange( const TextPosi
Int64 fromCol = mVisibleLines[mid - 1].column();
Int64 toCol = mid <= toIdx
? mVisibleLines[mid].column() - ( allowVisualLineEnd ? 0 : 1 )
: mDoc->line( pos.line() ).size();
: mDoc->getLineLength( pos.line() );
info.visibleIndex = static_cast<VisibleIndex>( mid - 1 );
info.range = { { pos.line(), fromCol }, { pos.line(), toCol } };
@@ -411,7 +411,7 @@ TextRange DocumentView::getVisibleIndexRange( VisibleIndex visibleIndex ) const
mVisibleLines[idx + 1].line() == start.line() ) {
end.setColumn( mVisibleLines[idx + 1].column() );
} else {
end.setColumn( mDoc->line( start.line() ).size() );
end.setColumn( mDoc->getLineLength( start.line() ) );
}
return { start, end };
}
+16 -14
View File
@@ -10,12 +10,13 @@ namespace EE { namespace UI { namespace Doc {
static std::vector<TextRange> findFoldingRangesBraces( TextDocument* doc ) {
Clock c;
std::vector<TextRange> regions;
if ( doc->linesCount() <= 2 )
auto lineCount = doc->linesCount();
if ( lineCount <= 2 )
return regions;
const auto& braces = doc->getSyntaxDefinition().getFoldBraces();
std::stack<TextPosition> braceStack;
auto highlighter = doc->getHighlighter();
for ( size_t lineIdx = 0; lineIdx < doc->linesCount(); lineIdx++ ) {
for ( size_t lineIdx = 0; lineIdx < lineCount; lineIdx++ ) {
const auto& line = doc->line( lineIdx ).getText();
size_t lineLength = line.length();
for ( size_t colIdx = 0; colIdx < lineLength; colIdx++ ) {
@@ -63,12 +64,13 @@ static std::vector<TextRange> findFoldingRangesIndentation( TextDocument* doc )
Clock c;
std::stack<TextPosition> indentStack;
std::vector<TextRange> regions;
if ( doc->linesCount() <= 2 )
auto lineCount = doc->linesCount();
if ( lineCount <= 2 )
return regions;
const auto& braces = doc->getSyntaxDefinition().getFoldBraces();
int currentIndent = 0;
for ( size_t lineIdx = 0; lineIdx < doc->linesCount(); lineIdx++ ) {
for ( size_t lineIdx = 0; lineIdx < lineCount; lineIdx++ ) {
const auto& line = doc->line( lineIdx ).getText();
int newIndent = countLeadingSpaces( line );
if ( newIndent > currentIndent ) {
@@ -91,7 +93,7 @@ static std::vector<TextRange> findFoldingRangesIndentation( TextDocument* doc )
auto top = indentStack.top();
indentStack.pop();
regions.emplace_back( TextPosition( top.line() + 1, 0 ),
TextPosition( static_cast<Int64>( doc->linesCount() ) - 1, 0 ) );
TextPosition( static_cast<Int64>( lineCount ) - 1, 0 ) );
}
Log::debug( "findFoldingRangesIndentation for \"%s\" took %s", doc->getFilePath(),
@@ -102,9 +104,10 @@ static std::vector<TextRange> findFoldingRangesIndentation( TextDocument* doc )
static std::vector<TextRange> findFoldingRangesMarkdown( TextDocument* doc ) {
Clock c; // For performance logging, consistent with existing functions
std::vector<TextRange> regions;
auto lineCount = doc->linesCount();
// Early return for small documents, as in other folding functions
if ( doc->linesCount() <= 2 )
if ( lineCount <= 2 )
return regions;
// Stack to track open heading sections: (line number, heading level)
@@ -115,7 +118,7 @@ static std::vector<TextRange> findFoldingRangesMarkdown( TextDocument* doc ) {
Int64 codeBlockStart = -1;
// Process each line
for ( size_t lineIdx = 0; lineIdx < doc->linesCount(); lineIdx++ ) {
for ( size_t lineIdx = 0; lineIdx < lineCount; lineIdx++ ) {
const String& lineText = doc->line( lineIdx ).getText();
String::View trimmed =
String::trim( lineText.view() ); // Remove leading and trailing whitespace
@@ -170,18 +173,17 @@ static std::vector<TextRange> findFoldingRangesMarkdown( TextDocument* doc ) {
while ( !sectionStack.empty() ) {
auto [headingLine, _] = sectionStack.back();
sectionStack.pop_back();
if ( headingLine < static_cast<Int64>( doc->linesCount() ) ) {
regions.emplace_back( TextPosition( headingLine, 0 ), // Start at heading
TextPosition( doc->linesCount() - 1, 0 ) // End at document end
if ( headingLine < static_cast<Int64>( lineCount ) ) {
regions.emplace_back( TextPosition( headingLine, 0 ), // Start at heading
TextPosition( lineCount - 1, 0 ) // End at document end
);
}
}
// Close any unterminated code block
if ( inCodeBlock && codeBlockStart != -1 &&
codeBlockStart < static_cast<Int64>( doc->linesCount() ) ) {
regions.emplace_back( TextPosition( codeBlockStart, 0 ), // Start at opening ```
TextPosition( doc->linesCount() - 1, 0 ) // End at document end
if ( inCodeBlock && codeBlockStart != -1 && codeBlockStart < static_cast<Int64>( lineCount ) ) {
regions.emplace_back( TextPosition( codeBlockStart, 0 ), // Start at opening ```
TextPosition( lineCount - 1, 0 ) // End at document end
);
}
+60 -14
View File
@@ -51,18 +51,18 @@ void SyntaxHighlighter::invalidate( Int64 lineIndex ) {
}
TokenizedLine SyntaxHighlighter::tokenizeLine( const size_t& line, const SyntaxState& state ) {
auto& ln = mDoc->line( line );
TokenizedLine tokenizedLine;
tokenizedLine.initState = state;
tokenizedLine.hash = ln.getHash();
if ( mMaxTokenizationLength != 0 && (Int64)ln.size() > mMaxTokenizationLength ) {
Int64 textSize = ln.size();
tokenizedLine.hash = mDoc->getLineHash( line );
auto len = mDoc->getLineLength( line );
if ( mMaxTokenizationLength != 0 && (Int64)len > mMaxTokenizationLength ) {
Int64 textSize = len;
SyntaxTokenLen pos = 0;
while ( textSize > 0 ) {
SyntaxTokenLen chunkSize =
textSize > mMaxTokenizationLength ? mMaxTokenizationLength : textSize;
SyntaxTokenPosition token{ SyntaxStyleTypes::Normal, pos, chunkSize };
token.len = ln.size();
token.len = len;
tokenizedLine.tokens.emplace_back( token );
textSize -= chunkSize;
pos += chunkSize;
@@ -71,7 +71,8 @@ TokenizedLine SyntaxHighlighter::tokenizeLine( const size_t& line, const SyntaxS
tokenizedLine.updateSignature();
return tokenizedLine;
}
auto res = SyntaxTokenizer::tokenizePosition( mDoc->getSyntaxDefinition(), ln.toUtf8(), state );
auto res = SyntaxTokenizer::tokenizePosition( mDoc->getSyntaxDefinition(),
mDoc->getLineTextUtf8( line ), state );
tokenizedLine.tokens = std::move( res.first );
tokenizedLine.state = std::move( res.second );
tokenizedLine.updateSignature();
@@ -94,7 +95,7 @@ void SyntaxHighlighter::moveHighlight( const Int64& fromLine, const Int64& /*toL
auto lineIt = mLines.find( i - numLines );
if ( lineIt != mLines.end() ) {
const auto& line = lineIt->second;
if ( line.hash == mDoc->line( i ).getHash() ) {
if ( line.hash == mDoc->getLineHash( i ) ) {
auto nl = mLines.extract( lineIt );
nl.key() = i;
mLines.insert( std::move( nl ) );
@@ -104,7 +105,7 @@ void SyntaxHighlighter::moveHighlight( const Int64& fromLine, const Int64& /*toL
} else if ( numLines < 0 ) {
for ( Int64 i = fromLine; i < linesCount; i++ ) {
auto lineIt = mLines.find( i - numLines );
if ( lineIt != mLines.end() && lineIt->second.hash == mDoc->line( i ).getHash() ) {
if ( lineIt != mLines.end() && lineIt->second.hash == mDoc->getLineHash( i ) ) {
auto nl = mLines.extract( lineIt );
nl.key() = i;
mLines[i] = std::move( nl.mapped() );
@@ -153,7 +154,7 @@ const std::vector<SyntaxTokenPosition>& SyntaxHighlighter::getLine( const size_t
static std::vector<SyntaxTokenPosition> noHighlightVector = {
{ SyntaxStyleTypes::Normal, 0, 0 } };
if ( mDoc->getSyntaxDefinition().getPatterns().empty() ) {
noHighlightVector[0].len = mDoc->line( index ).size();
noHighlightVector[0].len = mDoc->getLineLength( index );
return noHighlightVector;
}
@@ -162,7 +163,7 @@ const std::vector<SyntaxTokenPosition>& SyntaxHighlighter::getLine( const size_t
auto it = mLines.find( index );
bool needsTokenize =
it == mLines.end() ||
( index < mDoc->linesCount() && mDoc->line( index ).getHash() != it->second.hash );
( index < mDoc->linesCount() && mDoc->getLineHash( index ) != it->second.hash );
if ( !needsTokenize ) {
mMaxWantedLine = eemax<Int64>( mMaxWantedLine, index );
return it->second.tokens;
@@ -170,7 +171,7 @@ const std::vector<SyntaxTokenPosition>& SyntaxHighlighter::getLine( const size_t
}
if ( !mustTokenize ) {
noHighlightVector[0].len = mDoc->line( index ).size();
noHighlightVector[0].len = mDoc->getLineLength( index );
return noHighlightVector;
}
@@ -190,6 +191,52 @@ const std::vector<SyntaxTokenPosition>& SyntaxHighlighter::getLine( const size_t
return mLines[index].tokens;
}
void SyntaxHighlighter::copyLineToBuffer( const size_t& index,
std::vector<SyntaxTokenPosition>& buffer,
bool mustTokenize ) {
static std::vector<SyntaxTokenPosition> noHighlightVector = {
{ SyntaxStyleTypes::Normal, 0, 0 } };
if ( mDoc->getSyntaxDefinition().getPatterns().empty() ) {
noHighlightVector[0].len = mDoc->getLineLength( index );
buffer = noHighlightVector;
return;
}
{
Lock l( mLinesMutex );
auto it = mLines.find( index );
bool needsTokenize =
it == mLines.end() ||
( index < mDoc->linesCount() && mDoc->getLineHash( index ) != it->second.hash );
if ( !needsTokenize ) {
mMaxWantedLine = eemax<Int64>( mMaxWantedLine, index );
buffer = it->second.tokens;
return;
}
}
if ( !mustTokenize ) {
noHighlightVector[0].len = mDoc->getLineLength( index );
buffer = noHighlightVector;
return;
}
SyntaxState prevState;
if ( index > 0 ) {
Lock l( mLinesMutex );
auto prevIt = mLines.find( index - 1 );
if ( prevIt != mLines.end() )
prevState = prevIt->second.state;
}
auto tokenizedLine = tokenizeLine( index, prevState );
Lock l( mLinesMutex );
mLines[index] = std::move( tokenizedLine );
mTokenizerLines[index] = mLines[index];
mMaxWantedLine = eemax<Int64>( mMaxWantedLine, index );
buffer = mLines[index].tokens;
}
Int64 SyntaxHighlighter::getFirstInvalidLine() const {
return mFirstInvalidLine;
}
@@ -224,7 +271,7 @@ bool SyntaxHighlighter::updateDirty( int visibleLinesCount ) {
Lock l( mLinesMutex );
const auto& it = mLines.find( index );
mustTokenize = it == mLines.end() ||
it->second.hash != mDoc->line( index ).getHash() ||
it->second.hash != mDoc->getLineHash( index ) ||
it->second.initState != state;
}
@@ -307,8 +354,7 @@ void SyntaxHighlighter::mergeLine( const size_t& line, const TokenizedLine& toke
{
mLinesMutex.lock();
auto found = mTokenizerLines.find( line );
if ( found != mTokenizerLines.end() &&
mDoc->line( line ).getHash() == found->second.hash ) {
if ( found != mTokenizerLines.end() && mDoc->getLineHash( line ) == found->second.hash ) {
tline = found->second;
mLinesMutex.unlock();
} else {
+179 -90
View File
@@ -119,6 +119,7 @@ bool TextDocument::isNonWord( String::StringBaseType ch ) const {
TextDocument::TextDocument( bool verbose ) :
mUUID( true ),
mUndoStack( this ),
mDocumentMutex( std::make_shared<std::shared_mutex>() ),
mVerbose( verbose ),
mAutoCloseBracketsPairs(
{ { '(', ')' }, { '[', ']' }, { '{', '}' }, { '\'', '\'' }, { '"', '"' }, { '`', '`' } } ),
@@ -185,8 +186,12 @@ void TextDocument::reset() {
mSelection.clear();
mSelection.push_back( { { 0, 0 }, { 0, 0 } } );
mLastSelection = 0;
mLines.clear();
mLines.emplace_back( String( "\n" ) );
{
Lock l( mLinesMutex );
mLines.clear();
mLines.emplace_back( String( "\n" ), mDocumentMutex );
}
{
Lock l( mSyntaxDefinitionMutex );
mSyntaxDefinition = SyntaxDefinitionManager::instance()->getPlainDefinition();
@@ -301,7 +306,12 @@ TextDocument::LoadStatus TextDocument::loadFromStream( IOStream& file, std::stri
Clock clock;
if ( callReset )
reset();
mLines.clear();
{
Lock l( mLinesMutex );
mLines.clear();
}
MD5::Context md5Ctx;
if ( file.isOpen() ) {
const size_t BLOCK_SIZE = EE_1MB;
@@ -362,7 +372,7 @@ TextDocument::LoadStatus TextDocument::loadFromStream( IOStream& file, std::stri
char lastChar = lineBuffer[lineBufferSize - 1];
if ( lastChar == '\n' || lastChar == '\r' ) {
if ( mLines.empty() ) {
if ( linesCount() == 0 ) {
if ( lineBufferSize > 1 && lineBuffer[lineBufferSize - 2] == '\r' &&
lastChar == '\n' ) {
mLineEnding = TextFormat::LineEnding::CRLF;
@@ -389,10 +399,14 @@ TextDocument::LoadStatus TextDocument::loadFromStream( IOStream& file, std::stri
lineBuffer[lineBuffer.size() - 1] = '\n';
}
mLines.push_back( lineBuffer );
{
Lock l( mLinesMutex );
mLines.emplace_back( lineBuffer, mDocumentMutex );
}
lineBuffer.resize( 0 );
} else if ( consume <= 0 && pending - read == 0 ) {
mLines.push_back( lineBuffer );
Lock l( mLinesMutex );
mLines.emplace_back( lineBuffer, mDocumentMutex );
}
if ( consume < 0 ) {
@@ -408,15 +422,18 @@ TextDocument::LoadStatus TextDocument::loadFromStream( IOStream& file, std::stri
};
}
if ( !mLines.empty() ) {
const String& lastLine = mLines[mLines.size() - 1].getText();
auto lineCount = linesCount();
if ( lineCount != 0 ) {
Lock l( mLinesMutex );
const String& lastLine = mLines[lineCount - 1].getText();
if ( lastLine[lastLine.size() - 1] == '\n' ) {
mLines.push_back( String( "\n" ) );
mLines.emplace_back( String( "\n" ), mDocumentMutex );
} else {
mLines[mLines.size() - 1].append( "\n" );
mLines[lineCount - 1].append( "\n" );
}
} else {
mLines.push_back( String( "\n" ) );
Lock l( mLinesMutex );
mLines.emplace_back( String( "\n" ), mDocumentMutex );
}
if ( mAutoDetectIndentType )
@@ -465,14 +482,18 @@ void TextDocument::guessIndentType() {
}
};
size_t start = eemin<size_t>( 100, mLines.size() );
guessTabsFn( 0, start );
size_t start = eemin<size_t>( 100, linesCount() );
if ( !guessTabs && !guessSpaces ) {
if ( start == 100 )
guessTabsFn( start, eemin<size_t>( start + 100, mLines.size() ) );
if ( !guessTabs && !guessSpaces )
return;
{
Lock l( mLinesMutex );
guessTabsFn( 0, start );
if ( !guessTabs && !guessSpaces ) {
if ( start == 100 )
guessTabsFn( start, eemin<size_t>( start + 100, linesCount() ) );
if ( !guessTabs && !guessSpaces )
return;
}
}
if ( guessTabs >= guessSpaces ) {
@@ -781,7 +802,7 @@ bool TextDocument::save( const std::string& path ) {
}
bool TextDocument::save( IOStream& stream, bool keepUndoRedoStatus ) {
if ( !stream.isOpen() || mLines.empty() )
if ( !stream.isOpen() || linesCount() == 0 )
return false;
BoolScopedOp op( mDoingTextInput, true );
const std::string whitespaces( " \t\f\v\n\r" );
@@ -814,9 +835,9 @@ bool TextDocument::save( IOStream& stream, bool keepUndoRedoStatus ) {
}
}
size_t lastLine = mLines.size() - 1;
size_t lastLine = linesCount() - 1;
for ( size_t i = 0; i <= lastLine; i++ ) {
std::string text( mLines[i].toUtf8() );
std::string text( getLineTextUtf8( i ) );
if ( !keepUndoRedoStatus && mTrimTrailingWhitespaces && text.size() > 1 &&
whitespaces.find( text[text.size() - 2] ) != std::string::npos ) {
@@ -824,11 +845,11 @@ bool TextDocument::save( IOStream& stream, bool keepUndoRedoStatus ) {
Int64 curLine = i;
if ( pos != std::string::npos ) {
remove( 0, { { curLine, static_cast<Int64>( pos + 1 ) },
{ curLine, static_cast<Int64>( mLines[i].getText().size() ) } } );
{ curLine, static_cast<Int64>( getLineLength( i ) ) } } );
} else {
remove( 0, { startOfLine( { curLine, 0 } ), { endOfLine( { curLine, 0 } ) } } );
}
text = mLines[i].toUtf8();
text = getLineTextUtf8( i );
}
if ( i == lastLine ) {
@@ -1096,18 +1117,19 @@ const TextRange& TextDocument::getSelection() const {
}
TextDocumentLine& TextDocument::line( const size_t& index ) {
static TextDocumentLine safeLine = TextDocumentLine( "" );
eeASSERT( index < mLines.size() );
return index >= mLines.size() ? safeLine : mLines[index];
static TextDocumentLine safeLine = TextDocumentLine( "", nullptr );
eeASSERT( index < linesCount() );
return index >= linesCount() ? safeLine : mLines[index];
}
const TextDocumentLine& TextDocument::line( const size_t& index ) const {
static TextDocumentLine safeLine = TextDocumentLine( "" );
eeASSERT( index < mLines.size() );
return index >= mLines.size() ? safeLine : mLines[index];
static TextDocumentLine safeLine = TextDocumentLine( "", nullptr );
eeASSERT( index < linesCount() );
return index >= linesCount() ? safeLine : mLines[index];
}
size_t TextDocument::linesCount() const {
Lock l( mLinesMutex );
return mLines.size();
}
@@ -1129,6 +1151,7 @@ std::string TextDocument::getHashHexString() const {
String TextDocument::getText( const TextRange& range ) const {
TextRange nrange = sanitizeRange( range.normalized() );
Lock l( mLinesMutex );
if ( nrange.start().line() == nrange.end().line() ) {
return mLines[nrange.start().line()].substr(
nrange.start().column(), nrange.end().column() - nrange.start().column() );
@@ -1206,11 +1229,13 @@ String::StringBaseType TextDocument::getCurrentChar() const {
String::StringBaseType TextDocument::getChar( const TextPosition& position ) const {
auto pos = sanitizePosition( position );
Lock l( mLinesMutex );
return mLines[pos.line()][pos.column()];
}
String::StringBaseType
TextDocument::getCharFromUnsanitizedPosition( const TextPosition& position ) const {
Lock l( mLinesMutex );
return mLines[position.line()][position.column()];
}
@@ -1241,23 +1266,28 @@ TextPosition TextDocument::insert( const size_t& cursorIdx, TextPosition positio
}
position = sanitizePosition( position );
size_t lineCount = mLines.size();
size_t lineCount = linesCount();
Int64 linesAdd = 0;
String before = mLines[position.line()].substr( 0, position.column() );
String after = mLines[position.line()].substr( position.column() );
std::vector<String> lines = text.split( '\n', true );
Int64 linesAdd = eemax<Int64>( 0, static_cast<Int64>( lines.size() ) - 1 );
for ( auto i = 0; i < linesAdd; i++ )
lines[i] = lines[i] + "\n";
lines[0] = before + lines[0];
lines[lines.size() - 1] = lines[lines.size() - 1] + after;
{
Lock l( mLinesMutex );
String before = mLines[position.line()].substr( 0, position.column() );
String after = mLines[position.line()].substr( position.column() );
std::vector<String> lines = text.split( '\n', true );
linesAdd = eemax<Int64>( 0, static_cast<Int64>( lines.size() ) - 1 );
for ( auto i = 0; i < linesAdd; i++ )
lines[i] = lines[i] + "\n";
lines[0] = before + lines[0];
lines[lines.size() - 1] = lines[lines.size() - 1] + after;
mLines[position.line()] = TextDocumentLine( lines[0] );
notifyLineChanged( position.line() );
mLines[position.line()] = TextDocumentLine( lines[0], mDocumentMutex );
notifyLineChanged( position.line() );
for ( Int64 i = 1; i < (Int64)lines.size(); i++ ) {
mLines.insert( mLines.begin() + position.line() + i, TextDocumentLine( lines[i] ) );
notifyLineChanged( position.line() + i );
for ( Int64 i = 1; i < (Int64)lines.size(); i++ ) {
mLines.insert( mLines.begin() + position.line() + i,
TextDocumentLine( lines[i], mDocumentMutex ) );
notifyLineChanged( position.line() + i );
}
}
TextPosition cursor = positionOffset( position, text.size() );
@@ -1272,8 +1302,8 @@ TextPosition TextDocument::insert( const size_t& cursorIdx, TextPosition positio
notifyTextChanged( { { position, position }, text } );
if ( lineCount != mLines.size() ) {
notifyLineCountChanged( lineCount, mLines.size() );
if ( lineCount != linesCount() ) {
notifyLineCountChanged( lineCount, linesCount() );
}
if ( mSelection.size() > 1 ) {
@@ -1315,12 +1345,12 @@ TextPosition TextDocument::insert( const size_t& cursorIdx, TextPosition positio
size_t TextDocument::remove( const size_t& cursorIdx, TextRange range ) {
if ( !range.hasSelection() )
return 0;
size_t lineCount = mLines.size();
size_t lineCount = linesCount();
mUndoStack.clearRedoStack();
size_t linesRemoved = remove( cursorIdx, sanitizeRange( range.normalized() ),
mUndoStack.getUndoStackContainer(), mTimer.getElapsedTime() );
if ( lineCount != mLines.size() ) {
notifyLineCountChanged( lineCount, mLines.size() );
if ( lineCount != linesCount() ) {
notifyLineCountChanged( lineCount, linesCount() );
}
return linesRemoved;
}
@@ -1352,26 +1382,29 @@ size_t TextDocument::remove( const size_t& cursorIdx, TextRange range,
bool deletedAcrossNewLine = false;
// First delete all the lines in between the first and last one.
if ( range.start().line() + 1 < range.end().line() ) {
mLines.erase( mLines.begin() + range.start().line() + 1,
mLines.begin() + range.end().line() );
linesRemoved = range.end().line() - ( range.start().line() + 1 );
range.end().setLine( range.start().line() + 1 );
{
Lock l( mLinesMutex );
if ( range.start().line() + 1 < range.end().line() ) {
mLines.erase( mLines.begin() + range.start().line() + 1,
mLines.begin() + range.end().line() );
linesRemoved = range.end().line() - ( range.start().line() + 1 );
range.end().setLine( range.start().line() + 1 );
}
}
if ( range.start().line() == range.end().line() ) {
// Delete within same line.
TextDocumentLine& line = this->line( range.start().line() );
bool wholeLineIsSelected =
range.start().column() == 0 && range.end().column() == (Int64)line.length();
range.start().column() == 0 && range.end().column() == (Int64)line.size();
if ( wholeLineIsSelected ) {
line = "\n";
line.setText( "\n" );
} else {
auto beforeSelection = line.substr( 0, range.start().column() );
auto afterSelection =
!line.empty() && range.end().column() < (Int64)line.size()
? line.substr( range.end().column(), line.length() - range.end().column() )
? line.substr( range.end().column(), line.size() - range.end().column() )
: "";
if ( !beforeSelection.empty() && beforeSelection[beforeSelection.size() - 1] == '\n' )
@@ -1389,7 +1422,7 @@ size_t TextDocument::remove( const size_t& cursorIdx, TextRange range,
auto beforeSelection = firstLine.substr( 0, range.start().column() );
auto afterSelection = !secondLine.empty() && range.end().column() < (Int64)secondLine.size()
? secondLine.substr( range.end().column(),
secondLine.length() - range.end().column() )
secondLine.size() - range.end().column() )
: "";
if ( !beforeSelection.empty() && beforeSelection[beforeSelection.size() - 1] == '\n' )
@@ -1398,13 +1431,18 @@ size_t TextDocument::remove( const size_t& cursorIdx, TextRange range,
afterSelection += '\n';
firstLine.setText( beforeSelection + afterSelection );
Lock l( mLinesMutex );
mLines.erase( mLines.begin() + range.end().line() );
linesRemoved += 1;
deletedAcrossNewLine = true;
}
if ( mLines.empty() )
mLines.emplace_back( String( "\n" ) );
{
Lock l( mLinesMutex );
if ( mLines.empty() )
mLines.emplace_back( String( "\n" ), mDocumentMutex );
}
if ( mSelection.size() > 1 ) {
auto oriNm( originalRange.normalized() );
@@ -1477,12 +1515,12 @@ TextPosition TextDocument::positionOffset( TextPosition position, int columnOffs
while ( position.line() > 0 && position.column() < 0 ) {
position.setLine( position.line() - 1 );
position.setColumn(
eemax<Int64>( 0, position.column() + (Int64)mLines[position.line()].size() ) );
eemax<Int64>( 0, position.column() + (Int64)getLineLength( position.line() ) ) );
}
while ( position.line() < (Int64)mLines.size() - 1 &&
while ( position.line() < (Int64)linesCount() - 1 &&
position.column() >
(Int64)eemax<Int64>( 0, (Int64)mLines[position.line()].size() - 1 ) ) {
position.setColumn( position.column() - mLines[position.line()].size() );
(Int64)eemax<Int64>( 0, (Int64)getLineLength( position.line() ) - 1 ) ) {
position.setColumn( position.column() - getLineLength( position.line() ) );
position.setLine( position.line() + 1 );
}
return sanitizePosition( position );
@@ -1493,7 +1531,7 @@ TextPosition TextDocument::positionOffset( TextPosition position, TextPosition o
}
bool TextDocument::replaceLine( const Int64& lineNum, const String& text ) {
if ( lineNum >= 0 && lineNum < (Int64)mLines.size() ) {
if ( lineNum >= 0 && lineNum < (Int64)linesCount() ) {
TextRange oldSelection = getSelection();
setSelection( { startOfLine( { lineNum, 0 } ), endOfLine( { lineNum, 0 } ) } );
textInput( text, false );
@@ -1629,7 +1667,7 @@ TextPosition TextDocument::startOfLine( TextPosition position ) const {
TextPosition TextDocument::endOfLine( TextPosition position ) const {
position = sanitizePosition( position );
return TextPosition( position.line(), mLines[position.line()].size() - 1 );
return TextPosition( position.line(), getLineLength( position.line() ) - 1 );
}
TextPosition TextDocument::startOfContent( TextPosition start ) {
@@ -1652,7 +1690,8 @@ TextPosition TextDocument::startOfDoc() const {
}
TextPosition TextDocument::endOfDoc() const {
return TextPosition( mLines.size() - 1, mLines[mLines.size() - 1].size() - 1 );
auto lineCount = linesCount();
return TextPosition( lineCount - 1, getLineLength( lineCount - 1 ) - 1 );
}
TextRange TextDocument::getDocRange() const {
@@ -1663,6 +1702,46 @@ TextRange TextDocument::getLineRange( Int64 line ) const {
return { startOfLine( { line, 0 } ), endOfLine( { line, 0 } ) };
}
std::size_t TextDocument::getLineLength( Int64 line ) const {
Lock l( mLinesMutex );
return mLines[line].size();
}
String TextDocument::getLineText( Int64 line ) const {
Lock l( mLinesMutex );
return mLines[line].getText();
}
String TextDocument::getLineTextSubStr( Int64 line, std::size_t pos, std::size_t n ) const {
Lock l( mLinesMutex );
return mLines[line].getText().substr( pos, n );
}
String::HashType TextDocument::getLineHash( Int64 line ) const {
Lock l( mLinesMutex );
return mLines[line].getHash();
}
String TextDocument::getLineTextWithoutNewLine( Int64 line ) const {
Lock l( mLinesMutex );
return mLines[line].getTextWithoutNewLine();
}
void TextDocument::getLineTextToBuffer( Int64 line, String& buffer ) const {
Lock l( mLinesMutex );
buffer = mLines[line].getText();
}
std::string TextDocument::getLineTextUtf8( Int64 line ) const {
Lock l( mLinesMutex );
return mLines[line].getText().toUtf8();
}
void TextDocument::getLineTextToBufferUtf8( Int64 line, std::string& buffer ) const {
Lock l( mLinesMutex );
buffer = mLines[line].getText().toUtf8();
}
void TextDocument::deleteTo( const size_t& cursorIdx, int offset ) {
eeASSERT( cursorIdx < mSelection.size() );
BoolScopedOpOptional op( !mDoingTextInput, mDoingTextInput, true );
@@ -1902,6 +1981,15 @@ void TextDocument::unregisterClient( Client* client ) {
setActiveClient( nullptr );
}
std::size_t TextDocument::clientOfTypeCount( TextDocument::Client::Type type ) {
Lock l( mClientsMutex );
std::size_t count = 0;
for ( const auto& client : mClients )
if ( client->getTextDocumentClientType() == type )
count++;
return count;
}
void TextDocument::moveToPreviousChar() {
for ( size_t i = 0; i < mSelection.size(); ++i ) {
if ( mSelection[i].hasSelection() ) {
@@ -2380,7 +2468,7 @@ void TextDocument::moveLinesDown() {
TextRange range = getSelectionIndex( i ).normalized();
bool swap = getSelectionIndex( i ).normalized() != getSelection();
appendLineIfLastLine( i, range.end().line() + 1 );
if ( range.end().line() < (Int64)mLines.size() - 1 ) {
if ( range.end().line() < (Int64)linesCount() - 1 ) {
auto text = line( range.end().line() + 1 );
remove( i, { { range.end().line() + 1, 0 }, { range.end().line() + 2, 0 } } );
insert( i, { range.start().line(), 0 }, text.getText() );
@@ -2399,7 +2487,7 @@ bool TextDocument::hasRedo() const {
}
void TextDocument::appendLineIfLastLine( const size_t& cursorIdx, Int64 line ) {
if ( line >= (Int64)mLines.size() - 1 ) {
if ( line >= (Int64)linesCount() - 1 ) {
insert( cursorIdx, endOfDoc(), "\n" );
}
}
@@ -2433,8 +2521,8 @@ void TextDocument::deleteTo( const size_t& cursorIdx, TextPosition position ) {
}
void TextDocument::print() const {
for ( size_t i = 0; i < mLines.size(); i++ )
printf( "%s", mLines[i].toUtf8().c_str() );
for ( size_t i = 0; i < linesCount(); i++ )
printf( "%s", getLineTextUtf8( i ).c_str() );
}
TextRange TextDocument::sanitizeRange( const TextRange& range ) const {
@@ -2455,9 +2543,9 @@ TextRanges TextDocument::sanitizeRange( const TextRanges& ranges ) const {
}
bool TextDocument::isValidPosition( const TextPosition& position ) const {
return !( position.line() < 0 || position.line() > (Int64)mLines.size() - 1 ||
return !( position.line() < 0 || position.line() > (Int64)linesCount() - 1 ||
position.column() < 0 ||
position.column() > (Int64)mLines[position.line()].size() - 1 );
position.column() > (Int64)getLineLength( position.line() ) - 1 );
}
bool TextDocument::isValidRange( const TextRange& range ) const {
@@ -2498,9 +2586,10 @@ bool TextDocument::isSaving() const {
}
TextPosition TextDocument::sanitizePosition( const TextPosition& position ) const {
Int64 line = eeclamp<Int64>( position.line(), 0UL, mLines.size() ? mLines.size() - 1 : 0 );
auto lineCount = linesCount();
Int64 line = eeclamp<Int64>( position.line(), 0UL, lineCount ? lineCount - 1 : 0 );
Int64 col =
eeclamp<Int64>( position.column(), 0UL, eemax<Int64>( 0, mLines[line].size() - 1 ) );
eeclamp<Int64>( position.column(), 0UL, eemax<Int64>( 0, getLineLength( line ) - 1 ) );
return { line, col };
}
@@ -2877,7 +2966,7 @@ TextDocument::SearchResult TextDocument::find( const String& text, TextPosition
std::vector<String> textLines = type == FindReplaceType::Normal ? text.split( '\n', true, true )
: std::vector<String>{ text };
if ( textLines.empty() || textLines.size() > mLines.size() )
if ( textLines.empty() || textLines.size() > linesCount() )
return {};
from = sanitizePosition( from );
@@ -2907,7 +2996,7 @@ TextDocument::SearchResult TextDocument::find( const String& text, TextPosition
if ( initPos < from || initPos > to )
return find( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange );
String currentLine( mLines[initPos.line()].getText() );
String currentLine( getLineText( initPos.line() ) );
if ( TextPosition( initPos.line(), (Int64)currentLine.size() - 1 ) > to )
return find( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange );
@@ -2930,7 +3019,7 @@ TextDocument::SearchResult TextDocument::find( const String& text, TextPosition
if ( initPos < from || initPos > to )
return find( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange );
const String& lastLine = mLines[initPos.line()].getText();
const auto lastLine = getLineText( initPos.line() );
const String& curSearch = textLines[textLines.size() - 1];
if ( TextPosition( initPos.line(), (Int64)curSearch.size() - 1 ) > to )
@@ -2944,7 +3033,7 @@ TextDocument::SearchResult TextDocument::find( const String& text, TextPosition
String::startsWith( String( lastLine ).toLower(), String( curSearch ).toLower() ) ) ) {
TextRange foundRange( range.result.start(),
TextPosition( initPos.line(), curSearch.size() ) );
if ( foundRange.end().column() == (Int64)mLines[foundRange.end().line()].size() )
if ( foundRange.end().column() == (Int64)getLineLength( foundRange.end().line() ) )
foundRange.setEnd( positionOffset( foundRange.end(), 1 ) );
range.result = foundRange;
return range;
@@ -2960,7 +3049,7 @@ TextDocument::SearchResult TextDocument::findLast( const String& text, TextPosit
FindReplaceType type, TextRange restrictRange ) {
std::vector<String> textLines = text.split( '\n', true, true );
if ( textLines.empty() || textLines.size() > mLines.size() )
if ( textLines.empty() || textLines.size() > linesCount() )
return {};
from = sanitizePosition( from );
@@ -3013,7 +3102,7 @@ TextDocument::SearchResult TextDocument::findLast( const String& text, TextPosit
if ( initPos < from || initPos > to )
return findLast( text, range.result.end(), caseSensitive, wholeWord, type, restrictRange );
const String& lastLine = mLines[initPos.line()].getText();
const auto lastLine = getLineText( initPos.line() );
const String& curSearch = textLines[textLines.size() - 1];
if ( TextPosition( initPos.line(), (Int64)curSearch.size() - 1 ) > to )
@@ -3114,7 +3203,7 @@ int TextDocument::replaceAll( const String& text, const String& replace, const b
if ( found.isValid() ) {
if ( numCaptures && numCaptures <= found.captures.size() ) {
String finalReplace( replace );
std::string l( line( found.captures[0].start().line() ).toUtf8() );
std::string l( getLineTextUtf8( found.captures[0].start().line() ) );
for ( size_t i = 0; i < numCaptures; i++ ) {
String matchSubStr( replace.substr(
matchList[i].start, matchList[i].end - matchList[i].start ) ); // $1 $2 ...
@@ -3198,7 +3287,7 @@ TextPosition TextDocument::replace( String search, const String& replace, TextPo
if ( found.isValid() ) {
if ( numCaptures && numCaptures == found.captures.size() ) {
String finalReplace( replace );
std::string l( line( found.captures[0].start().line() ).toUtf8() );
std::string l( getLineTextUtf8( found.captures[0].start().line() ) );
for ( size_t i = 0; i < numCaptures; i++ ) {
String matchSubStr( replace.substr(
matchList[i].start, matchList[i].end - matchList[i].start ) ); // $1 $2 ...
@@ -3542,7 +3631,7 @@ void TextDocument::toggleLineComments() {
std::size_t startSpaces = std::string::npos;
for ( Int64 i = selection.start().line(); i <= selection.end().line(); i++ ) {
const String& text = mLines[i].getText();
const auto text = getLineText( i );
auto firstNonSpace = text.find_first_not_of( " \t\n" );
if ( firstNonSpace != std::string::npos && text[firstNonSpace] != '\n' ) {
startSpaces = eemin( startSpaces, firstNonSpace );
@@ -3562,7 +3651,7 @@ void TextDocument::toggleLineComments() {
bool swap = prevStart != range.start();
for ( Int64 lineIdx = selection.start().line(); lineIdx <= selection.end().line();
lineIdx++ ) {
const String& text = mLines[lineIdx].getText();
const auto text = getLineText( lineIdx );
auto pos = text.find( commentText, startSpaces );
if ( pos != std::string::npos ) {
remove( cursorIdx, { { lineIdx, static_cast<Int64>( pos ) },
@@ -3725,10 +3814,10 @@ void TextDocument::escape() {
resetCursor();
size_t linesCount = mLines.size();
for ( size_t i = 0; i < linesCount; i++ ) {
size_t lineCount = linesCount();
for ( size_t i = 0; i < lineCount; i++ ) {
Int64 lineIdx = static_cast<Int64>( i );
String newText( String::escape( mLines[i].getTextWithoutNewLine() ) );
String newText( String::escape( getLineTextWithoutNewLine( i ) ) );
setSelection( 0, { { lineIdx, 0 }, { lineIdx, (Int64)line( i ).size() - 1 } } );
deleteTo( 0, 0 );
setSelection( 0, insert( 0, getSelectionIndex( 0 ).start(), newText ) );
@@ -3754,10 +3843,10 @@ void TextDocument::unescape() {
resetCursor();
size_t linesCount = mLines.size();
for ( size_t i = 0; i < linesCount; i++ ) {
size_t lineCount = linesCount();
for ( size_t i = 0; i < lineCount; i++ ) {
Int64 lineIdx = static_cast<Int64>( i );
String newText( String::unescape( mLines[i].getTextWithoutNewLine() ) );
String newText( String::unescape( getLineTextWithoutNewLine( i ) ) );
setSelection( 0, { { lineIdx, 0 }, { lineIdx, (Int64)line( i ).size() - 1 } } );
deleteTo( 0, 0 );
setSelection( 0, insert( 0, getSelectionIndex( 0 ).start(), newText ) );
@@ -1782,4 +1782,13 @@ void UICodeEditorSplitter::updateTabWidgetVisualSplitting() {
}
}
std::shared_ptr<TextDocument> UICodeEditorSplitter::getTextDocumentRef( TextDocument* doc ) {
std::shared_ptr<TextDocument> ret;
forEachEditor( [doc, &ret]( UICodeEditor* editor ) {
if ( editor->getDocumentRef().get() == doc )
ret = editor->getDocumentRef();
} );
return ret;
}
}}} // namespace EE::UI::Tools
+28 -19
View File
@@ -204,13 +204,18 @@ UICodeEditor::~UICodeEditor() {
Sys::sleep( Milliseconds( 0.1 ) );
mDocView.setDocument( nullptr );
if ( mDoc.use_count() == 1 ) {
std::size_t clientsOfTypeCount = mDoc->clientOfTypeCount( TextDocument::Client::Type::Core );
long useCount = mDoc.use_count();
if ( clientsOfTypeCount == 1 || useCount == 1 ) {
getUISceneNode()->removeActionsByTag( mTagFoldRange );
DocEvent event( this, mDoc.get(), Event::OnDocumentClosed );
sendEvent( &event );
mDoc->unregisterClient( this );
mDoc.reset();
if ( useCount == 1 )
mDoc.reset();
} else {
mDoc->unregisterClient( this );
}
@@ -973,9 +978,12 @@ TextDocument& UICodeEditor::getDocument() {
void UICodeEditor::setDocument( std::shared_ptr<TextDocument> doc ) {
if ( mDoc.get() != doc.get() ) {
URI oldDocURI = mDoc->getURI();
std::size_t clientsOfTypeCount =
mDoc->clientOfTypeCount( TextDocument::Client::Type::Core );
long useCount = mDoc.use_count();
mDoc->unregisterClient( this );
mDocView.setDocument( nullptr );
if ( mDoc.use_count() == 1 )
if ( clientsOfTypeCount == 1 || useCount == 1 )
onDocumentClosed( mDoc.get() );
mDoc = doc;
mDoc->registerClient( this );
@@ -1187,8 +1195,6 @@ void UICodeEditor::setCodeEditorFlags( std::string flags, bool enable ) {
mFoldsAlwaysVisible = enable;
} else if ( "foldsvisible" == flag ) {
mFoldsVisible = enable;
} else if ( "flashcursor" == flag ) {
mEnableFlashCursor = enable;
} else if ( "defaultstyle" == flag ) {
mUseDefaultStyle = enable;
} else if ( !enable && "editorfeatures" == flag ) {
@@ -1242,8 +1248,6 @@ std::string UICodeEditor::getCodeEditorFlags( bool enabled ) const {
flags += "foldsalwaysvisible|";
if ( mFoldsVisible == enabled )
flags += "foldsvisible|";
if ( mEnableFlashCursor == enabled )
flags += "flashcursor|";
if ( mUseDefaultStyle == enabled )
flags += "defaultstyle|";
@@ -1956,9 +1960,9 @@ Float UICodeEditor::getLineWidth( const Int64& docLine ) {
Float width = 0;
if ( !isMonospaceLine ) {
auto& line = mDoc->line( docLine );
auto found = mLinesWidthCache.find( docLine );
if ( found != mLinesWidthCache.end() && line.getHash() == found->second.first )
if ( found != mLinesWidthCache.end() &&
mDoc->getLineHash( docLine ) == found->second.first )
return found->second.second;
}
@@ -2211,7 +2215,7 @@ void UICodeEditor::onDocumentLineMove( const Int64& fromLine, const Int64& toLin
auto lineIt = mLinesWidthCache.find( i - numLines );
if ( lineIt != mLinesWidthCache.end() ) {
const auto& line = lineIt->second;
if ( line.first == mDoc->line( i ).getHash() ) {
if ( line.first == mDoc->getLineHash( i ) ) {
auto nl = mLinesWidthCache.extract( lineIt );
nl.key() = i;
mLinesWidthCache.insert( std::move( nl ) );
@@ -2222,7 +2226,7 @@ void UICodeEditor::onDocumentLineMove( const Int64& fromLine, const Int64& toLin
for ( Int64 i = fromLine; i < linesCount; i++ ) {
auto lineIt = mLinesWidthCache.find( i - numLines );
if ( lineIt != mLinesWidthCache.end() &&
lineIt->second.first == mDoc->line( i ).getHash() ) {
lineIt->second.first == mDoc->getLineHash( i ) ) {
auto nl = mLinesWidthCache.extract( lineIt );
nl.key() = i;
mLinesWidthCache[i] = std::move( nl.mapped() );
@@ -2906,7 +2910,7 @@ std::string UICodeEditor::getPropertyString( const PropertyDefinition* propertyD
case PropertyId::LineWrapType:
return DocumentView::fromLineWrapType( getLineWrapType() );
case PropertyId::Text:
return mDoc->line( 0 ).toUtf8();
return mDoc->getLineTextUtf8( 0 );
default:
return UIWidget::getPropertyString( propertyDef, propertyIndex );
}
@@ -3030,12 +3034,13 @@ void UICodeEditor::checkMatchingBrackets() {
static const std::vector<String::StringBaseType> close{ '}', ')', ']' };
mMatchingBrackets = TextRange();
TextPosition pos = mDoc->sanitizePosition( mDoc->getSelection().start() );
TextDocumentLine& line = mDoc->line( pos.line() );
auto isOpenIt = std::find( open.begin(), open.end(), line[pos.column()] );
auto isCloseIt = std::find( close.begin(), close.end(), line[pos.column()] );
auto searchChar = mDoc->getChar( pos );
auto isOpenIt = std::find( open.begin(), open.end(), searchChar );
auto isCloseIt = std::find( close.begin(), close.end(), searchChar );
if ( ( isOpenIt == open.end() && isCloseIt == close.end() ) && pos.column() > 0 ) {
isOpenIt = std::find( open.begin(), open.end(), line[pos.column() - 1] );
isCloseIt = std::find( close.begin(), close.end(), line[pos.column() - 1] );
searchChar = mDoc->getChar( TextPosition( pos.line(), pos.column() - 1 ) );
isOpenIt = std::find( open.begin(), open.end(), searchChar );
isCloseIt = std::find( close.begin(), close.end(), searchChar );
if ( isOpenIt != open.end() ) {
pos.setColumn( pos.column() - 1 );
} else if ( isCloseIt != close.end() ) {
@@ -3786,7 +3791,9 @@ void UICodeEditor::drawLineText( const Int64& line, Vector2f position, const Flo
const Float& lineHeight,
const DocumentViewLineRange& visibleLineRange ) {
Vector2f originalPosition( position );
const auto& tokens = mDoc->getHighlighter()->getLine( line );
// const auto& tokens = mDoc->getHighlighter()->getLine( line );
mDoc->getHighlighter()->copyLineToBuffer( line, mTokens );
const auto& tokens = mTokens;
const auto& docLine = mDoc->line( line );
const String& strLine = docLine.getText();
Primitives primitives;
@@ -4827,7 +4834,9 @@ void UICodeEditor::drawMinimap( const Vector2f& start, const DocumentLineRange&,
if ( mHighlightWord.isEmpty() && !selectionString.empty() )
drawWordMatch( selectionString, line );
const auto& tokens = mDoc->getHighlighter()->getLine( line, false );
// const auto& tokens = mDoc->getHighlighter()->getLine( line, false );
mDoc->getHighlighter()->copyLineToBuffer( line, mTokens, false );
const auto& tokens = mTokens;
const auto& text = mDoc->line( line ).getText();
Int64 pos = 0;
bool wrappedLine = mDocView.isWrappedLine( line );
@@ -149,7 +149,7 @@ void AutoCompletePlugin::load( PluginManager* pluginManager ) {
Log::error(
"AutoCompletePlugin::load - Error parsing config from path %s, error: %s, config "
"file content:\n%s",
path.c_str(), e.what(), data.c_str() );
path, e.what(), data );
// Recreate it
j = json::parse( "{\n \"config\":{},\n \"keybindings\":{},\n}\n", nullptr, true, true );
}
@@ -610,7 +610,7 @@ void AutoCompletePlugin::updateDocCache( TextDocument* doc ) {
lang.insert( lang.end(), d.second.symbols.begin(), d.second.symbols.end() );
}
}
Log::debug( "Dictionary for %s updated in: %.2fms", doc->getFilename().c_str(),
Log::debug( "Dictionary for %s updated in: %.2fms", doc->getFilename(),
clock.getElapsedTime().asMilliseconds() );
}
@@ -624,7 +624,7 @@ void AutoCompletePlugin::updateLangCache( const std::string& langName ) {
if ( d.first->getSyntaxDefinition().getLanguageName() == langName )
lang.insert( lang.end(), d.second.symbols.begin(), d.second.symbols.end() );
}
Log::debug( "Lang dictionary for %s updated in: %.2fms", langName.c_str(),
Log::debug( "Lang dictionary for %s updated in: %.2fms", langName,
clock.getElapsedTime().asMilliseconds() );
}
@@ -882,7 +882,7 @@ AutoCompletePlugin::processSignatureHelp( const LSPSignatureHelp& signatureHelp
while ( 0 != doc.replaceAll( " ", " " ) )
;
nsig.label = doc.line( 0 ).getTextWithoutNewLine();
nsig.label = doc.getLineTextWithoutNewLine( 0 );
for ( const auto& param : parameters ) {
auto res = doc.find( param );
@@ -1394,17 +1394,22 @@ void AutoCompletePlugin::resetSignatureHelp() {
AutoCompletePlugin::SymbolsList AutoCompletePlugin::getDocumentSymbols( TextDocument* doc ) {
static constexpr auto MAX_LINE_LENGTH = EE_1KB * 10;
LuaPattern pattern( mSymbolPattern );
AutoCompletePlugin::SymbolsList symbols;
std::shared_ptr<TextDocument> docRef =
getPluginContext()->getSplitter()->getTextDocumentRef( doc ); // acquire a doc
if ( docRef == nullptr )
return symbols;
LuaPattern pattern( mSymbolPattern );
if ( doc->linesCount() == 0 || doc->isHuge() || mShuttingDown )
return symbols;
std::string current( getPartialSymbol( doc ) );
TextPosition end = doc->getSelection().end();
for ( Int64 i = 0; i < static_cast<Int64>( doc->linesCount() ); i++ ) {
const auto& line = doc->line( i );
if ( line.size() > MAX_LINE_LENGTH )
auto lineCount = doc->linesCount();
std::string string;
for ( Int64 i = 0; i < static_cast<Int64>( lineCount ); i++ ) {
if ( doc->getLineLength( i ) > MAX_LINE_LENGTH )
continue;
auto string = line.toUtf8();
doc->getLineTextToBufferUtf8( i, string );
for ( auto& match : pattern.gmatch( string ) ) {
std::string matchStr( match[0] );
// Ignore the symbol if is actually the current symbol being written
@@ -1416,7 +1421,7 @@ AutoCompletePlugin::SymbolsList AutoCompletePlugin::getDocumentSymbols( TextDocu
} ) )
symbols.push_back( std::move( matchStr ) );
}
if ( mShuttingDown )
if ( mShuttingDown || mDocs.find( doc ) == mDocs.end() )
break;
}
return symbols;
@@ -10,7 +10,6 @@
#include <eepp/system/sys.hpp>
#include <eepp/system/threadpool.hpp>
#include <eepp/ui/uicodeeditor.hpp>
#include <set>
#include <unordered_map>
using namespace EE;
using namespace EE::System;
@@ -125,7 +124,7 @@ class AutoCompletePlugin : public Plugin {
Mutex mDocMutex;
Time mUpdateFreq{ Seconds( 5 ) };
std::unordered_map<UICodeEditor*, std::vector<Uint32>> mEditors;
std::set<TextDocument*> mDocs;
std::unordered_set<TextDocument*> mDocs;
std::unordered_map<UICodeEditor*, TextDocument*> mEditorDocs;
bool mDirty{ false };
bool mReplacing{ false };
@@ -152,6 +152,7 @@ class DebuggerPlugin : public PluginBase {
virtual void onDocumentDirtyOnFileSystem( TextDocument* ) {}
virtual void onDocumentMoved( TextDocument* ) {}
virtual void onDocumentReset( TextDocument* ) {}
Client::Type getTextDocumentClientType() { return TextDocument::Client::Auxiliary; }
virtual void onDocumentLineMove( const Int64& fromLine, const Int64& toLine,
const Int64& numLines ) {
@@ -544,7 +544,7 @@ PluginRequestHandle LinterPlugin::processMessage( const PluginMessage& notificat
match.range = diag.range;
match.text = diag.message;
match.type = getLinterTypeFromSeverity( diag.severity );
match.lineCache = doc->line( match.range.start().line() ).getHash();
match.lineCache = doc->getLineHash( match.range.start().line() );
match.origin = MatchOrigin::Diagnostics;
match.diagnostic = std::move( diag );
matches[match.range.start().line()].emplace_back( std::move( match ) );
@@ -916,7 +916,7 @@ void LinterPlugin::runLinter( std::shared_ptr<TextDocument> doc, const Linter& l
linterMatch.range.setStart(
{ line > 0 ? line - 1 : 0, col > 0 ? col - 1 : 0 } );
const String& text = doc->line( linterMatch.range.start().line() ).getText();
String text = doc->getLineText( linterMatch.range.start().line() );
size_t minCol =
text.find_first_not_of( " \t\f\v\n\r", linterMatch.range.start().column() );
if ( minCol == String::InvalidPos )
@@ -937,7 +937,7 @@ void LinterPlugin::runLinter( std::shared_ptr<TextDocument> doc, const Linter& l
linterMatch.range.setEnd( endPos );
linterMatch.range = linterMatch.range.normalized();
linterMatch.lineCache = doc->line( linterMatch.range.start().line() ).getHash();
linterMatch.lineCache = doc->getLineHash( linterMatch.range.start().line() );
bool skip = false;
if ( linter.deduplicate && matches.find( line - 1 ) != matches.end() ) {
@@ -1033,7 +1033,7 @@ void LinterPlugin::drawAfterLineText( UICodeEditor* editor, const Int64& index,
if ( isDuplicate )
continue;
if ( match.lineCache != doc->line( index ).getHash() )
if ( match.lineCache != doc->getLineHash( index ) )
return;
Text line( "", editor->getFont(), editor->getFontSize() );
@@ -1133,7 +1133,7 @@ void LinterPlugin::minimapDrawBefore( UICodeEditor* editor, const DocumentLineRa
for ( const auto& matches : matchIt->second ) {
for ( const auto& match : matches.second ) {
if ( match.range.intersectsLineRange( docLineRange ) ) {
if ( match.lineCache != doc->line( match.range.start().line() ).getHash() )
if ( match.lineCache != doc->getLineHash( match.range.start().line() ) )
return;
Color col( editor->getColorScheme()
.getEditorSyntaxStyle( getMatchString( match.type ) )
@@ -1449,7 +1449,7 @@ void LinterPlugin::registerNativeLinters() {
doc->previousWordBoundary( match.range.start(), false ) };
match.text = result.description();
match.type = getLinterTypeFromSeverity( LSPDiagnosticSeverity::Error );
match.lineCache = doc->line( match.range.start().line() ).getHash();
match.lineCache = doc->getLineHash( match.range.start().line() );
match.origin = MatchOrigin::Linter;
matches[line] = { match };
}
@@ -1655,6 +1655,15 @@ bool LSPClientServer::hasDocument( const URI& uri ) const {
return false;
}
bool LSPClientServer::hasDocumentClient( LSPDocumentClient* cl ) const {
Lock l( mClientsMutex );
for ( const auto& client : mClients ) {
if ( client.second && client.second.get() == cl )
return true;
}
return false;
}
bool LSPClientServer::hasDocuments() const {
return !mDocs.empty();
}
@@ -141,6 +141,8 @@ class LSPClientServer {
bool hasDocument( const URI& uri ) const;
bool hasDocumentClient( LSPDocumentClient* client ) const;
bool hasDocuments() const;
LSPRequestHandle didChangeWorkspaceFolders( const std::vector<LSPWorkspaceFolder>& added,
@@ -271,7 +273,7 @@ class LSPClientServer {
std::unordered_map<TextDocument*, std::unique_ptr<LSPDocumentClient>> mClients;
using HandlersMap = std::map<PluginIDType, std::pair<JsonReplyHandler, JsonReplyHandler>>;
HandlersMap mHandlers;
Mutex mClientsMutex;
mutable Mutex mClientsMutex;
Mutex mHandlersMutex;
bool mReady{ false };
bool mEnded{ false };
@@ -394,7 +394,7 @@ void LSPClientServerManager::sendSymbolReferenceBroadcast( const std::vector<LSP
auto curDoc = mPluginManager->getSplitter()->findDocFromURI( r.uri );
if ( curDoc ) {
ProjectSearch::ResultData::Result rs( curDoc->line( r.range.start().line() ).getText(),
ProjectSearch::ResultData::Result rs( curDoc->getLineText( r.range.start().line() ),
r.range, -1, -1 );
rd.results.emplace_back( std::move( rs ) );
@@ -406,14 +406,14 @@ void LSPClientServerManager::sendSymbolReferenceBroadcast( const std::vector<LSP
if ( TextDocument::LoadStatus::Loaded != doc->loadFromFile( fspath ) )
continue;
lineText = doc->line( r.range.start().line() ).getText();
lineText = doc->getLineText( r.range.start().line() );
tmpDocs.insert( { std::move( fspath ), std::move( doc ) } );
} else {
lineText = foundDoc->second->line( r.range.start().line() ).getText();
lineText = foundDoc->second->getLineText( r.range.start().line() );
}
ProjectSearch::ResultData::Result rs( lineText, r.range, -1, -1 );
ProjectSearch::ResultData::Result rs( std::move( lineText ), r.range, -1, -1 );
rd.results.emplace_back( std::move( rs ) );
}
@@ -5,6 +5,7 @@
#include <eepp/system/filesystem.hpp>
#include <eepp/system/iostreamstring.hpp>
#include <eepp/system/log.hpp>
#include <eepp/system/scopedop.hpp>
#include <eepp/ui/doc/syntaxhighlighter.hpp>
#include <eepp/window/engine.hpp>
@@ -44,7 +45,7 @@ LSPDocumentClient::~LSPDocumentClient() {
if ( nullptr != sceneNode && 0 != mTagSemanticTokens )
sceneNode->removeActionsByTag( mTagSemanticTokens );
mShutdown = true;
while ( mRunningSemanticTokens )
while ( mRunningSemanticTokens || mProcessingSemanticTokensResponse )
Sys::sleep( Milliseconds( 0.1f ) );
}
@@ -174,8 +175,9 @@ void LSPDocumentClient::requestSemanticHighlighting( bool reqFull ) {
Uint64 docModId = mDoc->getModificationId();
mServer->documentSemanticTokensFull(
mDoc->getURI(), delta, reqId, range,
[docClient, uri, server, docModId]( const auto&, LSPSemanticTokensDelta&& deltas ) {
if ( server->hasDocument( uri ) ) {
[docClient, uri, server, docModId, this]( const auto&, LSPSemanticTokensDelta&& deltas ) {
BoolScopedOp op( mProcessingSemanticTokensResponse, true );
if ( server->hasDocumentClient( docClient ) && server->hasDocument( uri ) ) {
docClient->mWaitingSemanticTokensResponse = false;
docClient->processTokens( std::move( deltas ), docModId );
}
@@ -285,7 +287,7 @@ void LSPDocumentClient::processTokens( LSPSemanticTokensDelta&& tokens,
if ( docModificationId != mDoc->getModificationId() )
return requestSemanticHighlightingDelayed();
mRunningSemanticTokens = true;
BoolScopedOp op( mRunningSemanticTokens, true );
if ( !tokens.resultId.empty() )
mSemanticeResultId = tokens.resultId;
@@ -297,6 +299,9 @@ void LSPDocumentClient::processTokens( LSPSemanticTokensDelta&& tokens,
curTokens.begin() + edit.start + edit.deleteCount );
}
curTokens.insert( curTokens.begin() + edit.start, edit.data.begin(), edit.data.end() );
if ( mShutdown )
return;
}
if ( !tokens.data.empty() ) {
@@ -304,8 +309,6 @@ void LSPDocumentClient::processTokens( LSPSemanticTokensDelta&& tokens,
}
highlight();
mRunningSemanticTokens = false;
}
void LSPDocumentClient::highlight() {
@@ -351,7 +354,7 @@ void LSPDocumentClient::highlight() {
} else {
line->tokens.push_back( { SyntaxStyleTypes::Normal, start, len } );
}
line->hash = mDoc->line( currentLine ).getHash();
line->hash = mDoc->getLineHash( currentLine );
line->updateSignature();
auto curSignature = mDoc->getHighlighter()->getTokenizedLineSignature( lastLine );
@@ -39,6 +39,7 @@ class LSPDocumentClient : public TextDocument::Client, public FoldRangeProvider
virtual void onDocumentMoved( TextDocument* );
virtual void onDocumentReloaded( TextDocument* );
virtual void onDocumentReset( TextDocument* );
Client::Type getTextDocumentClientType() { return TextDocument::Client::Auxiliary; }
void notifyOpen();
@@ -82,6 +83,7 @@ class LSPDocumentClient : public TextDocument::Client, public FoldRangeProvider
std::vector<LSPCodeLens> mCodeLens;
bool mRunningSemanticTokens{ false };
bool mWaitingSemanticTokensResponse{ false };
bool mProcessingSemanticTokensResponse{ false };
bool mShutdown{ false };
bool mFirstHighlight{ true };
@@ -84,6 +84,7 @@ class XMLToolsPlugin : public PluginBase {
virtual void onDocumentDirtyOnFileSystem( TextDocument* ) {}
virtual void onDocumentMoved( TextDocument* ) {};
virtual void onDocumentReset( TextDocument* ) { mSelections.clear(); }
Client::Type getTextDocumentClientType() { return TextDocument::Client::Auxiliary; }
protected:
TextDocument* mDoc{ nullptr };
+5 -5
View File
@@ -74,7 +74,7 @@ searchInFileHorspool( const std::string& file, const std::string& text, const bo
totNl += countNewLines( fileText, lSearchRes, searchRes );
String str(
textLine( caseSensitive ? fileText : fileTextOriginal, searchRes, relCol ) );
res.push_back( { str,
res.push_back( { std::move( str ),
{ { (Int64)totNl, (Int64)relCol },
{ (Int64)totNl, (Int64)( relCol + String::utf8Length( text ) ) } },
searchRes,
@@ -168,11 +168,11 @@ ProjectSearch::fileResFromDoc( const std::string& string, bool caseSensitive, bo
for ( const auto& r : res ) {
ProjectSearch::ResultData::Result f;
f.position = r.result;
const auto& line = doc->line( r.result.start().line() );
if ( line.size() > EE_1KB )
f.line = line.substr( 0, EE_1KB );
auto lineLen = doc->getLineLength( r.result.start().line() );
if ( lineLen > EE_1KB )
f.line = doc->getLineTextSubStr( r.result.start().line(), 0, EE_1KB );
else
f.line = line.getTextWithoutNewLine();
f.line = doc->getLineTextWithoutNewLine( r.result.start().line() );
f.start = r.result.start().column();
f.end = r.result.end().column();
std::vector<std::string> captures;
+2 -2
View File
@@ -39,8 +39,8 @@ class ProjectSearch {
struct ResultData {
struct Result {
Result( const String& line, const TextRange& pos, Int64 s, Int64 e ) :
line( line ), position( pos ), start( s ), end( e ) {}
Result( String&& line, const TextRange& pos, Int64 s, Int64 e ) :
line( std::move( line ) ), position( pos ), start( s ), end( e ) {}
Result() {}
String line;
TextRange position;