diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py
index 92d9b1e..e31c3f9 100644
--- a/IPython/frontend/qt/console/console_widget.py
+++ b/IPython/frontend/qt/console/console_widget.py
@@ -268,6 +268,11 @@ class ConsoleWidget(QtGui.QWidget):
         A boolean indicating whether the source was executed.
         """
         if not hidden:
+            # Do everything here inside an edit block so continuation prompts
+            # are removed seamlessly via undo/redo.
+            cursor = self._control.textCursor()
+            cursor.beginEditBlock()
+
             if source is not None:
                 self.input_buffer = source
 
@@ -284,12 +289,20 @@ class ConsoleWidget(QtGui.QWidget):
                 # This ensures that _prompt_pos does not become invalid due to
                 # text truncation.
                 self._control.document().setMaximumBlockCount(self.buffer_size)
+
+                # Setting a positive maximum block count will automatically
+                # disable the undo/redo history, but just to be safe:
+                self._control.setUndoRedoEnabled(False)
+
             self._execute(real_source, hidden)
         elif hidden:
             raise RuntimeError('Incomplete noninteractive input: "%s"' % source)
         else:
             self._show_continuation_prompt()
 
+        if not hidden:
+            cursor.endEditBlock()
+
         return complete
 
     def _get_input_buffer(self):
@@ -546,6 +559,7 @@ class ConsoleWidget(QtGui.QWidget):
         control.redoAvailable.connect(self.redo_available)
         control.undoAvailable.connect(self.undo_available)
         control.setReadOnly(True)
+        control.setUndoRedoEnabled(False)
         control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
         return control
 
@@ -555,6 +569,7 @@ class ConsoleWidget(QtGui.QWidget):
         control = QtGui.QPlainTextEdit()
         control.installEventFilter(self)
         control.setReadOnly(True)
+        control.setUndoRedoEnabled(False)
         control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
         return control
 
@@ -664,10 +679,12 @@ class ConsoleWidget(QtGui.QWidget):
                 if not self._reading and \
                         cursor.columnNumber() == len_prompt and \
                         position != self._prompt_pos:
+                    cursor.beginEditBlock()
                     cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
                                         QtGui.QTextCursor.KeepAnchor)
                     cursor.removeSelectedText()
                     cursor.deletePreviousChar()
+                    cursor.endEditBlock()
                     intercepted = True
 
                 # Regular backwards deletion
@@ -679,8 +696,23 @@ class ConsoleWidget(QtGui.QWidget):
                         intercepted = not self._in_buffer(min(anchor, position))
 
             elif key == QtCore.Qt.Key_Delete:
-                anchor = cursor.anchor()
-                intercepted = not self._in_buffer(min(anchor, position))
+
+                # Line deletion (remove continuation prompt)
+                if not self._reading and cursor.atBlockEnd() and not \
+                        cursor.hasSelection():
+                    cursor.movePosition(QtGui.QTextCursor.NextBlock,
+                                        QtGui.QTextCursor.KeepAnchor)
+                    cursor.movePosition(QtGui.QTextCursor.Right,
+                                        QtGui.QTextCursor.KeepAnchor,
+                                        len(self._continuation_prompt))
+                    cursor.removeSelectedText()
+                    intercepted = True
+
+                # Regular forwards deletion:
+                else:
+                    anchor = cursor.anchor()
+                    intercepted = (not self._in_buffer(anchor) or
+                                   not self._in_buffer(position))
 
         # Don't move the cursor if control is down to allow copy-paste using
         # the keyboard in any part of the buffer.
@@ -1026,7 +1058,10 @@ class ConsoleWidget(QtGui.QWidget):
         """
         # Temporarily disable the maximum block count to permit undo/redo and 
         # to ensure that the prompt position does not change due to truncation.
-        self._control.document().setMaximumBlockCount(0)
+        # Because setting this property clears the undo/redo history, we only
+        # set it if we have to.
+        if self._control.document().maximumBlockCount() > 0:
+            self._control.document().setMaximumBlockCount(0)
         self._control.setUndoRedoEnabled(True)
 
         self._control.setReadOnly(False)
@@ -1039,7 +1074,6 @@ class ConsoleWidget(QtGui.QWidget):
         """ Called immediately after a prompt is finished, i.e. when some input
             will be processed and a new prompt displayed.
         """
-        self._control.setUndoRedoEnabled(False)
         self._control.setReadOnly(True)
         self._prompt_finished_hook()
 
diff --git a/IPython/frontend/qt/console/frontend_widget.py b/IPython/frontend/qt/console/frontend_widget.py
index 593683c..ae1c4b4 100644
--- a/IPython/frontend/qt/console/frontend_widget.py
+++ b/IPython/frontend/qt/console/frontend_widget.py
@@ -296,8 +296,7 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
         # Decide if it makes sense to show a call tip
         cursor = self._get_cursor()
         cursor.movePosition(QtGui.QTextCursor.Left)
-        document = self._control.document()
-        if document.characterAt(cursor.position()).toAscii() != '(':
+        if cursor.document().characterAt(cursor.position()).toAscii() != '(':
             return False
         context = self._get_context(cursor)
         if not context:
@@ -312,14 +311,6 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
     def _complete(self):
         """ Performs completion at the current cursor location.
         """
-        # Decide if it makes sense to do completion
-
-        # We should return only if the line is empty.  Otherwise, let the
-        # kernel split the line up.
-        line = self._get_input_buffer_cursor_line()
-        if not line:
-            return False
-
         # We let the kernel split the input line, so we *always* send an empty
         # text field.  Readline-based frontends do get a real text field which
         # they can use.
@@ -327,12 +318,11 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
         
         # Send the completion request to the kernel
         self._complete_id = self.kernel_manager.xreq_channel.complete(
-            text,                                      # text
-            line,                                      # line
+            text,                                    # text
+            self._get_input_buffer_cursor_line(),    # line
             self._get_input_buffer_cursor_column(),  # cursor_pos
             self.input_buffer)                       # block 
         self._complete_pos = self._get_cursor().position()
-        return True
 
     def _get_banner(self):
         """ Gets a banner to display at the beginning of a session.
@@ -342,7 +332,8 @@ class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
         return banner % (sys.version, sys.platform)
 
     def _get_context(self, cursor=None):
-        """ Gets the context at the current cursor location.
+        """ Gets the context for the specified cursor (or the current cursor
+            if none is specified).
         """
         if cursor is None:
             cursor = self._get_cursor()