From 455fcb2da404f8a2746d9ded630babe86c7f48ed Mon Sep 17 00:00:00 2001 From: jmbockhorst <44308390+jmbockhorst@users.noreply.github.com> Date: Fri, 8 Mar 2019 10:20:47 -0600 Subject: [PATCH] Add "Multi-Select Next Occurence" feature * Added Ctrl+Shift+D to select next occurence of selection * Left and right movement works for multiple cursors * Enter/return a new line works for multiple cursors Close #5322, close #5399 --- PowerEditor/src/NppCommands.cpp | 97 +++++++++++++++++ PowerEditor/src/Parameters.cpp | 11 +- .../ScitillaComponent/ScintillaEditView.cpp | 102 ++++++++++++++++++ .../src/ScitillaComponent/ScintillaEditView.h | 1 + PowerEditor/src/menuCmdID.h | 6 ++ 5 files changed, 212 insertions(+), 5 deletions(-) diff --git a/PowerEditor/src/NppCommands.cpp b/PowerEditor/src/NppCommands.cpp index 51b7205f..1b3891a8 100644 --- a/PowerEditor/src/NppCommands.cpp +++ b/PowerEditor/src/NppCommands.cpp @@ -1570,6 +1570,103 @@ void Notepad_plus::command(int id) } break; + case IDM_EDIT_SELECTNEXTOCCURENCE: + { + // Get the selected text length of the first selection + int textLen = abs(int(_pEditView->execute(SCI_GETSELECTIONNANCHOR, 0)) - int(_pEditView->execute(SCI_GETSELECTIONNCARET, 0))); + if (!textLen) + return; + + // Get selected text + char* selectedText = new char[textLen + 1]; + _pEditView->getSelectedText(selectedText, textLen + 1); + + // Get current position of the last selection + int numSelections = int(_pEditView->execute(SCI_GETSELECTIONS)); + int currentPos = int(_pEditView->execute(SCI_GETSELECTIONNCARET, numSelections - 1)); + int selectonEnd = max(int(_pEditView->execute(SCI_GETSELECTIONNANCHOR, numSelections - 1)), currentPos); + int docLength = int(_pEditView->execute(SCI_GETLENGTH)); + + // Convert char to TCHAR + WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance(); + UINT cp = static_cast(_pEditView->execute(SCI_GETCODEPAGE)); + const TCHAR* textToFind = wmc->char2wchar(selectedText, cp); + + // Find position of next occurence + int posFound = _pEditView->searchInTarget(textToFind, textLen, currentPos + 1, docLength); + + if (posFound > currentPos) + { + int start = posFound + textLen; + int end = posFound; + + // Detect if selection happened the other way + if (currentPos != selectonEnd) + { + start = posFound; + end = posFound + textLen; + } + + _pEditView->execute(SCI_ADDSELECTION, start, end); + + // Keep the main selection the first one + _pEditView->execute(SCI_SETMAINSELECTION, 0); + } + } + break; + + case IDM_EDIT_MULTICHARLEFT: + _pEditView->multiCursorLeftOrRight(-1); + break; + + case IDM_EDIT_MULTICHARLEFTEXTEND: + _pEditView->multiCursorLeftOrRight(-1, true); + break; + + case IDM_EDIT_MULTICHARRIGHT: + _pEditView->multiCursorLeftOrRight(1); + break; + + case IDM_EDIT_MULTICHARRIGHTEXTEND: + _pEditView->multiCursorLeftOrRight(1, true); + break; + + case IDM_EDIT_MULTINEWLINE: + { + // Get the number of selection + int numSelections = int(_pEditView->execute(SCI_GETSELECTIONS)); + + // Keep an array of the new positions + std::vector selectionPositions; + + // Loop through each selection + for (int i = 0; i < numSelections; ++i) + { + // Get the current caret position of the seleciton + int currentPos = int(_pEditView->execute(SCI_GETSELECTIONNCARET, i)); + + // Add the new position to the array + selectionPositions.push_back(currentPos + i * 2 + 2); + + // New line caret adjustment + _pEditView->execute(SCI_SETSELECTIONNCARET, i, currentPos + i * 2); + _pEditView->execute(SCI_SETSELECTIONNANCHOR, i, currentPos + i * 2); + + // Set the current selection to main + _pEditView->execute(SCI_SETMAINSELECTION, i); + + // Execute the new line command on the selection + _pEditView->execute(SCI_NEWLINE); + } + + // Add all of the cursors back where they should be + for (int i = 0; i < (static_cast(selectionPositions.size()) - 1); ++i) + { + _pEditView->execute(SCI_ADDSELECTION, selectionPositions[i], selectionPositions[i]); + } + } + break; + case IDM_SEARCH_CUTMARKEDLINES : cutMarkedLines(); break; diff --git a/PowerEditor/src/Parameters.cpp b/PowerEditor/src/Parameters.cpp index 4a81a76b..86137d7b 100644 --- a/PowerEditor/src/Parameters.cpp +++ b/PowerEditor/src/Parameters.cpp @@ -141,6 +141,12 @@ static const WinMenuKeyDefinition winKeyDefs[] = { VK_NULL, IDM_EDIT_SORTLINES_DECIMALCOMMA_DESCENDING, false, false, false, nullptr }, { VK_NULL, IDM_EDIT_SORTLINES_DECIMALDOT_ASCENDING, false, false, false, nullptr }, { VK_NULL, IDM_EDIT_SORTLINES_DECIMALDOT_DESCENDING, false, false, false, nullptr }, + { VK_D, IDM_EDIT_SELECTNEXTOCCURENCE, true, false, true, nullptr }, + { VK_LEFT, IDM_EDIT_MULTICHARLEFT, false, false, false, nullptr }, + { VK_LEFT, IDM_EDIT_MULTICHARLEFTEXTEND, false, false, true, nullptr }, + { VK_RIGHT, IDM_EDIT_MULTICHARRIGHT, false, false, false, nullptr }, + { VK_RIGHT, IDM_EDIT_MULTICHARRIGHTEXTEND, false, false, true, nullptr }, + { VK_RETURN, IDM_EDIT_MULTINEWLINE, false, false, false, nullptr }, { VK_Q, IDM_EDIT_BLOCK_COMMENT, true, false, false, nullptr }, { VK_K, IDM_EDIT_BLOCK_COMMENT_SET, true, false, false, nullptr }, { VK_K, IDM_EDIT_BLOCK_UNCOMMENT, true, false, true, nullptr }, @@ -420,7 +426,6 @@ static const ScintillaKeyDefinition scintKeyDefs[] = {TEXT(""), SCI_UNDO, false, true, false, VK_BACK, 0}, {TEXT("SCI_REDO"), SCI_REDO, true, false, false, VK_Y, IDM_EDIT_REDO}, {TEXT(""), SCI_REDO, true, false, true, VK_Z, 0}, - {TEXT("SCI_NEWLINE"), SCI_NEWLINE, false, false, false, VK_RETURN, 0}, {TEXT(""), SCI_NEWLINE, false, false, true, VK_RETURN, 0}, {TEXT("SCI_TAB"), SCI_TAB, false, false, false, VK_TAB, IDM_EDIT_INS_TAB}, {TEXT("SCI_BACKTAB"), SCI_BACKTAB, false, false, true, VK_TAB, IDM_EDIT_RMV_TAB}, @@ -445,11 +450,7 @@ static const ScintillaKeyDefinition scintKeyDefs[] = {TEXT("SCI_PARADOWNEXTEND"), SCI_PARADOWNEXTEND, true, false, true, VK_OEM_6, 0}, {TEXT("SCI_PARAUP"), SCI_PARAUP, true, false, false, VK_OEM_4, 0}, {TEXT("SCI_PARAUPEXTEND"), SCI_PARAUPEXTEND, true, false, true, VK_OEM_4, 0}, - {TEXT("SCI_CHARLEFT"), SCI_CHARLEFT, false, false, false, VK_LEFT, 0}, - {TEXT("SCI_CHARLEFTEXTEND"), SCI_CHARLEFTEXTEND, false, false, true, VK_LEFT, 0}, {TEXT("SCI_CHARLEFTRECTEXTEND"), SCI_CHARLEFTRECTEXTEND, false, true, true, VK_LEFT, 0}, - {TEXT("SCI_CHARRIGHT"), SCI_CHARRIGHT, false, false, false, VK_RIGHT, 0}, - {TEXT("SCI_CHARRIGHTEXTEND"), SCI_CHARRIGHTEXTEND, false, false, true, VK_RIGHT, 0}, {TEXT("SCI_CHARRIGHTRECTEXTEND"), SCI_CHARRIGHTRECTEXTEND, false, true, true, VK_RIGHT, 0}, {TEXT("SCI_WORDLEFT"), SCI_WORDLEFT, true, false, false, VK_LEFT, 0}, {TEXT("SCI_WORDLEFTEXTEND"), SCI_WORDLEFTEXTEND, true, false, true, VK_LEFT, 0}, diff --git a/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp b/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp index f925970a..6e0ab52d 100644 --- a/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp +++ b/PowerEditor/src/ScitillaComponent/ScintillaEditView.cpp @@ -2589,6 +2589,108 @@ void ScintillaEditView::currentLinesDown() const execute(SCI_SCROLLRANGE, execute(SCI_GETSELECTIONEND), execute(SCI_GETSELECTIONSTART)); } +void ScintillaEditView::multiCursorLeftOrRight(int direction, bool shouldExt) const +{ + const int NUM_SELECTIONS_FOR_DEFAULT = 1; + + // Get the number of selection + int numSelections = int(execute(SCI_GETSELECTIONS)); + + // Use default functions when there is one selection + if (numSelections == NUM_SELECTIONS_FOR_DEFAULT) + { + if (direction < 0 && !shouldExt) + { + execute(SCI_CHARLEFT); + } + else if (direction < 0 && shouldExt) + { + execute(SCI_CHARLEFTEXTEND); + } + else if (direction > 0 && !shouldExt) + { + execute(SCI_CHARRIGHT); + } + else if (direction > 0 && shouldExt) + { + execute(SCI_CHARRIGHTEXTEND); + } + + return; + } + + // Stop the caret blinking so it stays on + execute(SCI_SETCARETPERIOD, 0); + + // Loop through each selection + for (int i = 0; i < numSelections; ++i) + { + // Get the current caret position of the seleciton + int currentPos = int(execute(SCI_GETSELECTIONNCARET, i)); + int docEnd = getCurrentDocLen(); + + int newPos = currentPos + direction; + + // Cursor is at end, use virtual space + if ((currentPos >= docEnd && direction > 0)) + { + if (i == 0) + { + break; + } + + // Add virtual space + execute(SCI_SETSELECTIONNCARETVIRTUALSPACE, i, execute(SCI_GETSELECTIONNCARETVIRTUALSPACE, i) + 1); + + if (!shouldExt) + { + execute(SCI_SETSELECTIONNANCHORVIRTUALSPACE, i, execute(SCI_GETSELECTIONNANCHORVIRTUALSPACE, i) + 1); + } + } + else if (currentPos > docEnd) + { + // Moving back, remove virtual space + execute(SCI_SETSELECTIONNCARETVIRTUALSPACE, i, execute(SCI_GETSELECTIONNCARETVIRTUALSPACE, i) - 1); + + if (!shouldExt) + { + execute(SCI_SETSELECTIONNANCHORVIRTUALSPACE, i, execute(SCI_GETSELECTIONNANCHORVIRTUALSPACE, i) - 1); + } + } + else if (currentPos <= 0 && direction < 0) + { + break; + } + + // Get the starting and ending position of the line + int lineStart = int(execute(SCI_POSITIONFROMLINE, int(execute(SCI_LINEFROMPOSITION, currentPos)))); + int lineEnd = int(execute(SCI_POSITIONFROMLINE, int(execute(SCI_LINEFROMPOSITION, currentPos)) + 1)) - 1; + + // Go back or forward another position because of new line + if (newPos < lineStart && direction < 0) + { + newPos--; + } + else if (newPos >= lineEnd && lineEnd + 1 != docEnd && direction > 0) + { + newPos++; + } + + // Set the current caret position to the new position + execute(SCI_SETSELECTIONNCARET, i, newPos); + + if (!shouldExt) + { + execute(SCI_SETSELECTIONNANCHOR, i, newPos); + } + } + + const NppGUI& nppGUI = _pParameter->getNppGUI(); + + // Reset the caret period so it start blinking again + execute(SCI_SETCARETPERIOD, nppGUI._caretBlinkRate); +} + void ScintillaEditView::changeCase(__inout wchar_t * const strWToConvert, const int & nbChars, const TextCase & caseToConvert) const { if (strWToConvert == nullptr || nbChars == NULL) diff --git a/PowerEditor/src/ScitillaComponent/ScintillaEditView.h b/PowerEditor/src/ScitillaComponent/ScintillaEditView.h index 6cf1c5f0..4b690258 100644 --- a/PowerEditor/src/ScitillaComponent/ScintillaEditView.h +++ b/PowerEditor/src/ScitillaComponent/ScintillaEditView.h @@ -538,6 +538,7 @@ public: std::pair getSelectionLinesRange() const; void currentLinesUp() const; void currentLinesDown() const; + void multiCursorLeftOrRight(int direction, bool shouldExt = false) const; void changeCase(__inout wchar_t * const strWToConvert, const int & nbChars, const TextCase & caseToConvert) const; void convertSelectedTextTo(const TextCase & caseToConvert); diff --git a/PowerEditor/src/menuCmdID.h b/PowerEditor/src/menuCmdID.h index 3e1455ca..25b6c45f 100644 --- a/PowerEditor/src/menuCmdID.h +++ b/PowerEditor/src/menuCmdID.h @@ -130,6 +130,12 @@ #define IDM_EDIT_SORTLINES_DECIMALCOMMA_DESCENDING (IDM_EDIT + 64) #define IDM_EDIT_SORTLINES_DECIMALDOT_ASCENDING (IDM_EDIT + 65) #define IDM_EDIT_SORTLINES_DECIMALDOT_DESCENDING (IDM_EDIT + 66) + #define IDM_EDIT_SELECTNEXTOCCURENCE (IDM_EDIT + 78) + #define IDM_EDIT_MULTICHARLEFT (IDM_EDIT + 79) + #define IDM_EDIT_MULTICHARLEFTEXTEND (IDM_EDIT + 80) + #define IDM_EDIT_MULTICHARRIGHT (IDM_EDIT + 81) + #define IDM_EDIT_MULTICHARRIGHTEXTEND (IDM_EDIT + 82) + #define IDM_EDIT_MULTINEWLINE (IDM_EDIT + 83) #define IDM_EDIT_OPENASFILE (IDM_EDIT + 73) #define IDM_EDIT_OPENINFOLDER (IDM_EDIT + 74)