diff --git a/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java b/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java
index a1f04eaa57ff..4525afaa26e7 100644
--- a/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java
+++ b/python/src/com/jetbrains/python/codeInsight/editorActions/moveUpDown/PyStatementMover.java
@@ -341,6 +341,8 @@ public class PyStatementMover extends LineMover {
final CaretModel caretModel = editor.getCaretModel();
final int selectionStart = selectionModel.getSelectionStart();
+ final LogicalPosition selectionBeforeMoveStart = editor.offsetToLogicalPosition(selectionStart);
+ final LogicalPosition selectionBeforeMoveEnd = editor.offsetToLogicalPosition(selectionModel.getSelectionEnd());
boolean isSelectionStartAtCaret = caretModel.getOffset() == selectionStart;
final SelectionContainer selectionLen = getSelectionLenContainer(editor, ((MyLineRange)toMove));
@@ -354,8 +356,12 @@ public class PyStatementMover extends LineMover {
else {
offset = moveInOut(((MyLineRange)toMove), editor, info);
}
+ final LogicalPosition positionOffsetAfterMove = editor.offsetToLogicalPosition(offset);
restoreCaretAndSelection(file, editor, isSelectionStartAtCaret, hasSelection, selectionLen,
- shift, offset, (MyLineRange)toMove);
+ shift, offset, (MyLineRange)toMove,
+ selectionBeforeMoveStart,
+ selectionBeforeMoveEnd,
+ positionOffsetAfterMove);
info.toMove2 = info.toMove; //do not move further
});
}
@@ -388,7 +394,10 @@ public class PyStatementMover extends LineMover {
private static void restoreCaretAndSelection(@NotNull final PsiFile file, @NotNull final Editor editor, boolean selectionStartAtCaret,
boolean hasSelection, @NotNull final SelectionContainer selectionContainer, int shift,
- int offset, @NotNull final MyLineRange toMove) {
+ int offset, @NotNull final MyLineRange toMove,
+ LogicalPosition selectionBeforeMoveStart,
+ LogicalPosition selectionBeforeMoveEnd,
+ LogicalPosition positionOffsetAfterMove) {
final Document document = editor.getDocument();
final SelectionModel selectionModel = editor.getSelectionModel();
final CaretModel caretModel = editor.getCaretModel();
@@ -427,13 +436,31 @@ public class PyStatementMover extends LineMover {
caretModel.moveToOffset(newCaretOffset);
if (hasSelection) {
- if (selectionStartAtCaret) {
- int newSelectionEnd = newCaretOffset + selectionLen;
- selectionModel.setSelection(newCaretOffset, newSelectionEnd);
+ int selectionLinesDiff = selectionBeforeMoveEnd.line - selectionBeforeMoveStart.line;
+ if (selectionLinesDiff > 1) {
+ int endOffsetColumn = selectionBeforeMoveEnd.column;
+ if (endOffsetColumn > 0) endOffsetColumn += positionOffsetAfterMove.column - selectionBeforeMoveStart.column;
+ int startOffsetColumn = positionOffsetAfterMove.column;
+ if (selectionBeforeMoveStart.column == 0) startOffsetColumn = 0;
+ int startOffset = editor.logicalPositionToOffset(new LogicalPosition(positionOffsetAfterMove.line, startOffsetColumn));
+ int endOffset = editor.logicalPositionToOffset(new LogicalPosition(positionOffsetAfterMove.line + selectionLinesDiff, endOffsetColumn));
+ selectionModel.setSelection(startOffset, endOffset);
+ if (selectionStartAtCaret) {
+ caretModel.moveToOffset(startOffset);
+ }
+ else {
+ caretModel.moveToOffset(endOffset);
+ }
}
else {
- int newSelectionStart = newCaretOffset - selectionLen;
- selectionModel.setSelection(newSelectionStart, newCaretOffset);
+ if (selectionStartAtCaret) {
+ int newSelectionEnd = newCaretOffset + selectionLen;
+ selectionModel.setSelection(newCaretOffset, newSelectionEnd);
+ }
+ else {
+ int newSelectionStart = newCaretOffset - selectionLen;
+ selectionModel.setSelection(newSelectionStart, newCaretOffset);
+ }
}
}
}
diff --git a/python/testData/mover/multiLineSelectionDifferentLevelsMoveIntoFunction.py b/python/testData/mover/multiLineSelectionDifferentLevelsMoveIntoFunction.py
new file mode 100644
index 000000000000..7e0e8233ca66
--- /dev/null
+++ b/python/testData/mover/multiLineSelectionDifferentLevelsMoveIntoFunction.py
@@ -0,0 +1,11 @@
+import numpy as np
+
+
+def split_by_words(X):
+ X = np.core.chararray.lower(X)
+ return np.core.chararray.split(X)
+DELIMITERS = "!?:;,.\'-+/\\()"
+
+def parse(string):
+ return "".join((" " if char in DELIMITERS else char) for char in string).split()
+
\ No newline at end of file
diff --git a/python/testData/mover/multiLineSelectionDifferentLevelsMoveIntoFunction_afterDown.py b/python/testData/mover/multiLineSelectionDifferentLevelsMoveIntoFunction_afterDown.py
new file mode 100644
index 000000000000..534fefb52e10
--- /dev/null
+++ b/python/testData/mover/multiLineSelectionDifferentLevelsMoveIntoFunction_afterDown.py
@@ -0,0 +1,12 @@
+import numpy as np
+
+
+def split_by_words(X):
+ X = np.core.chararray.lower(X)
+ return np.core.chararray.split(X)
+
+DELIMITERS = "!?:;,.\'-+/\\()"
+
+def parse(string):
+ return "".join((" " if char in DELIMITERS else char) for char in string).split()
+
\ No newline at end of file
diff --git a/python/testData/mover/multiLineSelectionDifferentLevelsMoveIntoFunction_afterUp.py b/python/testData/mover/multiLineSelectionDifferentLevelsMoveIntoFunction_afterUp.py
new file mode 100644
index 000000000000..9bb00dba1fb6
--- /dev/null
+++ b/python/testData/mover/multiLineSelectionDifferentLevelsMoveIntoFunction_afterUp.py
@@ -0,0 +1,11 @@
+import numpy as np
+
+
+def split_by_words(X):
+ X = np.core.chararray.lower(X)
+ return np.core.chararray.split(X)
+ DELIMITERS = "!?:;,.\'-+/\\()"
+
+ def parse(string):
+ return "".join((" " if char in DELIMITERS else char) for char in string).split()
+
\ No newline at end of file
diff --git a/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromFunction.py b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromFunction.py
new file mode 100644
index 000000000000..ec3cb5784dbe
--- /dev/null
+++ b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromFunction.py
@@ -0,0 +1,4 @@
+def func():
+ n = 0
+ while n:
+ print("spam")
diff --git a/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromFunction_afterDown.py b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromFunction_afterDown.py
new file mode 100644
index 000000000000..b79d83dcdb63
--- /dev/null
+++ b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromFunction_afterDown.py
@@ -0,0 +1,5 @@
+def func():
+ pass
+n = 0
+while n:
+ print("spam")
diff --git a/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromFunction_afterUp.py b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromFunction_afterUp.py
new file mode 100644
index 000000000000..475ee3af816a
--- /dev/null
+++ b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromFunction_afterUp.py
@@ -0,0 +1,5 @@
+n = 0
+while n:
+ print("spam")
+def func():
+ pass
diff --git a/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromNestedFunction.py b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromNestedFunction.py
new file mode 100644
index 000000000000..008f5a39ad2a
--- /dev/null
+++ b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromNestedFunction.py
@@ -0,0 +1,5 @@
+def func():
+ def nested_func():
+ n = 0
+ while n:
+ print("spam")
diff --git a/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromNestedFunction_afterDown.py b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromNestedFunction_afterDown.py
new file mode 100644
index 000000000000..4a51027425b1
--- /dev/null
+++ b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromNestedFunction_afterDown.py
@@ -0,0 +1,6 @@
+def func():
+ def nested_func():
+ pass
+ n = 0
+ while n:
+ print("spam")
diff --git a/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromNestedFunction_afterUp.py b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromNestedFunction_afterUp.py
new file mode 100644
index 000000000000..b178cc6080b9
--- /dev/null
+++ b/python/testData/mover/multiLineSelectionDifferentLevelsMoveOutFromNestedFunction_afterUp.py
@@ -0,0 +1,6 @@
+def func():
+ n = 0
+ while n:
+ print("spam")
+ def nested_func():
+ pass
diff --git a/python/testData/mover/singleLineSelectionOutFromFunction.py b/python/testData/mover/singleLineSelectionOutFromFunction.py
new file mode 100644
index 000000000000..a2582d0a8300
--- /dev/null
+++ b/python/testData/mover/singleLineSelectionOutFromFunction.py
@@ -0,0 +1,2 @@
+def foo():
+ a = 42
diff --git a/python/testData/mover/singleLineSelectionOutFromFunction_afterDown.py b/python/testData/mover/singleLineSelectionOutFromFunction_afterDown.py
new file mode 100644
index 000000000000..5db354ee0f0b
--- /dev/null
+++ b/python/testData/mover/singleLineSelectionOutFromFunction_afterDown.py
@@ -0,0 +1,3 @@
+def foo():
+ pass
+a = 42
diff --git a/python/testData/mover/singleLineSelectionOutFromFunction_afterUp.py b/python/testData/mover/singleLineSelectionOutFromFunction_afterUp.py
new file mode 100644
index 000000000000..23d08c0df9b0
--- /dev/null
+++ b/python/testData/mover/singleLineSelectionOutFromFunction_afterUp.py
@@ -0,0 +1,3 @@
+a = 42
+def foo():
+ pass
diff --git a/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java b/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java
index c57faedfd50c..350f87496f04 100644
--- a/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java
+++ b/python/testSrc/com/jetbrains/python/PyStatementMoverTest.java
@@ -248,6 +248,26 @@ public class PyStatementMoverTest extends PyTestCase {
doTest();
}
+ // PY-41375
+ public void testMultiLineSelectionDifferentLevelsMoveOutFromFunction() {
+ doTest();
+ }
+
+ // PY-37765
+ public void testMultiLineSelectionDifferentLevelsMoveIntoFunction() {
+ doTest();
+ }
+
+ // PY-41375
+ public void testSingleLineSelectionOutFromFunction() {
+ doTest();
+ }
+
+ // PY-41375
+ public void testMultiLineSelectionDifferentLevelsMoveOutFromNestedFunction() {
+ doTest();
+ }
+
public void testTheSameLevelMultiple() { //PY-10947
doTest();
}