From 34cc4387e4aa6fa6dba99bdeccf589ffb5f8a334 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Fri, 7 Mar 2025 15:09:35 +0200 Subject: [PATCH 1/8] feat: Add handleMoveLines method with keystroke support and ActionListeners - Implement handleMoveLines to manage line movement functionality - Add keystroke bindings for triggering line movements - Integrate ActionListeners for responsive UI interactions - Ensure cross-platform compatibility for macOS and Linux --- app/src/processing/app/ui/Editor.java | 90 +++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index f87ba4ee1..5d54fd17b 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -821,6 +821,17 @@ protected JMenu buildEditMenu() { editMenuUpdatable.add(action); menu.add(item); + item = new JMenuItem("Move Selected Lines Up"); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK)); + item.addActionListener(e -> handleMoveLines(true)); + menu.add(item); + + item = new JMenuItem("Move Selected Lines Down"); + item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK)); + item.addActionListener(e -> handleMoveLines(false)); + menu.add(item); + + // Update copy/cut state on selection/de-selection menu.addMenuListener(new MenuListener() { // UndoAction and RedoAction do this for themselves. @@ -1865,6 +1876,7 @@ protected void handleCommentUncomment() { } + public void handleIndent() { handleIndentOutdent(true); } @@ -1918,6 +1930,84 @@ public void handleIndentOutdent(boolean indent) { stopCompoundEdit(); sketch.setModified(true); } + /** + * Moves the selected lines up or down in the text editor. + * + *

If {@code moveUp} is true, the selected lines are moved up. If false, they move down.

+ *

This method ensures proper selection updates and handles edge cases like moving + * the first or last line.

+ * + * @param moveUp {@code true} to move the selection up, {@code false} to move it down. + */ + public void handleMoveLines(boolean moveUp) { + startCompoundEdit(); + + int startLine = textarea.getSelectionStartLine(); + int stopLine = textarea.getSelectionStopLine(); + + // Adjust selection if the last line isn't fully selected + if (startLine != stopLine && + textarea.getSelectionStop() == textarea.getLineStartOffset(stopLine)) { + stopLine--; + } + + int replacedLine = moveUp ? startLine - 1 : stopLine + 1; + if (replacedLine < 0 || replacedLine >= textarea.getLineCount()) { + stopCompoundEdit(); + return; + } + + final String source = textarea.getText(); // Get full text from textarea + + int replaceStart = textarea.getLineStartOffset(replacedLine); + int replaceEnd = textarea.getLineStopOffset(replacedLine); + if (replaceEnd > source.length()) { + replaceEnd = source.length(); + } + + int selectionStart = textarea.getLineStartOffset(startLine); + int selectionEnd = textarea.getLineStopOffset(stopLine); + if (selectionEnd > source.length()) { + selectionEnd = source.length(); + } + + String replacedText = source.substring(replaceStart, replaceEnd); + String selectedText = source.substring(selectionStart, selectionEnd); + + if (replacedLine == textarea.getLineCount() - 1) { + replacedText += "\n"; + selectedText = selectedText.substring(0, Math.max(0, selectedText.length() - 1)); + } else if (stopLine == textarea.getLineCount() - 1) { + selectedText += "\n"; + replacedText = replacedText.substring(0, Math.max(0, replacedText.length() - 1)); + } + + int newSelectionStart, newSelectionEnd; + if (moveUp) { + textarea.select(selectionStart, selectionEnd); + textarea.setSelectedText(replacedText); // Use setSelectedText() + + textarea.select(replaceStart, replaceEnd); + textarea.setSelectedText(selectedText); + + newSelectionStart = textarea.getLineStartOffset(startLine - 1); + newSelectionEnd = textarea.getLineStopOffset(stopLine - 1); + } else { + textarea.select(replaceStart, replaceEnd); + textarea.setSelectedText(selectedText); + + textarea.select(selectionStart, selectionEnd); + textarea.setSelectedText(replacedText); + + newSelectionStart = textarea.getLineStartOffset(startLine + 1); + newSelectionEnd = stopLine + 1 < textarea.getLineCount() + ? Math.min(textarea.getLineStopOffset(stopLine + 1), source.length()) + : textarea.getLineStopOffset(stopLine); // Prevent out-of-bounds + } + + textarea.select(newSelectionStart, newSelectionEnd); + stopCompoundEdit(); + } static public boolean checkParen(char[] array, int index, int stop) { From 938109dbbd786d784b7515d542cab9f0001b4279 Mon Sep 17 00:00:00 2001 From: yehiarasheed Date: Fri, 7 Mar 2025 16:43:22 +0200 Subject: [PATCH 2/8] Fix selection bug in handleMoveLines on macOS by using invokeLater() Previously, moving lines down on macOS caused incorrect selection, moving the cursor to the end of the enclosing braces instead of properly selecting the moved line. This issue did not occur on Windows. The fix ensures that selection updates happen inside Swing's event dispatch thread by wrapping the selection logic in SwingUtilities.invokeLater(). This guarantees proper selection behavior across all platforms. Additionally, updated the JavaDoc for the method to reflect the fix and clarify the behavior of the selection update. Tested on Windows and macOS. --- app/src/processing/app/ui/Editor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 5d54fd17b..9736787d2 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -1936,6 +1936,8 @@ public void handleIndentOutdent(boolean indent) { *

If {@code moveUp} is true, the selected lines are moved up. If false, they move down.

*

This method ensures proper selection updates and handles edge cases like moving * the first or last line.

+ *

This operation is undo/redoable, allowing the user to revert the action using + * {@code Ctrl/Cmd + Z} (Undo) and redo with {@code Ctrl/Cmd + Y} (Redo).

* * @param moveUp {@code true} to move the selection up, {@code false} to move it down. */ @@ -2005,7 +2007,7 @@ public void handleMoveLines(boolean moveUp) { : textarea.getLineStopOffset(stopLine); // Prevent out-of-bounds } - textarea.select(newSelectionStart, newSelectionEnd); + SwingUtilities.invokeLater(() -> textarea.select(newSelectionStart, newSelectionEnd)); stopCompoundEdit(); } From 77b354d403e76194e783a04a3b82072c82349834 Mon Sep 17 00:00:00 2001 From: yehiarasheed Date: Fri, 7 Mar 2025 17:23:56 +0200 Subject: [PATCH 3/8] Update JavaDoc to clarify redo keybinding on macOS Updated the JavaDoc to reflect that the redo command on macOS is {@code Shift + Cmd + Z}, not {@code Cmd + Y}, and may vary on other platforms. This ensures the documentation is accurate and aligns with the actual keybindings. No functional code changes were made. --- app/src/processing/app/ui/Editor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 9736787d2..3817c4294 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -1937,7 +1937,8 @@ public void handleIndentOutdent(boolean indent) { *

This method ensures proper selection updates and handles edge cases like moving * the first or last line.

*

This operation is undo/redoable, allowing the user to revert the action using - * {@code Ctrl/Cmd + Z} (Undo) and redo with {@code Ctrl/Cmd + Y} (Redo).

+ * {@code Ctrl/Cmd + Z} (Undo). Redo functionality is available through the + * keybinding {@code Ctrl/Cmd + Z} on Windows/Linux and {@code Shift + Cmd + Z} on macOS.

* * @param moveUp {@code true} to move the selection up, {@code false} to move it down. */ From 07b5c8c2db986ea1327be933327312dc4213b186 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed Date: Mon, 10 Mar 2025 16:32:14 +0200 Subject: [PATCH 4/8] Improved line moving functionality in Editor - Updated line moving logic to select until the end of the line only, avoiding selection of the new line character. - Added functionality to move lines without selecting anything if no text is selected. --- app/src/processing/app/ui/Editor.java | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 3817c4294..bd1bf0fae 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -1944,6 +1944,15 @@ public void handleIndentOutdent(boolean indent) { */ public void handleMoveLines(boolean moveUp) { startCompoundEdit(); + boolean isSelected = false; + + if(textarea.isSelectionActive()) + isSelected = true; + + int caretPos = textarea.getCaretPosition(); + int currentLine = textarea.getCaretLine(); + int lineStart = textarea.getLineStartOffset(currentLine); + int column = caretPos - lineStart; int startLine = textarea.getSelectionStartLine(); int stopLine = textarea.getSelectionStopLine(); @@ -2007,11 +2016,22 @@ public void handleMoveLines(boolean moveUp) { ? Math.min(textarea.getLineStopOffset(stopLine + 1), source.length()) : textarea.getLineStopOffset(stopLine); // Prevent out-of-bounds } - - SwingUtilities.invokeLater(() -> textarea.select(newSelectionStart, newSelectionEnd)); stopCompoundEdit(); - } + if(isSelected) + SwingUtilities.invokeLater(() -> { + textarea.select(newSelectionStart, newSelectionEnd-1); + }); + else if (replacedLine >= 0 && replacedLine < textarea.getLineCount()) { + int replacedLineStart = textarea.getLineStartOffset(replacedLine); + int replacedLineEnd = textarea.getLineStopOffset(replacedLine); + + // Ensure caret stays within bounds of the new line + int newCaretPos = Math.min(replacedLineStart + column, replacedLineEnd - 1); + + SwingUtilities.invokeLater(() -> textarea.setCaretPosition(newCaretPos)); + } +} static public boolean checkParen(char[] array, int index, int stop) { while (index < stop) { From bb80a78a33e5441291c941f81028584ccb1c6a9f Mon Sep 17 00:00:00 2001 From: Yehia Rasheed Date: Tue, 11 Mar 2025 16:26:11 +0200 Subject: [PATCH 5/8] Remove "Move Selected Lines Up" and "Move Selected Lines Down" from the Edit Menu. --- app/src/processing/app/ui/Editor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index bd1bf0fae..2f1cbb8ce 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -824,12 +824,10 @@ protected JMenu buildEditMenu() { item = new JMenuItem("Move Selected Lines Up"); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK)); item.addActionListener(e -> handleMoveLines(true)); - menu.add(item); item = new JMenuItem("Move Selected Lines Down"); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK)); item.addActionListener(e -> handleMoveLines(false)); - menu.add(item); // Update copy/cut state on selection/de-selection From ab318974f30c354c69213ac1458e4d942ad124c3 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed Date: Tue, 11 Mar 2025 18:01:36 +0200 Subject: [PATCH 6/8] Fixed keyboard shortcut issue on all platforms, re-implemented manual shortcut registration. Added alternative shortcut handling for Move Lines functionality. --- app/src/processing/app/ui/Editor.java | 31 ++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 2f1cbb8ce..016143415 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -349,6 +349,29 @@ public void windowGainedFocus(WindowEvent e) { // Enable window resizing (which allows for full screen button) setResizable(true); + + { + // Move Lines Keyboard Shortcut (Alt + Arrow Up/Down) + KeyStroke moveUpKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK); + final String MOVE_UP_ACTION_KEY = "moveLinesUp"; + textarea.getInputMap(JComponent.WHEN_FOCUSED).put(moveUpKeyStroke, MOVE_UP_ACTION_KEY); + textarea.getActionMap().put(MOVE_UP_ACTION_KEY, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + handleMoveLines(true); + } + }); + + KeyStroke moveDownKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK); + final String MOVE_DOWN_ACTION_KEY = "moveLinesDown"; + textarea.getInputMap(JComponent.WHEN_FOCUSED).put(moveDownKeyStroke, MOVE_DOWN_ACTION_KEY); + textarea.getActionMap().put(MOVE_DOWN_ACTION_KEY, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + handleMoveLines(false); + } + }); + } } @@ -821,14 +844,6 @@ protected JMenu buildEditMenu() { editMenuUpdatable.add(action); menu.add(item); - item = new JMenuItem("Move Selected Lines Up"); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK)); - item.addActionListener(e -> handleMoveLines(true)); - - item = new JMenuItem("Move Selected Lines Down"); - item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK)); - item.addActionListener(e -> handleMoveLines(false)); - // Update copy/cut state on selection/de-selection menu.addMenuListener(new MenuListener() { From 44788e310ef1e52a5935959e954ddbc06c23d6cc Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Thu, 13 Mar 2025 13:59:40 +0200 Subject: [PATCH 7/8] Refactor code style to comply with style guidelines: removed unnecessary whitespace and added two blank lines between function blocks for improved readability. --- app/src/processing/app/ui/Editor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 016143415..65efb27f0 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -844,7 +844,6 @@ protected JMenu buildEditMenu() { editMenuUpdatable.add(action); menu.add(item); - // Update copy/cut state on selection/de-selection menu.addMenuListener(new MenuListener() { // UndoAction and RedoAction do this for themselves. @@ -1889,7 +1888,6 @@ protected void handleCommentUncomment() { } - public void handleIndent() { handleIndentOutdent(true); } @@ -1943,6 +1941,8 @@ public void handleIndentOutdent(boolean indent) { stopCompoundEdit(); sketch.setModified(true); } + + /** * Moves the selected lines up or down in the text editor. * @@ -2046,6 +2046,7 @@ else if (replacedLine >= 0 && replacedLine < textarea.getLineCount()) { } } + static public boolean checkParen(char[] array, int index, int stop) { while (index < stop) { switch (array[index]) { From 3482853c447f3ec63f1fc1e71a3922300930d527 Mon Sep 17 00:00:00 2001 From: Yehia Rasheed <157399068+yehiarasheed@users.noreply.github.com> Date: Thu, 13 Mar 2025 14:17:17 +0200 Subject: [PATCH 8/8] Update code style: added space after 'if' before the condition to comply with style guidelines. --- app/src/processing/app/ui/Editor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 65efb27f0..80298286a 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -1959,7 +1959,7 @@ public void handleMoveLines(boolean moveUp) { startCompoundEdit(); boolean isSelected = false; - if(textarea.isSelectionActive()) + if (textarea.isSelectionActive()) isSelected = true; int caretPos = textarea.getCaretPosition(); @@ -2031,7 +2031,7 @@ public void handleMoveLines(boolean moveUp) { } stopCompoundEdit(); - if(isSelected) + if (isSelected) SwingUtilities.invokeLater(() -> { textarea.select(newSelectionStart, newSelectionEnd-1); });