@@ -349,6 +349,29 @@ public void windowGainedFocus(WindowEvent e) {
349
349
350
350
// Enable window resizing (which allows for full screen button)
351
351
setResizable (true );
352
+
353
+ {
354
+ // Move Lines Keyboard Shortcut (Alt + Arrow Up/Down)
355
+ KeyStroke moveUpKeyStroke = KeyStroke .getKeyStroke (KeyEvent .VK_UP , InputEvent .ALT_DOWN_MASK );
356
+ final String MOVE_UP_ACTION_KEY = "moveLinesUp" ;
357
+ textarea .getInputMap (JComponent .WHEN_FOCUSED ).put (moveUpKeyStroke , MOVE_UP_ACTION_KEY );
358
+ textarea .getActionMap ().put (MOVE_UP_ACTION_KEY , new AbstractAction () {
359
+ @ Override
360
+ public void actionPerformed (ActionEvent e ) {
361
+ handleMoveLines (true );
362
+ }
363
+ });
364
+
365
+ KeyStroke moveDownKeyStroke = KeyStroke .getKeyStroke (KeyEvent .VK_DOWN , InputEvent .ALT_DOWN_MASK );
366
+ final String MOVE_DOWN_ACTION_KEY = "moveLinesDown" ;
367
+ textarea .getInputMap (JComponent .WHEN_FOCUSED ).put (moveDownKeyStroke , MOVE_DOWN_ACTION_KEY );
368
+ textarea .getActionMap ().put (MOVE_DOWN_ACTION_KEY , new AbstractAction () {
369
+ @ Override
370
+ public void actionPerformed (ActionEvent e ) {
371
+ handleMoveLines (false );
372
+ }
373
+ });
374
+ }
352
375
}
353
376
354
377
@@ -1919,6 +1942,110 @@ public void handleIndentOutdent(boolean indent) {
1919
1942
sketch .setModified (true );
1920
1943
}
1921
1944
1945
+
1946
+ /**
1947
+ * Moves the selected lines up or down in the text editor.
1948
+ *
1949
+ * <p>If {@code moveUp} is true, the selected lines are moved up. If false, they move down.</p>
1950
+ * <p>This method ensures proper selection updates and handles edge cases like moving
1951
+ * the first or last line.</p>
1952
+ * <p>This operation is undo/redoable, allowing the user to revert the action using
1953
+ * {@code Ctrl/Cmd + Z} (Undo). Redo functionality is available through the
1954
+ * keybinding {@code Ctrl/Cmd + Z} on Windows/Linux and {@code Shift + Cmd + Z} on macOS.</p>
1955
+ *
1956
+ * @param moveUp {@code true} to move the selection up, {@code false} to move it down.
1957
+ */
1958
+ public void handleMoveLines (boolean moveUp ) {
1959
+ startCompoundEdit ();
1960
+ boolean isSelected = false ;
1961
+
1962
+ if (textarea .isSelectionActive ())
1963
+ isSelected = true ;
1964
+
1965
+ int caretPos = textarea .getCaretPosition ();
1966
+ int currentLine = textarea .getCaretLine ();
1967
+ int lineStart = textarea .getLineStartOffset (currentLine );
1968
+ int column = caretPos - lineStart ;
1969
+
1970
+ int startLine = textarea .getSelectionStartLine ();
1971
+ int stopLine = textarea .getSelectionStopLine ();
1972
+
1973
+ // Adjust selection if the last line isn't fully selected
1974
+ if (startLine != stopLine &&
1975
+ textarea .getSelectionStop () == textarea .getLineStartOffset (stopLine )) {
1976
+ stopLine --;
1977
+ }
1978
+
1979
+ int replacedLine = moveUp ? startLine - 1 : stopLine + 1 ;
1980
+ if (replacedLine < 0 || replacedLine >= textarea .getLineCount ()) {
1981
+ stopCompoundEdit ();
1982
+ return ;
1983
+ }
1984
+
1985
+ final String source = textarea .getText (); // Get full text from textarea
1986
+
1987
+ int replaceStart = textarea .getLineStartOffset (replacedLine );
1988
+ int replaceEnd = textarea .getLineStopOffset (replacedLine );
1989
+ if (replaceEnd > source .length ()) {
1990
+ replaceEnd = source .length ();
1991
+ }
1992
+
1993
+ int selectionStart = textarea .getLineStartOffset (startLine );
1994
+ int selectionEnd = textarea .getLineStopOffset (stopLine );
1995
+ if (selectionEnd > source .length ()) {
1996
+ selectionEnd = source .length ();
1997
+ }
1998
+
1999
+ String replacedText = source .substring (replaceStart , replaceEnd );
2000
+ String selectedText = source .substring (selectionStart , selectionEnd );
2001
+
2002
+ if (replacedLine == textarea .getLineCount () - 1 ) {
2003
+ replacedText += "\n " ;
2004
+ selectedText = selectedText .substring (0 , Math .max (0 , selectedText .length () - 1 ));
2005
+ } else if (stopLine == textarea .getLineCount () - 1 ) {
2006
+ selectedText += "\n " ;
2007
+ replacedText = replacedText .substring (0 , Math .max (0 , replacedText .length () - 1 ));
2008
+ }
2009
+
2010
+ int newSelectionStart , newSelectionEnd ;
2011
+ if (moveUp ) {
2012
+ textarea .select (selectionStart , selectionEnd );
2013
+ textarea .setSelectedText (replacedText ); // Use setSelectedText()
2014
+
2015
+ textarea .select (replaceStart , replaceEnd );
2016
+ textarea .setSelectedText (selectedText );
2017
+
2018
+ newSelectionStart = textarea .getLineStartOffset (startLine - 1 );
2019
+ newSelectionEnd = textarea .getLineStopOffset (stopLine - 1 );
2020
+ } else {
2021
+ textarea .select (replaceStart , replaceEnd );
2022
+ textarea .setSelectedText (selectedText );
2023
+
2024
+ textarea .select (selectionStart , selectionEnd );
2025
+ textarea .setSelectedText (replacedText );
2026
+
2027
+ newSelectionStart = textarea .getLineStartOffset (startLine + 1 );
2028
+ newSelectionEnd = stopLine + 1 < textarea .getLineCount ()
2029
+ ? Math .min (textarea .getLineStopOffset (stopLine + 1 ), source .length ())
2030
+ : textarea .getLineStopOffset (stopLine ); // Prevent out-of-bounds
2031
+ }
2032
+ stopCompoundEdit ();
2033
+
2034
+ if (isSelected )
2035
+ SwingUtilities .invokeLater (() -> {
2036
+ textarea .select (newSelectionStart , newSelectionEnd -1 );
2037
+ });
2038
+ else if (replacedLine >= 0 && replacedLine < textarea .getLineCount ()) {
2039
+ int replacedLineStart = textarea .getLineStartOffset (replacedLine );
2040
+ int replacedLineEnd = textarea .getLineStopOffset (replacedLine );
2041
+
2042
+ // Ensure caret stays within bounds of the new line
2043
+ int newCaretPos = Math .min (replacedLineStart + column , replacedLineEnd - 1 );
2044
+
2045
+ SwingUtilities .invokeLater (() -> textarea .setCaretPosition (newCaretPos ));
2046
+ }
2047
+ }
2048
+
1922
2049
1923
2050
static public boolean checkParen (char [] array , int index , int stop ) {
1924
2051
while (index < stop ) {
0 commit comments