##// END OF EJS Templates
tweak history prefix search (up/^p) in qtconsole...
MinRK -
Show More
@@ -1,283 +1,295
1 1 # System library imports
2 2 from IPython.external.qt import QtGui
3 3
4 4 # Local imports
5 5 from IPython.utils.traitlets import Bool
6 6 from console_widget import ConsoleWidget
7 7
8 8
9 9 class HistoryConsoleWidget(ConsoleWidget):
10 10 """ A ConsoleWidget that keeps a history of the commands that have been
11 11 executed and provides a readline-esque interface to this history.
12 12 """
13 13
14 14 #------ Configuration ------------------------------------------------------
15 15
16 16 # If enabled, the input buffer will become "locked" to history movement when
17 17 # an edit is made to a multi-line input buffer. To override the lock, use
18 18 # Shift in conjunction with the standard history cycling keys.
19 19 history_lock = Bool(False, config=True)
20 20
21 21 #---------------------------------------------------------------------------
22 22 # 'object' interface
23 23 #---------------------------------------------------------------------------
24 24
25 25 def __init__(self, *args, **kw):
26 26 super(HistoryConsoleWidget, self).__init__(*args, **kw)
27 27
28 28 # HistoryConsoleWidget protected variables.
29 29 self._history = []
30 30 self._history_edits = {}
31 31 self._history_index = 0
32 32 self._history_prefix = ''
33 33
34 34 #---------------------------------------------------------------------------
35 35 # 'ConsoleWidget' public interface
36 36 #---------------------------------------------------------------------------
37 37
38 38 def execute(self, source=None, hidden=False, interactive=False):
39 39 """ Reimplemented to the store history.
40 40 """
41 41 if not hidden:
42 42 history = self.input_buffer if source is None else source
43 43
44 44 executed = super(HistoryConsoleWidget, self).execute(
45 45 source, hidden, interactive)
46 46
47 47 if executed and not hidden:
48 48 # Save the command unless it was an empty string or was identical
49 49 # to the previous command.
50 50 history = history.rstrip()
51 51 if history and (not self._history or self._history[-1] != history):
52 52 self._history.append(history)
53 53
54 54 # Emulate readline: reset all history edits.
55 55 self._history_edits = {}
56 56
57 57 # Move the history index to the most recent item.
58 58 self._history_index = len(self._history)
59 59
60 60 return executed
61 61
62 62 #---------------------------------------------------------------------------
63 63 # 'ConsoleWidget' abstract interface
64 64 #---------------------------------------------------------------------------
65 65
66 66 def _up_pressed(self, shift_modifier):
67 67 """ Called when the up key is pressed. Returns whether to continue
68 68 processing the event.
69 69 """
70 70 prompt_cursor = self._get_prompt_cursor()
71 71 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
72 72 # Bail out if we're locked.
73 73 if self._history_locked() and not shift_modifier:
74 74 return False
75 75
76 76 # Set a search prefix based on the cursor position.
77 77 col = self._get_input_buffer_cursor_column()
78 78 input_buffer = self.input_buffer
79 if self._history_index == len(self._history) or \
80 (self._history_prefix and col != len(self._history_prefix)):
79 # use the *shortest* of the cursor column and the history prefix
80 # to determine if the prefix has changed
81 n = min(col, len(self._history_prefix))
82
83 # prefix changed, restart search from the beginning
84 if (self._history_prefix[:n] != input_buffer[:n]):
81 85 self._history_index = len(self._history)
86
87 # the only time we shouldn't set the history prefix
88 # to the line up to the cursor is if we are already
89 # in a simple scroll (no prefix),
90 # and the cursor is at the end of the first line
91 first_line = input_buffer.split('\n', 1)[0]
92 if self._history_index == len(self._history) or \
93 not (self._history_prefix == '' and col == len(first_line)):
82 94 self._history_prefix = input_buffer[:col]
83 95
84 96 # Perform the search.
85 97 self.history_previous(self._history_prefix,
86 98 as_prefix=not shift_modifier)
87 99
88 100 # Go to the first line of the prompt for seemless history scrolling.
89 101 # Emulate readline: keep the cursor position fixed for a prefix
90 102 # search.
91 103 cursor = self._get_prompt_cursor()
92 104 if self._history_prefix:
93 105 cursor.movePosition(QtGui.QTextCursor.Right,
94 106 n=len(self._history_prefix))
95 107 else:
96 108 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
97 109 self._set_cursor(cursor)
98 110
99 111 return False
100 112
101 113 return True
102 114
103 115 def _down_pressed(self, shift_modifier):
104 116 """ Called when the down key is pressed. Returns whether to continue
105 117 processing the event.
106 118 """
107 119 end_cursor = self._get_end_cursor()
108 120 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
109 121 # Bail out if we're locked.
110 122 if self._history_locked() and not shift_modifier:
111 123 return False
112 124
113 125 # Perform the search.
114 126 replaced = self.history_next(self._history_prefix,
115 127 as_prefix=not shift_modifier)
116 128
117 129 # Emulate readline: keep the cursor position fixed for a prefix
118 130 # search. (We don't need to move the cursor to the end of the buffer
119 131 # in the other case because this happens automatically when the
120 132 # input buffer is set.)
121 133 if self._history_prefix and replaced:
122 134 cursor = self._get_prompt_cursor()
123 135 cursor.movePosition(QtGui.QTextCursor.Right,
124 136 n=len(self._history_prefix))
125 137 self._set_cursor(cursor)
126 138
127 139 return False
128 140
129 141 return True
130 142
131 143 #---------------------------------------------------------------------------
132 144 # 'HistoryConsoleWidget' public interface
133 145 #---------------------------------------------------------------------------
134 146
135 147 def history_previous(self, substring='', as_prefix=True):
136 148 """ If possible, set the input buffer to a previous history item.
137 149
138 150 Parameters:
139 151 -----------
140 152 substring : str, optional
141 153 If specified, search for an item with this substring.
142 154 as_prefix : bool, optional
143 155 If True, the substring must match at the beginning (default).
144 156
145 157 Returns:
146 158 --------
147 159 Whether the input buffer was changed.
148 160 """
149 161 index = self._history_index
150 162 replace = False
151 163 while index > 0:
152 164 index -= 1
153 165 history = self._get_edited_history(index)
154 166 if (as_prefix and history.startswith(substring)) \
155 167 or (not as_prefix and substring in history):
156 168 replace = True
157 169 break
158 170
159 171 if replace:
160 172 self._store_edits()
161 173 self._history_index = index
162 174 self.input_buffer = history
163 175
164 176 return replace
165 177
166 178 def history_next(self, substring='', as_prefix=True):
167 179 """ If possible, set the input buffer to a subsequent history item.
168 180
169 181 Parameters:
170 182 -----------
171 183 substring : str, optional
172 184 If specified, search for an item with this substring.
173 185 as_prefix : bool, optional
174 186 If True, the substring must match at the beginning (default).
175 187
176 188 Returns:
177 189 --------
178 190 Whether the input buffer was changed.
179 191 """
180 192 index = self._history_index
181 193 replace = False
182 194 while self._history_index < len(self._history):
183 195 index += 1
184 196 history = self._get_edited_history(index)
185 197 if (as_prefix and history.startswith(substring)) \
186 198 or (not as_prefix and substring in history):
187 199 replace = True
188 200 break
189 201
190 202 if replace:
191 203 self._store_edits()
192 204 self._history_index = index
193 205 self.input_buffer = history
194 206
195 207 return replace
196 208
197 209 def history_tail(self, n=10):
198 210 """ Get the local history list.
199 211
200 212 Parameters:
201 213 -----------
202 214 n : int
203 215 The (maximum) number of history items to get.
204 216 """
205 217 return self._history[-n:]
206 218
207 219 def _request_update_session_history_length(self):
208 220 msg_id = self.kernel_manager.shell_channel.execute('',
209 221 silent=True,
210 222 user_expressions={
211 223 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
212 224 }
213 225 )
214 226 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
215 227
216 228 def _handle_execute_reply(self, msg):
217 229 """ Handles replies for code execution, here only session history length
218 230 """
219 231 msg_id = msg['parent_header']['msg_id']
220 232 info = self._request_info['execute'].pop(msg_id,None)
221 233 if info and info.kind == 'save_magic' and not self._hidden:
222 234 content = msg['content']
223 235 status = content['status']
224 236 if status == 'ok':
225 237 self._max_session_history=(int(content['user_expressions']['hlen']))
226 238
227 239 def save_magic(self):
228 240 # update the session history length
229 241 self._request_update_session_history_length()
230 242
231 243 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
232 244 "Enter A filename",
233 245 filter='Python File (*.py);; All files (*.*)'
234 246 )
235 247
236 248 # let's the user search/type for a file name, while the history length
237 249 # is fetched
238 250
239 251 if file_name:
240 252 hist_range, ok = QtGui.QInputDialog.getText(self,
241 253 'Please enter an interval of command to save',
242 254 'Saving commands:',
243 255 text=str('1-'+str(self._max_session_history))
244 256 )
245 257 if ok:
246 258 self.execute("%save"+" "+file_name+" "+str(hist_range))
247 259
248 260 #---------------------------------------------------------------------------
249 261 # 'HistoryConsoleWidget' protected interface
250 262 #---------------------------------------------------------------------------
251 263
252 264 def _history_locked(self):
253 265 """ Returns whether history movement is locked.
254 266 """
255 267 return (self.history_lock and
256 268 (self._get_edited_history(self._history_index) !=
257 269 self.input_buffer) and
258 270 (self._get_prompt_cursor().blockNumber() !=
259 271 self._get_end_cursor().blockNumber()))
260 272
261 273 def _get_edited_history(self, index):
262 274 """ Retrieves a history item, possibly with temporary edits.
263 275 """
264 276 if index in self._history_edits:
265 277 return self._history_edits[index]
266 278 elif index == len(self._history):
267 279 return unicode()
268 280 return self._history[index]
269 281
270 282 def _set_history(self, history):
271 283 """ Replace the current history with a sequence of history items.
272 284 """
273 285 self._history = list(history)
274 286 self._history_edits = {}
275 287 self._history_index = len(self._history)
276 288
277 289 def _store_edits(self):
278 290 """ If there are edits to the current input buffer, store them.
279 291 """
280 292 current = self.input_buffer
281 293 if self._history_index == len(self._history) or \
282 294 self._history[self._history_index] != current:
283 295 self._history_edits[self._history_index] = current
General Comments 0
You need to be logged in to leave comments. Login now