Multi cursor WIP.

This commit is contained in:
Martín Lucas Golini
2023-01-22 01:54:28 -03:00
parent e44e20d81b
commit 0c520246ee
5 changed files with 358 additions and 77 deletions

View File

@@ -31,6 +31,36 @@ struct DocumentContentChange {
String text;
};
class EE_API TextRanges : public std::vector<TextRange> {
public:
bool isValid() {
for ( const auto& selection : *this ) {
if ( !selection.isValid() )
return false;
}
return true;
}
bool exists( const TextRange& range ) {
for ( const auto& r : *this )
if ( range == r )
return true;
return false;
}
TextRanges& merge() {
if ( size() <= 1 )
return *this;
TextRanges newRanges;
newRanges.emplace_back( ( *this )[0] );
for ( size_t i = 1; i < size(); ++i )
if ( !newRanges.exists( ( *this )[i] ) )
newRanges.emplace_back( ( *this )[i] );
*this = newRanges;
return *this;
}
};
class EE_API TextDocument {
public:
typedef std::function<void()> DocumentCommand;
@@ -78,6 +108,8 @@ class EE_API TextDocument {
void reset();
void resetCursor();
LoadStatus loadFromStream( IOStream& path );
LoadStatus loadFromFile( const std::string& path );
@@ -116,16 +148,23 @@ class EE_API TextDocument {
std::string getFilename() const;
void setSelection( TextPosition position );
void setSelection( const TextPosition& position );
void setSelection( const size_t& cursorIdx, TextPosition start, TextPosition end,
bool swap = false );
void setSelection( TextPosition start, TextPosition end, bool swap = false );
void setSelection( TextRange range );
void setSelection( const TextRange& range );
TextRange getSelection( bool sort ) const;
const std::vector<TextRange>& getSelections() const;
const TextRange& getSelection() const;
const TextRange& getSelectionIndex( const size_t& index ) const;
TextDocumentLine& line( const size_t& index );
const TextDocumentLine& line( const size_t& index ) const;
@@ -416,6 +455,8 @@ class EE_API TextDocument {
TextRange sanitizeRange( const TextRange& range ) const;
TextRanges sanitizeRange( const TextRanges& ranges ) const;
bool getAutoCloseBrackets() const;
void setAutoCloseBrackets( bool autoCloseBrackets );
@@ -451,14 +492,43 @@ class EE_API TextDocument {
const std::string& getLoadingFilePath() const;
void setSelection( const TextRanges& selection );
std::vector<TextRange> getSelectionsSorted() const;
void addCursorAbove();
void addCursorBelow();
void addCursor( const TextRange& cursor );
TextRange getTopMostCursor();
TextRange getBottomMostCursor();
void moveTo( const size_t& cursorIdx, TextPosition offset );
void moveTo( const size_t& cursorIdx, int columnOffset );
void setSelection( const size_t& cursorIdx, const TextPosition& position );
void mergeSelection();
void selectTo( const size_t& cursorIdx, TextPosition position );
void selectTo( const size_t& cursorIdx, int offset );
void setSelection( const size_t& cursorIdx, const TextRange& range );
protected:
friend class UndoStack;
UndoStack mUndoStack;
std::string mFilePath;
std::string mLoadingFilePath;
FileInfo mFileRealPath;
std::vector<TextDocumentLine> mLines;
TextRange mSelection;
TextRanges mSelection;
std::unordered_set<Client*> mClients;
Mutex mClientsMutex;
LineEnding mLineEnding{ LineEnding::LF };

View File

@@ -28,6 +28,13 @@ class EE_API TextRange {
TextRange normalized() const { return TextRange( normalizedStart(), normalizedEnd() ); }
TextRange& normalize() {
auto normalize( normalized() );
mStart = normalize.start();
mEnd = normalize.end();
return *this;
}
TextRange reversed() { return TextRange( mEnd, mStart ); }
void setStart( const TextPosition& position ) { mStart = position; }
@@ -47,6 +54,22 @@ class EE_API TextRange {
return mStart != other.mStart || mEnd != other.mEnd;
}
bool operator<( const TextRange& other ) const {
return mStart < other.mStart && mEnd < other.mEnd;
}
bool operator>( const TextRange& other ) const {
return mStart > other.mStart && mEnd > other.mEnd;
}
bool operator<=( const TextRange& other ) const {
return mStart <= other.mStart && mEnd <= other.mEnd;
}
bool operator>=( const TextRange& other ) const {
return mStart >= other.mStart && mEnd >= other.mEnd;
}
bool contains( const TextPosition& position ) const {
if ( !( position.line() > mStart.line() ||
( position.line() == mStart.line() && position.column() >= mStart.column() ) ) )

View File

@@ -386,7 +386,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
void setFindLongestLineWidthUpdateFrequency( const Time& findLongestLineWidthUpdateFrequency );
/** Doc commands executed in this editor. */
TextPosition moveToLineOffset( const TextPosition& position, int offset );
TextPosition moveToLineOffset( const TextPosition& position, int offset,
const size_t& cursorIdx = 0 );
void moveToPreviousLine();
@@ -568,8 +569,8 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
protected:
struct LastXOffset {
TextPosition position;
Float offset;
TextPosition position{ 0, 0 };
Float offset{ 0.f };
};
Font* mFont;
UIFontStyleConfig mFontStyleConfig;
@@ -631,7 +632,7 @@ class EE_API UICodeEditor : public UIWidget, public TextDocument::Client {
SyntaxHighlighter mHighlighter;
UIScrollBar* mVScrollBar;
UIScrollBar* mHScrollBar;
LastXOffset mLastXOffset{ { 0, 0 }, 0.f };
std::map<size_t, LastXOffset> mLastXOffset;
KeyBindings mKeyBindings;
std::unordered_set<std::string> mUnlockedCmd;
Clock mLastDoubleClick;

View File

@@ -57,17 +57,23 @@ bool TextDocument::isEmpty() {
}
void TextDocument::reset() {
auto oldSelection = sanitizeRange( mSelection );
mFilePath = mDefaultFileName;
mFileRealPath = FileInfo();
mSelection.set( { 0, 0 }, { 0, 0 } );
mSelection.clear();
mSelection.push_back( { { 0, 0 }, { 0, 0 } } );
mLines.clear();
mLines.emplace_back( String( "\n" ) );
mSyntaxDefinition = SyntaxDefinitionManager::instance()->getPlainStyle();
mUndoStack.clear();
cleanChangeId();
if ( oldSelection.isValid() )
notifyTextChanged( { { oldSelection.end(), oldSelection.start() }, "" } );
notifyCursorChanged();
notifySelectionChanged();
}
void TextDocument::resetCursor() {
auto cursor = sanitizeRange( mSelection.front() );
mSelection.clear();
mSelection.push_back( cursor );
notifyCursorChanged();
notifySelectionChanged();
}
@@ -222,6 +228,10 @@ void TextDocument::guessIndentType() {
mIndentWidth = 4;
}
void TextDocument::mergeSelection() {
mSelection.merge();
}
bool TextDocument::hasSyntaxDefinition() const {
return !mSyntaxDefinition.getPatterns().empty();
}
@@ -463,7 +473,7 @@ TextDocument::LoadStatus TextDocument::reload() {
: FileInfo( mFilePath );
resetSyntax();
notifyDocumentReloaded();
setSelection( sanitizePosition( selection.start() ) );
setSelection( sanitizeRange( selection ) );
}
return ret;
}
@@ -564,11 +574,12 @@ bool TextDocument::save() {
}
void TextDocument::sanitizeCurrentSelection() {
auto newSelection =
TextRange( sanitizePosition( mSelection.start() ), sanitizePosition( mSelection.end() ) );
if ( mSelection != newSelection )
setSelection( newSelection );
for ( size_t i = 0; i < mSelection.size(); ++i ) {
auto& selection = mSelection[i];
auto newSelection = sanitizeRange( selection );
if ( selection != newSelection )
setSelection( i, newSelection );
}
}
bool TextDocument::isLoading() const {
@@ -587,13 +598,31 @@ std::string TextDocument::getFilename() const {
return FileSystem::fileNameFromPath( mFilePath );
}
void TextDocument::setSelection( TextPosition position ) {
void TextDocument::setSelection( const TextRanges& selection ) {
for ( size_t i = 0; i < selection.size(); ++i ) {
if ( i >= mSelection.size() )
mSelection.push_back( selection[i] );
setSelection( i, selection[i].start(), selection[i].end(), false );
}
}
void TextDocument::setSelection( const 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 ) )
void TextDocument::setSelection( const size_t& cursorIdx, const TextPosition& position ) {
setSelection( cursorIdx, position, position );
}
void TextDocument::setSelection( const size_t& cursorIdx, TextPosition start, TextPosition end,
bool swap ) {
eeASSERT( cursorIdx < mSelection.size() );
if ( cursorIdx >= mSelection.size() )
return;
if ( ( start == mSelection[cursorIdx].start() && end == mSelection[cursorIdx].end() &&
!swap ) ||
( start == mSelection[cursorIdx].end() && end == mSelection[cursorIdx].start() && swap ) )
return;
if ( swap ) {
@@ -609,23 +638,47 @@ void TextDocument::setSelection( TextPosition start, TextPosition end, bool swap
end = sanitizePosition( end );
}
if ( mSelection != TextRange( start, end ) ) {
mSelection.set( start, end );
if ( mSelection[cursorIdx] != TextRange( start, end ) ) {
mSelection[cursorIdx].set( start, end );
notifyCursorChanged();
notifySelectionChanged();
}
}
void TextDocument::setSelection( TextRange range ) {
void TextDocument::setSelection( TextPosition start, TextPosition end, bool swap ) {
setSelection( 0, start, end, swap );
}
void TextDocument::setSelection( const TextRange& range ) {
setSelection( range.start(), range.end() );
}
void TextDocument::setSelection( const size_t& cursorIdx, const TextRange& range ) {
setSelection( cursorIdx, range.start(), range.end() );
}
TextRange TextDocument::getSelection( bool sort ) const {
return sort ? mSelection.normalized() : mSelection;
return sort ? mSelection.front().normalized() : mSelection.front();
}
const std::vector<TextRange>& TextDocument::getSelections() const {
return mSelection;
}
std::vector<TextRange> TextDocument::getSelectionsSorted() const {
std::vector<TextRange> selections( mSelection );
for ( auto& selection : selections )
selection.normalize();
return selections;
}
const TextRange& TextDocument::getSelectionIndex( const size_t& index ) const {
eeASSERT( index < mSelection.size() );
return mSelection[index];
}
const TextRange& TextDocument::getSelection() const {
return mSelection;
return mSelection.front();
}
TextDocumentLine& TextDocument::line( const size_t& index ) {
@@ -641,7 +694,7 @@ size_t TextDocument::linesCount() const {
}
const TextDocumentLine& TextDocument::getCurrentLine() const {
return mLines[mSelection.start().line()];
return mLines[mSelection.front().start().line()];
}
std::vector<TextDocumentLine>& TextDocument::lines() {
@@ -649,7 +702,7 @@ std::vector<TextDocumentLine>& TextDocument::lines() {
}
bool TextDocument::hasSelection() const {
return mSelection.start() != mSelection.end();
return mSelection.front().start() != mSelection.front().end();
}
String TextDocument::getText( const TextRange& range ) const {
@@ -1010,6 +1063,16 @@ void TextDocument::selectTo( int offset ) {
setSelection( TextRange( posOffset, range.end() ) );
}
void TextDocument::selectTo( const size_t& cursorIdx, TextPosition position ) {
setSelection( cursorIdx, TextRange( sanitizePosition( position ), getSelection().end() ) );
}
void TextDocument::selectTo( const size_t& cursorIdx, int offset ) {
const TextRange& range = getSelection();
TextPosition posOffset = positionOffset( range.start(), offset );
setSelection( cursorIdx, TextRange( posOffset, range.end() ) );
}
void TextDocument::moveTo( TextPosition offset ) {
setSelection( offset );
}
@@ -1018,6 +1081,14 @@ void TextDocument::moveTo( int columnOffset ) {
setSelection( positionOffset( getSelection().start(), columnOffset ) );
}
void TextDocument::moveTo( const size_t& cursorIdx, TextPosition offset ) {
setSelection( cursorIdx, offset );
}
void TextDocument::moveTo( const size_t& cursorIdx, int columnOffset ) {
setSelection( cursorIdx, positionOffset( getSelection().start(), columnOffset ) );
}
void TextDocument::textInput( const String& text ) {
if ( mAutoCloseBrackets && 1 == text.size() ) {
size_t pos = 0xFFFFFFFF;
@@ -1070,47 +1141,65 @@ void TextDocument::unregisterClient( Client* client ) {
}
void TextDocument::moveToPreviousChar() {
if ( hasSelection() ) {
setSelection( getSelection( true ).start() );
} else {
setSelection( positionOffset( getSelection().start(), -1 ) );
for ( size_t i = 0; i < mSelection.size(); ++i ) {
if ( mSelection[i].hasSelection() ) {
setSelection( i, mSelection[i].normalize().start() );
} else {
setSelection( i, positionOffset( mSelection[i].start(), -1 ) );
}
}
mergeSelection();
}
void TextDocument::moveToNextChar() {
if ( hasSelection() ) {
setSelection( getSelection( true ).end() );
} else {
setSelection( positionOffset( getSelection().start(), 1 ) );
for ( size_t i = 0; i < mSelection.size(); ++i ) {
if ( mSelection[i].hasSelection() ) {
setSelection( i, mSelection[i].normalize().end() );
} else {
setSelection( i, positionOffset( mSelection[i].start(), 1 ) );
}
}
mergeSelection();
}
void TextDocument::moveToPreviousWord() {
if ( hasSelection() ) {
setSelection( getSelection( true ).start() );
} else {
setSelection( previousWordBoundary( getSelection().start() ) );
for ( size_t i = 0; i < mSelection.size(); ++i ) {
if ( mSelection[i].hasSelection() ) {
setSelection( i, mSelection[i].normalize().start() );
} else {
setSelection( i, previousWordBoundary( mSelection[i].start() ) );
}
}
mergeSelection();
}
void TextDocument::moveToNextWord() {
if ( hasSelection() ) {
setSelection( getSelection( true ).end() );
} else {
setSelection( nextWordBoundary( getSelection().start() ) );
for ( size_t i = 0; i < mSelection.size(); ++i ) {
if ( mSelection[i].hasSelection() ) {
setSelection( i, mSelection[i].normalize().end() );
} else {
setSelection( i, nextWordBoundary( mSelection[i].start() ) );
}
}
mergeSelection();
}
void TextDocument::moveToPreviousLine() {
TextPosition pos = getSelection().start();
pos.setLine( pos.line() - 1 );
setSelection( pos );
for ( size_t i = 0; i < mSelection.size(); ++i ) {
TextPosition pos = mSelection[i].start();
pos.setLine( pos.line() - 1 );
setSelection( i, pos, pos );
}
mergeSelection();
}
void TextDocument::moveToNextLine() {
TextPosition pos = getSelection().start();
pos.setLine( pos.line() + 1 );
setSelection( pos );
for ( size_t i = 0; i < mSelection.size(); ++i ) {
TextPosition pos = mSelection[i].start();
pos.setLine( pos.line() + 1 );
setSelection( i, pos, pos );
}
mergeSelection();
}
void TextDocument::moveToPreviousPage( Int64 pageSize ) {
@@ -1126,10 +1215,12 @@ void TextDocument::moveToNextPage( Int64 pageSize ) {
}
void TextDocument::moveToStartOfDoc() {
resetCursor();
setSelection( startOfDoc() );
}
void TextDocument::moveToEndOfDoc() {
resetCursor();
setSelection( endOfDoc() );
}
@@ -1426,6 +1517,17 @@ TextRange TextDocument::sanitizeRange( const TextRange& range ) const {
return { sanitizePosition( range.start() ), sanitizePosition( range.end() ) };
}
TextRanges TextDocument::sanitizeRange( const TextRanges& ranges ) const {
TextRanges sanitizedRanges;
for ( const auto& range : ranges ) {
if ( !range.isValid() )
return sanitizedRanges;
sanitizedRanges.push_back(
{ sanitizePosition( range.start() ), sanitizePosition( range.end() ) } );
}
return sanitizedRanges;
}
bool TextDocument::getAutoCloseBrackets() const {
return mAutoCloseBrackets;
}
@@ -2125,6 +2227,55 @@ void TextDocument::initializeCommands() {
mCommands["toggle-line-comments"] = [&] { toggleLineComments(); };
mCommands["selection-to-upper"] = [&] { toUpperSelection(); };
mCommands["selection-to-lower"] = [&] { toLowerSelection(); };
mCommands["reset-cursor"] = [&] { resetCursor(); };
mCommands["add-cursor-above"] = [&] { addCursorAbove(); };
mCommands["add-cursor-below"] = [&] { addCursorBelow(); };
}
TextRange TextDocument::getTopMostCursor() {
if ( mSelection.size() == 1 )
return mSelection.front();
TextRange topMost( mSelection[0] );
for ( size_t i = 1; i < mSelection.size(); ++i ) {
if ( mSelection[i] < topMost )
topMost = mSelection[i];
}
return topMost;
}
TextRange TextDocument::getBottomMostCursor() {
if ( mSelection.size() == 1 )
return mSelection.front();
TextRange bottomMost( mSelection[0] );
for ( size_t i = 1; i < mSelection.size(); ++i ) {
if ( mSelection[i] > bottomMost )
bottomMost = mSelection[i];
}
return bottomMost;
}
void TextDocument::addCursor( const TextRange& cursor ) {
mSelection.emplace_back( cursor );
}
void TextDocument::addCursorAbove() {
auto curPos( getTopMostCursor().normalize().start() );
if ( curPos.line() == 0 )
return;
curPos.setLine( curPos.line() - 1 );
curPos = sanitizePosition( curPos );
addCursor( { curPos, curPos } );
notifyCursorChanged();
}
void TextDocument::addCursorBelow() {
auto curPos( getBottomMostCursor().normalize().start() );
if ( curPos.line() >= (Int64)linesCount() - 1 )
return;
curPos.setLine( curPos.line() + 1 );
curPos = sanitizePosition( curPos );
addCursor( { curPos, curPos } );
notifyCursorChanged();
}
TextDocument::Client::~Client() {}

View File

@@ -98,6 +98,10 @@ const std::map<KeyBindings::Shortcut, std::string> UICodeEditor::getDefaultKeybi
{ { KEY_UP, KEYMOD_CTRL | KEYMOD_LALT | KEYMOD_SHIFT }, "selection-to-upper" },
{ { KEY_DOWN, KEYMOD_CTRL | KEYMOD_LALT | KEYMOD_SHIFT }, "selection-to-lower" },
{ { KEY_F, KeyMod::getDefaultModifier() }, "find-replace" },
{ { KEY_D, KeyMod::getDefaultModifier() }, "select-word" },
{ { KEY_UP, KEYMOD_LALT }, "add-cursor-above" },
{ { KEY_DOWN, KEYMOD_LALT }, "add-cursor-below" },
{ { KEY_ESCAPE }, "reset-cursor" },
};
}
@@ -235,10 +239,13 @@ void UICodeEditor::draw() {
}
if ( !mLocked && mHighlightCurrentLine ) {
primitives.setColor( Color( mCurrentLineBackgroundColor ).blendAlpha( mAlpha ) );
primitives.drawRectangle( Rectf(
Vector2f( startScroll.x + mScroll.x, startScroll.y + cursor.line() * lineHeight ),
Sizef( mSize.getWidth(), lineHeight ) ) );
for ( const auto& cursor : mDoc->getSelections() ) {
primitives.setColor( Color( mCurrentLineBackgroundColor ).blendAlpha( mAlpha ) );
primitives.drawRectangle(
Rectf( Vector2f( startScroll.x + mScroll.x,
startScroll.y + cursor.start().line() * lineHeight ),
Sizef( mSize.getWidth(), lineHeight ) ) );
}
}
if ( mLineBreakingColumn ) {
@@ -260,8 +267,11 @@ void UICodeEditor::draw() {
}
if ( mDoc->hasSelection() ) {
drawTextRange( mDoc->getSelection( true ), lineRange, startScroll, lineHeight,
mFontStyleConfig.getFontSelectionBackColor() );
auto selections = mDoc->getSelectionsSorted();
for ( const auto& sel : selections ) {
drawTextRange( sel, lineRange, startScroll, lineHeight,
mFontStyleConfig.getFontSelectionBackColor() );
}
}
if ( mHighlightSelectionMatch && mDoc->hasSelection() && mDoc->getSelection().inSameLine() ) {
@@ -306,7 +316,8 @@ void UICodeEditor::draw() {
}
}
drawCursor( startScroll, lineHeight, cursor );
for ( const auto& cursor : mDoc->getSelections() )
drawCursor( startScroll, lineHeight, cursor.start() );
if ( mShowLineNumber ) {
drawLineNumbers( lineRange, startScroll,
@@ -1467,8 +1478,14 @@ void UICodeEditor::scrollToCursor( bool centered ) {
void UICodeEditor::updateEditor() {
mDoc->setPageSize( getVisibleLinesCount() );
if ( mDirtyScroll && mDoc->getActiveClient() == this )
scrollTo( mDoc->getSelection().start() );
if ( mDirtyScroll && mDoc->getActiveClient() == this ) {
if ( mDoc->getSelections().size() == 1 ) {
scrollTo( mDoc->getSelection().start() );
} else {
scrollTo( mDoc->getBottomMostCursor().normalize().end() );
scrollTo( mDoc->getTopMostCursor().normalize().start() );
}
}
updateScrollBar();
mDirtyEditor = false;
mDirtyScroll = false;
@@ -2270,8 +2287,9 @@ void UICodeEditor::resetCursor() {
mBlinkTimer.restart();
}
TextPosition UICodeEditor::moveToLineOffset( const TextPosition& position, int offset ) {
auto& xo = mLastXOffset;
TextPosition UICodeEditor::moveToLineOffset( const TextPosition& position, int offset,
const size_t& cursorIdx ) {
auto& xo = mLastXOffset[cursorIdx];
if ( xo.position != position )
xo.offset = getXOffsetColSanitized( position );
xo.position.setLine( position.line() + offset );
@@ -2280,31 +2298,49 @@ TextPosition UICodeEditor::moveToLineOffset( const TextPosition& position, int o
}
void UICodeEditor::moveToPreviousLine() {
TextPosition position = mDoc->getSelection().start();
if ( position.line() == 0 )
return mDoc->moveToStartOfDoc();
mDoc->moveTo( moveToLineOffset( position, -1 ) );
for ( size_t i = 0; i < mDoc->getSelections().size(); ++i ) {
TextPosition position = mDoc->getSelections()[i].start();
if ( position.line() == 0 ) {
mDoc->setSelection( i, mDoc->startOfDoc(), mDoc->startOfDoc() );
} else {
mDoc->moveTo( i, moveToLineOffset( position, -1, i ) );
}
}
mDoc->mergeSelection();
}
void UICodeEditor::moveToNextLine() {
TextPosition position = mDoc->getSelection().start();
if ( position.line() == (Int64)mDoc->linesCount() - 1 )
return mDoc->moveToEndOfDoc();
mDoc->moveTo( moveToLineOffset( position, 1 ) );
for ( size_t i = 0; i < mDoc->getSelections().size(); ++i ) {
TextPosition position = mDoc->getSelections()[i].start();
if ( position.line() == (Int64)mDoc->linesCount() - 1 ) {
mDoc->setSelection( i, mDoc->endOfDoc(), mDoc->endOfDoc() );
} else {
mDoc->moveTo( i, moveToLineOffset( position, 1, i ) );
}
}
mDoc->mergeSelection();
}
void UICodeEditor::selectToPreviousLine() {
TextPosition position = mDoc->getSelection().start();
if ( position.line() == 0 )
return mDoc->selectToStartOfDoc();
mDoc->selectTo( moveToLineOffset( position, -1 ) );
for ( size_t i = 0; i < mDoc->getSelections().size(); ++i ) {
TextPosition position = mDoc->getSelectionIndex( i ).start();
if ( position.line() == 0 ) {
mDoc->selectTo( i, mDoc->startOfDoc() );
} else {
mDoc->selectTo( i, moveToLineOffset( position, -1 ) );
}
}
}
void UICodeEditor::selectToNextLine() {
TextPosition position = mDoc->getSelection().start();
if ( position.line() == (Int64)mDoc->linesCount() - 1 )
return mDoc->selectToEndOfDoc();
mDoc->selectTo( moveToLineOffset( position, 1 ) );
for ( size_t i = 0; i < mDoc->getSelections().size(); ++i ) {
TextPosition position = mDoc->getSelectionIndex( i ).start();
if ( position.line() == (Int64)mDoc->linesCount() - 1 ) {
mDoc->selectTo( i, mDoc->endOfDoc() );
} else {
mDoc->selectTo( i, moveToLineOffset( position, 1 ) );
}
}
}
void UICodeEditor::moveScrollUp() {