From 176475866985b43e767222c786870e2024ee104f Mon Sep 17 00:00:00 2001 From: Don Ho Date: Tue, 14 Nov 2023 18:24:24 +0100 Subject: [PATCH] Enhance multi-edit paste and Enter key type Also disable auto-indent during multi-editing. Ref: https://github.com/notepad-plus-plus/notepad-plus-plus/pull/14338#issuecomment-1809045648 Close #14355 --- PowerEditor/src/Notepad_plus.cpp | 16 +- PowerEditor/src/NppCommands.cpp | 8 +- .../ScintillaComponent/ScintillaEditView.cpp | 185 +++++++++++++----- 3 files changed, 151 insertions(+), 58 deletions(-) diff --git a/PowerEditor/src/Notepad_plus.cpp b/PowerEditor/src/Notepad_plus.cpp index 2f2efa3de..f12560c93 100644 --- a/PowerEditor/src/Notepad_plus.cpp +++ b/PowerEditor/src/Notepad_plus.cpp @@ -356,6 +356,10 @@ LRESULT Notepad_plus::init(HWND hwnd) _mainEditView.execute(SCI_SETMULTIPASTE, SC_MULTIPASTE_EACH); _subEditView.execute(SCI_SETMULTIPASTE, SC_MULTIPASTE_EACH); + // Turn auto-completion into each multi-select on + _mainEditView.execute(SCI_AUTOCSETMULTI, SC_MULTIAUTOC_EACH); + _subEditView.execute(SCI_AUTOCSETMULTI, SC_MULTIAUTOC_EACH); + // allow user to start selecting as a stream block, then switch to a column block by adding Alt keypress _mainEditView.execute(SCI_SETMOUSESELECTIONRECTANGULARSWITCH, true); _subEditView.execute(SCI_SETMOUSESELECTIONRECTANGULARSWITCH, true); @@ -4218,8 +4222,8 @@ void Notepad_plus::updateStatusBar() TCHAR strSel[64]; - size_t numSelections = _pEditView->execute(SCI_GETSELECTIONS); - if (numSelections == 1) + size_t nbSelections = _pEditView->execute(SCI_GETSELECTIONS); + if (nbSelections == 1) { if (_pEditView->execute(SCI_GETSELECTIONEMPTY)) { @@ -4241,7 +4245,7 @@ void Notepad_plus::updateStatusBar() bool sameCharCountOnEveryLine = true; size_t maxLineCharCount = 0; - for (size_t sel = 0; sel < numSelections; ++sel) + for (size_t sel = 0; sel < nbSelections; ++sel) { size_t start = _pEditView->execute(SCI_GETSELECTIONNSTART, sel); size_t end = _pEditView->execute(SCI_GETSELECTIONNEND, sel); @@ -4265,7 +4269,7 @@ void Notepad_plus::updateStatusBar() } wsprintf(strSel, TEXT("Sel : %sx%s %s %s"), - commafyInt(numSelections).c_str(), // lines (rows) in rectangular selection + commafyInt(nbSelections).c_str(), // lines (rows) in rectangular selection commafyInt(maxLineCharCount).c_str(), // show maximum width for columns sameCharCountOnEveryLine ? TEXT("=") : TEXT("->"), commafyInt(rectSelCharsAndLines.first).c_str()); @@ -4276,9 +4280,9 @@ void Notepad_plus::updateStatusBar() const std::pair multipleSelCharsAndLines = _pEditView->getSelectedCharsAndLinesCount(maxSelsToProcessLineCount); wsprintf(strSel, TEXT("Sel %s : %s | %s"), - commafyInt(numSelections).c_str(), + commafyInt(nbSelections).c_str(), commafyInt(multipleSelCharsAndLines.first).c_str(), - numSelections <= maxSelsToProcessLineCount ? + nbSelections <= maxSelsToProcessLineCount ? commafyInt(multipleSelCharsAndLines.second).c_str() : TEXT("...")); // show ellipsis for line count if too many selections are active } diff --git a/PowerEditor/src/NppCommands.cpp b/PowerEditor/src/NppCommands.cpp index d37717997..d81af65c4 100644 --- a/PowerEditor/src/NppCommands.cpp +++ b/PowerEditor/src/NppCommands.cpp @@ -443,10 +443,10 @@ void Notepad_plus::command(int id) { std::lock_guard lock(command_mutex); - size_t numSelections = _pEditView->execute(SCI_GETSELECTIONS); + size_t nbSelections = _pEditView->execute(SCI_GETSELECTIONS); Buffer* buf = getCurrentBuffer(); bool isRO = buf->isReadOnly(); - if (numSelections > 1 && !isRO) + if (nbSelections > 1 && !isRO) { bool isPasteDone = _pEditView->pasteToMultiSelection(); if (isPasteDone) @@ -1783,10 +1783,10 @@ void Notepad_plus::command(int id) bool forwards = id == IDM_EDIT_INS_TAB; size_t selStartPos = _pEditView->execute(SCI_GETSELECTIONSTART); size_t lineNumber = _pEditView->execute(SCI_LINEFROMPOSITION, selStartPos); - size_t numSelections = _pEditView->execute(SCI_GETSELECTIONS); + size_t nbSelections = _pEditView->execute(SCI_GETSELECTIONS); size_t selEndPos = _pEditView->execute(SCI_GETSELECTIONEND); size_t selEndLineNumber = _pEditView->execute(SCI_LINEFROMPOSITION, selEndPos); - if ((numSelections > 1) || (lineNumber != selEndLineNumber)) + if ((nbSelections > 1) || (lineNumber != selEndLineNumber)) { // multiple-selection or multi-line selection; use Scintilla SCI_TAB / SCI_BACKTAB behavior _pEditView->execute(forwards ? SCI_TAB : SCI_BACKTAB); diff --git a/PowerEditor/src/ScintillaComponent/ScintillaEditView.cpp b/PowerEditor/src/ScintillaComponent/ScintillaEditView.cpp index 078593bb5..9b7a934f4 100644 --- a/PowerEditor/src/ScintillaComponent/ScintillaEditView.cpp +++ b/PowerEditor/src/ScintillaComponent/ScintillaEditView.cpp @@ -524,6 +524,7 @@ LRESULT ScintillaEditView::scintillaNew_Proc(HWND hwnd, UINT Message, WPARAM wPa case VK_DOWN: case VK_HOME: case VK_END: + case VK_RETURN: execute(SCI_SETSELECTIONMODE, SC_SEL_STREAM); // When it's rectangular selection and the arrow keys are pressed, we switch the mode for having multiple carets. execute(SCI_SETSELECTIONMODE, SC_SEL_STREAM); // the 2nd call for removing the unwanted selection while moving carets. @@ -590,8 +591,8 @@ LRESULT ScintillaEditView::scintillaNew_Proc(HWND hwnd, UINT Message, WPARAM wPa { Buffer* buf = getCurrentBuffer(); bool isRO = buf->isReadOnly(); - size_t numSelections = execute(SCI_GETSELECTIONS); - if (numSelections > 1 && !isRO) + size_t nbSelections = execute(SCI_GETSELECTIONS); + if (nbSelections > 1 && !isRO) { if (pasteToMultiSelection()) { @@ -603,7 +604,6 @@ LRESULT ScintillaEditView::scintillaNew_Proc(HWND hwnd, UINT Message, WPARAM wPa } } } - break; } } break; @@ -3092,43 +3092,101 @@ void ScintillaEditView::showIndentGuideLine(bool willBeShowed) void ScintillaEditView::setLineIndent(size_t line, size_t indent) const { - Sci_CharacterRangeFull crange = getSelection(); - int64_t posBefore = execute(SCI_GETLINEINDENTPOSITION, line); - execute(SCI_SETLINEINDENTATION, line, indent); - int64_t posAfter = execute(SCI_GETLINEINDENTPOSITION, line); - long long posDifference = posAfter - posBefore; - if (posAfter > posBefore) - { - // Move selection on - if (crange.cpMin >= posBefore) + size_t nbSelections = execute(SCI_GETSELECTIONS); + + if (nbSelections == 1) + { + Sci_CharacterRangeFull crange = getSelection(); + int64_t posBefore = execute(SCI_GETLINEINDENTPOSITION, line); + execute(SCI_SETLINEINDENTATION, line, indent); + int64_t posAfter = execute(SCI_GETLINEINDENTPOSITION, line); + long long posDifference = posAfter - posBefore; + if (posAfter > posBefore) { - crange.cpMin += static_cast(posDifference); + // Move selection on + if (crange.cpMin >= posBefore) + { + crange.cpMin += static_cast(posDifference); + } + if (crange.cpMax >= posBefore) + { + crange.cpMax += static_cast(posDifference); + } } - if (crange.cpMax >= posBefore) + else if (posAfter < posBefore) { - crange.cpMax += static_cast(posDifference); + // Move selection back + if (crange.cpMin >= posAfter) + { + if (crange.cpMin >= posBefore) + crange.cpMin += static_cast(posDifference); + else + crange.cpMin = static_cast(posAfter); + } + + if (crange.cpMax >= posAfter) + { + if (crange.cpMax >= posBefore) + crange.cpMax += static_cast(posDifference); + else + crange.cpMax = static_cast(posAfter); + } } + execute(SCI_SETSEL, crange.cpMin, crange.cpMax); } - else if (posAfter < posBefore) + else { - // Move selection back - if (crange.cpMin >= posAfter) + execute(SCI_BEGINUNDOACTION); + for (size_t i = 0; i < nbSelections; ++i) { - if (crange.cpMin >= posBefore) - crange.cpMin += static_cast(posDifference); - else - crange.cpMin = static_cast(posAfter); - } + LRESULT posStart = execute(SCI_GETSELECTIONNSTART, i); + LRESULT posEnd = execute(SCI_GETSELECTIONNEND, i); + + + size_t l = execute(SCI_LINEFROMPOSITION, posStart); + + int64_t posBefore = execute(SCI_GETLINEINDENTPOSITION, l); + execute(SCI_SETLINEINDENTATION, l, indent); + int64_t posAfter = execute(SCI_GETLINEINDENTPOSITION, l); + + long long posDifference = posAfter - posBefore; + if (posAfter > posBefore) + { + // Move selection on + if (posStart >= posBefore) + { + posStart += static_cast(posDifference); + } + if (posEnd >= posBefore) + { + posEnd += static_cast(posDifference); + } + } + else if (posAfter < posBefore) + { + // Move selection back + if (posStart >= posAfter) + { + if (posStart >= posBefore) + posStart += static_cast(posDifference); + else + posStart = static_cast(posAfter); + } - if (crange.cpMax >= posAfter) - { - if (crange.cpMax >= posBefore) - crange.cpMax += static_cast(posDifference); - else - crange.cpMax = static_cast(posAfter); + if (posEnd >= posAfter) + { + if (posEnd >= posBefore) + posEnd += static_cast(posDifference); + else + posEnd = static_cast(posAfter); + } + } + + execute(SCI_SETSELECTIONNSTART, i, posStart); + execute(SCI_SETSELECTIONNEND, i, posEnd); } + execute(SCI_ENDUNDOACTION); } - execute(SCI_SETSEL, crange.cpMin, crange.cpMax); } void ScintillaEditView::updateLineNumberWidth() @@ -3199,11 +3257,11 @@ void ScintillaEditView::setMultiSelections(const ColumnModeInfos & cmi) // specify selectionNumber = -1 for the MAIN selection pair ScintillaEditView::getSelectionLinesRange(intptr_t selectionNumber /* = -1 */) const { - size_t numSelections = execute(SCI_GETSELECTIONS); + size_t nbSelections = execute(SCI_GETSELECTIONS); size_t start_pos, end_pos; - if ((selectionNumber < 0) || (static_cast(selectionNumber) >= numSelections)) + if ((selectionNumber < 0) || (static_cast(selectionNumber) >= nbSelections)) { start_pos = execute(SCI_GETSELECTIONSTART); end_pos = execute(SCI_GETSELECTIONEND); @@ -4243,19 +4301,19 @@ pair ScintillaEditView::getSelectedCharsAndLinesCount(long long selectedCharsAndLines.first = getUnicodeSelectedLength(); - size_t numSelections = execute(SCI_GETSELECTIONS); + size_t nbSelections = execute(SCI_GETSELECTIONS); - if (numSelections == 1) + if (nbSelections == 1) { pair lineRange = getSelectionLinesRange(); selectedCharsAndLines.second = lineRange.second - lineRange.first + 1; } else if (execute(SCI_SELECTIONISRECTANGLE)) { - selectedCharsAndLines.second = numSelections; + selectedCharsAndLines.second = nbSelections; } else if ((maxSelectionsForLineCount == -1) || // -1 means process ALL of the selections - (numSelections <= static_cast(maxSelectionsForLineCount))) + (nbSelections <= static_cast(maxSelectionsForLineCount))) { // selections are obtained from Scintilla in the order user creates them, // not in a lowest-to-highest position-based order; @@ -4264,7 +4322,7 @@ pair ScintillaEditView::getSelectedCharsAndLinesCount(long long // by selection into low-to-high line number order before processing them further vector< pair > v; - for (size_t s = 0; s < numSelections; ++s) + for (size_t s = 0; s < nbSelections; ++s) { v.push_back(getSelectionLinesRange(s)); } @@ -4287,9 +4345,9 @@ pair ScintillaEditView::getSelectedCharsAndLinesCount(long long size_t ScintillaEditView::getUnicodeSelectedLength() const { size_t length = 0; - size_t numSelections = execute(SCI_GETSELECTIONS); + size_t nbSelections = execute(SCI_GETSELECTIONS); - for (size_t s = 0; s < numSelections; ++s) + for (size_t s = 0; s < nbSelections; ++s) { size_t start = execute(SCI_GETSELECTIONNSTART, s); size_t end = execute(SCI_GETSELECTIONNEND, s); @@ -4451,8 +4509,8 @@ void ScintillaEditView::removeAnyDuplicateLines() bool ScintillaEditView::pasteToMultiSelection() const { - size_t numSelections = execute(SCI_GETSELECTIONS); - if (numSelections <= 1) + size_t nbSelections = execute(SCI_GETSELECTIONS); + if (nbSelections <= 1) return false; // "MSDEVColumnSelect" is column format from Scintilla @@ -4468,19 +4526,50 @@ bool ScintillaEditView::pasteToMultiSelection() const ::GlobalUnlock(clipboardData); ::CloseClipboard(); - vector stringArray; - stringSplit(clipboardStr, getEOLString(), stringArray); - stringArray.erase(stringArray.cend() - 1); // remove the last empty string + vector clipboardStrings; + stringSplit(clipboardStr, getEOLString(), clipboardStrings); + clipboardStrings.erase(clipboardStrings.cend() - 1); // remove the last empty string + size_t nbClipboardStr = clipboardStrings.size(); - if (numSelections == stringArray.size()) + if (nbSelections >= nbClipboardStr) // enough holes for every insertion, keep holes empty if there are some left { execute(SCI_BEGINUNDOACTION); - for (size_t i = 0; i < numSelections; ++i) + for (size_t i = 0; i < nbClipboardStr; ++i) { LRESULT posStart = execute(SCI_GETSELECTIONNSTART, i); LRESULT posEnd = execute(SCI_GETSELECTIONNEND, i); - replaceTarget(stringArray[i].c_str(), posStart, posEnd); - posStart += stringArray[i].length(); + replaceTarget(clipboardStrings[i].c_str(), posStart, posEnd); + posStart += clipboardStrings[i].length(); + execute(SCI_SETSELECTIONNSTART, i, posStart); + execute(SCI_SETSELECTIONNEND, i, posStart); + } + execute(SCI_ENDUNDOACTION); + return true; + } + else if (nbSelections < nbClipboardStr) // not enough holes for insertion, every hole has several insertions + { + size_t nbStr2takeFromClipboard = nbClipboardStr / nbSelections; + + execute(SCI_BEGINUNDOACTION); + size_t j = 0; + for (size_t i = 0; i < nbSelections; ++i) + { + LRESULT posStart = execute(SCI_GETSELECTIONNSTART, i); + LRESULT posEnd = execute(SCI_GETSELECTIONNEND, i); + wstring severalStr; + wstring eol = getEOLString(); + for (size_t k = 0; k < nbStr2takeFromClipboard && j < nbClipboardStr; ++k) + { + severalStr += clipboardStrings[j]; + severalStr += eol; + ++j; + } + + // remove the latest added EOL + severalStr.erase(severalStr.length() - eol.length()); + + replaceTarget(severalStr.c_str(), posStart, posEnd); + posStart += severalStr.length(); execute(SCI_SETSELECTIONNSTART, i, posStart); execute(SCI_SETSELECTIONNEND, i, posStart); }