From 0c520246ee9497f271c7710302cc77554e410aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 22 Jan 2023 01:54:28 -0300 Subject: [PATCH] Multi cursor WIP. --- include/eepp/ui/doc/textdocument.hpp | 76 ++++++++- include/eepp/ui/doc/textrange.hpp | 23 +++ include/eepp/ui/uicodeeditor.hpp | 9 +- src/eepp/ui/doc/textdocument.cpp | 237 ++++++++++++++++++++++----- src/eepp/ui/uicodeeditor.cpp | 90 +++++++--- 5 files changed, 358 insertions(+), 77 deletions(-) diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 6e46f1c05..3f9d8e6ad 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -31,6 +31,36 @@ struct DocumentContentChange { String text; }; +class EE_API TextRanges : public std::vector { + 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 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& 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 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 mLines; - TextRange mSelection; + TextRanges mSelection; std::unordered_set mClients; Mutex mClientsMutex; LineEnding mLineEnding{ LineEnding::LF }; diff --git a/include/eepp/ui/doc/textrange.hpp b/include/eepp/ui/doc/textrange.hpp index 4117fd83e..5dbcf1c9a 100644 --- a/include/eepp/ui/doc/textrange.hpp +++ b/include/eepp/ui/doc/textrange.hpp @@ -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() ) ) ) diff --git a/include/eepp/ui/uicodeeditor.hpp b/include/eepp/ui/uicodeeditor.hpp index 509a2733c..cac8560cc 100644 --- a/include/eepp/ui/uicodeeditor.hpp +++ b/include/eepp/ui/uicodeeditor.hpp @@ -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 mLastXOffset; KeyBindings mKeyBindings; std::unordered_set mUnlockedCmd; Clock mLastDoubleClick; diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index f1314ff6b..56880c81b 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -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& TextDocument::getSelections() const { + return mSelection; +} + +std::vector TextDocument::getSelectionsSorted() const { + std::vector 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& TextDocument::lines() { @@ -649,7 +702,7 @@ std::vector& 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() {} diff --git a/src/eepp/ui/uicodeeditor.cpp b/src/eepp/ui/uicodeeditor.cpp index 94d195ec3..c156b8a35 100644 --- a/src/eepp/ui/uicodeeditor.cpp +++ b/src/eepp/ui/uicodeeditor.cpp @@ -98,6 +98,10 @@ const std::map 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() {