mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-06-05 04:56:31 +03:00
728 lines
21 KiB
C++
728 lines
21 KiB
C++
#include <eepp/core/debug.hpp>
|
|
#include <eepp/ui/doc/textdocument.hpp>
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
namespace EE { namespace UI { namespace Doc {
|
|
|
|
const char NON_WORD_CHARS[] = " \t\n/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-";
|
|
|
|
bool TextDocument::isNonWord( String::StringBaseType ch ) {
|
|
for ( size_t i = 0; i < eeARRAY_SIZE( NON_WORD_CHARS ); i++ ) {
|
|
if ( static_cast<String::StringBaseType>( NON_WORD_CHARS[i] ) == ch ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TextDocument::TextDocument() : mUndoStack( this ) {}
|
|
|
|
void TextDocument::reset() {
|
|
mFilename = "unsaved";
|
|
mSelection.set( {0, 0}, {0, 0} );
|
|
mLines.clear();
|
|
notifyTextChanged();
|
|
notifyCursorChanged();
|
|
notifySelectionChanged();
|
|
}
|
|
|
|
void TextDocument::loadFromPath( const std::string& path ) {
|
|
reset();
|
|
mFilename = path;
|
|
std::string line;
|
|
std::ifstream file( path );
|
|
while ( std::getline( file, line ) ) {
|
|
std::istringstream iss( line );
|
|
if ( mLines.empty() && line.size() >= 3 ) {
|
|
// Check UTF-8 BOM header
|
|
if ( (char)0xef == line[0] && (char)0xbb == line[1] && (char)0xbf == line[2] ) {
|
|
line = line.substr( 3 );
|
|
}
|
|
}
|
|
// Check CLRF
|
|
if ( !line.empty() && line[line.size() - 1] == '\r' ) {
|
|
line = line.substr( 0, line.size() - 1 );
|
|
mIsCLRF = true;
|
|
}
|
|
mLines.emplace_back( String( line + "\n" ) );
|
|
}
|
|
if ( mLines.empty() ) {
|
|
mLines.emplace_back( String( "\n" ) );
|
|
} else if ( mLines[mLines.size() - 1].at( mLines[mLines.size() - 1].size() - 1 ) == '\n' ) {
|
|
mLines.emplace_back( String( "\n" ) );
|
|
}
|
|
}
|
|
|
|
void TextDocument::save( const std::string& ) {}
|
|
|
|
const std::string TextDocument::getFilename() const {
|
|
return mFilename;
|
|
}
|
|
|
|
void TextDocument::setSelection( TextPosition position ) {
|
|
setSelection( position, position );
|
|
}
|
|
|
|
void TextDocument::setSelection( TextPosition start, TextPosition end, bool swap ) {
|
|
if ( ( start == mSelection.start() && end == mSelection.end() && !swap ) ||
|
|
( start == mSelection.end() && end == mSelection.start() && swap ) )
|
|
return;
|
|
|
|
if ( swap ) {
|
|
auto posT = start;
|
|
start = end;
|
|
end = posT;
|
|
}
|
|
|
|
if ( start == end ) {
|
|
start = end = sanitizePosition( start );
|
|
} else {
|
|
start = sanitizePosition( start );
|
|
end = sanitizePosition( end );
|
|
}
|
|
|
|
if ( mSelection != TextRange( start, end ) ) {
|
|
mSelection.set( start, end );
|
|
notifyCursorChanged();
|
|
notifySelectionChanged();
|
|
}
|
|
}
|
|
|
|
void TextDocument::setSelection( TextRange range ) {
|
|
setSelection( range.start(), range.end() );
|
|
}
|
|
|
|
TextRange TextDocument::getSelection( bool sort ) const {
|
|
return sort ? mSelection.normalized() : mSelection;
|
|
}
|
|
|
|
const TextRange& TextDocument::getSelection() const {
|
|
return mSelection;
|
|
}
|
|
|
|
String& TextDocument::line( const size_t& index ) {
|
|
return mLines[index];
|
|
}
|
|
|
|
const String& TextDocument::line( const size_t& index ) const {
|
|
return mLines[index];
|
|
}
|
|
|
|
size_t TextDocument::lineCount() const {
|
|
return mLines.size();
|
|
}
|
|
|
|
std::vector<String>& TextDocument::lines() {
|
|
return mLines;
|
|
}
|
|
|
|
bool TextDocument::hasSelection() const {
|
|
return mSelection.start() != mSelection.end();
|
|
}
|
|
|
|
String TextDocument::getText( const TextRange& range ) const {
|
|
TextRange nrange = range.normalized();
|
|
if ( nrange.start().line() == nrange.end().line() ) {
|
|
return mLines[nrange.start().line()].substr(
|
|
nrange.start().column(), nrange.end().column() - nrange.start().column() );
|
|
}
|
|
std::vector<String> lines = {mLines[nrange.start().line()].substr( nrange.start().column() )};
|
|
for ( auto i = nrange.start().line() + 1; i <= nrange.end().line() - 1; i++ ) {
|
|
lines.emplace_back( mLines[i] );
|
|
}
|
|
lines.emplace_back( mLines[nrange.end().line()].substr( 0, nrange.end().column() ) );
|
|
return String::join( lines, -1 );
|
|
}
|
|
|
|
String TextDocument::getSelectedText() const {
|
|
return getText( getSelection() );
|
|
}
|
|
|
|
String::StringBaseType TextDocument::getChar( const TextPosition& position ) const {
|
|
auto pos = sanitizePosition( position );
|
|
return mLines[pos.line()][pos.column()];
|
|
}
|
|
|
|
TextPosition TextDocument::insert( const TextPosition& position, const String& text ) {
|
|
mUndoStack.clearRedoStack();
|
|
return insert( position, text, mUndoStack.getUndoStackContainer(), mTimer.getElapsedTime() );
|
|
}
|
|
|
|
TextPosition TextDocument::insert( const TextPosition& position, const String& text,
|
|
UndoStackContainer& undoStack, const Time& time ) {
|
|
TextPosition cursor = position;
|
|
|
|
for ( size_t i = 0; i < text.length(); ++i ) {
|
|
cursor = insert( cursor, text[i] );
|
|
}
|
|
|
|
mUndoStack.pushSelection( undoStack, getSelection(), time );
|
|
mUndoStack.pushRemove( undoStack, {position, cursor}, time );
|
|
|
|
notifyTextChanged();
|
|
|
|
return cursor;
|
|
}
|
|
|
|
TextPosition TextDocument::insert( TextPosition position, const String::StringBaseType& ch ) {
|
|
position = sanitizePosition( position );
|
|
bool atHead = position.column() == 0;
|
|
bool atTail = position.column() == (Int64)line( position.line() ).length() - 1;
|
|
if ( ch == '\n' ) {
|
|
if ( atTail || atHead ) {
|
|
size_t row = position.line();
|
|
String line_content;
|
|
for ( size_t i = position.column(); i < line( row ).length(); i++ )
|
|
line_content.append( line( row )[i] );
|
|
mLines.insert( mLines.begin() + position.line() + ( atTail ? 1 : 0 ), String( "\n" ) );
|
|
return atTail
|
|
? TextPosition( position.line() + 1, line( position.line() + 1 ).length() )
|
|
: TextPosition( position.line() + 1, 0 );
|
|
}
|
|
String newLine =
|
|
line( position.line() )
|
|
.substr( position.column(), line( position.line() ).length() - position.column() );
|
|
line( position.line() ) = line( position.line() ).substr( 0, position.column() );
|
|
if ( newLine.empty() ) {
|
|
eePRINTL( "wtf" );
|
|
}
|
|
mLines.insert( mLines.begin() + position.line() + 1, std::move( newLine ) );
|
|
return {position.line() + 1, 0};
|
|
}
|
|
line( position.line() ).insert( line( position.line() ).begin() + position.column(), ch );
|
|
return {position.line(), position.column() + 1};
|
|
}
|
|
|
|
void TextDocument::remove( TextPosition position ) {
|
|
remove( TextRange( position, position ) );
|
|
}
|
|
|
|
void TextDocument::remove( TextRange range ) {
|
|
mUndoStack.clearRedoStack();
|
|
range = range.normalized();
|
|
range.setStart( sanitizePosition( range.start() ) );
|
|
range.setEnd( sanitizePosition( range.end() ) );
|
|
remove( range, mUndoStack.getUndoStackContainer(), mTimer.getElapsedTime() );
|
|
}
|
|
|
|
void TextDocument::remove( TextRange range, UndoStackContainer& undoStack, const Time& time ) {
|
|
if ( !range.isValid() )
|
|
return;
|
|
|
|
mUndoStack.pushSelection( undoStack, getSelection(), time );
|
|
mUndoStack.pushInsert( undoStack, getText( range ), range.start(), time );
|
|
|
|
// First delete all the lines in between the first and last one.
|
|
for ( auto i = range.start().line() + 1; i < range.end().line(); ) {
|
|
mLines.erase( mLines.begin() + i );
|
|
range.end().setLine( range.end().line() - 1 );
|
|
}
|
|
|
|
if ( range.start().line() == range.end().line() ) {
|
|
// Delete within same line.
|
|
auto& line = this->line( range.start().line() );
|
|
bool wholeLineIsSelected =
|
|
range.start().column() == 0 && range.end().column() == (Int64)line.length();
|
|
|
|
if ( wholeLineIsSelected ) {
|
|
line.clear();
|
|
} else {
|
|
auto beforeSelection = line.substr( 0, range.start().column() );
|
|
auto afterSelection =
|
|
line.substr( range.end().column(), line.length() - range.end().column() );
|
|
line.assign( beforeSelection + afterSelection );
|
|
}
|
|
} else {
|
|
// Delete across a newline, merging lines.
|
|
eeASSERT( range.start().line() == range.end().line() - 1 );
|
|
auto& firstLine = line( range.start().line() );
|
|
auto& secondLine = line( range.end().line() );
|
|
auto beforeSelection = firstLine.substr( 0, range.start().column() );
|
|
auto afterSelection =
|
|
secondLine.substr( range.end().column(), secondLine.length() - range.end().column() );
|
|
if ( beforeSelection.empty() && afterSelection.empty() ) {
|
|
beforeSelection += '\n';
|
|
}
|
|
firstLine.assign( beforeSelection + afterSelection );
|
|
mLines.erase( mLines.begin() + range.end().line() );
|
|
}
|
|
|
|
if ( lines().empty() ) {
|
|
mLines.emplace_back( String( "\n" ) );
|
|
}
|
|
notifyTextChanged();
|
|
}
|
|
|
|
TextPosition TextDocument::positionOffset( TextPosition position, int columnOffset ) const {
|
|
position = sanitizePosition( position );
|
|
position.setColumn( position.column() + columnOffset );
|
|
while ( position.line() > 0 && position.column() < 0 ) {
|
|
position.setLine( position.line() - 1 );
|
|
position.setColumn( eemax<Int64>( 0, position.column() + mLines[position.line()].size() ) );
|
|
}
|
|
while ( position.line() < (Int64)mLines.size() &&
|
|
position.column() > (Int64)eemax<Int64>( 0, mLines[position.line()].size() - 1 ) ) {
|
|
position.setColumn( position.column() - mLines[position.line()].size() - 1 );
|
|
position.setLine( position.line() + 1 );
|
|
}
|
|
return sanitizePosition( position );
|
|
}
|
|
|
|
TextPosition TextDocument::positionOffset( TextPosition position, TextPosition offset ) const {
|
|
return sanitizePosition( position + offset );
|
|
}
|
|
|
|
TextPosition TextDocument::nextChar( TextPosition position ) const {
|
|
return positionOffset( position, TextPosition( 0, 1 ) );
|
|
}
|
|
|
|
TextPosition TextDocument::previousChar( TextPosition position ) const {
|
|
return positionOffset( position, TextPosition( 0, -1 ) );
|
|
}
|
|
|
|
TextPosition TextDocument::previousWordBoundary( TextPosition position ) const {
|
|
auto ch = getChar( positionOffset( position, -1 ) );
|
|
bool inWord = !isNonWord( ch );
|
|
String::StringBaseType nextChar = 0;
|
|
do {
|
|
TextPosition curPos = position;
|
|
position = positionOffset( position, -1 );
|
|
if ( curPos == position ) {
|
|
break;
|
|
}
|
|
nextChar = getChar( positionOffset( position, -1 ) );
|
|
} while ( ( inWord && !isNonWord( nextChar ) ) || ( !inWord && nextChar == ch ) );
|
|
return position;
|
|
}
|
|
|
|
TextPosition TextDocument::nextWordBoundary( TextPosition position ) const {
|
|
auto ch = getChar( position );
|
|
bool inWord = !isNonWord( ch );
|
|
String::StringBaseType nextChar = 0;
|
|
do {
|
|
TextPosition curPos = position;
|
|
position = positionOffset( position, 1 );
|
|
if ( curPos == position ) {
|
|
break;
|
|
}
|
|
nextChar = getChar( position );
|
|
} while ( ( inWord && !isNonWord( nextChar ) ) || ( !inWord && nextChar == ch ) );
|
|
return position;
|
|
}
|
|
|
|
TextPosition TextDocument::startOfWord( TextPosition position ) const {
|
|
while ( true ) {
|
|
TextPosition curPos = positionOffset( position, -1 );
|
|
String::StringBaseType ch = getChar( curPos );
|
|
if ( isNonWord( ch ) or position == curPos ) {
|
|
break;
|
|
}
|
|
position = curPos;
|
|
}
|
|
return position;
|
|
}
|
|
|
|
TextPosition TextDocument::endOfWord( TextPosition position ) const {
|
|
while ( true ) {
|
|
TextPosition curPos = positionOffset( position, 1 );
|
|
String::StringBaseType ch = getChar( position );
|
|
if ( isNonWord( ch ) or position == curPos ) {
|
|
break;
|
|
}
|
|
position = curPos;
|
|
}
|
|
return position;
|
|
}
|
|
|
|
TextPosition TextDocument::startOfLine( TextPosition position ) const {
|
|
position = sanitizePosition( position );
|
|
return TextPosition( position.line(), 0 );
|
|
}
|
|
|
|
TextPosition TextDocument::endOfLine( TextPosition position ) const {
|
|
position = sanitizePosition( position );
|
|
return TextPosition( position.line(), mLines[position.line()].size() - 1 );
|
|
}
|
|
|
|
TextPosition TextDocument::startOfDoc() const {
|
|
return TextPosition( 0, 0 );
|
|
}
|
|
|
|
TextPosition TextDocument::endOfDoc() const {
|
|
return TextPosition( mLines.size() - 1, mLines[mLines.size() - 1].size() - 1 );
|
|
}
|
|
|
|
TextPosition TextDocument::getAbsolutePosition( TextPosition position ) const {
|
|
position = sanitizePosition( position );
|
|
const String& string = line( position.line() );
|
|
size_t tabCount = string.substr( 0, position.column() ).countChar( '\t' );
|
|
return TextPosition( position.line(), position.column() - tabCount + tabCount * getTabWidth() );
|
|
}
|
|
|
|
Int64 TextDocument::getRelativeColumnOffset( TextPosition position ) const {
|
|
const String& line = mLines[position.line()];
|
|
Int64 length = eemin<Int64>( position.column(), line.size() - 1 );
|
|
Int64 offset = 0;
|
|
for ( Int64 i = 0; i <= length; ++i ) {
|
|
if ( offset >= position.column() ) {
|
|
return i;
|
|
}
|
|
if ( line[i] == '\t' ) {
|
|
offset += getTabWidth();
|
|
} else if ( line[i] != '\n' && line[i] != '\r' ) {
|
|
offset += 1;
|
|
}
|
|
}
|
|
return length;
|
|
}
|
|
|
|
void TextDocument::deleteTo( int offset ) {
|
|
TextPosition cursorPos = getSelection( true ).start();
|
|
if ( hasSelection() ) {
|
|
remove( getSelection() );
|
|
} else {
|
|
TextPosition delPos = positionOffset( cursorPos, offset );
|
|
TextRange range( cursorPos, delPos );
|
|
remove( range );
|
|
range = range.normalized();
|
|
cursorPos = range.start();
|
|
}
|
|
setSelection( cursorPos );
|
|
}
|
|
|
|
void TextDocument::deleteSelection() {
|
|
TextPosition cursorPos = getSelection( true ).start();
|
|
remove( getSelection() );
|
|
setSelection( cursorPos );
|
|
}
|
|
|
|
void TextDocument::selectTo( TextPosition position ) {
|
|
setSelection( TextRange( sanitizePosition( position ), getSelection().end() ) );
|
|
}
|
|
|
|
void TextDocument::selectTo( int offset ) {
|
|
const TextRange& range = getSelection();
|
|
TextPosition posOffset = positionOffset( range.start(), offset );
|
|
setSelection( TextRange( posOffset, range.end() ) );
|
|
}
|
|
|
|
void TextDocument::moveTo( TextPosition offset ) {
|
|
setSelection( getSelection().start() + offset );
|
|
}
|
|
|
|
void TextDocument::moveTo( int columnOffset ) {
|
|
setSelection( positionOffset( getSelection().start(), columnOffset ) );
|
|
}
|
|
|
|
void TextDocument::textInput( const String& text ) {
|
|
if ( hasSelection() ) {
|
|
deleteTo( 0 );
|
|
}
|
|
setSelection( insert( getSelection().start(), text ) );
|
|
}
|
|
|
|
void TextDocument::registerClient( TextDocument::Client& client ) {
|
|
mClients.insert( &client );
|
|
}
|
|
|
|
void TextDocument::unregisterClient( TextDocument::Client& client ) {
|
|
mClients.erase( &client );
|
|
}
|
|
|
|
void TextDocument::moveToPreviousChar() {
|
|
if ( hasSelection() ) {
|
|
TextRange selection = getSelection( true );
|
|
setSelection( selection.end() );
|
|
} else {
|
|
setSelection( positionOffset( getSelection().start(), -1 ) );
|
|
}
|
|
}
|
|
|
|
void TextDocument::moveToNextChar() {
|
|
if ( hasSelection() ) {
|
|
TextRange selection = getSelection( true );
|
|
setSelection( selection.start() );
|
|
} else {
|
|
setSelection( positionOffset( getSelection().start(), 1 ) );
|
|
}
|
|
}
|
|
|
|
void TextDocument::moveToPreviousWord() {
|
|
if ( hasSelection() ) {
|
|
TextRange selection = getSelection( true );
|
|
setSelection( selection.end() );
|
|
} else {
|
|
setSelection( previousWordBoundary( getSelection().start() ) );
|
|
}
|
|
}
|
|
|
|
void TextDocument::moveToNextWord() {
|
|
if ( hasSelection() ) {
|
|
TextRange selection = getSelection( true );
|
|
setSelection( selection.start() );
|
|
} else {
|
|
setSelection( nextWordBoundary( getSelection().start() ) );
|
|
}
|
|
}
|
|
|
|
void TextDocument::moveToPreviousLine( Int64 lastColIndex ) {
|
|
TextPosition pos = getSelection().start();
|
|
pos.setLine( pos.line() - 1 );
|
|
if ( pos.line() >= 0 ) {
|
|
lastColIndex = getRelativeColumnOffset( TextPosition( pos.line(), lastColIndex ) );
|
|
}
|
|
pos.setColumn( lastColIndex );
|
|
setSelection( pos );
|
|
}
|
|
|
|
void TextDocument::moveToNextLine( Int64 lastColIndex ) {
|
|
TextPosition pos = getSelection().start();
|
|
pos.setLine( pos.line() + 1 );
|
|
if ( pos.line() < (Int64)mLines.size() ) {
|
|
lastColIndex = getRelativeColumnOffset( TextPosition( pos.line(), lastColIndex ) );
|
|
}
|
|
pos.setColumn( lastColIndex );
|
|
setSelection( pos );
|
|
}
|
|
|
|
void TextDocument::moveToPreviousPage( Int64 pageSize ) {
|
|
TextPosition pos = getSelection().start();
|
|
pos.setLine( pos.line() - pageSize );
|
|
setSelection( pos );
|
|
}
|
|
|
|
void TextDocument::moveToNextPage( Int64 pageSize ) {
|
|
TextPosition pos = getSelection().start();
|
|
pos.setLine( pos.line() + pageSize );
|
|
setSelection( pos );
|
|
}
|
|
|
|
void TextDocument::deleteToPreviousChar() {
|
|
deleteTo( -1 );
|
|
}
|
|
|
|
void TextDocument::deleteToNextChar() {
|
|
deleteTo( 1 );
|
|
}
|
|
|
|
void TextDocument::deleteToPreviousWord() {
|
|
deleteTo( previousWordBoundary( getSelection().start() ) );
|
|
}
|
|
|
|
void TextDocument::deleteToNextWord() {
|
|
deleteTo( nextWordBoundary( getSelection().start() ) );
|
|
}
|
|
|
|
void TextDocument::selectToPreviousChar() {
|
|
selectTo( -1 );
|
|
}
|
|
|
|
void TextDocument::selectToNextChar() {
|
|
selectTo( 1 );
|
|
}
|
|
|
|
void TextDocument::selectToPreviousWord() {
|
|
setSelection( {previousWordBoundary( getSelection().start() ), getSelection().end()} );
|
|
}
|
|
|
|
void TextDocument::selectToNextWord() {
|
|
setSelection( {nextWordBoundary( getSelection().start() ), getSelection().end()} );
|
|
}
|
|
|
|
void TextDocument::selectWord() {
|
|
setSelection( {nextWordBoundary( getSelection().start() ),
|
|
previousWordBoundary( getSelection().start() )} );
|
|
}
|
|
|
|
void TextDocument::selectToPreviousLine( Int64 lastColIndex ) {
|
|
TextPosition pos = getSelection().start();
|
|
pos.setLine( pos.line() - 1 );
|
|
if ( pos.line() >= 0 ) {
|
|
lastColIndex = getRelativeColumnOffset( TextPosition( pos.line(), lastColIndex ) );
|
|
}
|
|
pos.setColumn( lastColIndex );
|
|
setSelection( TextRange( pos, getSelection().end() ) );
|
|
}
|
|
|
|
void TextDocument::selectToNextLine( Int64 lastColIndex ) {
|
|
TextPosition pos = getSelection().start();
|
|
pos.setLine( pos.line() + 1 );
|
|
if ( pos.line() < (Int64)mLines.size() ) {
|
|
lastColIndex = getRelativeColumnOffset( TextPosition( pos.line(), lastColIndex ) );
|
|
}
|
|
pos.setColumn( lastColIndex );
|
|
setSelection( TextRange( pos, getSelection().end() ) );
|
|
}
|
|
|
|
void TextDocument::selectToStartOfLine() {
|
|
selectTo( startOfLine( getSelection().start() ) );
|
|
}
|
|
|
|
void TextDocument::selectToEndOfLine() {
|
|
selectTo( endOfLine( getSelection().start() ) );
|
|
}
|
|
|
|
void TextDocument::selectToPreviousPage( Int64 pageSize ) {
|
|
TextPosition pos = getSelection().start();
|
|
pos.setLine( pos.line() - pageSize );
|
|
selectTo( pos );
|
|
}
|
|
|
|
void TextDocument::selectToNextPage( Int64 pageSize ) {
|
|
TextPosition pos = getSelection().start();
|
|
pos.setLine( pos.line() + pageSize );
|
|
selectTo( pos );
|
|
}
|
|
|
|
void TextDocument::selectAll() {
|
|
setSelection( startOfDoc(), endOfDoc() );
|
|
}
|
|
|
|
void TextDocument::newLine() {
|
|
String input( "\n" );
|
|
TextPosition start = getSelection().start();
|
|
if ( start.line() >= 0 && start.line() < (Int64)mLines.size() ) {
|
|
String& ln = line( start.line() );
|
|
size_t to = eemin<size_t>( ln.size(), start.column() );
|
|
int indent = 0;
|
|
for ( size_t i = 0; i < to; i++ ) {
|
|
if ( '\t' == ln[i] || ' ' == ln[i] ) {
|
|
indent++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if ( indent ) {
|
|
input.append( ln.substr( 0, indent ) );
|
|
}
|
|
}
|
|
textInput( input );
|
|
}
|
|
|
|
void TextDocument::insertAtStartOfSelectedLines( String text, bool skipEmpty ) {
|
|
TextPosition prevStart = getSelection().start();
|
|
TextRange range = getSelection( true );
|
|
bool swap = prevStart != range.start();
|
|
for ( auto i = range.start().line(); i <= range.end().line(); i++ ) {
|
|
const String& line = this->line( i );
|
|
if ( !skipEmpty || line.length() != 1 ) {
|
|
insert( {i, 0}, text );
|
|
}
|
|
}
|
|
setSelection( TextPosition( range.start().line(), range.start().column() + text.size() ),
|
|
TextPosition( range.end().line(), range.end().column() + text.size() ), swap );
|
|
}
|
|
|
|
void TextDocument::removeFromStartOfSelectedLines( String text, bool skipEmpty ) {
|
|
TextPosition prevStart = getSelection().start();
|
|
TextRange range = getSelection( true );
|
|
bool swap = prevStart != range.start();
|
|
for ( auto i = range.start().line(); i <= range.end().line(); i++ ) {
|
|
const String& line = this->line( i );
|
|
if ( !skipEmpty || line.length() != 1 ) {
|
|
if ( line.substr( 0, text.length() ) == text ) {
|
|
remove( {{i, 0}, {i, static_cast<Int64>( text.length() )}} );
|
|
}
|
|
}
|
|
}
|
|
setSelection( TextPosition( range.start().line(), range.start().column() - text.size() ),
|
|
TextPosition( range.end().line(), range.end().column() - text.size() ), swap );
|
|
}
|
|
|
|
void TextDocument::indent() {
|
|
if ( hasSelection() ) {
|
|
insertAtStartOfSelectedLines( getIndentString(), false );
|
|
} else {
|
|
textInput( getIndentString() );
|
|
}
|
|
}
|
|
|
|
void TextDocument::unindent() {
|
|
removeFromStartOfSelectedLines( getIndentString(), false );
|
|
}
|
|
|
|
String TextDocument::getIndentString() {
|
|
if ( IndentSpaces == mIndentType ) {
|
|
return String( std::string( mTabWidth, ' ' ) );
|
|
}
|
|
return String( "\t" );
|
|
}
|
|
|
|
const Uint32& TextDocument::getTabWidth() const {
|
|
return mTabWidth;
|
|
}
|
|
|
|
void TextDocument::setTabWidth( const Uint32& tabWidth ) {
|
|
mTabWidth = tabWidth;
|
|
}
|
|
|
|
void TextDocument::deleteTo( TextPosition position ) {
|
|
TextPosition cursorPos = getSelection( true ).start();
|
|
if ( hasSelection() ) {
|
|
remove( getSelection() );
|
|
} else {
|
|
TextRange range( cursorPos, position );
|
|
remove( range );
|
|
range = range.normalized();
|
|
cursorPos = range.start();
|
|
}
|
|
setSelection( cursorPos );
|
|
}
|
|
|
|
void TextDocument::print() const {
|
|
for ( size_t i = 0; i < mLines.size(); i++ )
|
|
printf( "%s", mLines[i].toUtf8().c_str() );
|
|
}
|
|
|
|
TextPosition TextDocument::sanitizePosition( const TextPosition& position ) const {
|
|
Int64 line = eeclamp<Int64>( position.line(), 0UL, mLines.size() - 1 );
|
|
Int64 col =
|
|
eeclamp<Int64>( position.column(), 0UL, eemax<Int64>( 0, mLines[line].size() - 1 ) );
|
|
return {line, col};
|
|
}
|
|
|
|
const TextDocument::IndentType& TextDocument::getIndentType() const {
|
|
return mIndentType;
|
|
}
|
|
|
|
void TextDocument::setIndentType( const IndentType& indentType ) {
|
|
mIndentType = indentType;
|
|
}
|
|
|
|
void TextDocument::undo() {
|
|
mUndoStack.undo();
|
|
}
|
|
|
|
void TextDocument::redo() {
|
|
mUndoStack.redo();
|
|
}
|
|
|
|
void TextDocument::notifyTextChanged() {
|
|
for ( size_t i = 0; i < mLines.size(); i++ ) {
|
|
if ( mLines[i].empty() ) {
|
|
eePRINTL( "wtf" );
|
|
}
|
|
}
|
|
|
|
for ( auto& client : mClients ) {
|
|
client->onDocumentTextChanged();
|
|
}
|
|
}
|
|
|
|
void TextDocument::notifyCursorChanged() {
|
|
for ( auto& client : mClients ) {
|
|
client->onDocumentCursorChange( getSelection().start() );
|
|
}
|
|
}
|
|
|
|
void TextDocument::notifySelectionChanged() {
|
|
for ( auto& client : mClients ) {
|
|
client->onDocumentSelectionChange( getSelection() );
|
|
}
|
|
}
|
|
|
|
TextDocument::Client::~Client() {}
|
|
|
|
}}} // namespace EE::UI::Doc
|