##// END OF EJS Templates
Move cast_unicode_py2 calls into HistoryAccessor
Thomas Kluyver -
Show More
@@ -1,911 +1,912 b''
1 """ History related magics and functionality """
1 """ History related magics and functionality """
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 import atexit
8 import atexit
9 import datetime
9 import datetime
10 import os
10 import os
11 import re
11 import re
12 try:
12 try:
13 import sqlite3
13 import sqlite3
14 except ImportError:
14 except ImportError:
15 try:
15 try:
16 from pysqlite2 import dbapi2 as sqlite3
16 from pysqlite2 import dbapi2 as sqlite3
17 except ImportError:
17 except ImportError:
18 sqlite3 = None
18 sqlite3 = None
19 import threading
19 import threading
20
20
21 from traitlets.config.configurable import LoggingConfigurable
21 from traitlets.config.configurable import LoggingConfigurable
22 from decorator import decorator
22 from decorator import decorator
23 from IPython.utils.decorators import undoc
23 from IPython.utils.decorators import undoc
24 from IPython.utils.path import locate_profile
24 from IPython.utils.path import locate_profile
25 from IPython.utils import py3compat
25 from IPython.utils import py3compat
26 from traitlets import (
26 from traitlets import (
27 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
27 Any, Bool, Dict, Instance, Integer, List, Unicode, TraitError,
28 default, observe,
28 default, observe,
29 )
29 )
30 from warnings import warn
30 from warnings import warn
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Classes and functions
33 # Classes and functions
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 @undoc
36 @undoc
37 class DummyDB(object):
37 class DummyDB(object):
38 """Dummy DB that will act as a black hole for history.
38 """Dummy DB that will act as a black hole for history.
39
39
40 Only used in the absence of sqlite"""
40 Only used in the absence of sqlite"""
41 def execute(*args, **kwargs):
41 def execute(*args, **kwargs):
42 return []
42 return []
43
43
44 def commit(self, *args, **kwargs):
44 def commit(self, *args, **kwargs):
45 pass
45 pass
46
46
47 def __enter__(self, *args, **kwargs):
47 def __enter__(self, *args, **kwargs):
48 pass
48 pass
49
49
50 def __exit__(self, *args, **kwargs):
50 def __exit__(self, *args, **kwargs):
51 pass
51 pass
52
52
53
53
54 @decorator
54 @decorator
55 def needs_sqlite(f, self, *a, **kw):
55 def needs_sqlite(f, self, *a, **kw):
56 """Decorator: return an empty list in the absence of sqlite."""
56 """Decorator: return an empty list in the absence of sqlite."""
57 if sqlite3 is None or not self.enabled:
57 if sqlite3 is None or not self.enabled:
58 return []
58 return []
59 else:
59 else:
60 return f(self, *a, **kw)
60 return f(self, *a, **kw)
61
61
62
62
63 if sqlite3 is not None:
63 if sqlite3 is not None:
64 DatabaseError = sqlite3.DatabaseError
64 DatabaseError = sqlite3.DatabaseError
65 OperationalError = sqlite3.OperationalError
65 OperationalError = sqlite3.OperationalError
66 else:
66 else:
67 @undoc
67 @undoc
68 class DatabaseError(Exception):
68 class DatabaseError(Exception):
69 "Dummy exception when sqlite could not be imported. Should never occur."
69 "Dummy exception when sqlite could not be imported. Should never occur."
70
70
71 @undoc
71 @undoc
72 class OperationalError(Exception):
72 class OperationalError(Exception):
73 "Dummy exception when sqlite could not be imported. Should never occur."
73 "Dummy exception when sqlite could not be imported. Should never occur."
74
74
75 # use 16kB as threshold for whether a corrupt history db should be saved
75 # use 16kB as threshold for whether a corrupt history db should be saved
76 # that should be at least 100 entries or so
76 # that should be at least 100 entries or so
77 _SAVE_DB_SIZE = 16384
77 _SAVE_DB_SIZE = 16384
78
78
79 @decorator
79 @decorator
80 def catch_corrupt_db(f, self, *a, **kw):
80 def catch_corrupt_db(f, self, *a, **kw):
81 """A decorator which wraps HistoryAccessor method calls to catch errors from
81 """A decorator which wraps HistoryAccessor method calls to catch errors from
82 a corrupt SQLite database, move the old database out of the way, and create
82 a corrupt SQLite database, move the old database out of the way, and create
83 a new one.
83 a new one.
84
84
85 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
85 We avoid clobbering larger databases because this may be triggered due to filesystem issues,
86 not just a corrupt file.
86 not just a corrupt file.
87 """
87 """
88 try:
88 try:
89 return f(self, *a, **kw)
89 return f(self, *a, **kw)
90 except (DatabaseError, OperationalError) as e:
90 except (DatabaseError, OperationalError) as e:
91 self._corrupt_db_counter += 1
91 self._corrupt_db_counter += 1
92 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
92 self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
93 if self.hist_file != ':memory:':
93 if self.hist_file != ':memory:':
94 if self._corrupt_db_counter > self._corrupt_db_limit:
94 if self._corrupt_db_counter > self._corrupt_db_limit:
95 self.hist_file = ':memory:'
95 self.hist_file = ':memory:'
96 self.log.error("Failed to load history too many times, history will not be saved.")
96 self.log.error("Failed to load history too many times, history will not be saved.")
97 elif os.path.isfile(self.hist_file):
97 elif os.path.isfile(self.hist_file):
98 # move the file out of the way
98 # move the file out of the way
99 base, ext = os.path.splitext(self.hist_file)
99 base, ext = os.path.splitext(self.hist_file)
100 size = os.stat(self.hist_file).st_size
100 size = os.stat(self.hist_file).st_size
101 if size >= _SAVE_DB_SIZE:
101 if size >= _SAVE_DB_SIZE:
102 # if there's significant content, avoid clobbering
102 # if there's significant content, avoid clobbering
103 now = datetime.datetime.now().isoformat().replace(':', '.')
103 now = datetime.datetime.now().isoformat().replace(':', '.')
104 newpath = base + '-corrupt-' + now + ext
104 newpath = base + '-corrupt-' + now + ext
105 # don't clobber previous corrupt backups
105 # don't clobber previous corrupt backups
106 for i in range(100):
106 for i in range(100):
107 if not os.path.isfile(newpath):
107 if not os.path.isfile(newpath):
108 break
108 break
109 else:
109 else:
110 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
110 newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
111 else:
111 else:
112 # not much content, possibly empty; don't worry about clobbering
112 # not much content, possibly empty; don't worry about clobbering
113 # maybe we should just delete it?
113 # maybe we should just delete it?
114 newpath = base + '-corrupt' + ext
114 newpath = base + '-corrupt' + ext
115 os.rename(self.hist_file, newpath)
115 os.rename(self.hist_file, newpath)
116 self.log.error("History file was moved to %s and a new file created.", newpath)
116 self.log.error("History file was moved to %s and a new file created.", newpath)
117 self.init_db()
117 self.init_db()
118 return []
118 return []
119 else:
119 else:
120 # Failed with :memory:, something serious is wrong
120 # Failed with :memory:, something serious is wrong
121 raise
121 raise
122
122
123 class HistoryAccessorBase(LoggingConfigurable):
123 class HistoryAccessorBase(LoggingConfigurable):
124 """An abstract class for History Accessors """
124 """An abstract class for History Accessors """
125
125
126 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
126 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
127 raise NotImplementedError
127 raise NotImplementedError
128
128
129 def search(self, pattern="*", raw=True, search_raw=True,
129 def search(self, pattern="*", raw=True, search_raw=True,
130 output=False, n=None, unique=False):
130 output=False, n=None, unique=False):
131 raise NotImplementedError
131 raise NotImplementedError
132
132
133 def get_range(self, session, start=1, stop=None, raw=True,output=False):
133 def get_range(self, session, start=1, stop=None, raw=True,output=False):
134 raise NotImplementedError
134 raise NotImplementedError
135
135
136 def get_range_by_str(self, rangestr, raw=True, output=False):
136 def get_range_by_str(self, rangestr, raw=True, output=False):
137 raise NotImplementedError
137 raise NotImplementedError
138
138
139
139
140 class HistoryAccessor(HistoryAccessorBase):
140 class HistoryAccessor(HistoryAccessorBase):
141 """Access the history database without adding to it.
141 """Access the history database without adding to it.
142
142
143 This is intended for use by standalone history tools. IPython shells use
143 This is intended for use by standalone history tools. IPython shells use
144 HistoryManager, below, which is a subclass of this."""
144 HistoryManager, below, which is a subclass of this."""
145
145
146 # counter for init_db retries, so we don't keep trying over and over
146 # counter for init_db retries, so we don't keep trying over and over
147 _corrupt_db_counter = 0
147 _corrupt_db_counter = 0
148 # after two failures, fallback on :memory:
148 # after two failures, fallback on :memory:
149 _corrupt_db_limit = 2
149 _corrupt_db_limit = 2
150
150
151 # String holding the path to the history file
151 # String holding the path to the history file
152 hist_file = Unicode(
152 hist_file = Unicode(
153 help="""Path to file to use for SQLite history database.
153 help="""Path to file to use for SQLite history database.
154
154
155 By default, IPython will put the history database in the IPython
155 By default, IPython will put the history database in the IPython
156 profile directory. If you would rather share one history among
156 profile directory. If you would rather share one history among
157 profiles, you can set this value in each, so that they are consistent.
157 profiles, you can set this value in each, so that they are consistent.
158
158
159 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
159 Due to an issue with fcntl, SQLite is known to misbehave on some NFS
160 mounts. If you see IPython hanging, try setting this to something on a
160 mounts. If you see IPython hanging, try setting this to something on a
161 local disk, e.g::
161 local disk, e.g::
162
162
163 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
163 ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
164
164
165 you can also use the specific value `:memory:` (including the colon
165 you can also use the specific value `:memory:` (including the colon
166 at both end but not the back ticks), to avoid creating an history file.
166 at both end but not the back ticks), to avoid creating an history file.
167
167
168 """).tag(config=True)
168 """).tag(config=True)
169
169
170 enabled = Bool(True,
170 enabled = Bool(True,
171 help="""enable the SQLite history
171 help="""enable the SQLite history
172
172
173 set enabled=False to disable the SQLite history,
173 set enabled=False to disable the SQLite history,
174 in which case there will be no stored history, no SQLite connection,
174 in which case there will be no stored history, no SQLite connection,
175 and no background saving thread. This may be necessary in some
175 and no background saving thread. This may be necessary in some
176 threaded environments where IPython is embedded.
176 threaded environments where IPython is embedded.
177 """
177 """
178 ).tag(config=True)
178 ).tag(config=True)
179
179
180 connection_options = Dict(
180 connection_options = Dict(
181 help="""Options for configuring the SQLite connection
181 help="""Options for configuring the SQLite connection
182
182
183 These options are passed as keyword args to sqlite3.connect
183 These options are passed as keyword args to sqlite3.connect
184 when establishing database conenctions.
184 when establishing database conenctions.
185 """
185 """
186 ).tag(config=True)
186 ).tag(config=True)
187
187
188 # The SQLite database
188 # The SQLite database
189 db = Any()
189 db = Any()
190 @observe('db')
190 @observe('db')
191 def _db_changed(self, change):
191 def _db_changed(self, change):
192 """validate the db, since it can be an Instance of two different types"""
192 """validate the db, since it can be an Instance of two different types"""
193 new = change['new']
193 new = change['new']
194 connection_types = (DummyDB,)
194 connection_types = (DummyDB,)
195 if sqlite3 is not None:
195 if sqlite3 is not None:
196 connection_types = (DummyDB, sqlite3.Connection)
196 connection_types = (DummyDB, sqlite3.Connection)
197 if not isinstance(new, connection_types):
197 if not isinstance(new, connection_types):
198 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
198 msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
199 (self.__class__.__name__, new)
199 (self.__class__.__name__, new)
200 raise TraitError(msg)
200 raise TraitError(msg)
201
201
202 def __init__(self, profile='default', hist_file=u'', **traits):
202 def __init__(self, profile='default', hist_file=u'', **traits):
203 """Create a new history accessor.
203 """Create a new history accessor.
204
204
205 Parameters
205 Parameters
206 ----------
206 ----------
207 profile : str
207 profile : str
208 The name of the profile from which to open history.
208 The name of the profile from which to open history.
209 hist_file : str
209 hist_file : str
210 Path to an SQLite history database stored by IPython. If specified,
210 Path to an SQLite history database stored by IPython. If specified,
211 hist_file overrides profile.
211 hist_file overrides profile.
212 config : :class:`~traitlets.config.loader.Config`
212 config : :class:`~traitlets.config.loader.Config`
213 Config object. hist_file can also be set through this.
213 Config object. hist_file can also be set through this.
214 """
214 """
215 # We need a pointer back to the shell for various tasks.
215 # We need a pointer back to the shell for various tasks.
216 super(HistoryAccessor, self).__init__(**traits)
216 super(HistoryAccessor, self).__init__(**traits)
217 # defer setting hist_file from kwarg until after init,
217 # defer setting hist_file from kwarg until after init,
218 # otherwise the default kwarg value would clobber any value
218 # otherwise the default kwarg value would clobber any value
219 # set by config
219 # set by config
220 if hist_file:
220 if hist_file:
221 self.hist_file = hist_file
221 self.hist_file = hist_file
222
222
223 if self.hist_file == u'':
223 if self.hist_file == u'':
224 # No one has set the hist_file, yet.
224 # No one has set the hist_file, yet.
225 self.hist_file = self._get_hist_file_name(profile)
225 self.hist_file = self._get_hist_file_name(profile)
226
226
227 if sqlite3 is None and self.enabled:
227 if sqlite3 is None and self.enabled:
228 warn("IPython History requires SQLite, your history will not be saved")
228 warn("IPython History requires SQLite, your history will not be saved")
229 self.enabled = False
229 self.enabled = False
230
230
231 self.init_db()
231 self.init_db()
232
232
233 def _get_hist_file_name(self, profile='default'):
233 def _get_hist_file_name(self, profile='default'):
234 """Find the history file for the given profile name.
234 """Find the history file for the given profile name.
235
235
236 This is overridden by the HistoryManager subclass, to use the shell's
236 This is overridden by the HistoryManager subclass, to use the shell's
237 active profile.
237 active profile.
238
238
239 Parameters
239 Parameters
240 ----------
240 ----------
241 profile : str
241 profile : str
242 The name of a profile which has a history file.
242 The name of a profile which has a history file.
243 """
243 """
244 return os.path.join(locate_profile(profile), 'history.sqlite')
244 return os.path.join(locate_profile(profile), 'history.sqlite')
245
245
246 @catch_corrupt_db
246 @catch_corrupt_db
247 def init_db(self):
247 def init_db(self):
248 """Connect to the database, and create tables if necessary."""
248 """Connect to the database, and create tables if necessary."""
249 if not self.enabled:
249 if not self.enabled:
250 self.db = DummyDB()
250 self.db = DummyDB()
251 return
251 return
252
252
253 # use detect_types so that timestamps return datetime objects
253 # use detect_types so that timestamps return datetime objects
254 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
254 kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
255 kwargs.update(self.connection_options)
255 kwargs.update(self.connection_options)
256 self.db = sqlite3.connect(self.hist_file, **kwargs)
256 self.db = sqlite3.connect(self.hist_file, **kwargs)
257 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
257 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
258 primary key autoincrement, start timestamp,
258 primary key autoincrement, start timestamp,
259 end timestamp, num_cmds integer, remark text)""")
259 end timestamp, num_cmds integer, remark text)""")
260 self.db.execute("""CREATE TABLE IF NOT EXISTS history
260 self.db.execute("""CREATE TABLE IF NOT EXISTS history
261 (session integer, line integer, source text, source_raw text,
261 (session integer, line integer, source text, source_raw text,
262 PRIMARY KEY (session, line))""")
262 PRIMARY KEY (session, line))""")
263 # Output history is optional, but ensure the table's there so it can be
263 # Output history is optional, but ensure the table's there so it can be
264 # enabled later.
264 # enabled later.
265 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
265 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
266 (session integer, line integer, output text,
266 (session integer, line integer, output text,
267 PRIMARY KEY (session, line))""")
267 PRIMARY KEY (session, line))""")
268 self.db.commit()
268 self.db.commit()
269 # success! reset corrupt db count
269 # success! reset corrupt db count
270 self._corrupt_db_counter = 0
270 self._corrupt_db_counter = 0
271
271
272 def writeout_cache(self):
272 def writeout_cache(self):
273 """Overridden by HistoryManager to dump the cache before certain
273 """Overridden by HistoryManager to dump the cache before certain
274 database lookups."""
274 database lookups."""
275 pass
275 pass
276
276
277 ## -------------------------------
277 ## -------------------------------
278 ## Methods for retrieving history:
278 ## Methods for retrieving history:
279 ## -------------------------------
279 ## -------------------------------
280 def _run_sql(self, sql, params, raw=True, output=False):
280 def _run_sql(self, sql, params, raw=True, output=False):
281 """Prepares and runs an SQL query for the history database.
281 """Prepares and runs an SQL query for the history database.
282
282
283 Parameters
283 Parameters
284 ----------
284 ----------
285 sql : str
285 sql : str
286 Any filtering expressions to go after SELECT ... FROM ...
286 Any filtering expressions to go after SELECT ... FROM ...
287 params : tuple
287 params : tuple
288 Parameters passed to the SQL query (to replace "?")
288 Parameters passed to the SQL query (to replace "?")
289 raw, output : bool
289 raw, output : bool
290 See :meth:`get_range`
290 See :meth:`get_range`
291
291
292 Returns
292 Returns
293 -------
293 -------
294 Tuples as :meth:`get_range`
294 Tuples as :meth:`get_range`
295 """
295 """
296 toget = 'source_raw' if raw else 'source'
296 toget = 'source_raw' if raw else 'source'
297 sqlfrom = "history"
297 sqlfrom = "history"
298 if output:
298 if output:
299 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
299 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
300 toget = "history.%s, output_history.output" % toget
300 toget = "history.%s, output_history.output" % toget
301 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
301 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
302 (toget, sqlfrom) + sql, params)
302 (toget, sqlfrom) + sql, params)
303 if output: # Regroup into 3-tuples, and parse JSON
303 if output: # Regroup into 3-tuples, and parse JSON
304 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
304 return ((ses, lin, (py3compat.cast_unicode_py2(inp), py3compat.cast_unicode_py2(out)))
305 for ses, lin, inp, out in cur)
305 return cur
306 return cur
306
307
307 @needs_sqlite
308 @needs_sqlite
308 @catch_corrupt_db
309 @catch_corrupt_db
309 def get_session_info(self, session):
310 def get_session_info(self, session):
310 """Get info about a session.
311 """Get info about a session.
311
312
312 Parameters
313 Parameters
313 ----------
314 ----------
314
315
315 session : int
316 session : int
316 Session number to retrieve.
317 Session number to retrieve.
317
318
318 Returns
319 Returns
319 -------
320 -------
320
321
321 session_id : int
322 session_id : int
322 Session ID number
323 Session ID number
323 start : datetime
324 start : datetime
324 Timestamp for the start of the session.
325 Timestamp for the start of the session.
325 end : datetime
326 end : datetime
326 Timestamp for the end of the session, or None if IPython crashed.
327 Timestamp for the end of the session, or None if IPython crashed.
327 num_cmds : int
328 num_cmds : int
328 Number of commands run, or None if IPython crashed.
329 Number of commands run, or None if IPython crashed.
329 remark : unicode
330 remark : unicode
330 A manually set description.
331 A manually set description.
331 """
332 """
332 query = "SELECT * from sessions where session == ?"
333 query = "SELECT * from sessions where session == ?"
333 return self.db.execute(query, (session,)).fetchone()
334 return self.db.execute(query, (session,)).fetchone()
334
335
335 @catch_corrupt_db
336 @catch_corrupt_db
336 def get_last_session_id(self):
337 def get_last_session_id(self):
337 """Get the last session ID currently in the database.
338 """Get the last session ID currently in the database.
338
339
339 Within IPython, this should be the same as the value stored in
340 Within IPython, this should be the same as the value stored in
340 :attr:`HistoryManager.session_number`.
341 :attr:`HistoryManager.session_number`.
341 """
342 """
342 for record in self.get_tail(n=1, include_latest=True):
343 for record in self.get_tail(n=1, include_latest=True):
343 return record[0]
344 return record[0]
344
345
345 @catch_corrupt_db
346 @catch_corrupt_db
346 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
347 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
347 """Get the last n lines from the history database.
348 """Get the last n lines from the history database.
348
349
349 Parameters
350 Parameters
350 ----------
351 ----------
351 n : int
352 n : int
352 The number of lines to get
353 The number of lines to get
353 raw, output : bool
354 raw, output : bool
354 See :meth:`get_range`
355 See :meth:`get_range`
355 include_latest : bool
356 include_latest : bool
356 If False (default), n+1 lines are fetched, and the latest one
357 If False (default), n+1 lines are fetched, and the latest one
357 is discarded. This is intended to be used where the function
358 is discarded. This is intended to be used where the function
358 is called by a user command, which it should not return.
359 is called by a user command, which it should not return.
359
360
360 Returns
361 Returns
361 -------
362 -------
362 Tuples as :meth:`get_range`
363 Tuples as :meth:`get_range`
363 """
364 """
364 self.writeout_cache()
365 self.writeout_cache()
365 if not include_latest:
366 if not include_latest:
366 n += 1
367 n += 1
367 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
368 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
368 (n,), raw=raw, output=output)
369 (n,), raw=raw, output=output)
369 if not include_latest:
370 if not include_latest:
370 return reversed(list(cur)[1:])
371 return reversed(list(cur)[1:])
371 return reversed(list(cur))
372 return reversed(list(cur))
372
373
373 @catch_corrupt_db
374 @catch_corrupt_db
374 def search(self, pattern="*", raw=True, search_raw=True,
375 def search(self, pattern="*", raw=True, search_raw=True,
375 output=False, n=None, unique=False):
376 output=False, n=None, unique=False):
376 """Search the database using unix glob-style matching (wildcards
377 """Search the database using unix glob-style matching (wildcards
377 * and ?).
378 * and ?).
378
379
379 Parameters
380 Parameters
380 ----------
381 ----------
381 pattern : str
382 pattern : str
382 The wildcarded pattern to match when searching
383 The wildcarded pattern to match when searching
383 search_raw : bool
384 search_raw : bool
384 If True, search the raw input, otherwise, the parsed input
385 If True, search the raw input, otherwise, the parsed input
385 raw, output : bool
386 raw, output : bool
386 See :meth:`get_range`
387 See :meth:`get_range`
387 n : None or int
388 n : None or int
388 If an integer is given, it defines the limit of
389 If an integer is given, it defines the limit of
389 returned entries.
390 returned entries.
390 unique : bool
391 unique : bool
391 When it is true, return only unique entries.
392 When it is true, return only unique entries.
392
393
393 Returns
394 Returns
394 -------
395 -------
395 Tuples as :meth:`get_range`
396 Tuples as :meth:`get_range`
396 """
397 """
397 tosearch = "source_raw" if search_raw else "source"
398 tosearch = "source_raw" if search_raw else "source"
398 if output:
399 if output:
399 tosearch = "history." + tosearch
400 tosearch = "history." + tosearch
400 self.writeout_cache()
401 self.writeout_cache()
401 sqlform = "WHERE %s GLOB ?" % tosearch
402 sqlform = "WHERE %s GLOB ?" % tosearch
402 params = (pattern,)
403 params = (pattern,)
403 if unique:
404 if unique:
404 sqlform += ' GROUP BY {0}'.format(tosearch)
405 sqlform += ' GROUP BY {0}'.format(tosearch)
405 if n is not None:
406 if n is not None:
406 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
407 sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
407 params += (n,)
408 params += (n,)
408 elif unique:
409 elif unique:
409 sqlform += " ORDER BY session, line"
410 sqlform += " ORDER BY session, line"
410 cur = self._run_sql(sqlform, params, raw=raw, output=output)
411 cur = self._run_sql(sqlform, params, raw=raw, output=output)
411 if n is not None:
412 if n is not None:
412 return reversed(list(cur))
413 return reversed(list(cur))
413 return cur
414 return cur
414
415
415 @catch_corrupt_db
416 @catch_corrupt_db
416 def get_range(self, session, start=1, stop=None, raw=True,output=False):
417 def get_range(self, session, start=1, stop=None, raw=True,output=False):
417 """Retrieve input by session.
418 """Retrieve input by session.
418
419
419 Parameters
420 Parameters
420 ----------
421 ----------
421 session : int
422 session : int
422 Session number to retrieve.
423 Session number to retrieve.
423 start : int
424 start : int
424 First line to retrieve.
425 First line to retrieve.
425 stop : int
426 stop : int
426 End of line range (excluded from output itself). If None, retrieve
427 End of line range (excluded from output itself). If None, retrieve
427 to the end of the session.
428 to the end of the session.
428 raw : bool
429 raw : bool
429 If True, return untranslated input
430 If True, return untranslated input
430 output : bool
431 output : bool
431 If True, attempt to include output. This will be 'real' Python
432 If True, attempt to include output. This will be 'real' Python
432 objects for the current session, or text reprs from previous
433 objects for the current session, or text reprs from previous
433 sessions if db_log_output was enabled at the time. Where no output
434 sessions if db_log_output was enabled at the time. Where no output
434 is found, None is used.
435 is found, None is used.
435
436
436 Returns
437 Returns
437 -------
438 -------
438 entries
439 entries
439 An iterator over the desired lines. Each line is a 3-tuple, either
440 An iterator over the desired lines. Each line is a 3-tuple, either
440 (session, line, input) if output is False, or
441 (session, line, input) if output is False, or
441 (session, line, (input, output)) if output is True.
442 (session, line, (input, output)) if output is True.
442 """
443 """
443 if stop:
444 if stop:
444 lineclause = "line >= ? AND line < ?"
445 lineclause = "line >= ? AND line < ?"
445 params = (session, start, stop)
446 params = (session, start, stop)
446 else:
447 else:
447 lineclause = "line>=?"
448 lineclause = "line>=?"
448 params = (session, start)
449 params = (session, start)
449
450
450 return self._run_sql("WHERE session==? AND %s" % lineclause,
451 return self._run_sql("WHERE session==? AND %s" % lineclause,
451 params, raw=raw, output=output)
452 params, raw=raw, output=output)
452
453
453 def get_range_by_str(self, rangestr, raw=True, output=False):
454 def get_range_by_str(self, rangestr, raw=True, output=False):
454 """Get lines of history from a string of ranges, as used by magic
455 """Get lines of history from a string of ranges, as used by magic
455 commands %hist, %save, %macro, etc.
456 commands %hist, %save, %macro, etc.
456
457
457 Parameters
458 Parameters
458 ----------
459 ----------
459 rangestr : str
460 rangestr : str
460 A string specifying ranges, e.g. "5 ~2/1-4". See
461 A string specifying ranges, e.g. "5 ~2/1-4". See
461 :func:`magic_history` for full details.
462 :func:`magic_history` for full details.
462 raw, output : bool
463 raw, output : bool
463 As :meth:`get_range`
464 As :meth:`get_range`
464
465
465 Returns
466 Returns
466 -------
467 -------
467 Tuples as :meth:`get_range`
468 Tuples as :meth:`get_range`
468 """
469 """
469 for sess, s, e in extract_hist_ranges(rangestr):
470 for sess, s, e in extract_hist_ranges(rangestr):
470 for line in self.get_range(sess, s, e, raw=raw, output=output):
471 for line in self.get_range(sess, s, e, raw=raw, output=output):
471 yield line
472 yield line
472
473
473
474
474 class HistoryManager(HistoryAccessor):
475 class HistoryManager(HistoryAccessor):
475 """A class to organize all history-related functionality in one place.
476 """A class to organize all history-related functionality in one place.
476 """
477 """
477 # Public interface
478 # Public interface
478
479
479 # An instance of the IPython shell we are attached to
480 # An instance of the IPython shell we are attached to
480 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
481 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
481 allow_none=True)
482 allow_none=True)
482 # Lists to hold processed and raw history. These start with a blank entry
483 # Lists to hold processed and raw history. These start with a blank entry
483 # so that we can index them starting from 1
484 # so that we can index them starting from 1
484 input_hist_parsed = List([""])
485 input_hist_parsed = List([""])
485 input_hist_raw = List([""])
486 input_hist_raw = List([""])
486 # A list of directories visited during session
487 # A list of directories visited during session
487 dir_hist = List()
488 dir_hist = List()
488 @default('dir_hist')
489 @default('dir_hist')
489 def _dir_hist_default(self):
490 def _dir_hist_default(self):
490 try:
491 try:
491 return [py3compat.getcwd()]
492 return [py3compat.getcwd()]
492 except OSError:
493 except OSError:
493 return []
494 return []
494
495
495 # A dict of output history, keyed with ints from the shell's
496 # A dict of output history, keyed with ints from the shell's
496 # execution count.
497 # execution count.
497 output_hist = Dict()
498 output_hist = Dict()
498 # The text/plain repr of outputs.
499 # The text/plain repr of outputs.
499 output_hist_reprs = Dict()
500 output_hist_reprs = Dict()
500
501
501 # The number of the current session in the history database
502 # The number of the current session in the history database
502 session_number = Integer()
503 session_number = Integer()
503
504
504 db_log_output = Bool(False,
505 db_log_output = Bool(False,
505 help="Should the history database include output? (default: no)"
506 help="Should the history database include output? (default: no)"
506 ).tag(config=True)
507 ).tag(config=True)
507 db_cache_size = Integer(0,
508 db_cache_size = Integer(0,
508 help="Write to database every x commands (higher values save disk access & power).\n"
509 help="Write to database every x commands (higher values save disk access & power).\n"
509 "Values of 1 or less effectively disable caching."
510 "Values of 1 or less effectively disable caching."
510 ).tag(config=True)
511 ).tag(config=True)
511 # The input and output caches
512 # The input and output caches
512 db_input_cache = List()
513 db_input_cache = List()
513 db_output_cache = List()
514 db_output_cache = List()
514
515
515 # History saving in separate thread
516 # History saving in separate thread
516 save_thread = Instance('IPython.core.history.HistorySavingThread',
517 save_thread = Instance('IPython.core.history.HistorySavingThread',
517 allow_none=True)
518 allow_none=True)
518 try: # Event is a function returning an instance of _Event...
519 try: # Event is a function returning an instance of _Event...
519 save_flag = Instance(threading._Event, allow_none=True)
520 save_flag = Instance(threading._Event, allow_none=True)
520 except AttributeError: # ...until Python 3.3, when it's a class.
521 except AttributeError: # ...until Python 3.3, when it's a class.
521 save_flag = Instance(threading.Event, allow_none=True)
522 save_flag = Instance(threading.Event, allow_none=True)
522
523
523 # Private interface
524 # Private interface
524 # Variables used to store the three last inputs from the user. On each new
525 # Variables used to store the three last inputs from the user. On each new
525 # history update, we populate the user's namespace with these, shifted as
526 # history update, we populate the user's namespace with these, shifted as
526 # necessary.
527 # necessary.
527 _i00 = Unicode(u'')
528 _i00 = Unicode(u'')
528 _i = Unicode(u'')
529 _i = Unicode(u'')
529 _ii = Unicode(u'')
530 _ii = Unicode(u'')
530 _iii = Unicode(u'')
531 _iii = Unicode(u'')
531
532
532 # A regex matching all forms of the exit command, so that we don't store
533 # A regex matching all forms of the exit command, so that we don't store
533 # them in the history (it's annoying to rewind the first entry and land on
534 # them in the history (it's annoying to rewind the first entry and land on
534 # an exit call).
535 # an exit call).
535 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
536 _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
536
537
537 def __init__(self, shell=None, config=None, **traits):
538 def __init__(self, shell=None, config=None, **traits):
538 """Create a new history manager associated with a shell instance.
539 """Create a new history manager associated with a shell instance.
539 """
540 """
540 # We need a pointer back to the shell for various tasks.
541 # We need a pointer back to the shell for various tasks.
541 super(HistoryManager, self).__init__(shell=shell, config=config,
542 super(HistoryManager, self).__init__(shell=shell, config=config,
542 **traits)
543 **traits)
543 self.save_flag = threading.Event()
544 self.save_flag = threading.Event()
544 self.db_input_cache_lock = threading.Lock()
545 self.db_input_cache_lock = threading.Lock()
545 self.db_output_cache_lock = threading.Lock()
546 self.db_output_cache_lock = threading.Lock()
546
547
547 try:
548 try:
548 self.new_session()
549 self.new_session()
549 except OperationalError:
550 except OperationalError:
550 self.log.error("Failed to create history session in %s. History will not be saved.",
551 self.log.error("Failed to create history session in %s. History will not be saved.",
551 self.hist_file, exc_info=True)
552 self.hist_file, exc_info=True)
552 self.hist_file = ':memory:'
553 self.hist_file = ':memory:'
553
554
554 if self.enabled and self.hist_file != ':memory:':
555 if self.enabled and self.hist_file != ':memory:':
555 self.save_thread = HistorySavingThread(self)
556 self.save_thread = HistorySavingThread(self)
556 self.save_thread.start()
557 self.save_thread.start()
557
558
558 def _get_hist_file_name(self, profile=None):
559 def _get_hist_file_name(self, profile=None):
559 """Get default history file name based on the Shell's profile.
560 """Get default history file name based on the Shell's profile.
560
561
561 The profile parameter is ignored, but must exist for compatibility with
562 The profile parameter is ignored, but must exist for compatibility with
562 the parent class."""
563 the parent class."""
563 profile_dir = self.shell.profile_dir.location
564 profile_dir = self.shell.profile_dir.location
564 return os.path.join(profile_dir, 'history.sqlite')
565 return os.path.join(profile_dir, 'history.sqlite')
565
566
566 @needs_sqlite
567 @needs_sqlite
567 def new_session(self, conn=None):
568 def new_session(self, conn=None):
568 """Get a new session number."""
569 """Get a new session number."""
569 if conn is None:
570 if conn is None:
570 conn = self.db
571 conn = self.db
571
572
572 with conn:
573 with conn:
573 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
574 cur = conn.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
574 NULL, "") """, (datetime.datetime.now(),))
575 NULL, "") """, (datetime.datetime.now(),))
575 self.session_number = cur.lastrowid
576 self.session_number = cur.lastrowid
576
577
577 def end_session(self):
578 def end_session(self):
578 """Close the database session, filling in the end time and line count."""
579 """Close the database session, filling in the end time and line count."""
579 self.writeout_cache()
580 self.writeout_cache()
580 with self.db:
581 with self.db:
581 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
582 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
582 session==?""", (datetime.datetime.now(),
583 session==?""", (datetime.datetime.now(),
583 len(self.input_hist_parsed)-1, self.session_number))
584 len(self.input_hist_parsed)-1, self.session_number))
584 self.session_number = 0
585 self.session_number = 0
585
586
586 def name_session(self, name):
587 def name_session(self, name):
587 """Give the current session a name in the history database."""
588 """Give the current session a name in the history database."""
588 with self.db:
589 with self.db:
589 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
590 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
590 (name, self.session_number))
591 (name, self.session_number))
591
592
592 def reset(self, new_session=True):
593 def reset(self, new_session=True):
593 """Clear the session history, releasing all object references, and
594 """Clear the session history, releasing all object references, and
594 optionally open a new session."""
595 optionally open a new session."""
595 self.output_hist.clear()
596 self.output_hist.clear()
596 # The directory history can't be completely empty
597 # The directory history can't be completely empty
597 self.dir_hist[:] = [py3compat.getcwd()]
598 self.dir_hist[:] = [py3compat.getcwd()]
598
599
599 if new_session:
600 if new_session:
600 if self.session_number:
601 if self.session_number:
601 self.end_session()
602 self.end_session()
602 self.input_hist_parsed[:] = [""]
603 self.input_hist_parsed[:] = [""]
603 self.input_hist_raw[:] = [""]
604 self.input_hist_raw[:] = [""]
604 self.new_session()
605 self.new_session()
605
606
606 # ------------------------------
607 # ------------------------------
607 # Methods for retrieving history
608 # Methods for retrieving history
608 # ------------------------------
609 # ------------------------------
609 def get_session_info(self, session=0):
610 def get_session_info(self, session=0):
610 """Get info about a session.
611 """Get info about a session.
611
612
612 Parameters
613 Parameters
613 ----------
614 ----------
614
615
615 session : int
616 session : int
616 Session number to retrieve. The current session is 0, and negative
617 Session number to retrieve. The current session is 0, and negative
617 numbers count back from current session, so -1 is the previous session.
618 numbers count back from current session, so -1 is the previous session.
618
619
619 Returns
620 Returns
620 -------
621 -------
621
622
622 session_id : int
623 session_id : int
623 Session ID number
624 Session ID number
624 start : datetime
625 start : datetime
625 Timestamp for the start of the session.
626 Timestamp for the start of the session.
626 end : datetime
627 end : datetime
627 Timestamp for the end of the session, or None if IPython crashed.
628 Timestamp for the end of the session, or None if IPython crashed.
628 num_cmds : int
629 num_cmds : int
629 Number of commands run, or None if IPython crashed.
630 Number of commands run, or None if IPython crashed.
630 remark : unicode
631 remark : unicode
631 A manually set description.
632 A manually set description.
632 """
633 """
633 if session <= 0:
634 if session <= 0:
634 session += self.session_number
635 session += self.session_number
635
636
636 return super(HistoryManager, self).get_session_info(session=session)
637 return super(HistoryManager, self).get_session_info(session=session)
637
638
638 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
639 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
639 """Get input and output history from the current session. Called by
640 """Get input and output history from the current session. Called by
640 get_range, and takes similar parameters."""
641 get_range, and takes similar parameters."""
641 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
642 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
642
643
643 n = len(input_hist)
644 n = len(input_hist)
644 if start < 0:
645 if start < 0:
645 start += n
646 start += n
646 if not stop or (stop > n):
647 if not stop or (stop > n):
647 stop = n
648 stop = n
648 elif stop < 0:
649 elif stop < 0:
649 stop += n
650 stop += n
650
651
651 for i in range(start, stop):
652 for i in range(start, stop):
652 if output:
653 if output:
653 line = (input_hist[i], self.output_hist_reprs.get(i))
654 line = (input_hist[i], self.output_hist_reprs.get(i))
654 else:
655 else:
655 line = input_hist[i]
656 line = input_hist[i]
656 yield (0, i, line)
657 yield (0, i, line)
657
658
658 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
659 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
659 """Retrieve input by session.
660 """Retrieve input by session.
660
661
661 Parameters
662 Parameters
662 ----------
663 ----------
663 session : int
664 session : int
664 Session number to retrieve. The current session is 0, and negative
665 Session number to retrieve. The current session is 0, and negative
665 numbers count back from current session, so -1 is previous session.
666 numbers count back from current session, so -1 is previous session.
666 start : int
667 start : int
667 First line to retrieve.
668 First line to retrieve.
668 stop : int
669 stop : int
669 End of line range (excluded from output itself). If None, retrieve
670 End of line range (excluded from output itself). If None, retrieve
670 to the end of the session.
671 to the end of the session.
671 raw : bool
672 raw : bool
672 If True, return untranslated input
673 If True, return untranslated input
673 output : bool
674 output : bool
674 If True, attempt to include output. This will be 'real' Python
675 If True, attempt to include output. This will be 'real' Python
675 objects for the current session, or text reprs from previous
676 objects for the current session, or text reprs from previous
676 sessions if db_log_output was enabled at the time. Where no output
677 sessions if db_log_output was enabled at the time. Where no output
677 is found, None is used.
678 is found, None is used.
678
679
679 Returns
680 Returns
680 -------
681 -------
681 entries
682 entries
682 An iterator over the desired lines. Each line is a 3-tuple, either
683 An iterator over the desired lines. Each line is a 3-tuple, either
683 (session, line, input) if output is False, or
684 (session, line, input) if output is False, or
684 (session, line, (input, output)) if output is True.
685 (session, line, (input, output)) if output is True.
685 """
686 """
686 if session <= 0:
687 if session <= 0:
687 session += self.session_number
688 session += self.session_number
688 if session==self.session_number: # Current session
689 if session==self.session_number: # Current session
689 return self._get_range_session(start, stop, raw, output)
690 return self._get_range_session(start, stop, raw, output)
690 return super(HistoryManager, self).get_range(session, start, stop, raw,
691 return super(HistoryManager, self).get_range(session, start, stop, raw,
691 output)
692 output)
692
693
693 ## ----------------------------
694 ## ----------------------------
694 ## Methods for storing history:
695 ## Methods for storing history:
695 ## ----------------------------
696 ## ----------------------------
696 def store_inputs(self, line_num, source, source_raw=None):
697 def store_inputs(self, line_num, source, source_raw=None):
697 """Store source and raw input in history and create input cache
698 """Store source and raw input in history and create input cache
698 variables ``_i*``.
699 variables ``_i*``.
699
700
700 Parameters
701 Parameters
701 ----------
702 ----------
702 line_num : int
703 line_num : int
703 The prompt number of this input.
704 The prompt number of this input.
704
705
705 source : str
706 source : str
706 Python input.
707 Python input.
707
708
708 source_raw : str, optional
709 source_raw : str, optional
709 If given, this is the raw input without any IPython transformations
710 If given, this is the raw input without any IPython transformations
710 applied to it. If not given, ``source`` is used.
711 applied to it. If not given, ``source`` is used.
711 """
712 """
712 if source_raw is None:
713 if source_raw is None:
713 source_raw = source
714 source_raw = source
714 source = source.rstrip('\n')
715 source = source.rstrip('\n')
715 source_raw = source_raw.rstrip('\n')
716 source_raw = source_raw.rstrip('\n')
716
717
717 # do not store exit/quit commands
718 # do not store exit/quit commands
718 if self._exit_re.match(source_raw.strip()):
719 if self._exit_re.match(source_raw.strip()):
719 return
720 return
720
721
721 self.input_hist_parsed.append(source)
722 self.input_hist_parsed.append(source)
722 self.input_hist_raw.append(source_raw)
723 self.input_hist_raw.append(source_raw)
723
724
724 with self.db_input_cache_lock:
725 with self.db_input_cache_lock:
725 self.db_input_cache.append((line_num, source, source_raw))
726 self.db_input_cache.append((line_num, source, source_raw))
726 # Trigger to flush cache and write to DB.
727 # Trigger to flush cache and write to DB.
727 if len(self.db_input_cache) >= self.db_cache_size:
728 if len(self.db_input_cache) >= self.db_cache_size:
728 self.save_flag.set()
729 self.save_flag.set()
729
730
730 # update the auto _i variables
731 # update the auto _i variables
731 self._iii = self._ii
732 self._iii = self._ii
732 self._ii = self._i
733 self._ii = self._i
733 self._i = self._i00
734 self._i = self._i00
734 self._i00 = source_raw
735 self._i00 = source_raw
735
736
736 # hackish access to user namespace to create _i1,_i2... dynamically
737 # hackish access to user namespace to create _i1,_i2... dynamically
737 new_i = '_i%s' % line_num
738 new_i = '_i%s' % line_num
738 to_main = {'_i': self._i,
739 to_main = {'_i': self._i,
739 '_ii': self._ii,
740 '_ii': self._ii,
740 '_iii': self._iii,
741 '_iii': self._iii,
741 new_i : self._i00 }
742 new_i : self._i00 }
742
743
743 if self.shell is not None:
744 if self.shell is not None:
744 self.shell.push(to_main, interactive=False)
745 self.shell.push(to_main, interactive=False)
745
746
746 def store_output(self, line_num):
747 def store_output(self, line_num):
747 """If database output logging is enabled, this saves all the
748 """If database output logging is enabled, this saves all the
748 outputs from the indicated prompt number to the database. It's
749 outputs from the indicated prompt number to the database. It's
749 called by run_cell after code has been executed.
750 called by run_cell after code has been executed.
750
751
751 Parameters
752 Parameters
752 ----------
753 ----------
753 line_num : int
754 line_num : int
754 The line number from which to save outputs
755 The line number from which to save outputs
755 """
756 """
756 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
757 if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
757 return
758 return
758 output = self.output_hist_reprs[line_num]
759 output = self.output_hist_reprs[line_num]
759
760
760 with self.db_output_cache_lock:
761 with self.db_output_cache_lock:
761 self.db_output_cache.append((line_num, output))
762 self.db_output_cache.append((line_num, output))
762 if self.db_cache_size <= 1:
763 if self.db_cache_size <= 1:
763 self.save_flag.set()
764 self.save_flag.set()
764
765
765 def _writeout_input_cache(self, conn):
766 def _writeout_input_cache(self, conn):
766 with conn:
767 with conn:
767 for line in self.db_input_cache:
768 for line in self.db_input_cache:
768 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
769 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
769 (self.session_number,)+line)
770 (self.session_number,)+line)
770
771
771 def _writeout_output_cache(self, conn):
772 def _writeout_output_cache(self, conn):
772 with conn:
773 with conn:
773 for line in self.db_output_cache:
774 for line in self.db_output_cache:
774 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
775 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
775 (self.session_number,)+line)
776 (self.session_number,)+line)
776
777
777 @needs_sqlite
778 @needs_sqlite
778 def writeout_cache(self, conn=None):
779 def writeout_cache(self, conn=None):
779 """Write any entries in the cache to the database."""
780 """Write any entries in the cache to the database."""
780 if conn is None:
781 if conn is None:
781 conn = self.db
782 conn = self.db
782
783
783 with self.db_input_cache_lock:
784 with self.db_input_cache_lock:
784 try:
785 try:
785 self._writeout_input_cache(conn)
786 self._writeout_input_cache(conn)
786 except sqlite3.IntegrityError:
787 except sqlite3.IntegrityError:
787 self.new_session(conn)
788 self.new_session(conn)
788 print("ERROR! Session/line number was not unique in",
789 print("ERROR! Session/line number was not unique in",
789 "database. History logging moved to new session",
790 "database. History logging moved to new session",
790 self.session_number)
791 self.session_number)
791 try:
792 try:
792 # Try writing to the new session. If this fails, don't
793 # Try writing to the new session. If this fails, don't
793 # recurse
794 # recurse
794 self._writeout_input_cache(conn)
795 self._writeout_input_cache(conn)
795 except sqlite3.IntegrityError:
796 except sqlite3.IntegrityError:
796 pass
797 pass
797 finally:
798 finally:
798 self.db_input_cache = []
799 self.db_input_cache = []
799
800
800 with self.db_output_cache_lock:
801 with self.db_output_cache_lock:
801 try:
802 try:
802 self._writeout_output_cache(conn)
803 self._writeout_output_cache(conn)
803 except sqlite3.IntegrityError:
804 except sqlite3.IntegrityError:
804 print("!! Session/line number for output was not unique",
805 print("!! Session/line number for output was not unique",
805 "in database. Output will not be stored.")
806 "in database. Output will not be stored.")
806 finally:
807 finally:
807 self.db_output_cache = []
808 self.db_output_cache = []
808
809
809
810
810 class HistorySavingThread(threading.Thread):
811 class HistorySavingThread(threading.Thread):
811 """This thread takes care of writing history to the database, so that
812 """This thread takes care of writing history to the database, so that
812 the UI isn't held up while that happens.
813 the UI isn't held up while that happens.
813
814
814 It waits for the HistoryManager's save_flag to be set, then writes out
815 It waits for the HistoryManager's save_flag to be set, then writes out
815 the history cache. The main thread is responsible for setting the flag when
816 the history cache. The main thread is responsible for setting the flag when
816 the cache size reaches a defined threshold."""
817 the cache size reaches a defined threshold."""
817 daemon = True
818 daemon = True
818 stop_now = False
819 stop_now = False
819 enabled = True
820 enabled = True
820 def __init__(self, history_manager):
821 def __init__(self, history_manager):
821 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
822 super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
822 self.history_manager = history_manager
823 self.history_manager = history_manager
823 self.enabled = history_manager.enabled
824 self.enabled = history_manager.enabled
824 atexit.register(self.stop)
825 atexit.register(self.stop)
825
826
826 @needs_sqlite
827 @needs_sqlite
827 def run(self):
828 def run(self):
828 # We need a separate db connection per thread:
829 # We need a separate db connection per thread:
829 try:
830 try:
830 self.db = sqlite3.connect(self.history_manager.hist_file,
831 self.db = sqlite3.connect(self.history_manager.hist_file,
831 **self.history_manager.connection_options
832 **self.history_manager.connection_options
832 )
833 )
833 while True:
834 while True:
834 self.history_manager.save_flag.wait()
835 self.history_manager.save_flag.wait()
835 if self.stop_now:
836 if self.stop_now:
836 self.db.close()
837 self.db.close()
837 return
838 return
838 self.history_manager.save_flag.clear()
839 self.history_manager.save_flag.clear()
839 self.history_manager.writeout_cache(self.db)
840 self.history_manager.writeout_cache(self.db)
840 except Exception as e:
841 except Exception as e:
841 print(("The history saving thread hit an unexpected error (%s)."
842 print(("The history saving thread hit an unexpected error (%s)."
842 "History will not be written to the database.") % repr(e))
843 "History will not be written to the database.") % repr(e))
843
844
844 def stop(self):
845 def stop(self):
845 """This can be called from the main thread to safely stop this thread.
846 """This can be called from the main thread to safely stop this thread.
846
847
847 Note that it does not attempt to write out remaining history before
848 Note that it does not attempt to write out remaining history before
848 exiting. That should be done by calling the HistoryManager's
849 exiting. That should be done by calling the HistoryManager's
849 end_session method."""
850 end_session method."""
850 self.stop_now = True
851 self.stop_now = True
851 self.history_manager.save_flag.set()
852 self.history_manager.save_flag.set()
852 self.join()
853 self.join()
853
854
854
855
855 # To match, e.g. ~5/8-~2/3
856 # To match, e.g. ~5/8-~2/3
856 range_re = re.compile(r"""
857 range_re = re.compile(r"""
857 ((?P<startsess>~?\d+)/)?
858 ((?P<startsess>~?\d+)/)?
858 (?P<start>\d+)?
859 (?P<start>\d+)?
859 ((?P<sep>[\-:])
860 ((?P<sep>[\-:])
860 ((?P<endsess>~?\d+)/)?
861 ((?P<endsess>~?\d+)/)?
861 (?P<end>\d+))?
862 (?P<end>\d+))?
862 $""", re.VERBOSE)
863 $""", re.VERBOSE)
863
864
864
865
865 def extract_hist_ranges(ranges_str):
866 def extract_hist_ranges(ranges_str):
866 """Turn a string of history ranges into 3-tuples of (session, start, stop).
867 """Turn a string of history ranges into 3-tuples of (session, start, stop).
867
868
868 Examples
869 Examples
869 --------
870 --------
870 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
871 >>> list(extract_hist_ranges("~8/5-~7/4 2"))
871 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
872 [(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
872 """
873 """
873 for range_str in ranges_str.split():
874 for range_str in ranges_str.split():
874 rmatch = range_re.match(range_str)
875 rmatch = range_re.match(range_str)
875 if not rmatch:
876 if not rmatch:
876 continue
877 continue
877 start = rmatch.group("start")
878 start = rmatch.group("start")
878 if start:
879 if start:
879 start = int(start)
880 start = int(start)
880 end = rmatch.group("end")
881 end = rmatch.group("end")
881 # If no end specified, get (a, a + 1)
882 # If no end specified, get (a, a + 1)
882 end = int(end) if end else start + 1
883 end = int(end) if end else start + 1
883 else: # start not specified
884 else: # start not specified
884 if not rmatch.group('startsess'): # no startsess
885 if not rmatch.group('startsess'): # no startsess
885 continue
886 continue
886 start = 1
887 start = 1
887 end = None # provide the entire session hist
888 end = None # provide the entire session hist
888
889
889 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
890 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
890 end += 1
891 end += 1
891 startsess = rmatch.group("startsess") or "0"
892 startsess = rmatch.group("startsess") or "0"
892 endsess = rmatch.group("endsess") or startsess
893 endsess = rmatch.group("endsess") or startsess
893 startsess = int(startsess.replace("~","-"))
894 startsess = int(startsess.replace("~","-"))
894 endsess = int(endsess.replace("~","-"))
895 endsess = int(endsess.replace("~","-"))
895 assert endsess >= startsess, "start session must be earlier than end session"
896 assert endsess >= startsess, "start session must be earlier than end session"
896
897
897 if endsess == startsess:
898 if endsess == startsess:
898 yield (startsess, start, end)
899 yield (startsess, start, end)
899 continue
900 continue
900 # Multiple sessions in one range:
901 # Multiple sessions in one range:
901 yield (startsess, start, None)
902 yield (startsess, start, None)
902 for sess in range(startsess+1, endsess):
903 for sess in range(startsess+1, endsess):
903 yield (sess, 1, None)
904 yield (sess, 1, None)
904 yield (endsess, 1, end)
905 yield (endsess, 1, end)
905
906
906
907
907 def _format_lineno(session, line):
908 def _format_lineno(session, line):
908 """Helper function to format line numbers properly."""
909 """Helper function to format line numbers properly."""
909 if session == 0:
910 if session == 0:
910 return str(line)
911 return str(line)
911 return "%s#%s" % (session, line)
912 return "%s#%s" % (session, line)
@@ -1,543 +1,543 b''
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
2 from __future__ import print_function
2 from __future__ import print_function
3
3
4 import os
4 import os
5 import sys
5 import sys
6 import warnings
6 import warnings
7 from warnings import warn
7 from warnings import warn
8
8
9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 from IPython.utils import io
10 from IPython.utils import io
11 from IPython.utils.py3compat import PY3, cast_unicode_py2, input, string_types
11 from IPython.utils.py3compat import PY3, cast_unicode_py2, input, string_types
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title
13 from IPython.utils.process import abbrev_cwd
13 from IPython.utils.process import abbrev_cwd
14 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union
14 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union
15
15
16 from prompt_toolkit.document import Document
16 from prompt_toolkit.document import Document
17 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
17 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
18 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
18 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
19 from prompt_toolkit.history import InMemoryHistory
19 from prompt_toolkit.history import InMemoryHistory
20 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
20 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
21 from prompt_toolkit.interface import CommandLineInterface
21 from prompt_toolkit.interface import CommandLineInterface
22 from prompt_toolkit.key_binding.manager import KeyBindingManager
22 from prompt_toolkit.key_binding.manager import KeyBindingManager
23 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
24 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
24 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
25
25
26 from pygments.styles import get_style_by_name, get_all_styles
26 from pygments.styles import get_style_by_name, get_all_styles
27 from pygments.style import Style
27 from pygments.style import Style
28 from pygments.token import Token
28 from pygments.token import Token
29
29
30 from .debugger import TerminalPdb, Pdb
30 from .debugger import TerminalPdb, Pdb
31 from .magics import TerminalMagics
31 from .magics import TerminalMagics
32 from .pt_inputhooks import get_inputhook_name_and_func
32 from .pt_inputhooks import get_inputhook_name_and_func
33 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
33 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
34 from .ptutils import IPythonPTCompleter, IPythonPTLexer
34 from .ptutils import IPythonPTCompleter, IPythonPTLexer
35 from .shortcuts import register_ipython_shortcuts
35 from .shortcuts import register_ipython_shortcuts
36
36
37 DISPLAY_BANNER_DEPRECATED = object()
37 DISPLAY_BANNER_DEPRECATED = object()
38
38
39
39
40 from pygments.style import Style
40 from pygments.style import Style
41
41
42 class _NoStyle(Style): pass
42 class _NoStyle(Style): pass
43
43
44
44
45
45
46 _style_overrides_light_bg = {
46 _style_overrides_light_bg = {
47 Token.Prompt: '#0000ff',
47 Token.Prompt: '#0000ff',
48 Token.PromptNum: '#0000ee bold',
48 Token.PromptNum: '#0000ee bold',
49 Token.OutPrompt: '#cc0000',
49 Token.OutPrompt: '#cc0000',
50 Token.OutPromptNum: '#bb0000 bold',
50 Token.OutPromptNum: '#bb0000 bold',
51 }
51 }
52
52
53 _style_overrides_linux = {
53 _style_overrides_linux = {
54 Token.Prompt: '#00cc00',
54 Token.Prompt: '#00cc00',
55 Token.PromptNum: '#00bb00 bold',
55 Token.PromptNum: '#00bb00 bold',
56 Token.OutPrompt: '#cc0000',
56 Token.OutPrompt: '#cc0000',
57 Token.OutPromptNum: '#bb0000 bold',
57 Token.OutPromptNum: '#bb0000 bold',
58 }
58 }
59
59
60
60
61
61
62 def get_default_editor():
62 def get_default_editor():
63 try:
63 try:
64 ed = os.environ['EDITOR']
64 ed = os.environ['EDITOR']
65 if not PY3:
65 if not PY3:
66 ed = ed.decode()
66 ed = ed.decode()
67 return ed
67 return ed
68 except KeyError:
68 except KeyError:
69 pass
69 pass
70 except UnicodeError:
70 except UnicodeError:
71 warn("$EDITOR environment variable is not pure ASCII. Using platform "
71 warn("$EDITOR environment variable is not pure ASCII. Using platform "
72 "default editor.")
72 "default editor.")
73
73
74 if os.name == 'posix':
74 if os.name == 'posix':
75 return 'vi' # the only one guaranteed to be there!
75 return 'vi' # the only one guaranteed to be there!
76 else:
76 else:
77 return 'notepad' # same in Windows!
77 return 'notepad' # same in Windows!
78
78
79 # conservatively check for tty
79 # conservatively check for tty
80 # overridden streams can result in things like:
80 # overridden streams can result in things like:
81 # - sys.stdin = None
81 # - sys.stdin = None
82 # - no isatty method
82 # - no isatty method
83 for _name in ('stdin', 'stdout', 'stderr'):
83 for _name in ('stdin', 'stdout', 'stderr'):
84 _stream = getattr(sys, _name)
84 _stream = getattr(sys, _name)
85 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
85 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
86 _is_tty = False
86 _is_tty = False
87 break
87 break
88 else:
88 else:
89 _is_tty = True
89 _is_tty = True
90
90
91
91
92 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
92 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
93
93
94 class TerminalInteractiveShell(InteractiveShell):
94 class TerminalInteractiveShell(InteractiveShell):
95 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
95 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
96 'to reserve for the completion menu'
96 'to reserve for the completion menu'
97 ).tag(config=True)
97 ).tag(config=True)
98
98
99 def _space_for_menu_changed(self, old, new):
99 def _space_for_menu_changed(self, old, new):
100 self._update_layout()
100 self._update_layout()
101
101
102 pt_cli = None
102 pt_cli = None
103 debugger_history = None
103 debugger_history = None
104 _pt_app = None
104 _pt_app = None
105
105
106 simple_prompt = Bool(_use_simple_prompt,
106 simple_prompt = Bool(_use_simple_prompt,
107 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
107 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
108
108
109 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
109 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
110 IPython own testing machinery, and emacs inferior-shell integration through elpy.
110 IPython own testing machinery, and emacs inferior-shell integration through elpy.
111
111
112 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
112 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
113 environment variable is set, or the current terminal is not a tty.
113 environment variable is set, or the current terminal is not a tty.
114
114
115 """
115 """
116 ).tag(config=True)
116 ).tag(config=True)
117
117
118 @property
118 @property
119 def debugger_cls(self):
119 def debugger_cls(self):
120 return Pdb if self.simple_prompt else TerminalPdb
120 return Pdb if self.simple_prompt else TerminalPdb
121
121
122 confirm_exit = Bool(True,
122 confirm_exit = Bool(True,
123 help="""
123 help="""
124 Set to confirm when you try to exit IPython with an EOF (Control-D
124 Set to confirm when you try to exit IPython with an EOF (Control-D
125 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
125 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
126 you can force a direct exit without any confirmation.""",
126 you can force a direct exit without any confirmation.""",
127 ).tag(config=True)
127 ).tag(config=True)
128
128
129 editing_mode = Unicode('emacs',
129 editing_mode = Unicode('emacs',
130 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
130 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
131 ).tag(config=True)
131 ).tag(config=True)
132
132
133 mouse_support = Bool(False,
133 mouse_support = Bool(False,
134 help="Enable mouse support in the prompt"
134 help="Enable mouse support in the prompt"
135 ).tag(config=True)
135 ).tag(config=True)
136
136
137 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
137 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
138 help="""The name or class of a Pygments style to use for syntax
138 help="""The name or class of a Pygments style to use for syntax
139 highlighting: \n %s""" % ', '.join(get_all_styles())
139 highlighting: \n %s""" % ', '.join(get_all_styles())
140 ).tag(config=True)
140 ).tag(config=True)
141
141
142
142
143 @observe('highlighting_style')
143 @observe('highlighting_style')
144 @observe('colors')
144 @observe('colors')
145 def _highlighting_style_changed(self, change):
145 def _highlighting_style_changed(self, change):
146 self.refresh_style()
146 self.refresh_style()
147
147
148 def refresh_style(self):
148 def refresh_style(self):
149 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
149 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
150
150
151
151
152 highlighting_style_overrides = Dict(
152 highlighting_style_overrides = Dict(
153 help="Override highlighting format for specific tokens"
153 help="Override highlighting format for specific tokens"
154 ).tag(config=True)
154 ).tag(config=True)
155
155
156 true_color = Bool(False,
156 true_color = Bool(False,
157 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
157 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
158 "If your terminal supports true color, the following command "
158 "If your terminal supports true color, the following command "
159 "should print 'TRUECOLOR' in orange: "
159 "should print 'TRUECOLOR' in orange: "
160 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
160 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
161 ).tag(config=True)
161 ).tag(config=True)
162
162
163 editor = Unicode(get_default_editor(),
163 editor = Unicode(get_default_editor(),
164 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
164 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
165 ).tag(config=True)
165 ).tag(config=True)
166
166
167 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
167 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
168
168
169 prompts = Instance(Prompts)
169 prompts = Instance(Prompts)
170
170
171 @default('prompts')
171 @default('prompts')
172 def _prompts_default(self):
172 def _prompts_default(self):
173 return self.prompts_class(self)
173 return self.prompts_class(self)
174
174
175 @observe('prompts')
175 @observe('prompts')
176 def _(self, change):
176 def _(self, change):
177 self._update_layout()
177 self._update_layout()
178
178
179 @default('displayhook_class')
179 @default('displayhook_class')
180 def _displayhook_class_default(self):
180 def _displayhook_class_default(self):
181 return RichPromptDisplayHook
181 return RichPromptDisplayHook
182
182
183 term_title = Bool(True,
183 term_title = Bool(True,
184 help="Automatically set the terminal title"
184 help="Automatically set the terminal title"
185 ).tag(config=True)
185 ).tag(config=True)
186
186
187 display_completions = Enum(('column', 'multicolumn','readlinelike'),
187 display_completions = Enum(('column', 'multicolumn','readlinelike'),
188 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
188 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
189 "'readlinelike'. These options are for `prompt_toolkit`, see "
189 "'readlinelike'. These options are for `prompt_toolkit`, see "
190 "`prompt_toolkit` documentation for more information."
190 "`prompt_toolkit` documentation for more information."
191 ),
191 ),
192 default_value='multicolumn').tag(config=True)
192 default_value='multicolumn').tag(config=True)
193
193
194 highlight_matching_brackets = Bool(True,
194 highlight_matching_brackets = Bool(True,
195 help="Highlight matching brackets.",
195 help="Highlight matching brackets.",
196 ).tag(config=True)
196 ).tag(config=True)
197
197
198 extra_open_editor_shortcuts = Bool(False,
198 extra_open_editor_shortcuts = Bool(False,
199 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
199 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
200 "This is in addition to the F2 binding, which is always enabled."
200 "This is in addition to the F2 binding, which is always enabled."
201 ).tag(config=True)
201 ).tag(config=True)
202
202
203 @observe('term_title')
203 @observe('term_title')
204 def init_term_title(self, change=None):
204 def init_term_title(self, change=None):
205 # Enable or disable the terminal title.
205 # Enable or disable the terminal title.
206 if self.term_title:
206 if self.term_title:
207 toggle_set_term_title(True)
207 toggle_set_term_title(True)
208 set_term_title('IPython: ' + abbrev_cwd())
208 set_term_title('IPython: ' + abbrev_cwd())
209 else:
209 else:
210 toggle_set_term_title(False)
210 toggle_set_term_title(False)
211
211
212 def init_display_formatter(self):
212 def init_display_formatter(self):
213 super(TerminalInteractiveShell, self).init_display_formatter()
213 super(TerminalInteractiveShell, self).init_display_formatter()
214 # terminal only supports plain text
214 # terminal only supports plain text
215 self.display_formatter.active_types = ['text/plain']
215 self.display_formatter.active_types = ['text/plain']
216 # disable `_ipython_display_`
216 # disable `_ipython_display_`
217 self.display_formatter.ipython_display_formatter.enabled = False
217 self.display_formatter.ipython_display_formatter.enabled = False
218
218
219 def init_prompt_toolkit_cli(self):
219 def init_prompt_toolkit_cli(self):
220 if self.simple_prompt:
220 if self.simple_prompt:
221 # Fall back to plain non-interactive output for tests.
221 # Fall back to plain non-interactive output for tests.
222 # This is very limited, and only accepts a single line.
222 # This is very limited, and only accepts a single line.
223 def prompt():
223 def prompt():
224 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
224 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
225 self.prompt_for_code = prompt
225 self.prompt_for_code = prompt
226 return
226 return
227
227
228 # Set up keyboard shortcuts
228 # Set up keyboard shortcuts
229 kbmanager = KeyBindingManager.for_prompt(
229 kbmanager = KeyBindingManager.for_prompt(
230 enable_open_in_editor=self.extra_open_editor_shortcuts,
230 enable_open_in_editor=self.extra_open_editor_shortcuts,
231 )
231 )
232 register_ipython_shortcuts(kbmanager.registry, self)
232 register_ipython_shortcuts(kbmanager.registry, self)
233
233
234 # Pre-populate history from IPython's history database
234 # Pre-populate history from IPython's history database
235 history = InMemoryHistory()
235 history = InMemoryHistory()
236 last_cell = u""
236 last_cell = u""
237 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
237 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
238 include_latest=True):
238 include_latest=True):
239 # Ignore blank lines and consecutive duplicates
239 # Ignore blank lines and consecutive duplicates
240 cell = cell.rstrip()
240 cell = cell.rstrip()
241 if cell and (cell != last_cell):
241 if cell and (cell != last_cell):
242 history.append(cast_unicode_py2(cell))
242 history.append(cell)
243 last_cell = cell
243 last_cell = cell
244
244
245 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
245 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
246 style = DynamicStyle(lambda: self._style)
246 style = DynamicStyle(lambda: self._style)
247
247
248 editing_mode = getattr(EditingMode, self.editing_mode.upper())
248 editing_mode = getattr(EditingMode, self.editing_mode.upper())
249
249
250 def patch_stdout(**kwargs):
250 def patch_stdout(**kwargs):
251 return self.pt_cli.patch_stdout_context(**kwargs)
251 return self.pt_cli.patch_stdout_context(**kwargs)
252
252
253 self._pt_app = create_prompt_application(
253 self._pt_app = create_prompt_application(
254 editing_mode=editing_mode,
254 editing_mode=editing_mode,
255 key_bindings_registry=kbmanager.registry,
255 key_bindings_registry=kbmanager.registry,
256 history=history,
256 history=history,
257 completer=IPythonPTCompleter(shell=self,
257 completer=IPythonPTCompleter(shell=self,
258 patch_stdout=patch_stdout),
258 patch_stdout=patch_stdout),
259 enable_history_search=True,
259 enable_history_search=True,
260 style=style,
260 style=style,
261 mouse_support=self.mouse_support,
261 mouse_support=self.mouse_support,
262 **self._layout_options()
262 **self._layout_options()
263 )
263 )
264 self._eventloop = create_eventloop(self.inputhook)
264 self._eventloop = create_eventloop(self.inputhook)
265 self.pt_cli = CommandLineInterface(
265 self.pt_cli = CommandLineInterface(
266 self._pt_app, eventloop=self._eventloop,
266 self._pt_app, eventloop=self._eventloop,
267 output=create_output(true_color=self.true_color))
267 output=create_output(true_color=self.true_color))
268
268
269 def _make_style_from_name_or_cls(self, name_or_cls):
269 def _make_style_from_name_or_cls(self, name_or_cls):
270 """
270 """
271 Small wrapper that make an IPython compatible style from a style name
271 Small wrapper that make an IPython compatible style from a style name
272
272
273 We need that to add style for prompt ... etc.
273 We need that to add style for prompt ... etc.
274 """
274 """
275 style_overrides = {}
275 style_overrides = {}
276 if name_or_cls == 'legacy':
276 if name_or_cls == 'legacy':
277 legacy = self.colors.lower()
277 legacy = self.colors.lower()
278 if legacy == 'linux':
278 if legacy == 'linux':
279 style_cls = get_style_by_name('monokai')
279 style_cls = get_style_by_name('monokai')
280 style_overrides = _style_overrides_linux
280 style_overrides = _style_overrides_linux
281 elif legacy == 'lightbg':
281 elif legacy == 'lightbg':
282 style_overrides = _style_overrides_light_bg
282 style_overrides = _style_overrides_light_bg
283 style_cls = get_style_by_name('pastie')
283 style_cls = get_style_by_name('pastie')
284 elif legacy == 'neutral':
284 elif legacy == 'neutral':
285 # The default theme needs to be visible on both a dark background
285 # The default theme needs to be visible on both a dark background
286 # and a light background, because we can't tell what the terminal
286 # and a light background, because we can't tell what the terminal
287 # looks like. These tweaks to the default theme help with that.
287 # looks like. These tweaks to the default theme help with that.
288 style_cls = get_style_by_name('default')
288 style_cls = get_style_by_name('default')
289 style_overrides.update({
289 style_overrides.update({
290 Token.Number: '#007700',
290 Token.Number: '#007700',
291 Token.Operator: 'noinherit',
291 Token.Operator: 'noinherit',
292 Token.String: '#BB6622',
292 Token.String: '#BB6622',
293 Token.Name.Function: '#2080D0',
293 Token.Name.Function: '#2080D0',
294 Token.Name.Class: 'bold #2080D0',
294 Token.Name.Class: 'bold #2080D0',
295 Token.Name.Namespace: 'bold #2080D0',
295 Token.Name.Namespace: 'bold #2080D0',
296 Token.Prompt: '#009900',
296 Token.Prompt: '#009900',
297 Token.PromptNum: '#00ff00 bold',
297 Token.PromptNum: '#00ff00 bold',
298 Token.OutPrompt: '#990000',
298 Token.OutPrompt: '#990000',
299 Token.OutPromptNum: '#ff0000 bold',
299 Token.OutPromptNum: '#ff0000 bold',
300 })
300 })
301
301
302 # Hack: Due to limited color support on the Windows console
302 # Hack: Due to limited color support on the Windows console
303 # the prompt colors will be wrong without this
303 # the prompt colors will be wrong without this
304 if os.name == 'nt':
304 if os.name == 'nt':
305 style_overrides.update({
305 style_overrides.update({
306 Token.Prompt: '#ansidarkgreen',
306 Token.Prompt: '#ansidarkgreen',
307 Token.PromptNum: '#ansigreen bold',
307 Token.PromptNum: '#ansigreen bold',
308 Token.OutPrompt: '#ansidarkred',
308 Token.OutPrompt: '#ansidarkred',
309 Token.OutPromptNum: '#ansired bold',
309 Token.OutPromptNum: '#ansired bold',
310 })
310 })
311 elif legacy =='nocolor':
311 elif legacy =='nocolor':
312 style_cls=_NoStyle
312 style_cls=_NoStyle
313 style_overrides = {}
313 style_overrides = {}
314 else :
314 else :
315 raise ValueError('Got unknown colors: ', legacy)
315 raise ValueError('Got unknown colors: ', legacy)
316 else :
316 else :
317 if isinstance(name_or_cls, string_types):
317 if isinstance(name_or_cls, string_types):
318 style_cls = get_style_by_name(name_or_cls)
318 style_cls = get_style_by_name(name_or_cls)
319 else:
319 else:
320 style_cls = name_or_cls
320 style_cls = name_or_cls
321 style_overrides = {
321 style_overrides = {
322 Token.Prompt: '#009900',
322 Token.Prompt: '#009900',
323 Token.PromptNum: '#00ff00 bold',
323 Token.PromptNum: '#00ff00 bold',
324 Token.OutPrompt: '#990000',
324 Token.OutPrompt: '#990000',
325 Token.OutPromptNum: '#ff0000 bold',
325 Token.OutPromptNum: '#ff0000 bold',
326 }
326 }
327 style_overrides.update(self.highlighting_style_overrides)
327 style_overrides.update(self.highlighting_style_overrides)
328 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
328 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
329 style_dict=style_overrides)
329 style_dict=style_overrides)
330
330
331 return style
331 return style
332
332
333 def _layout_options(self):
333 def _layout_options(self):
334 """
334 """
335 Return the current layout option for the current Terminal InteractiveShell
335 Return the current layout option for the current Terminal InteractiveShell
336 """
336 """
337 return {
337 return {
338 'lexer':IPythonPTLexer(),
338 'lexer':IPythonPTLexer(),
339 'reserve_space_for_menu':self.space_for_menu,
339 'reserve_space_for_menu':self.space_for_menu,
340 'get_prompt_tokens':self.prompts.in_prompt_tokens,
340 'get_prompt_tokens':self.prompts.in_prompt_tokens,
341 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
341 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
342 'multiline':True,
342 'multiline':True,
343 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
343 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
344
344
345 # Highlight matching brackets, but only when this setting is
345 # Highlight matching brackets, but only when this setting is
346 # enabled, and only when the DEFAULT_BUFFER has the focus.
346 # enabled, and only when the DEFAULT_BUFFER has the focus.
347 'extra_input_processors': [ConditionalProcessor(
347 'extra_input_processors': [ConditionalProcessor(
348 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
348 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
349 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
349 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
350 Condition(lambda cli: self.highlight_matching_brackets))],
350 Condition(lambda cli: self.highlight_matching_brackets))],
351 }
351 }
352
352
353 def _update_layout(self):
353 def _update_layout(self):
354 """
354 """
355 Ask for a re computation of the application layout, if for example ,
355 Ask for a re computation of the application layout, if for example ,
356 some configuration options have changed.
356 some configuration options have changed.
357 """
357 """
358 if self._pt_app:
358 if self._pt_app:
359 self._pt_app.layout = create_prompt_layout(**self._layout_options())
359 self._pt_app.layout = create_prompt_layout(**self._layout_options())
360
360
361 def prompt_for_code(self):
361 def prompt_for_code(self):
362 document = self.pt_cli.run(
362 document = self.pt_cli.run(
363 pre_run=self.pre_prompt, reset_current_buffer=True)
363 pre_run=self.pre_prompt, reset_current_buffer=True)
364 return document.text
364 return document.text
365
365
366 def enable_win_unicode_console(self):
366 def enable_win_unicode_console(self):
367 if sys.version_info >= (3, 6):
367 if sys.version_info >= (3, 6):
368 # Since PEP 528, Python uses the unicode APIs for the Windows
368 # Since PEP 528, Python uses the unicode APIs for the Windows
369 # console by default, so WUC shouldn't be needed.
369 # console by default, so WUC shouldn't be needed.
370 return
370 return
371
371
372 import win_unicode_console
372 import win_unicode_console
373
373
374 if PY3:
374 if PY3:
375 win_unicode_console.enable()
375 win_unicode_console.enable()
376 else:
376 else:
377 # https://github.com/ipython/ipython/issues/9768
377 # https://github.com/ipython/ipython/issues/9768
378 from win_unicode_console.streams import (TextStreamWrapper,
378 from win_unicode_console.streams import (TextStreamWrapper,
379 stdout_text_transcoded, stderr_text_transcoded)
379 stdout_text_transcoded, stderr_text_transcoded)
380
380
381 class LenientStrStreamWrapper(TextStreamWrapper):
381 class LenientStrStreamWrapper(TextStreamWrapper):
382 def write(self, s):
382 def write(self, s):
383 if isinstance(s, bytes):
383 if isinstance(s, bytes):
384 s = s.decode(self.encoding, 'replace')
384 s = s.decode(self.encoding, 'replace')
385
385
386 self.base.write(s)
386 self.base.write(s)
387
387
388 stdout_text_str = LenientStrStreamWrapper(stdout_text_transcoded)
388 stdout_text_str = LenientStrStreamWrapper(stdout_text_transcoded)
389 stderr_text_str = LenientStrStreamWrapper(stderr_text_transcoded)
389 stderr_text_str = LenientStrStreamWrapper(stderr_text_transcoded)
390
390
391 win_unicode_console.enable(stdout=stdout_text_str,
391 win_unicode_console.enable(stdout=stdout_text_str,
392 stderr=stderr_text_str)
392 stderr=stderr_text_str)
393
393
394 def init_io(self):
394 def init_io(self):
395 if sys.platform not in {'win32', 'cli'}:
395 if sys.platform not in {'win32', 'cli'}:
396 return
396 return
397
397
398 self.enable_win_unicode_console()
398 self.enable_win_unicode_console()
399
399
400 import colorama
400 import colorama
401 colorama.init()
401 colorama.init()
402
402
403 # For some reason we make these wrappers around stdout/stderr.
403 # For some reason we make these wrappers around stdout/stderr.
404 # For now, we need to reset them so all output gets coloured.
404 # For now, we need to reset them so all output gets coloured.
405 # https://github.com/ipython/ipython/issues/8669
405 # https://github.com/ipython/ipython/issues/8669
406 # io.std* are deprecated, but don't show our own deprecation warnings
406 # io.std* are deprecated, but don't show our own deprecation warnings
407 # during initialization of the deprecated API.
407 # during initialization of the deprecated API.
408 with warnings.catch_warnings():
408 with warnings.catch_warnings():
409 warnings.simplefilter('ignore', DeprecationWarning)
409 warnings.simplefilter('ignore', DeprecationWarning)
410 io.stdout = io.IOStream(sys.stdout)
410 io.stdout = io.IOStream(sys.stdout)
411 io.stderr = io.IOStream(sys.stderr)
411 io.stderr = io.IOStream(sys.stderr)
412
412
413 def init_magics(self):
413 def init_magics(self):
414 super(TerminalInteractiveShell, self).init_magics()
414 super(TerminalInteractiveShell, self).init_magics()
415 self.register_magics(TerminalMagics)
415 self.register_magics(TerminalMagics)
416
416
417 def init_alias(self):
417 def init_alias(self):
418 # The parent class defines aliases that can be safely used with any
418 # The parent class defines aliases that can be safely used with any
419 # frontend.
419 # frontend.
420 super(TerminalInteractiveShell, self).init_alias()
420 super(TerminalInteractiveShell, self).init_alias()
421
421
422 # Now define aliases that only make sense on the terminal, because they
422 # Now define aliases that only make sense on the terminal, because they
423 # need direct access to the console in a way that we can't emulate in
423 # need direct access to the console in a way that we can't emulate in
424 # GUI or web frontend
424 # GUI or web frontend
425 if os.name == 'posix':
425 if os.name == 'posix':
426 for cmd in ['clear', 'more', 'less', 'man']:
426 for cmd in ['clear', 'more', 'less', 'man']:
427 self.alias_manager.soft_define_alias(cmd, cmd)
427 self.alias_manager.soft_define_alias(cmd, cmd)
428
428
429
429
430 def __init__(self, *args, **kwargs):
430 def __init__(self, *args, **kwargs):
431 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
431 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
432 self.init_prompt_toolkit_cli()
432 self.init_prompt_toolkit_cli()
433 self.init_term_title()
433 self.init_term_title()
434 self.keep_running = True
434 self.keep_running = True
435
435
436 self.debugger_history = InMemoryHistory()
436 self.debugger_history = InMemoryHistory()
437
437
438 def ask_exit(self):
438 def ask_exit(self):
439 self.keep_running = False
439 self.keep_running = False
440
440
441 rl_next_input = None
441 rl_next_input = None
442
442
443 def pre_prompt(self):
443 def pre_prompt(self):
444 if self.rl_next_input:
444 if self.rl_next_input:
445 # We can't set the buffer here, because it will be reset just after
445 # We can't set the buffer here, because it will be reset just after
446 # this. Adding a callable to pre_run_callables does what we need
446 # this. Adding a callable to pre_run_callables does what we need
447 # after the buffer is reset.
447 # after the buffer is reset.
448 s = cast_unicode_py2(self.rl_next_input)
448 s = cast_unicode_py2(self.rl_next_input)
449 def set_doc():
449 def set_doc():
450 self.pt_cli.application.buffer.document = Document(s)
450 self.pt_cli.application.buffer.document = Document(s)
451 if hasattr(self.pt_cli, 'pre_run_callables'):
451 if hasattr(self.pt_cli, 'pre_run_callables'):
452 self.pt_cli.pre_run_callables.append(set_doc)
452 self.pt_cli.pre_run_callables.append(set_doc)
453 else:
453 else:
454 # Older version of prompt_toolkit; it's OK to set the document
454 # Older version of prompt_toolkit; it's OK to set the document
455 # directly here.
455 # directly here.
456 set_doc()
456 set_doc()
457 self.rl_next_input = None
457 self.rl_next_input = None
458
458
459 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
459 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
460
460
461 if display_banner is not DISPLAY_BANNER_DEPRECATED:
461 if display_banner is not DISPLAY_BANNER_DEPRECATED:
462 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
462 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
463
463
464 self.keep_running = True
464 self.keep_running = True
465 while self.keep_running:
465 while self.keep_running:
466 print(self.separate_in, end='')
466 print(self.separate_in, end='')
467
467
468 try:
468 try:
469 code = self.prompt_for_code()
469 code = self.prompt_for_code()
470 except EOFError:
470 except EOFError:
471 if (not self.confirm_exit) \
471 if (not self.confirm_exit) \
472 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
472 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
473 self.ask_exit()
473 self.ask_exit()
474
474
475 else:
475 else:
476 if code:
476 if code:
477 self.run_cell(code, store_history=True)
477 self.run_cell(code, store_history=True)
478
478
479 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
479 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
480 # An extra layer of protection in case someone mashing Ctrl-C breaks
480 # An extra layer of protection in case someone mashing Ctrl-C breaks
481 # out of our internal code.
481 # out of our internal code.
482 if display_banner is not DISPLAY_BANNER_DEPRECATED:
482 if display_banner is not DISPLAY_BANNER_DEPRECATED:
483 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
483 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
484 while True:
484 while True:
485 try:
485 try:
486 self.interact()
486 self.interact()
487 break
487 break
488 except KeyboardInterrupt as e:
488 except KeyboardInterrupt as e:
489 print("\n%s escaped interact()\n" % type(e).__name__)
489 print("\n%s escaped interact()\n" % type(e).__name__)
490 finally:
490 finally:
491 # An interrupt during the eventloop will mess up the
491 # An interrupt during the eventloop will mess up the
492 # internal state of the prompt_toolkit library.
492 # internal state of the prompt_toolkit library.
493 # Stopping the eventloop fixes this, see
493 # Stopping the eventloop fixes this, see
494 # https://github.com/ipython/ipython/pull/9867
494 # https://github.com/ipython/ipython/pull/9867
495 if hasattr(self, '_eventloop'):
495 if hasattr(self, '_eventloop'):
496 self._eventloop.stop()
496 self._eventloop.stop()
497
497
498 _inputhook = None
498 _inputhook = None
499 def inputhook(self, context):
499 def inputhook(self, context):
500 if self._inputhook is not None:
500 if self._inputhook is not None:
501 self._inputhook(context)
501 self._inputhook(context)
502
502
503 active_eventloop = None
503 active_eventloop = None
504 def enable_gui(self, gui=None):
504 def enable_gui(self, gui=None):
505 if gui:
505 if gui:
506 self.active_eventloop, self._inputhook =\
506 self.active_eventloop, self._inputhook =\
507 get_inputhook_name_and_func(gui)
507 get_inputhook_name_and_func(gui)
508 else:
508 else:
509 self.active_eventloop = self._inputhook = None
509 self.active_eventloop = self._inputhook = None
510
510
511 # Run !system commands directly, not through pipes, so terminal programs
511 # Run !system commands directly, not through pipes, so terminal programs
512 # work correctly.
512 # work correctly.
513 system = InteractiveShell.system_raw
513 system = InteractiveShell.system_raw
514
514
515 def auto_rewrite_input(self, cmd):
515 def auto_rewrite_input(self, cmd):
516 """Overridden from the parent class to use fancy rewriting prompt"""
516 """Overridden from the parent class to use fancy rewriting prompt"""
517 if not self.show_rewritten_input:
517 if not self.show_rewritten_input:
518 return
518 return
519
519
520 tokens = self.prompts.rewrite_prompt_tokens()
520 tokens = self.prompts.rewrite_prompt_tokens()
521 if self.pt_cli:
521 if self.pt_cli:
522 self.pt_cli.print_tokens(tokens)
522 self.pt_cli.print_tokens(tokens)
523 print(cmd)
523 print(cmd)
524 else:
524 else:
525 prompt = ''.join(s for t, s in tokens)
525 prompt = ''.join(s for t, s in tokens)
526 print(prompt, cmd, sep='')
526 print(prompt, cmd, sep='')
527
527
528 _prompts_before = None
528 _prompts_before = None
529 def switch_doctest_mode(self, mode):
529 def switch_doctest_mode(self, mode):
530 """Switch prompts to classic for %doctest_mode"""
530 """Switch prompts to classic for %doctest_mode"""
531 if mode:
531 if mode:
532 self._prompts_before = self.prompts
532 self._prompts_before = self.prompts
533 self.prompts = ClassicPrompts(self)
533 self.prompts = ClassicPrompts(self)
534 elif self._prompts_before:
534 elif self._prompts_before:
535 self.prompts = self._prompts_before
535 self.prompts = self._prompts_before
536 self._prompts_before = None
536 self._prompts_before = None
537 self._update_layout()
537 self._update_layout()
538
538
539
539
540 InteractiveShellABC.register(TerminalInteractiveShell)
540 InteractiveShellABC.register(TerminalInteractiveShell)
541
541
542 if __name__ == '__main__':
542 if __name__ == '__main__':
543 TerminalInteractiveShell.instance().interact()
543 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now