Repository: spyder-ide/spyder

Issue:
Shift+del should delete line only when empty selection, otherwise clipboard cut
## Description of your problem

**What steps will reproduce the problem?**

1.  select some text
2.  press shift-del

**What is the expected output? What do you see instead?**
selected text should be removed and inserted to clipboard so that shift-ins brings it back. (cut & paste)

instead it just deletes all lines that were part of the selection and one line more without putting any of it to the clipboard.

proposed solution:
* when the selection is empty then delete the line and put it to the clipboard
* when the selection is not empty then fall back to the standards conforming behavior of cut exactly the selected text to the clipboard.

Generally it should be expected that after the sequence shift-del, shift-ins the text should be in the same state as it was before, as it is in **any** other code editor out there. 

Many people wo use both hands on the keyboard use these keys as a replacement for the alternative Crtl-X, Ctrl-V because when selecting text with the keyboard (shift and cursor keys) instead of using the mouse then the left hand is already sitting on the shift key while the right index finger is conveniently near del and ins.

When not using it for cutting selected text and instead just deleting the current line without a selection then it should be put to the clipboard anyways, as this is also standard practice in many code editors that have line delete function, there is a corresponding paste key that brings back the line. In spyder it is just lost, not useful when rearranging lines.

Please do not close this bug too quickly, if nobody wants to work on this then I might give it a try myself, this odd behavior of spyder has caused me so much annoyance already I really want to find a solution to make the standard keys behave more standards conforming.



Discussion:
This is a good proposal, thanks. Please send us a pull request if you want to see this implemented in a future release because we don't have time to do it.
@csabella, maybe you'd like to work on this one?

Resolving patch:
diff --git a/spyder/plugins/shortcuts.py b/spyder/plugins/shortcuts.py
--- a/spyder/plugins/shortcuts.py
+++ b/spyder/plugins/shortcuts.py
@@ -62,7 +62,8 @@
 VALID_FINDER_CHARS = "[A-Za-z\s{0}]".format(VALID_ACCENT_CHARS)
 
 BLACKLIST = {
-    'Shift+Del': _('Currently used to delete lines on editor')
+    'Shift+Del': _('Currently used to delete lines on editor/Cut a word'),
+    'Shift+Ins': _('Currently used to paste a word')
 }
 
 if os.name == 'nt':
diff --git a/spyder/widgets/sourcecode/codeeditor.py b/spyder/widgets/sourcecode/codeeditor.py
--- a/spyder/widgets/sourcecode/codeeditor.py
+++ b/spyder/widgets/sourcecode/codeeditor.py
@@ -2759,6 +2759,7 @@ def keyPressEvent(self, event):
         ctrl = event.modifiers() & Qt.ControlModifier
         shift = event.modifiers() & Qt.ShiftModifier
         text = to_text_string(event.text())
+        has_selection = self.has_selected_text()
         if text:
             self.__clear_occurrences()
         if QToolTip.isVisible():
@@ -2774,6 +2775,10 @@ def keyPressEvent(self, event):
             seq = getattr(QKeySequence, qname)
             sc = get_shortcut('editor', name)
             default = QKeySequence(seq).toString()
+            # XXX - Using debug_print, it can be seen that event and seq
+            # will never be equal, so this code is never executed.
+            # Need to find out the intended purpose and if it should be
+            # retained.
             if event == seq and sc != default:
                 # if we have overridden it, call our action
                 for shortcut in self.shortcuts:
@@ -2824,14 +2829,25 @@ def keyPressEvent(self, event):
         elif shift and key == Qt.Key_Delete:
             # Shift + Del is a Key sequence reserved by most OSes
             # https://github.com/spyder-ide/spyder/issues/3405
-            self.delete_line()
+            # For now, add back reserved sequence for cut (issue 5973).
+            # Since Ctrl+X is configurable, this should also use preferences.
+            # https://doc.qt.io/qt-5/qkeysequence.html
+            if has_selection:
+                self.cut()
+            else:
+                self.delete_line()
+        elif shift and key == Qt.Key_Insert:
+            # For now, add back reserved sequence for paste (issue 5973).
+            # Since Ctrl+V is configurable, this should also use preferences.
+            # https://doc.qt.io/qt-5/qkeysequence.html
+            self.paste()
         elif key == Qt.Key_Insert and not shift and not ctrl:
             self.setOverwriteMode(not self.overwriteMode())
         elif key == Qt.Key_Backspace and not shift and not ctrl:
             leading_text = self.get_text('sol', 'cursor')
             leading_length = len(leading_text)
             trailing_spaces = leading_length-len(leading_text.rstrip())
-            if self.has_selected_text() or not self.intelligent_backspace:
+            if has_selection or not self.intelligent_backspace:
                 TextEditBaseWidget.keyPressEvent(self, event)
             else:
                 trailing_text = self.get_text('cursor', 'eol')
@@ -2869,10 +2885,10 @@ def keyPressEvent(self, event):
             # See Issue 495: on MacOS X, it is necessary to redefine this
             # basic action which should have been implemented natively
             self.stdkey_end(shift, ctrl)
-        elif text == '(' and not self.has_selected_text():
+        elif text == '(' and not has_selection:
             self.hide_completion_widget()
             self.handle_parentheses(text)
-        elif (text in ('[', '{') and not self.has_selected_text() and
+        elif (text in ('[', '{') and not has_selection and
               self.close_parentheses_enabled):
             s_trailing_text = self.get_text('cursor', 'eol').strip()
             if len(s_trailing_text) == 0 or \
@@ -2887,7 +2903,7 @@ def keyPressEvent(self, event):
           self.close_quotes_enabled:
             self.autoinsert_quotes(key)
         elif key in (Qt.Key_ParenRight, Qt.Key_BraceRight, Qt.Key_BracketRight)\
-          and not self.has_selected_text() and self.close_parentheses_enabled \
+          and not has_selection and self.close_parentheses_enabled \
           and not self.textCursor().atBlockEnd():
             cursor = self.textCursor()
             cursor.movePosition(QTextCursor.NextCharacter,
@@ -2904,7 +2920,7 @@ def keyPressEvent(self, event):
                 self.setTextCursor(cursor)
             else:
                 TextEditBaseWidget.keyPressEvent(self, event)
-        elif key == Qt.Key_Colon and not self.has_selected_text() \
+        elif key == Qt.Key_Colon and not has_selection \
              and self.auto_unindent_enabled:
             leading_text = self.get_text('sol', 'cursor')
             if leading_text.lstrip() in ('else', 'finally'):
@@ -2915,7 +2931,7 @@ def keyPressEvent(self, event):
                     self.unindent(force=True)
             TextEditBaseWidget.keyPressEvent(self, event)
         elif key == Qt.Key_Space and not shift and not ctrl \
-             and not self.has_selected_text() and self.auto_unindent_enabled:
+             and not has_selection and self.auto_unindent_enabled:
             leading_text = self.get_text('sol', 'cursor')
             if leading_text.lstrip() in ('elif', 'except'):
                 ind = lambda txt: len(txt)-len(txt.lstrip())
@@ -2927,7 +2943,7 @@ def keyPressEvent(self, event):
         elif key == Qt.Key_Tab:
             # Important note: <TAB> can't be called with a QShortcut because
             # of its singular role with respect to widget focus management
-            if not self.has_selected_text() and not self.tab_mode:
+            if not has_selection and not self.tab_mode:
                 self.intelligent_tab()
             else:
                 # indent the selected text
@@ -2935,7 +2951,7 @@ def keyPressEvent(self, event):
         elif key == Qt.Key_Backtab:
             # Backtab, i.e. Shift+<TAB>, could be treated as a QShortcut but
             # there is no point since <TAB> can't (see above)
-            if not self.has_selected_text() and not self.tab_mode:
+            if not has_selection and not self.tab_mode:
                 self.intelligent_backtab()
             else:
                 # indent the selected text


Test patch: