From ab738ea79d24744513f99f651b4fc201e655c0ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sat, 6 Dec 2025 12:28:35 -0300 Subject: [PATCH] Added `duplicate-line-or-selection` TextDocument command. Updated README. --- README.md | 4 +-- include/eepp/ui/doc/textdocument.hpp | 2 ++ src/eepp/ui/doc/textdocument.cpp | 41 ++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1bd4d0a60..f6520aca7 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,10 @@ framework heavily focused on the development of rich graphical user interfaces. ### Cross platform functionality -* Official support for Linux, Windows, macOS, Android and iOS. +* Official support for Linux, Windows, macOS, FreeBSD, Haiku, Android and iOS. * Exports to HTML5 using emscripten with some minor limitations. -* Also works on BSD and Haiku. - ### UI Module * Base widgets to manage the app/game objects as nodes, with all basic input interaction events ( clicks, keypress, mouser over, focus, etc ). diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 6b5a92f29..56563d0be 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -717,6 +717,8 @@ class EE_API TextDocument { void joinLines(); + void duplicateLineOrSelection(); + void moveToNextParagraph(); void moveToPreviousParagraph(); diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 5d40e2938..f4100c2ba 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -4378,6 +4378,46 @@ void TextDocument::joinLines() { mergeSelection(); } +void TextDocument::duplicateLineOrSelection() { + AtomicBoolScopedOp op( mRunningTransaction, true ); + + // Must process from bottom to top to not invalidate positions of cursors above. + for ( Int64 i = mSelection.size() - 1; i >= 0; --i ) { + TextRange sel = getSelectionIndex( i ); + + if ( sel.hasSelection() ) { + TextRange normalizedSel = sel.normalized(); + String selectedText = getText( normalizedSel ); + if ( !selectedText.empty() ) { + TextPosition insertionPoint = normalizedSel.end(); + TextPosition endOfInsertion = insert( i, insertionPoint, selectedText ); + + // After duplicating selection, select the new duplicated text. + // The selection direction should be preserved. + if ( sel.isNormalized() ) { + setSelection( i, { insertionPoint, endOfInsertion } ); + } else { + setSelection( i, { endOfInsertion, insertionPoint } ); + } + } + } else { + Int64 lineNum = sel.start().line(); + // Don't duplicate the last empty line. + if ( lineNum >= (Int64)linesCount() - 1 && getLineLength( lineNum ) <= 1 ) { + continue; + } + + String lineText = getLineText( lineNum ); + TextPosition insertionPoint = { lineNum + 1, 0 }; + insert( i, insertionPoint, lineText ); + TextPosition newCursorPos = { lineNum + 1, sel.start().column() }; + setSelection( i, newCursorPos ); + } + } + + mergeSelection(); +} + TextPosition TextDocument::findPreviousEmptyLines( size_t selIdx ) { Int64 startLine = mSelection[selIdx].normalized().start().line() - 1; bool found = false; @@ -4625,6 +4665,7 @@ void TextDocument::initializeCommands() { mCommands["from-base64"] = [this] { fromBase64(); }; mCommands["trim-trailing-whitespace"] = [this] { trimTrailingWhitespace(); }; mCommands["join-lines"] = [this] { joinLines(); }; + mCommands["duplicate-line-or-selection"] = [this] { duplicateLineOrSelection(); }; mCommands["convert-indentation-to-tabs"] = [this] { convertIndentationToTabs(); }; mCommands["convert-indentation-to-spaces"] = [this] { convertIndentationToSpaces(); };