##// END OF EJS Templates
Merge pull request #388 from epatters/qt-kill-ring...
Evan Patterson -
r3841:a4b5f347 merge
parent child Browse files
Show More
@@ -0,0 +1,128 b''
1 """ A generic Emacs-style kill ring, as well as a Qt-specific version.
2 """
3 #-----------------------------------------------------------------------------
4 # Imports
5 #-----------------------------------------------------------------------------
6
7 # System library imports
8 from IPython.external.qt import QtCore, QtGui
9
10 #-----------------------------------------------------------------------------
11 # Classes
12 #-----------------------------------------------------------------------------
13
14 class KillRing(object):
15 """ A generic Emacs-style kill ring.
16 """
17
18 def __init__(self):
19 self.clear()
20
21 def clear(self):
22 """ Clears the kill ring.
23 """
24 self._index = -1
25 self._ring = []
26
27 def kill(self, text):
28 """ Adds some killed text to the ring.
29 """
30 self._ring.append(text)
31
32 def yank(self):
33 """ Yank back the most recently killed text.
34
35 Returns:
36 --------
37 A text string or None.
38 """
39 self._index = len(self._ring)
40 return self.rotate()
41
42 def rotate(self):
43 """ Rotate the kill ring, then yank back the new top.
44
45 Returns:
46 --------
47 A text string or None.
48 """
49 self._index -= 1
50 if self._index >= 0:
51 return self._ring[self._index]
52 return None
53
54 class QtKillRing(QtCore.QObject):
55 """ A kill ring attached to Q[Plain]TextEdit.
56 """
57
58 #--------------------------------------------------------------------------
59 # QtKillRing interface
60 #--------------------------------------------------------------------------
61
62 def __init__(self, text_edit):
63 """ Create a kill ring attached to the specified Qt text edit.
64 """
65 assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
66 super(QtKillRing, self).__init__()
67
68 self._ring = KillRing()
69 self._prev_yank = None
70 self._skip_cursor = False
71 self._text_edit = text_edit
72
73 text_edit.cursorPositionChanged.connect(self._cursor_position_changed)
74
75 def clear(self):
76 """ Clears the kill ring.
77 """
78 self._ring.clear()
79 self._prev_yank = None
80
81 def kill(self, text):
82 """ Adds some killed text to the ring.
83 """
84 self._ring.kill(text)
85
86 def kill_cursor(self, cursor):
87 """ Kills the text selected by the give cursor.
88 """
89 text = cursor.selectedText()
90 if text:
91 cursor.removeSelectedText()
92 self.kill(text)
93
94 def yank(self):
95 """ Yank back the most recently killed text.
96 """
97 text = self._ring.yank()
98 if text:
99 self._skip_cursor = True
100 cursor = self._text_edit.textCursor()
101 cursor.insertText(text)
102 self._prev_yank = text
103
104 def rotate(self):
105 """ Rotate the kill ring, then yank back the new top.
106 """
107 if self._prev_yank:
108 text = self._ring.rotate()
109 if text:
110 self._skip_cursor = True
111 cursor = self._text_edit.textCursor()
112 cursor.movePosition(QtGui.QTextCursor.Left,
113 QtGui.QTextCursor.KeepAnchor,
114 n = len(self._prev_yank))
115 cursor.insertText(text)
116 self._prev_yank = text
117
118 #--------------------------------------------------------------------------
119 # Protected interface
120 #--------------------------------------------------------------------------
121
122 #------ Signal handlers ----------------------------------------------------
123
124 def _cursor_position_changed(self):
125 if self._skip_cursor:
126 self._skip_cursor = False
127 else:
128 self._prev_yank = None
@@ -0,0 +1,83 b''
1 # Standard library imports
2 import unittest
3
4 # System library imports
5 from IPython.external.qt import QtCore, QtGui
6
7 # Local imports
8 from IPython.frontend.qt.console.kill_ring import KillRing, QtKillRing
9
10
11 class TestKillRing(unittest.TestCase):
12
13 @classmethod
14 def setUpClass(cls):
15 """ Create the application for the test case.
16 """
17 cls._app = QtGui.QApplication([])
18 cls._app.setQuitOnLastWindowClosed(False)
19
20 @classmethod
21 def tearDownClass(cls):
22 """ Exit the application.
23 """
24 QtGui.QApplication.quit()
25
26 def test_generic(self):
27 """ Does the generic kill ring work?
28 """
29 ring = KillRing()
30 self.assert_(ring.yank() is None)
31 self.assert_(ring.rotate() is None)
32
33 ring.kill('foo')
34 self.assertEqual(ring.yank(), 'foo')
35 self.assert_(ring.rotate() is None)
36 self.assertEqual(ring.yank(), 'foo')
37
38 ring.kill('bar')
39 self.assertEqual(ring.yank(), 'bar')
40 self.assertEqual(ring.rotate(), 'foo')
41
42 ring.clear()
43 self.assert_(ring.yank() is None)
44 self.assert_(ring.rotate() is None)
45
46 def test_qt_basic(self):
47 """ Does the Qt kill ring work?
48 """
49 text_edit = QtGui.QPlainTextEdit()
50 ring = QtKillRing(text_edit)
51
52 ring.kill('foo')
53 ring.kill('bar')
54 ring.yank()
55 ring.rotate()
56 ring.yank()
57 self.assertEqual(text_edit.toPlainText(), 'foobar')
58
59 text_edit.clear()
60 ring.kill('baz')
61 ring.yank()
62 ring.rotate()
63 ring.rotate()
64 ring.rotate()
65 self.assertEqual(text_edit.toPlainText(), 'foo')
66
67 def test_qt_cursor(self):
68 """ Does the Qt kill ring maintain state with cursor movement?
69 """
70 text_edit = QtGui.QPlainTextEdit()
71 ring = QtKillRing(text_edit)
72
73 ring.kill('foo')
74 ring.kill('bar')
75 ring.yank()
76 text_edit.moveCursor(QtGui.QTextCursor.Left)
77 ring.rotate()
78 self.assertEqual(text_edit.toPlainText(), 'bar')
79
80
81 if __name__ == '__main__':
82 import nose
83 nose.main()
@@ -22,6 +22,7 b' from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font'
22 22 from IPython.utils.traitlets import Bool, Enum, Int
23 23 from ansi_code_processor import QtAnsiCodeProcessor
24 24 from completion_widget import CompletionWidget
25 from kill_ring import QtKillRing
25 26
26 27 #-----------------------------------------------------------------------------
27 28 # Functions
@@ -173,6 +174,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
173 174 self._filter_drag = False
174 175 self._filter_resize = False
175 176 self._html_exporter = HtmlExporter(self._control)
177 self._kill_ring = QtKillRing(self._control)
176 178 self._prompt = ''
177 179 self._prompt_html = None
178 180 self._prompt_pos = 0
@@ -953,7 +955,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
953 955 cursor.movePosition(QtGui.QTextCursor.Right,
954 956 QtGui.QTextCursor.KeepAnchor,
955 957 len(self._continuation_prompt))
956 cursor.removeSelectedText()
958 self._kill_ring.kill_cursor(cursor)
957 959 intercepted = True
958 960
959 961 elif key == QtCore.Qt.Key_L:
@@ -976,11 +978,12 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
976 978 QtGui.QTextCursor.KeepAnchor)
977 979 cursor.movePosition(QtGui.QTextCursor.Right,
978 980 QtGui.QTextCursor.KeepAnchor, offset)
979 cursor.removeSelectedText()
981 self._kill_ring.kill_cursor(cursor)
980 982 intercepted = True
981 983
982 984 elif key == QtCore.Qt.Key_Y:
983 self.paste()
985 self._keep_cursor_in_buffer()
986 self._kill_ring.yank()
984 987 intercepted = True
985 988
986 989 elif key in (QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete):
@@ -1005,16 +1008,20 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
1005 1008 self._set_cursor(self._get_word_end_cursor(position))
1006 1009 intercepted = True
1007 1010
1011 elif key == QtCore.Qt.Key_Y:
1012 self._kill_ring.rotate()
1013 intercepted = True
1014
1008 1015 elif key == QtCore.Qt.Key_Backspace:
1009 1016 cursor = self._get_word_start_cursor(position)
1010 1017 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1011 cursor.removeSelectedText()
1018 self._kill_ring.kill_cursor(cursor)
1012 1019 intercepted = True
1013 1020
1014 1021 elif key == QtCore.Qt.Key_D:
1015 1022 cursor = self._get_word_end_cursor(position)
1016 1023 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1017 cursor.removeSelectedText()
1024 self._kill_ring.kill_cursor(cursor)
1018 1025 intercepted = True
1019 1026
1020 1027 elif key == QtCore.Qt.Key_Delete:
General Comments 0
You need to be logged in to leave comments. Login now