##// END OF EJS Templates
Merge pull request #9472 from Carreau/stupid-me...
Thomas Kluyver -
r22339:6ad2eb91 merge
parent child Browse files
Show More
@@ -1,159 +1,162 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for managing IPython history.
3 An application for managing IPython history.
4
4
5 To be invoked as the `ipython history` subcommand.
5 To be invoked as the `ipython history` subcommand.
6 """
6 """
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import os
9 import os
10 import sqlite3
10 import sqlite3
11
11
12 from traitlets.config.application import Application
12 from traitlets.config.application import Application
13 from IPython.core.application import BaseIPythonApplication
13 from IPython.core.application import BaseIPythonApplication
14 from traitlets import Bool, Int, Dict
14 from traitlets import Bool, Int, Dict
15 from IPython.utils.io import ask_yes_no
15 from IPython.utils.io import ask_yes_no
16
16
17 trim_hist_help = """Trim the IPython history database to the last 1000 entries.
17 trim_hist_help = """Trim the IPython history database to the last 1000 entries.
18
18
19 This actually copies the last 1000 entries to a new database, and then replaces
19 This actually copies the last 1000 entries to a new database, and then replaces
20 the old file with the new. Use the `--keep=` argument to specify a number
20 the old file with the new. Use the `--keep=` argument to specify a number
21 other than 1000.
21 other than 1000.
22 """
22 """
23
23
24 clear_hist_help = """Clear the IPython history database, deleting all entries.
24 clear_hist_help = """Clear the IPython history database, deleting all entries.
25
25
26 Because this is a destructive operation, IPython will prompt the user if they
26 Because this is a destructive operation, IPython will prompt the user if they
27 really want to do this. Passing a `-f` flag will force clearing without a
27 really want to do this. Passing a `-f` flag will force clearing without a
28 prompt.
28 prompt.
29
29
30 This is an handy alias to `ipython history trim --keep=0`
30 This is an handy alias to `ipython history trim --keep=0`
31 """
31 """
32
32
33
33
34 class HistoryTrim(BaseIPythonApplication):
34 class HistoryTrim(BaseIPythonApplication):
35 description = trim_hist_help
35 description = trim_hist_help
36
36
37 backup = Bool(False).tag(config=True,
37 backup = Bool(False,
38 help="Keep the old history file as history.sqlite.<N>")
38 help="Keep the old history file as history.sqlite.<N>"
39 ).tag(config=True)
39
40
40 keep = Int(1000).tag(config=True,
41 keep = Int(1000,
41 help="Number of recent lines to keep in the database.")
42 help="Number of recent lines to keep in the database."
43 ).tag(config=True)
42
44
43 flags = Dict(dict(
45 flags = Dict(dict(
44 backup = ({'HistoryTrim' : {'backup' : True}},
46 backup = ({'HistoryTrim' : {'backup' : True}},
45 backup.help
47 backup.help
46 )
48 )
47 ))
49 ))
48
50
49 aliases=Dict(dict(
51 aliases=Dict(dict(
50 keep = 'HistoryTrim.keep'
52 keep = 'HistoryTrim.keep'
51 ))
53 ))
52
54
53 def start(self):
55 def start(self):
54 profile_dir = self.profile_dir.location
56 profile_dir = self.profile_dir.location
55 hist_file = os.path.join(profile_dir, 'history.sqlite')
57 hist_file = os.path.join(profile_dir, 'history.sqlite')
56 con = sqlite3.connect(hist_file)
58 con = sqlite3.connect(hist_file)
57
59
58 # Grab the recent history from the current database.
60 # Grab the recent history from the current database.
59 inputs = list(con.execute('SELECT session, line, source, source_raw FROM '
61 inputs = list(con.execute('SELECT session, line, source, source_raw FROM '
60 'history ORDER BY session DESC, line DESC LIMIT ?', (self.keep+1,)))
62 'history ORDER BY session DESC, line DESC LIMIT ?', (self.keep+1,)))
61 if len(inputs) <= self.keep:
63 if len(inputs) <= self.keep:
62 print("There are already at most %d entries in the history database." % self.keep)
64 print("There are already at most %d entries in the history database." % self.keep)
63 print("Not doing anything. Use --keep= argument to keep fewer entries")
65 print("Not doing anything. Use --keep= argument to keep fewer entries")
64 return
66 return
65
67
66 print("Trimming history to the most recent %d entries." % self.keep)
68 print("Trimming history to the most recent %d entries." % self.keep)
67
69
68 inputs.pop() # Remove the extra element we got to check the length.
70 inputs.pop() # Remove the extra element we got to check the length.
69 inputs.reverse()
71 inputs.reverse()
70 if inputs:
72 if inputs:
71 first_session = inputs[0][0]
73 first_session = inputs[0][0]
72 outputs = list(con.execute('SELECT session, line, output FROM '
74 outputs = list(con.execute('SELECT session, line, output FROM '
73 'output_history WHERE session >= ?', (first_session,)))
75 'output_history WHERE session >= ?', (first_session,)))
74 sessions = list(con.execute('SELECT session, start, end, num_cmds, remark FROM '
76 sessions = list(con.execute('SELECT session, start, end, num_cmds, remark FROM '
75 'sessions WHERE session >= ?', (first_session,)))
77 'sessions WHERE session >= ?', (first_session,)))
76 con.close()
78 con.close()
77
79
78 # Create the new history database.
80 # Create the new history database.
79 new_hist_file = os.path.join(profile_dir, 'history.sqlite.new')
81 new_hist_file = os.path.join(profile_dir, 'history.sqlite.new')
80 i = 0
82 i = 0
81 while os.path.exists(new_hist_file):
83 while os.path.exists(new_hist_file):
82 # Make sure we don't interfere with an existing file.
84 # Make sure we don't interfere with an existing file.
83 i += 1
85 i += 1
84 new_hist_file = os.path.join(profile_dir, 'history.sqlite.new'+str(i))
86 new_hist_file = os.path.join(profile_dir, 'history.sqlite.new'+str(i))
85 new_db = sqlite3.connect(new_hist_file)
87 new_db = sqlite3.connect(new_hist_file)
86 new_db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
88 new_db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
87 primary key autoincrement, start timestamp,
89 primary key autoincrement, start timestamp,
88 end timestamp, num_cmds integer, remark text)""")
90 end timestamp, num_cmds integer, remark text)""")
89 new_db.execute("""CREATE TABLE IF NOT EXISTS history
91 new_db.execute("""CREATE TABLE IF NOT EXISTS history
90 (session integer, line integer, source text, source_raw text,
92 (session integer, line integer, source text, source_raw text,
91 PRIMARY KEY (session, line))""")
93 PRIMARY KEY (session, line))""")
92 new_db.execute("""CREATE TABLE IF NOT EXISTS output_history
94 new_db.execute("""CREATE TABLE IF NOT EXISTS output_history
93 (session integer, line integer, output text,
95 (session integer, line integer, output text,
94 PRIMARY KEY (session, line))""")
96 PRIMARY KEY (session, line))""")
95 new_db.commit()
97 new_db.commit()
96
98
97
99
98 if inputs:
100 if inputs:
99 with new_db:
101 with new_db:
100 # Add the recent history into the new database.
102 # Add the recent history into the new database.
101 new_db.executemany('insert into sessions values (?,?,?,?,?)', sessions)
103 new_db.executemany('insert into sessions values (?,?,?,?,?)', sessions)
102 new_db.executemany('insert into history values (?,?,?,?)', inputs)
104 new_db.executemany('insert into history values (?,?,?,?)', inputs)
103 new_db.executemany('insert into output_history values (?,?,?)', outputs)
105 new_db.executemany('insert into output_history values (?,?,?)', outputs)
104 new_db.close()
106 new_db.close()
105
107
106 if self.backup:
108 if self.backup:
107 i = 1
109 i = 1
108 backup_hist_file = os.path.join(profile_dir, 'history.sqlite.old.%d' % i)
110 backup_hist_file = os.path.join(profile_dir, 'history.sqlite.old.%d' % i)
109 while os.path.exists(backup_hist_file):
111 while os.path.exists(backup_hist_file):
110 i += 1
112 i += 1
111 backup_hist_file = os.path.join(profile_dir, 'history.sqlite.old.%d' % i)
113 backup_hist_file = os.path.join(profile_dir, 'history.sqlite.old.%d' % i)
112 os.rename(hist_file, backup_hist_file)
114 os.rename(hist_file, backup_hist_file)
113 print("Backed up longer history file to", backup_hist_file)
115 print("Backed up longer history file to", backup_hist_file)
114 else:
116 else:
115 os.remove(hist_file)
117 os.remove(hist_file)
116
118
117 os.rename(new_hist_file, hist_file)
119 os.rename(new_hist_file, hist_file)
118
120
119 class HistoryClear(HistoryTrim):
121 class HistoryClear(HistoryTrim):
120 description = clear_hist_help
122 description = clear_hist_help
121 keep = Int(0).tag(config=False,
123 keep = Int(0,
122 help="Number of recent lines to keep in the database.")
124 help="Number of recent lines to keep in the database.")
123
125
124 force = Bool(False).tag(config=True,
126 force = Bool(False,
125 help="Don't prompt user for confirmation")
127 help="Don't prompt user for confirmation"
128 ).tag(config=True)
126
129
127 flags = Dict(dict(
130 flags = Dict(dict(
128 force = ({'HistoryClear' : {'force' : True}},
131 force = ({'HistoryClear' : {'force' : True}},
129 force.help),
132 force.help),
130 f = ({'HistoryTrim' : {'force' : True}},
133 f = ({'HistoryTrim' : {'force' : True}},
131 force.help
134 force.help
132 )
135 )
133 ))
136 ))
134 aliases = Dict()
137 aliases = Dict()
135
138
136 def start(self):
139 def start(self):
137 if self.force or ask_yes_no("Really delete all ipython history? ",
140 if self.force or ask_yes_no("Really delete all ipython history? ",
138 default="no", interrupt="no"):
141 default="no", interrupt="no"):
139 HistoryTrim.start(self)
142 HistoryTrim.start(self)
140
143
141 class HistoryApp(Application):
144 class HistoryApp(Application):
142 name = u'ipython-history'
145 name = u'ipython-history'
143 description = "Manage the IPython history database."
146 description = "Manage the IPython history database."
144
147
145 subcommands = Dict(dict(
148 subcommands = Dict(dict(
146 trim = (HistoryTrim, HistoryTrim.description.splitlines()[0]),
149 trim = (HistoryTrim, HistoryTrim.description.splitlines()[0]),
147 clear = (HistoryClear, HistoryClear.description.splitlines()[0]),
150 clear = (HistoryClear, HistoryClear.description.splitlines()[0]),
148 ))
151 ))
149
152
150 def start(self):
153 def start(self):
151 if self.subapp is None:
154 if self.subapp is None:
152 print("No subcommand specified. Must specify one of: %s" % \
155 print("No subcommand specified. Must specify one of: %s" % \
153 (self.subcommands.keys()))
156 (self.subcommands.keys()))
154 print()
157 print()
155 self.print_description()
158 self.print_description()
156 self.print_subcommands()
159 self.print_subcommands()
157 self.exit(1)
160 self.exit(1)
158 else:
161 else:
159 return self.subapp.start()
162 return self.subapp.start()
@@ -1,713 +1,715 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Prefiltering components.
3 Prefiltering components.
4
4
5 Prefilters transform user input before it is exec'd by Python. These
5 Prefilters transform user input before it is exec'd by Python. These
6 transforms are used to implement additional syntax such as !ls and %magic.
6 transforms are used to implement additional syntax such as !ls and %magic.
7
7
8 Authors:
8 Authors:
9
9
10 * Brian Granger
10 * Brian Granger
11 * Fernando Perez
11 * Fernando Perez
12 * Dan Milstein
12 * Dan Milstein
13 * Ville Vainio
13 * Ville Vainio
14 """
14 """
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Copyright (C) 2008-2011 The IPython Development Team
17 # Copyright (C) 2008-2011 The IPython Development Team
18 #
18 #
19 # Distributed under the terms of the BSD License. The full license is in
19 # Distributed under the terms of the BSD License. The full license is in
20 # the file COPYING, distributed as part of this software.
20 # the file COPYING, distributed as part of this software.
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Imports
24 # Imports
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 from keyword import iskeyword
27 from keyword import iskeyword
28 import re
28 import re
29
29
30 from IPython.core.autocall import IPyAutocall
30 from IPython.core.autocall import IPyAutocall
31 from traitlets.config.configurable import Configurable
31 from traitlets.config.configurable import Configurable
32 from IPython.core.inputsplitter import (
32 from IPython.core.inputsplitter import (
33 ESC_MAGIC,
33 ESC_MAGIC,
34 ESC_QUOTE,
34 ESC_QUOTE,
35 ESC_QUOTE2,
35 ESC_QUOTE2,
36 ESC_PAREN,
36 ESC_PAREN,
37 )
37 )
38 from IPython.core.macro import Macro
38 from IPython.core.macro import Macro
39 from IPython.core.splitinput import LineInfo
39 from IPython.core.splitinput import LineInfo
40
40
41 from traitlets import (
41 from traitlets import (
42 List, Integer, Unicode, CBool, Bool, Instance, CRegExp
42 List, Integer, Unicode, CBool, Bool, Instance, CRegExp
43 )
43 )
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Global utilities, errors and constants
46 # Global utilities, errors and constants
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49
49
50 class PrefilterError(Exception):
50 class PrefilterError(Exception):
51 pass
51 pass
52
52
53
53
54 # RegExp to identify potential function names
54 # RegExp to identify potential function names
55 re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$')
55 re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$')
56
56
57 # RegExp to exclude strings with this start from autocalling. In
57 # RegExp to exclude strings with this start from autocalling. In
58 # particular, all binary operators should be excluded, so that if foo is
58 # particular, all binary operators should be excluded, so that if foo is
59 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
59 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
60 # characters '!=()' don't need to be checked for, as the checkPythonChars
60 # characters '!=()' don't need to be checked for, as the checkPythonChars
61 # routine explicitely does so, to catch direct calls and rebindings of
61 # routine explicitely does so, to catch direct calls and rebindings of
62 # existing names.
62 # existing names.
63
63
64 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
64 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
65 # it affects the rest of the group in square brackets.
65 # it affects the rest of the group in square brackets.
66 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
66 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
67 r'|^is |^not |^in |^and |^or ')
67 r'|^is |^not |^in |^and |^or ')
68
68
69 # try to catch also methods for stuff in lists/tuples/dicts: off
69 # try to catch also methods for stuff in lists/tuples/dicts: off
70 # (experimental). For this to work, the line_split regexp would need
70 # (experimental). For this to work, the line_split regexp would need
71 # to be modified so it wouldn't break things at '['. That line is
71 # to be modified so it wouldn't break things at '['. That line is
72 # nasty enough that I shouldn't change it until I can test it _well_.
72 # nasty enough that I shouldn't change it until I can test it _well_.
73 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
73 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
74
74
75
75
76 # Handler Check Utilities
76 # Handler Check Utilities
77 def is_shadowed(identifier, ip):
77 def is_shadowed(identifier, ip):
78 """Is the given identifier defined in one of the namespaces which shadow
78 """Is the given identifier defined in one of the namespaces which shadow
79 the alias and magic namespaces? Note that an identifier is different
79 the alias and magic namespaces? Note that an identifier is different
80 than ifun, because it can not contain a '.' character."""
80 than ifun, because it can not contain a '.' character."""
81 # This is much safer than calling ofind, which can change state
81 # This is much safer than calling ofind, which can change state
82 return (identifier in ip.user_ns \
82 return (identifier in ip.user_ns \
83 or identifier in ip.user_global_ns \
83 or identifier in ip.user_global_ns \
84 or identifier in ip.ns_table['builtin']\
84 or identifier in ip.ns_table['builtin']\
85 or iskeyword(identifier))
85 or iskeyword(identifier))
86
86
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Main Prefilter manager
89 # Main Prefilter manager
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92
92
93 class PrefilterManager(Configurable):
93 class PrefilterManager(Configurable):
94 """Main prefilter component.
94 """Main prefilter component.
95
95
96 The IPython prefilter is run on all user input before it is run. The
96 The IPython prefilter is run on all user input before it is run. The
97 prefilter consumes lines of input and produces transformed lines of
97 prefilter consumes lines of input and produces transformed lines of
98 input.
98 input.
99
99
100 The iplementation consists of two phases:
100 The iplementation consists of two phases:
101
101
102 1. Transformers
102 1. Transformers
103 2. Checkers and handlers
103 2. Checkers and handlers
104
104
105 Over time, we plan on deprecating the checkers and handlers and doing
105 Over time, we plan on deprecating the checkers and handlers and doing
106 everything in the transformers.
106 everything in the transformers.
107
107
108 The transformers are instances of :class:`PrefilterTransformer` and have
108 The transformers are instances of :class:`PrefilterTransformer` and have
109 a single method :meth:`transform` that takes a line and returns a
109 a single method :meth:`transform` that takes a line and returns a
110 transformed line. The transformation can be accomplished using any
110 transformed line. The transformation can be accomplished using any
111 tool, but our current ones use regular expressions for speed.
111 tool, but our current ones use regular expressions for speed.
112
112
113 After all the transformers have been run, the line is fed to the checkers,
113 After all the transformers have been run, the line is fed to the checkers,
114 which are instances of :class:`PrefilterChecker`. The line is passed to
114 which are instances of :class:`PrefilterChecker`. The line is passed to
115 the :meth:`check` method, which either returns `None` or a
115 the :meth:`check` method, which either returns `None` or a
116 :class:`PrefilterHandler` instance. If `None` is returned, the other
116 :class:`PrefilterHandler` instance. If `None` is returned, the other
117 checkers are tried. If an :class:`PrefilterHandler` instance is returned,
117 checkers are tried. If an :class:`PrefilterHandler` instance is returned,
118 the line is passed to the :meth:`handle` method of the returned
118 the line is passed to the :meth:`handle` method of the returned
119 handler and no further checkers are tried.
119 handler and no further checkers are tried.
120
120
121 Both transformers and checkers have a `priority` attribute, that determines
121 Both transformers and checkers have a `priority` attribute, that determines
122 the order in which they are called. Smaller priorities are tried first.
122 the order in which they are called. Smaller priorities are tried first.
123
123
124 Both transformers and checkers also have `enabled` attribute, which is
124 Both transformers and checkers also have `enabled` attribute, which is
125 a boolean that determines if the instance is used.
125 a boolean that determines if the instance is used.
126
126
127 Users or developers can change the priority or enabled attribute of
127 Users or developers can change the priority or enabled attribute of
128 transformers or checkers, but they must call the :meth:`sort_checkers`
128 transformers or checkers, but they must call the :meth:`sort_checkers`
129 or :meth:`sort_transformers` method after changing the priority.
129 or :meth:`sort_transformers` method after changing the priority.
130 """
130 """
131
131
132 multi_line_specials = CBool(True).tag(config=True)
132 multi_line_specials = CBool(True).tag(config=True)
133 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
133 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
134
134
135 def __init__(self, shell=None, **kwargs):
135 def __init__(self, shell=None, **kwargs):
136 super(PrefilterManager, self).__init__(shell=shell, **kwargs)
136 super(PrefilterManager, self).__init__(shell=shell, **kwargs)
137 self.shell = shell
137 self.shell = shell
138 self.init_transformers()
138 self.init_transformers()
139 self.init_handlers()
139 self.init_handlers()
140 self.init_checkers()
140 self.init_checkers()
141
141
142 #-------------------------------------------------------------------------
142 #-------------------------------------------------------------------------
143 # API for managing transformers
143 # API for managing transformers
144 #-------------------------------------------------------------------------
144 #-------------------------------------------------------------------------
145
145
146 def init_transformers(self):
146 def init_transformers(self):
147 """Create the default transformers."""
147 """Create the default transformers."""
148 self._transformers = []
148 self._transformers = []
149 for transformer_cls in _default_transformers:
149 for transformer_cls in _default_transformers:
150 transformer_cls(
150 transformer_cls(
151 shell=self.shell, prefilter_manager=self, parent=self
151 shell=self.shell, prefilter_manager=self, parent=self
152 )
152 )
153
153
154 def sort_transformers(self):
154 def sort_transformers(self):
155 """Sort the transformers by priority.
155 """Sort the transformers by priority.
156
156
157 This must be called after the priority of a transformer is changed.
157 This must be called after the priority of a transformer is changed.
158 The :meth:`register_transformer` method calls this automatically.
158 The :meth:`register_transformer` method calls this automatically.
159 """
159 """
160 self._transformers.sort(key=lambda x: x.priority)
160 self._transformers.sort(key=lambda x: x.priority)
161
161
162 @property
162 @property
163 def transformers(self):
163 def transformers(self):
164 """Return a list of checkers, sorted by priority."""
164 """Return a list of checkers, sorted by priority."""
165 return self._transformers
165 return self._transformers
166
166
167 def register_transformer(self, transformer):
167 def register_transformer(self, transformer):
168 """Register a transformer instance."""
168 """Register a transformer instance."""
169 if transformer not in self._transformers:
169 if transformer not in self._transformers:
170 self._transformers.append(transformer)
170 self._transformers.append(transformer)
171 self.sort_transformers()
171 self.sort_transformers()
172
172
173 def unregister_transformer(self, transformer):
173 def unregister_transformer(self, transformer):
174 """Unregister a transformer instance."""
174 """Unregister a transformer instance."""
175 if transformer in self._transformers:
175 if transformer in self._transformers:
176 self._transformers.remove(transformer)
176 self._transformers.remove(transformer)
177
177
178 #-------------------------------------------------------------------------
178 #-------------------------------------------------------------------------
179 # API for managing checkers
179 # API for managing checkers
180 #-------------------------------------------------------------------------
180 #-------------------------------------------------------------------------
181
181
182 def init_checkers(self):
182 def init_checkers(self):
183 """Create the default checkers."""
183 """Create the default checkers."""
184 self._checkers = []
184 self._checkers = []
185 for checker in _default_checkers:
185 for checker in _default_checkers:
186 checker(
186 checker(
187 shell=self.shell, prefilter_manager=self, parent=self
187 shell=self.shell, prefilter_manager=self, parent=self
188 )
188 )
189
189
190 def sort_checkers(self):
190 def sort_checkers(self):
191 """Sort the checkers by priority.
191 """Sort the checkers by priority.
192
192
193 This must be called after the priority of a checker is changed.
193 This must be called after the priority of a checker is changed.
194 The :meth:`register_checker` method calls this automatically.
194 The :meth:`register_checker` method calls this automatically.
195 """
195 """
196 self._checkers.sort(key=lambda x: x.priority)
196 self._checkers.sort(key=lambda x: x.priority)
197
197
198 @property
198 @property
199 def checkers(self):
199 def checkers(self):
200 """Return a list of checkers, sorted by priority."""
200 """Return a list of checkers, sorted by priority."""
201 return self._checkers
201 return self._checkers
202
202
203 def register_checker(self, checker):
203 def register_checker(self, checker):
204 """Register a checker instance."""
204 """Register a checker instance."""
205 if checker not in self._checkers:
205 if checker not in self._checkers:
206 self._checkers.append(checker)
206 self._checkers.append(checker)
207 self.sort_checkers()
207 self.sort_checkers()
208
208
209 def unregister_checker(self, checker):
209 def unregister_checker(self, checker):
210 """Unregister a checker instance."""
210 """Unregister a checker instance."""
211 if checker in self._checkers:
211 if checker in self._checkers:
212 self._checkers.remove(checker)
212 self._checkers.remove(checker)
213
213
214 #-------------------------------------------------------------------------
214 #-------------------------------------------------------------------------
215 # API for managing handlers
215 # API for managing handlers
216 #-------------------------------------------------------------------------
216 #-------------------------------------------------------------------------
217
217
218 def init_handlers(self):
218 def init_handlers(self):
219 """Create the default handlers."""
219 """Create the default handlers."""
220 self._handlers = {}
220 self._handlers = {}
221 self._esc_handlers = {}
221 self._esc_handlers = {}
222 for handler in _default_handlers:
222 for handler in _default_handlers:
223 handler(
223 handler(
224 shell=self.shell, prefilter_manager=self, parent=self
224 shell=self.shell, prefilter_manager=self, parent=self
225 )
225 )
226
226
227 @property
227 @property
228 def handlers(self):
228 def handlers(self):
229 """Return a dict of all the handlers."""
229 """Return a dict of all the handlers."""
230 return self._handlers
230 return self._handlers
231
231
232 def register_handler(self, name, handler, esc_strings):
232 def register_handler(self, name, handler, esc_strings):
233 """Register a handler instance by name with esc_strings."""
233 """Register a handler instance by name with esc_strings."""
234 self._handlers[name] = handler
234 self._handlers[name] = handler
235 for esc_str in esc_strings:
235 for esc_str in esc_strings:
236 self._esc_handlers[esc_str] = handler
236 self._esc_handlers[esc_str] = handler
237
237
238 def unregister_handler(self, name, handler, esc_strings):
238 def unregister_handler(self, name, handler, esc_strings):
239 """Unregister a handler instance by name with esc_strings."""
239 """Unregister a handler instance by name with esc_strings."""
240 try:
240 try:
241 del self._handlers[name]
241 del self._handlers[name]
242 except KeyError:
242 except KeyError:
243 pass
243 pass
244 for esc_str in esc_strings:
244 for esc_str in esc_strings:
245 h = self._esc_handlers.get(esc_str)
245 h = self._esc_handlers.get(esc_str)
246 if h is handler:
246 if h is handler:
247 del self._esc_handlers[esc_str]
247 del self._esc_handlers[esc_str]
248
248
249 def get_handler_by_name(self, name):
249 def get_handler_by_name(self, name):
250 """Get a handler by its name."""
250 """Get a handler by its name."""
251 return self._handlers.get(name)
251 return self._handlers.get(name)
252
252
253 def get_handler_by_esc(self, esc_str):
253 def get_handler_by_esc(self, esc_str):
254 """Get a handler by its escape string."""
254 """Get a handler by its escape string."""
255 return self._esc_handlers.get(esc_str)
255 return self._esc_handlers.get(esc_str)
256
256
257 #-------------------------------------------------------------------------
257 #-------------------------------------------------------------------------
258 # Main prefiltering API
258 # Main prefiltering API
259 #-------------------------------------------------------------------------
259 #-------------------------------------------------------------------------
260
260
261 def prefilter_line_info(self, line_info):
261 def prefilter_line_info(self, line_info):
262 """Prefilter a line that has been converted to a LineInfo object.
262 """Prefilter a line that has been converted to a LineInfo object.
263
263
264 This implements the checker/handler part of the prefilter pipe.
264 This implements the checker/handler part of the prefilter pipe.
265 """
265 """
266 # print "prefilter_line_info: ", line_info
266 # print "prefilter_line_info: ", line_info
267 handler = self.find_handler(line_info)
267 handler = self.find_handler(line_info)
268 return handler.handle(line_info)
268 return handler.handle(line_info)
269
269
270 def find_handler(self, line_info):
270 def find_handler(self, line_info):
271 """Find a handler for the line_info by trying checkers."""
271 """Find a handler for the line_info by trying checkers."""
272 for checker in self.checkers:
272 for checker in self.checkers:
273 if checker.enabled:
273 if checker.enabled:
274 handler = checker.check(line_info)
274 handler = checker.check(line_info)
275 if handler:
275 if handler:
276 return handler
276 return handler
277 return self.get_handler_by_name('normal')
277 return self.get_handler_by_name('normal')
278
278
279 def transform_line(self, line, continue_prompt):
279 def transform_line(self, line, continue_prompt):
280 """Calls the enabled transformers in order of increasing priority."""
280 """Calls the enabled transformers in order of increasing priority."""
281 for transformer in self.transformers:
281 for transformer in self.transformers:
282 if transformer.enabled:
282 if transformer.enabled:
283 line = transformer.transform(line, continue_prompt)
283 line = transformer.transform(line, continue_prompt)
284 return line
284 return line
285
285
286 def prefilter_line(self, line, continue_prompt=False):
286 def prefilter_line(self, line, continue_prompt=False):
287 """Prefilter a single input line as text.
287 """Prefilter a single input line as text.
288
288
289 This method prefilters a single line of text by calling the
289 This method prefilters a single line of text by calling the
290 transformers and then the checkers/handlers.
290 transformers and then the checkers/handlers.
291 """
291 """
292
292
293 # print "prefilter_line: ", line, continue_prompt
293 # print "prefilter_line: ", line, continue_prompt
294 # All handlers *must* return a value, even if it's blank ('').
294 # All handlers *must* return a value, even if it's blank ('').
295
295
296 # save the line away in case we crash, so the post-mortem handler can
296 # save the line away in case we crash, so the post-mortem handler can
297 # record it
297 # record it
298 self.shell._last_input_line = line
298 self.shell._last_input_line = line
299
299
300 if not line:
300 if not line:
301 # Return immediately on purely empty lines, so that if the user
301 # Return immediately on purely empty lines, so that if the user
302 # previously typed some whitespace that started a continuation
302 # previously typed some whitespace that started a continuation
303 # prompt, he can break out of that loop with just an empty line.
303 # prompt, he can break out of that loop with just an empty line.
304 # This is how the default python prompt works.
304 # This is how the default python prompt works.
305 return ''
305 return ''
306
306
307 # At this point, we invoke our transformers.
307 # At this point, we invoke our transformers.
308 if not continue_prompt or (continue_prompt and self.multi_line_specials):
308 if not continue_prompt or (continue_prompt and self.multi_line_specials):
309 line = self.transform_line(line, continue_prompt)
309 line = self.transform_line(line, continue_prompt)
310
310
311 # Now we compute line_info for the checkers and handlers
311 # Now we compute line_info for the checkers and handlers
312 line_info = LineInfo(line, continue_prompt)
312 line_info = LineInfo(line, continue_prompt)
313
313
314 # the input history needs to track even empty lines
314 # the input history needs to track even empty lines
315 stripped = line.strip()
315 stripped = line.strip()
316
316
317 normal_handler = self.get_handler_by_name('normal')
317 normal_handler = self.get_handler_by_name('normal')
318 if not stripped:
318 if not stripped:
319 return normal_handler.handle(line_info)
319 return normal_handler.handle(line_info)
320
320
321 # special handlers are only allowed for single line statements
321 # special handlers are only allowed for single line statements
322 if continue_prompt and not self.multi_line_specials:
322 if continue_prompt and not self.multi_line_specials:
323 return normal_handler.handle(line_info)
323 return normal_handler.handle(line_info)
324
324
325 prefiltered = self.prefilter_line_info(line_info)
325 prefiltered = self.prefilter_line_info(line_info)
326 # print "prefiltered line: %r" % prefiltered
326 # print "prefiltered line: %r" % prefiltered
327 return prefiltered
327 return prefiltered
328
328
329 def prefilter_lines(self, lines, continue_prompt=False):
329 def prefilter_lines(self, lines, continue_prompt=False):
330 """Prefilter multiple input lines of text.
330 """Prefilter multiple input lines of text.
331
331
332 This is the main entry point for prefiltering multiple lines of
332 This is the main entry point for prefiltering multiple lines of
333 input. This simply calls :meth:`prefilter_line` for each line of
333 input. This simply calls :meth:`prefilter_line` for each line of
334 input.
334 input.
335
335
336 This covers cases where there are multiple lines in the user entry,
336 This covers cases where there are multiple lines in the user entry,
337 which is the case when the user goes back to a multiline history
337 which is the case when the user goes back to a multiline history
338 entry and presses enter.
338 entry and presses enter.
339 """
339 """
340 llines = lines.rstrip('\n').split('\n')
340 llines = lines.rstrip('\n').split('\n')
341 # We can get multiple lines in one shot, where multiline input 'blends'
341 # We can get multiple lines in one shot, where multiline input 'blends'
342 # into one line, in cases like recalling from the readline history
342 # into one line, in cases like recalling from the readline history
343 # buffer. We need to make sure that in such cases, we correctly
343 # buffer. We need to make sure that in such cases, we correctly
344 # communicate downstream which line is first and which are continuation
344 # communicate downstream which line is first and which are continuation
345 # ones.
345 # ones.
346 if len(llines) > 1:
346 if len(llines) > 1:
347 out = '\n'.join([self.prefilter_line(line, lnum>0)
347 out = '\n'.join([self.prefilter_line(line, lnum>0)
348 for lnum, line in enumerate(llines) ])
348 for lnum, line in enumerate(llines) ])
349 else:
349 else:
350 out = self.prefilter_line(llines[0], continue_prompt)
350 out = self.prefilter_line(llines[0], continue_prompt)
351
351
352 return out
352 return out
353
353
354 #-----------------------------------------------------------------------------
354 #-----------------------------------------------------------------------------
355 # Prefilter transformers
355 # Prefilter transformers
356 #-----------------------------------------------------------------------------
356 #-----------------------------------------------------------------------------
357
357
358
358
359 class PrefilterTransformer(Configurable):
359 class PrefilterTransformer(Configurable):
360 """Transform a line of user input."""
360 """Transform a line of user input."""
361
361
362 priority = Integer(100).tag(config=True)
362 priority = Integer(100).tag(config=True)
363 # Transformers don't currently use shell or prefilter_manager, but as we
363 # Transformers don't currently use shell or prefilter_manager, but as we
364 # move away from checkers and handlers, they will need them.
364 # move away from checkers and handlers, they will need them.
365 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
365 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
366 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
366 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
367 enabled = Bool(True).tag(config=True)
367 enabled = Bool(True).tag(config=True)
368
368
369 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
369 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
370 super(PrefilterTransformer, self).__init__(
370 super(PrefilterTransformer, self).__init__(
371 shell=shell, prefilter_manager=prefilter_manager, **kwargs
371 shell=shell, prefilter_manager=prefilter_manager, **kwargs
372 )
372 )
373 self.prefilter_manager.register_transformer(self)
373 self.prefilter_manager.register_transformer(self)
374
374
375 def transform(self, line, continue_prompt):
375 def transform(self, line, continue_prompt):
376 """Transform a line, returning the new one."""
376 """Transform a line, returning the new one."""
377 return None
377 return None
378
378
379 def __repr__(self):
379 def __repr__(self):
380 return "<%s(priority=%r, enabled=%r)>" % (
380 return "<%s(priority=%r, enabled=%r)>" % (
381 self.__class__.__name__, self.priority, self.enabled)
381 self.__class__.__name__, self.priority, self.enabled)
382
382
383
383
384 #-----------------------------------------------------------------------------
384 #-----------------------------------------------------------------------------
385 # Prefilter checkers
385 # Prefilter checkers
386 #-----------------------------------------------------------------------------
386 #-----------------------------------------------------------------------------
387
387
388
388
389 class PrefilterChecker(Configurable):
389 class PrefilterChecker(Configurable):
390 """Inspect an input line and return a handler for that line."""
390 """Inspect an input line and return a handler for that line."""
391
391
392 priority = Integer(100).tag(config=True)
392 priority = Integer(100).tag(config=True)
393 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
393 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
394 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
394 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
395 enabled = Bool(True).tag(config=True)
395 enabled = Bool(True).tag(config=True)
396
396
397 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
397 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
398 super(PrefilterChecker, self).__init__(
398 super(PrefilterChecker, self).__init__(
399 shell=shell, prefilter_manager=prefilter_manager, **kwargs
399 shell=shell, prefilter_manager=prefilter_manager, **kwargs
400 )
400 )
401 self.prefilter_manager.register_checker(self)
401 self.prefilter_manager.register_checker(self)
402
402
403 def check(self, line_info):
403 def check(self, line_info):
404 """Inspect line_info and return a handler instance or None."""
404 """Inspect line_info and return a handler instance or None."""
405 return None
405 return None
406
406
407 def __repr__(self):
407 def __repr__(self):
408 return "<%s(priority=%r, enabled=%r)>" % (
408 return "<%s(priority=%r, enabled=%r)>" % (
409 self.__class__.__name__, self.priority, self.enabled)
409 self.__class__.__name__, self.priority, self.enabled)
410
410
411
411
412 class EmacsChecker(PrefilterChecker):
412 class EmacsChecker(PrefilterChecker):
413
413
414 priority = Integer(100).tag(config=True)
414 priority = Integer(100).tag(config=True)
415 enabled = Bool(False).tag(config=True)
415 enabled = Bool(False).tag(config=True)
416
416
417 def check(self, line_info):
417 def check(self, line_info):
418 "Emacs ipython-mode tags certain input lines."
418 "Emacs ipython-mode tags certain input lines."
419 if line_info.line.endswith('# PYTHON-MODE'):
419 if line_info.line.endswith('# PYTHON-MODE'):
420 return self.prefilter_manager.get_handler_by_name('emacs')
420 return self.prefilter_manager.get_handler_by_name('emacs')
421 else:
421 else:
422 return None
422 return None
423
423
424
424
425 class MacroChecker(PrefilterChecker):
425 class MacroChecker(PrefilterChecker):
426
426
427 priority = Integer(250).tag(config=True)
427 priority = Integer(250).tag(config=True)
428
428
429 def check(self, line_info):
429 def check(self, line_info):
430 obj = self.shell.user_ns.get(line_info.ifun)
430 obj = self.shell.user_ns.get(line_info.ifun)
431 if isinstance(obj, Macro):
431 if isinstance(obj, Macro):
432 return self.prefilter_manager.get_handler_by_name('macro')
432 return self.prefilter_manager.get_handler_by_name('macro')
433 else:
433 else:
434 return None
434 return None
435
435
436
436
437 class IPyAutocallChecker(PrefilterChecker):
437 class IPyAutocallChecker(PrefilterChecker):
438
438
439 priority = Integer(300).tag(config=True)
439 priority = Integer(300).tag(config=True)
440
440
441 def check(self, line_info):
441 def check(self, line_info):
442 "Instances of IPyAutocall in user_ns get autocalled immediately"
442 "Instances of IPyAutocall in user_ns get autocalled immediately"
443 obj = self.shell.user_ns.get(line_info.ifun, None)
443 obj = self.shell.user_ns.get(line_info.ifun, None)
444 if isinstance(obj, IPyAutocall):
444 if isinstance(obj, IPyAutocall):
445 obj.set_ip(self.shell)
445 obj.set_ip(self.shell)
446 return self.prefilter_manager.get_handler_by_name('auto')
446 return self.prefilter_manager.get_handler_by_name('auto')
447 else:
447 else:
448 return None
448 return None
449
449
450
450
451 class AssignmentChecker(PrefilterChecker):
451 class AssignmentChecker(PrefilterChecker):
452
452
453 priority = Integer(600).tag(config=True)
453 priority = Integer(600).tag(config=True)
454
454
455 def check(self, line_info):
455 def check(self, line_info):
456 """Check to see if user is assigning to a var for the first time, in
456 """Check to see if user is assigning to a var for the first time, in
457 which case we want to avoid any sort of automagic / autocall games.
457 which case we want to avoid any sort of automagic / autocall games.
458
458
459 This allows users to assign to either alias or magic names true python
459 This allows users to assign to either alias or magic names true python
460 variables (the magic/alias systems always take second seat to true
460 variables (the magic/alias systems always take second seat to true
461 python code). E.g. ls='hi', or ls,that=1,2"""
461 python code). E.g. ls='hi', or ls,that=1,2"""
462 if line_info.the_rest:
462 if line_info.the_rest:
463 if line_info.the_rest[0] in '=,':
463 if line_info.the_rest[0] in '=,':
464 return self.prefilter_manager.get_handler_by_name('normal')
464 return self.prefilter_manager.get_handler_by_name('normal')
465 else:
465 else:
466 return None
466 return None
467
467
468
468
469 class AutoMagicChecker(PrefilterChecker):
469 class AutoMagicChecker(PrefilterChecker):
470
470
471 priority = Integer(700).tag(config=True)
471 priority = Integer(700).tag(config=True)
472
472
473 def check(self, line_info):
473 def check(self, line_info):
474 """If the ifun is magic, and automagic is on, run it. Note: normal,
474 """If the ifun is magic, and automagic is on, run it. Note: normal,
475 non-auto magic would already have been triggered via '%' in
475 non-auto magic would already have been triggered via '%' in
476 check_esc_chars. This just checks for automagic. Also, before
476 check_esc_chars. This just checks for automagic. Also, before
477 triggering the magic handler, make sure that there is nothing in the
477 triggering the magic handler, make sure that there is nothing in the
478 user namespace which could shadow it."""
478 user namespace which could shadow it."""
479 if not self.shell.automagic or not self.shell.find_magic(line_info.ifun):
479 if not self.shell.automagic or not self.shell.find_magic(line_info.ifun):
480 return None
480 return None
481
481
482 # We have a likely magic method. Make sure we should actually call it.
482 # We have a likely magic method. Make sure we should actually call it.
483 if line_info.continue_prompt and not self.prefilter_manager.multi_line_specials:
483 if line_info.continue_prompt and not self.prefilter_manager.multi_line_specials:
484 return None
484 return None
485
485
486 head = line_info.ifun.split('.',1)[0]
486 head = line_info.ifun.split('.',1)[0]
487 if is_shadowed(head, self.shell):
487 if is_shadowed(head, self.shell):
488 return None
488 return None
489
489
490 return self.prefilter_manager.get_handler_by_name('magic')
490 return self.prefilter_manager.get_handler_by_name('magic')
491
491
492
492
493 class PythonOpsChecker(PrefilterChecker):
493 class PythonOpsChecker(PrefilterChecker):
494
494
495 priority = Integer(900).tag(config=True)
495 priority = Integer(900).tag(config=True)
496
496
497 def check(self, line_info):
497 def check(self, line_info):
498 """If the 'rest' of the line begins with a function call or pretty much
498 """If the 'rest' of the line begins with a function call or pretty much
499 any python operator, we should simply execute the line (regardless of
499 any python operator, we should simply execute the line (regardless of
500 whether or not there's a possible autocall expansion). This avoids
500 whether or not there's a possible autocall expansion). This avoids
501 spurious (and very confusing) geattr() accesses."""
501 spurious (and very confusing) geattr() accesses."""
502 if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|':
502 if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|':
503 return self.prefilter_manager.get_handler_by_name('normal')
503 return self.prefilter_manager.get_handler_by_name('normal')
504 else:
504 else:
505 return None
505 return None
506
506
507
507
508 class AutocallChecker(PrefilterChecker):
508 class AutocallChecker(PrefilterChecker):
509
509
510 priority = Integer(1000).tag(config=True)
510 priority = Integer(1000).tag(config=True)
511
511
512 function_name_regexp = CRegExp(re_fun_name).tag(config=True,
512 function_name_regexp = CRegExp(re_fun_name,
513 help="RegExp to identify potential function names.")
513 help="RegExp to identify potential function names."
514 exclude_regexp = CRegExp(re_exclude_auto).tag(config=True,
514 ).tag(config=True)
515 help="RegExp to exclude strings with this start from autocalling.")
515 exclude_regexp = CRegExp(re_exclude_auto,
516 help="RegExp to exclude strings with this start from autocalling."
517 ).tag(config=True)
516
518
517 def check(self, line_info):
519 def check(self, line_info):
518 "Check if the initial word/function is callable and autocall is on."
520 "Check if the initial word/function is callable and autocall is on."
519 if not self.shell.autocall:
521 if not self.shell.autocall:
520 return None
522 return None
521
523
522 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
524 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
523 if not oinfo['found']:
525 if not oinfo['found']:
524 return None
526 return None
525
527
526 if callable(oinfo['obj']) \
528 if callable(oinfo['obj']) \
527 and (not self.exclude_regexp.match(line_info.the_rest)) \
529 and (not self.exclude_regexp.match(line_info.the_rest)) \
528 and self.function_name_regexp.match(line_info.ifun):
530 and self.function_name_regexp.match(line_info.ifun):
529 return self.prefilter_manager.get_handler_by_name('auto')
531 return self.prefilter_manager.get_handler_by_name('auto')
530 else:
532 else:
531 return None
533 return None
532
534
533
535
534 #-----------------------------------------------------------------------------
536 #-----------------------------------------------------------------------------
535 # Prefilter handlers
537 # Prefilter handlers
536 #-----------------------------------------------------------------------------
538 #-----------------------------------------------------------------------------
537
539
538
540
539 class PrefilterHandler(Configurable):
541 class PrefilterHandler(Configurable):
540
542
541 handler_name = Unicode('normal')
543 handler_name = Unicode('normal')
542 esc_strings = List([])
544 esc_strings = List([])
543 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
545 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
544 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
546 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
545
547
546 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
548 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
547 super(PrefilterHandler, self).__init__(
549 super(PrefilterHandler, self).__init__(
548 shell=shell, prefilter_manager=prefilter_manager, **kwargs
550 shell=shell, prefilter_manager=prefilter_manager, **kwargs
549 )
551 )
550 self.prefilter_manager.register_handler(
552 self.prefilter_manager.register_handler(
551 self.handler_name,
553 self.handler_name,
552 self,
554 self,
553 self.esc_strings
555 self.esc_strings
554 )
556 )
555
557
556 def handle(self, line_info):
558 def handle(self, line_info):
557 # print "normal: ", line_info
559 # print "normal: ", line_info
558 """Handle normal input lines. Use as a template for handlers."""
560 """Handle normal input lines. Use as a template for handlers."""
559
561
560 # With autoindent on, we need some way to exit the input loop, and I
562 # With autoindent on, we need some way to exit the input loop, and I
561 # don't want to force the user to have to backspace all the way to
563 # don't want to force the user to have to backspace all the way to
562 # clear the line. The rule will be in this case, that either two
564 # clear the line. The rule will be in this case, that either two
563 # lines of pure whitespace in a row, or a line of pure whitespace but
565 # lines of pure whitespace in a row, or a line of pure whitespace but
564 # of a size different to the indent level, will exit the input loop.
566 # of a size different to the indent level, will exit the input loop.
565 line = line_info.line
567 line = line_info.line
566 continue_prompt = line_info.continue_prompt
568 continue_prompt = line_info.continue_prompt
567
569
568 if (continue_prompt and
570 if (continue_prompt and
569 self.shell.autoindent and
571 self.shell.autoindent and
570 line.isspace() and
572 line.isspace() and
571 0 < abs(len(line) - self.shell.indent_current_nsp) <= 2):
573 0 < abs(len(line) - self.shell.indent_current_nsp) <= 2):
572 line = ''
574 line = ''
573
575
574 return line
576 return line
575
577
576 def __str__(self):
578 def __str__(self):
577 return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name)
579 return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name)
578
580
579
581
580 class MacroHandler(PrefilterHandler):
582 class MacroHandler(PrefilterHandler):
581 handler_name = Unicode("macro")
583 handler_name = Unicode("macro")
582
584
583 def handle(self, line_info):
585 def handle(self, line_info):
584 obj = self.shell.user_ns.get(line_info.ifun)
586 obj = self.shell.user_ns.get(line_info.ifun)
585 pre_space = line_info.pre_whitespace
587 pre_space = line_info.pre_whitespace
586 line_sep = "\n" + pre_space
588 line_sep = "\n" + pre_space
587 return pre_space + line_sep.join(obj.value.splitlines())
589 return pre_space + line_sep.join(obj.value.splitlines())
588
590
589
591
590 class MagicHandler(PrefilterHandler):
592 class MagicHandler(PrefilterHandler):
591
593
592 handler_name = Unicode('magic')
594 handler_name = Unicode('magic')
593 esc_strings = List([ESC_MAGIC])
595 esc_strings = List([ESC_MAGIC])
594
596
595 def handle(self, line_info):
597 def handle(self, line_info):
596 """Execute magic functions."""
598 """Execute magic functions."""
597 ifun = line_info.ifun
599 ifun = line_info.ifun
598 the_rest = line_info.the_rest
600 the_rest = line_info.the_rest
599 cmd = '%sget_ipython().magic(%r)' % (line_info.pre_whitespace,
601 cmd = '%sget_ipython().magic(%r)' % (line_info.pre_whitespace,
600 (ifun + " " + the_rest))
602 (ifun + " " + the_rest))
601 return cmd
603 return cmd
602
604
603
605
604 class AutoHandler(PrefilterHandler):
606 class AutoHandler(PrefilterHandler):
605
607
606 handler_name = Unicode('auto')
608 handler_name = Unicode('auto')
607 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
609 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
608
610
609 def handle(self, line_info):
611 def handle(self, line_info):
610 """Handle lines which can be auto-executed, quoting if requested."""
612 """Handle lines which can be auto-executed, quoting if requested."""
611 line = line_info.line
613 line = line_info.line
612 ifun = line_info.ifun
614 ifun = line_info.ifun
613 the_rest = line_info.the_rest
615 the_rest = line_info.the_rest
614 esc = line_info.esc
616 esc = line_info.esc
615 continue_prompt = line_info.continue_prompt
617 continue_prompt = line_info.continue_prompt
616 obj = line_info.ofind(self.shell)['obj']
618 obj = line_info.ofind(self.shell)['obj']
617
619
618 # This should only be active for single-line input!
620 # This should only be active for single-line input!
619 if continue_prompt:
621 if continue_prompt:
620 return line
622 return line
621
623
622 force_auto = isinstance(obj, IPyAutocall)
624 force_auto = isinstance(obj, IPyAutocall)
623
625
624 # User objects sometimes raise exceptions on attribute access other
626 # User objects sometimes raise exceptions on attribute access other
625 # than AttributeError (we've seen it in the past), so it's safest to be
627 # than AttributeError (we've seen it in the past), so it's safest to be
626 # ultra-conservative here and catch all.
628 # ultra-conservative here and catch all.
627 try:
629 try:
628 auto_rewrite = obj.rewrite
630 auto_rewrite = obj.rewrite
629 except Exception:
631 except Exception:
630 auto_rewrite = True
632 auto_rewrite = True
631
633
632 if esc == ESC_QUOTE:
634 if esc == ESC_QUOTE:
633 # Auto-quote splitting on whitespace
635 # Auto-quote splitting on whitespace
634 newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) )
636 newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) )
635 elif esc == ESC_QUOTE2:
637 elif esc == ESC_QUOTE2:
636 # Auto-quote whole string
638 # Auto-quote whole string
637 newcmd = '%s("%s")' % (ifun,the_rest)
639 newcmd = '%s("%s")' % (ifun,the_rest)
638 elif esc == ESC_PAREN:
640 elif esc == ESC_PAREN:
639 newcmd = '%s(%s)' % (ifun,",".join(the_rest.split()))
641 newcmd = '%s(%s)' % (ifun,",".join(the_rest.split()))
640 else:
642 else:
641 # Auto-paren.
643 # Auto-paren.
642 if force_auto:
644 if force_auto:
643 # Don't rewrite if it is already a call.
645 # Don't rewrite if it is already a call.
644 do_rewrite = not the_rest.startswith('(')
646 do_rewrite = not the_rest.startswith('(')
645 else:
647 else:
646 if not the_rest:
648 if not the_rest:
647 # We only apply it to argument-less calls if the autocall
649 # We only apply it to argument-less calls if the autocall
648 # parameter is set to 2.
650 # parameter is set to 2.
649 do_rewrite = (self.shell.autocall >= 2)
651 do_rewrite = (self.shell.autocall >= 2)
650 elif the_rest.startswith('[') and hasattr(obj, '__getitem__'):
652 elif the_rest.startswith('[') and hasattr(obj, '__getitem__'):
651 # Don't autocall in this case: item access for an object
653 # Don't autocall in this case: item access for an object
652 # which is BOTH callable and implements __getitem__.
654 # which is BOTH callable and implements __getitem__.
653 do_rewrite = False
655 do_rewrite = False
654 else:
656 else:
655 do_rewrite = True
657 do_rewrite = True
656
658
657 # Figure out the rewritten command
659 # Figure out the rewritten command
658 if do_rewrite:
660 if do_rewrite:
659 if the_rest.endswith(';'):
661 if the_rest.endswith(';'):
660 newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1])
662 newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1])
661 else:
663 else:
662 newcmd = '%s(%s)' % (ifun.rstrip(), the_rest)
664 newcmd = '%s(%s)' % (ifun.rstrip(), the_rest)
663 else:
665 else:
664 normal_handler = self.prefilter_manager.get_handler_by_name('normal')
666 normal_handler = self.prefilter_manager.get_handler_by_name('normal')
665 return normal_handler.handle(line_info)
667 return normal_handler.handle(line_info)
666
668
667 # Display the rewritten call
669 # Display the rewritten call
668 if auto_rewrite:
670 if auto_rewrite:
669 self.shell.auto_rewrite_input(newcmd)
671 self.shell.auto_rewrite_input(newcmd)
670
672
671 return newcmd
673 return newcmd
672
674
673
675
674 class EmacsHandler(PrefilterHandler):
676 class EmacsHandler(PrefilterHandler):
675
677
676 handler_name = Unicode('emacs')
678 handler_name = Unicode('emacs')
677 esc_strings = List([])
679 esc_strings = List([])
678
680
679 def handle(self, line_info):
681 def handle(self, line_info):
680 """Handle input lines marked by python-mode."""
682 """Handle input lines marked by python-mode."""
681
683
682 # Currently, nothing is done. Later more functionality can be added
684 # Currently, nothing is done. Later more functionality can be added
683 # here if needed.
685 # here if needed.
684
686
685 # The input cache shouldn't be updated
687 # The input cache shouldn't be updated
686 return line_info.line
688 return line_info.line
687
689
688
690
689 #-----------------------------------------------------------------------------
691 #-----------------------------------------------------------------------------
690 # Defaults
692 # Defaults
691 #-----------------------------------------------------------------------------
693 #-----------------------------------------------------------------------------
692
694
693
695
694 _default_transformers = [
696 _default_transformers = [
695 ]
697 ]
696
698
697 _default_checkers = [
699 _default_checkers = [
698 EmacsChecker,
700 EmacsChecker,
699 MacroChecker,
701 MacroChecker,
700 IPyAutocallChecker,
702 IPyAutocallChecker,
701 AssignmentChecker,
703 AssignmentChecker,
702 AutoMagicChecker,
704 AutoMagicChecker,
703 PythonOpsChecker,
705 PythonOpsChecker,
704 AutocallChecker
706 AutocallChecker
705 ]
707 ]
706
708
707 _default_handlers = [
709 _default_handlers = [
708 PrefilterHandler,
710 PrefilterHandler,
709 MacroHandler,
711 MacroHandler,
710 MagicHandler,
712 MagicHandler,
711 AutoHandler,
713 AutoHandler,
712 EmacsHandler
714 EmacsHandler
713 ]
715 ]
@@ -1,418 +1,420 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Classes for handling input/output prompts.
2 """Classes for handling input/output prompts.
3
3
4 Authors:
4 Authors:
5
5
6 * Fernando Perez
6 * Fernando Perez
7 * Brian Granger
7 * Brian Granger
8 * Thomas Kluyver
8 * Thomas Kluyver
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 import os
23 import os
24 import re
24 import re
25 import socket
25 import socket
26 import sys
26 import sys
27 import time
27 import time
28
28
29 from string import Formatter
29 from string import Formatter
30
30
31 from traitlets.config.configurable import Configurable
31 from traitlets.config.configurable import Configurable
32 from IPython.core import release
32 from IPython.core import release
33 from IPython.utils import coloransi, py3compat
33 from IPython.utils import coloransi, py3compat
34 from traitlets import Unicode, Instance, Dict, Bool, Int, observe
34 from traitlets import Unicode, Instance, Dict, Bool, Int, observe
35
35
36 from IPython.utils.PyColorize import LightBGColors, LinuxColors, NoColor
36 from IPython.utils.PyColorize import LightBGColors, LinuxColors, NoColor
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Color schemes for prompts
39 # Color schemes for prompts
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 InputColors = coloransi.InputTermColors # just a shorthand
42 InputColors = coloransi.InputTermColors # just a shorthand
43 Colors = coloransi.TermColors # just a shorthand
43 Colors = coloransi.TermColors # just a shorthand
44
44
45 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
45 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # Utilities
48 # Utilities
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50
50
51 class LazyEvaluate(object):
51 class LazyEvaluate(object):
52 """This is used for formatting strings with values that need to be updated
52 """This is used for formatting strings with values that need to be updated
53 at that time, such as the current time or working directory."""
53 at that time, such as the current time or working directory."""
54 def __init__(self, func, *args, **kwargs):
54 def __init__(self, func, *args, **kwargs):
55 self.func = func
55 self.func = func
56 self.args = args
56 self.args = args
57 self.kwargs = kwargs
57 self.kwargs = kwargs
58
58
59 def __call__(self, **kwargs):
59 def __call__(self, **kwargs):
60 self.kwargs.update(kwargs)
60 self.kwargs.update(kwargs)
61 return self.func(*self.args, **self.kwargs)
61 return self.func(*self.args, **self.kwargs)
62
62
63 def __str__(self):
63 def __str__(self):
64 return str(self())
64 return str(self())
65
65
66 def __unicode__(self):
66 def __unicode__(self):
67 return py3compat.unicode_type(self())
67 return py3compat.unicode_type(self())
68
68
69 def __format__(self, format_spec):
69 def __format__(self, format_spec):
70 return format(self(), format_spec)
70 return format(self(), format_spec)
71
71
72 def multiple_replace(dict, text):
72 def multiple_replace(dict, text):
73 """ Replace in 'text' all occurrences of any key in the given
73 """ Replace in 'text' all occurrences of any key in the given
74 dictionary by its corresponding value. Returns the new string."""
74 dictionary by its corresponding value. Returns the new string."""
75
75
76 # Function by Xavier Defrang, originally found at:
76 # Function by Xavier Defrang, originally found at:
77 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
77 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
78
78
79 # Create a regular expression from the dictionary keys
79 # Create a regular expression from the dictionary keys
80 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
80 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
81 # For each match, look-up corresponding value in dictionary
81 # For each match, look-up corresponding value in dictionary
82 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
82 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
83
83
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85 # Special characters that can be used in prompt templates, mainly bash-like
85 # Special characters that can be used in prompt templates, mainly bash-like
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87
87
88 # If $HOME isn't defined (Windows), make it an absurd string so that it can
88 # If $HOME isn't defined (Windows), make it an absurd string so that it can
89 # never be expanded out into '~'. Basically anything which can never be a
89 # never be expanded out into '~'. Basically anything which can never be a
90 # reasonable directory name will do, we just want the $HOME -> '~' operation
90 # reasonable directory name will do, we just want the $HOME -> '~' operation
91 # to become a no-op. We pre-compute $HOME here so it's not done on every
91 # to become a no-op. We pre-compute $HOME here so it's not done on every
92 # prompt call.
92 # prompt call.
93
93
94 # FIXME:
94 # FIXME:
95
95
96 # - This should be turned into a class which does proper namespace management,
96 # - This should be turned into a class which does proper namespace management,
97 # since the prompt specials need to be evaluated in a certain namespace.
97 # since the prompt specials need to be evaluated in a certain namespace.
98 # Currently it's just globals, which need to be managed manually by code
98 # Currently it's just globals, which need to be managed manually by code
99 # below.
99 # below.
100
100
101 # - I also need to split up the color schemes from the prompt specials
101 # - I also need to split up the color schemes from the prompt specials
102 # somehow. I don't have a clean design for that quite yet.
102 # somehow. I don't have a clean design for that quite yet.
103
103
104 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
104 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
105
105
106 # This is needed on FreeBSD, and maybe other systems which symlink /home to
106 # This is needed on FreeBSD, and maybe other systems which symlink /home to
107 # /usr/home, but retain the $HOME variable as pointing to /home
107 # /usr/home, but retain the $HOME variable as pointing to /home
108 HOME = os.path.realpath(HOME)
108 HOME = os.path.realpath(HOME)
109
109
110 # We precompute a few more strings here for the prompt_specials, which are
110 # We precompute a few more strings here for the prompt_specials, which are
111 # fixed once ipython starts. This reduces the runtime overhead of computing
111 # fixed once ipython starts. This reduces the runtime overhead of computing
112 # prompt strings.
112 # prompt strings.
113 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
113 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
114 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
114 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
115 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
115 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
116
116
117 # IronPython doesn't currently have os.getuid() even if
117 # IronPython doesn't currently have os.getuid() even if
118 # os.name == 'posix'; 2/8/2014
118 # os.name == 'posix'; 2/8/2014
119 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
119 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
120
120
121 prompt_abbreviations = {
121 prompt_abbreviations = {
122 # Prompt/history count
122 # Prompt/history count
123 '%n' : '{color.number}' '{count}' '{color.prompt}',
123 '%n' : '{color.number}' '{count}' '{color.prompt}',
124 r'\#': '{color.number}' '{count}' '{color.prompt}',
124 r'\#': '{color.number}' '{count}' '{color.prompt}',
125 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
125 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
126 # can get numbers displayed in whatever color they want.
126 # can get numbers displayed in whatever color they want.
127 r'\N': '{count}',
127 r'\N': '{count}',
128
128
129 # Prompt/history count, with the actual digits replaced by dots or
129 # Prompt/history count, with the actual digits replaced by dots or
130 # spaces. Used mainly in continuation prompts (prompt_in2).
130 # spaces. Used mainly in continuation prompts (prompt_in2).
131 r'\D': '{dots}',
131 r'\D': '{dots}',
132 r'\S': '{spaces}',
132 r'\S': '{spaces}',
133
133
134 # Current time
134 # Current time
135 r'\T' : '{time}',
135 r'\T' : '{time}',
136 # Current working directory
136 # Current working directory
137 r'\w': '{cwd}',
137 r'\w': '{cwd}',
138 # Basename of current working directory.
138 # Basename of current working directory.
139 # (use os.sep to make this portable across OSes)
139 # (use os.sep to make this portable across OSes)
140 r'\W' : '{cwd_last}',
140 r'\W' : '{cwd_last}',
141 # These X<N> are an extension to the normal bash prompts. They return
141 # These X<N> are an extension to the normal bash prompts. They return
142 # N terms of the path, after replacing $HOME with '~'
142 # N terms of the path, after replacing $HOME with '~'
143 r'\X0': '{cwd_x[0]}',
143 r'\X0': '{cwd_x[0]}',
144 r'\X1': '{cwd_x[1]}',
144 r'\X1': '{cwd_x[1]}',
145 r'\X2': '{cwd_x[2]}',
145 r'\X2': '{cwd_x[2]}',
146 r'\X3': '{cwd_x[3]}',
146 r'\X3': '{cwd_x[3]}',
147 r'\X4': '{cwd_x[4]}',
147 r'\X4': '{cwd_x[4]}',
148 r'\X5': '{cwd_x[5]}',
148 r'\X5': '{cwd_x[5]}',
149 # Y<N> are similar to X<N>, but they show '~' if it's the directory
149 # Y<N> are similar to X<N>, but they show '~' if it's the directory
150 # N+1 in the list. Somewhat like %cN in tcsh.
150 # N+1 in the list. Somewhat like %cN in tcsh.
151 r'\Y0': '{cwd_y[0]}',
151 r'\Y0': '{cwd_y[0]}',
152 r'\Y1': '{cwd_y[1]}',
152 r'\Y1': '{cwd_y[1]}',
153 r'\Y2': '{cwd_y[2]}',
153 r'\Y2': '{cwd_y[2]}',
154 r'\Y3': '{cwd_y[3]}',
154 r'\Y3': '{cwd_y[3]}',
155 r'\Y4': '{cwd_y[4]}',
155 r'\Y4': '{cwd_y[4]}',
156 r'\Y5': '{cwd_y[5]}',
156 r'\Y5': '{cwd_y[5]}',
157 # Hostname up to first .
157 # Hostname up to first .
158 r'\h': HOSTNAME_SHORT,
158 r'\h': HOSTNAME_SHORT,
159 # Full hostname
159 # Full hostname
160 r'\H': HOSTNAME,
160 r'\H': HOSTNAME,
161 # Username of current user
161 # Username of current user
162 r'\u': USER,
162 r'\u': USER,
163 # Escaped '\'
163 # Escaped '\'
164 '\\\\': '\\',
164 '\\\\': '\\',
165 # Newline
165 # Newline
166 r'\n': '\n',
166 r'\n': '\n',
167 # Carriage return
167 # Carriage return
168 r'\r': '\r',
168 r'\r': '\r',
169 # Release version
169 # Release version
170 r'\v': release.version,
170 r'\v': release.version,
171 # Root symbol ($ or #)
171 # Root symbol ($ or #)
172 r'\$': ROOT_SYMBOL,
172 r'\$': ROOT_SYMBOL,
173 }
173 }
174
174
175 #-----------------------------------------------------------------------------
175 #-----------------------------------------------------------------------------
176 # More utilities
176 # More utilities
177 #-----------------------------------------------------------------------------
177 #-----------------------------------------------------------------------------
178
178
179 def cwd_filt(depth):
179 def cwd_filt(depth):
180 """Return the last depth elements of the current working directory.
180 """Return the last depth elements of the current working directory.
181
181
182 $HOME is always replaced with '~'.
182 $HOME is always replaced with '~'.
183 If depth==0, the full path is returned."""
183 If depth==0, the full path is returned."""
184
184
185 cwd = py3compat.getcwd().replace(HOME,"~")
185 cwd = py3compat.getcwd().replace(HOME,"~")
186 out = os.sep.join(cwd.split(os.sep)[-depth:])
186 out = os.sep.join(cwd.split(os.sep)[-depth:])
187 return out or os.sep
187 return out or os.sep
188
188
189 def cwd_filt2(depth):
189 def cwd_filt2(depth):
190 """Return the last depth elements of the current working directory.
190 """Return the last depth elements of the current working directory.
191
191
192 $HOME is always replaced with '~'.
192 $HOME is always replaced with '~'.
193 If depth==0, the full path is returned."""
193 If depth==0, the full path is returned."""
194
194
195 full_cwd = py3compat.getcwd()
195 full_cwd = py3compat.getcwd()
196 cwd = full_cwd.replace(HOME,"~").split(os.sep)
196 cwd = full_cwd.replace(HOME,"~").split(os.sep)
197 if '~' in cwd and len(cwd) == depth+1:
197 if '~' in cwd and len(cwd) == depth+1:
198 depth += 1
198 depth += 1
199 drivepart = ''
199 drivepart = ''
200 if sys.platform == 'win32' and len(cwd) > depth:
200 if sys.platform == 'win32' and len(cwd) > depth:
201 drivepart = os.path.splitdrive(full_cwd)[0]
201 drivepart = os.path.splitdrive(full_cwd)[0]
202 out = drivepart + '/'.join(cwd[-depth:])
202 out = drivepart + '/'.join(cwd[-depth:])
203
203
204 return out or os.sep
204 return out or os.sep
205
205
206 #-----------------------------------------------------------------------------
206 #-----------------------------------------------------------------------------
207 # Prompt classes
207 # Prompt classes
208 #-----------------------------------------------------------------------------
208 #-----------------------------------------------------------------------------
209
209
210 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
210 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
211 'cwd': LazyEvaluate(py3compat.getcwd),
211 'cwd': LazyEvaluate(py3compat.getcwd),
212 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
212 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
213 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
213 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
214 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
214 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
215 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
215 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
216 }
216 }
217
217
218 def _lenlastline(s):
218 def _lenlastline(s):
219 """Get the length of the last line. More intelligent than
219 """Get the length of the last line. More intelligent than
220 len(s.splitlines()[-1]).
220 len(s.splitlines()[-1]).
221 """
221 """
222 if not s or s.endswith(('\n', '\r')):
222 if not s or s.endswith(('\n', '\r')):
223 return 0
223 return 0
224 return len(s.splitlines()[-1])
224 return len(s.splitlines()[-1])
225
225
226
226
227 invisible_chars_re = re.compile('\001[^\001\002]*\002')
227 invisible_chars_re = re.compile('\001[^\001\002]*\002')
228 def _invisible_characters(s):
228 def _invisible_characters(s):
229 """
229 """
230 Get the number of invisible ANSI characters in s. Invisible characters
230 Get the number of invisible ANSI characters in s. Invisible characters
231 must be delimited by \001 and \002.
231 must be delimited by \001 and \002.
232 """
232 """
233 return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s))
233 return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s))
234
234
235 class UserNSFormatter(Formatter):
235 class UserNSFormatter(Formatter):
236 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
236 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
237 def __init__(self, shell):
237 def __init__(self, shell):
238 self.shell = shell
238 self.shell = shell
239
239
240 def get_value(self, key, args, kwargs):
240 def get_value(self, key, args, kwargs):
241 # try regular formatting first:
241 # try regular formatting first:
242 try:
242 try:
243 return Formatter.get_value(self, key, args, kwargs)
243 return Formatter.get_value(self, key, args, kwargs)
244 except Exception:
244 except Exception:
245 pass
245 pass
246 # next, look in user_ns and builtins:
246 # next, look in user_ns and builtins:
247 for container in (self.shell.user_ns, __builtins__):
247 for container in (self.shell.user_ns, __builtins__):
248 if key in container:
248 if key in container:
249 return container[key]
249 return container[key]
250 # nothing found, put error message in its place
250 # nothing found, put error message in its place
251 return "<ERROR: '%s' not found>" % key
251 return "<ERROR: '%s' not found>" % key
252
252
253
253
254 class PromptManager(Configurable):
254 class PromptManager(Configurable):
255 """This is the primary interface for producing IPython's prompts."""
255 """This is the primary interface for producing IPython's prompts."""
256 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
256 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
257
257
258 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
258 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
259 color_scheme = Unicode('Linux').tag(config=True)
259 color_scheme = Unicode('Linux').tag(config=True)
260
260
261 @observe('color_scheme')
261 @observe('color_scheme')
262 def _color_scheme_changed(self, change):
262 def _color_scheme_changed(self, change):
263 self.color_scheme_table.set_active_scheme(change['new'])
263 self.color_scheme_table.set_active_scheme(change['new'])
264 for pname in ['in', 'in2', 'out', 'rewrite']:
264 for pname in ['in', 'in2', 'out', 'rewrite']:
265 # We need to recalculate the number of invisible characters
265 # We need to recalculate the number of invisible characters
266 self.update_prompt(pname)
266 self.update_prompt(pname)
267
267
268 lazy_evaluate_fields = Dict(help="""
268 lazy_evaluate_fields = Dict(help="""
269 This maps field names used in the prompt templates to functions which
269 This maps field names used in the prompt templates to functions which
270 will be called when the prompt is rendered. This allows us to include
270 will be called when the prompt is rendered. This allows us to include
271 things like the current time in the prompts. Functions are only called
271 things like the current time in the prompts. Functions are only called
272 if they are used in the prompt.
272 if they are used in the prompt.
273 """)
273 """)
274 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
274 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
275
275
276 in_template = Unicode('In [\\#]: ').tag(config=True,
276 in_template = Unicode('In [\\#]: ',
277 help="Input prompt. '\\#' will be transformed to the prompt number")
277 help="Input prompt. '\\#' will be transformed to the prompt number"
278 in2_template = Unicode(' .\\D.: ').tag(config=True,
278 ).tag(config=True)
279 help="Continuation prompt.")
279 in2_template = Unicode(' .\\D.: ',
280 out_template = Unicode('Out[\\#]: ').tag(config=True,
280 help="Continuation prompt.").tag(config=True)
281 help="Output prompt. '\\#' will be transformed to the prompt number")
281 out_template = Unicode('Out[\\#]: ',
282
282 help="Output prompt. '\\#' will be transformed to the prompt number"
283 justify = Bool(True).tag(config=True, help="""
283 ).tag(config=True)
284
285 justify = Bool(True, help="""
284 If True (default), each prompt will be right-aligned with the
286 If True (default), each prompt will be right-aligned with the
285 preceding one.
287 preceding one.
286 """)
288 """).tag(config=True)
287
289
288 # We actually store the expanded templates here:
290 # We actually store the expanded templates here:
289 templates = Dict()
291 templates = Dict()
290
292
291 # The number of characters in the last prompt rendered, not including
293 # The number of characters in the last prompt rendered, not including
292 # colour characters.
294 # colour characters.
293 width = Int()
295 width = Int()
294 txtwidth = Int() # Not including right-justification
296 txtwidth = Int() # Not including right-justification
295
297
296 # The number of characters in each prompt which don't contribute to width
298 # The number of characters in each prompt which don't contribute to width
297 invisible_chars = Dict()
299 invisible_chars = Dict()
298 def _invisible_chars_default(self):
300 def _invisible_chars_default(self):
299 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
301 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
300
302
301 def __init__(self, shell, **kwargs):
303 def __init__(self, shell, **kwargs):
302 super(PromptManager, self).__init__(shell=shell, **kwargs)
304 super(PromptManager, self).__init__(shell=shell, **kwargs)
303
305
304 # Prepare colour scheme table
306 # Prepare colour scheme table
305 self.color_scheme_table = coloransi.ColorSchemeTable([NoColor,
307 self.color_scheme_table = coloransi.ColorSchemeTable([NoColor,
306 LinuxColors, LightBGColors], self.color_scheme)
308 LinuxColors, LightBGColors], self.color_scheme)
307
309
308 self._formatter = UserNSFormatter(shell)
310 self._formatter = UserNSFormatter(shell)
309 # Prepare templates & numbers of invisible characters
311 # Prepare templates & numbers of invisible characters
310 self.update_prompt('in', self.in_template)
312 self.update_prompt('in', self.in_template)
311 self.update_prompt('in2', self.in2_template)
313 self.update_prompt('in2', self.in2_template)
312 self.update_prompt('out', self.out_template)
314 self.update_prompt('out', self.out_template)
313 self.update_prompt('rewrite')
315 self.update_prompt('rewrite')
314 self.on_trait_change(self._update_prompt_trait, ['in_template',
316 self.on_trait_change(self._update_prompt_trait, ['in_template',
315 'in2_template', 'out_template'])
317 'in2_template', 'out_template'])
316
318
317 def update_prompt(self, name, new_template=None):
319 def update_prompt(self, name, new_template=None):
318 """This is called when a prompt template is updated. It processes
320 """This is called when a prompt template is updated. It processes
319 abbreviations used in the prompt template (like \#) and calculates how
321 abbreviations used in the prompt template (like \#) and calculates how
320 many invisible characters (ANSI colour escapes) the resulting prompt
322 many invisible characters (ANSI colour escapes) the resulting prompt
321 contains.
323 contains.
322
324
323 It is also called for each prompt on changing the colour scheme. In both
325 It is also called for each prompt on changing the colour scheme. In both
324 cases, traitlets should take care of calling this automatically.
326 cases, traitlets should take care of calling this automatically.
325 """
327 """
326 if new_template is not None:
328 if new_template is not None:
327 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
329 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
328 # We count invisible characters (colour escapes) on the last line of the
330 # We count invisible characters (colour escapes) on the last line of the
329 # prompt, to calculate the width for lining up subsequent prompts.
331 # prompt, to calculate the width for lining up subsequent prompts.
330 invis_chars = _invisible_characters(self._render(name, color=True))
332 invis_chars = _invisible_characters(self._render(name, color=True))
331 self.invisible_chars[name] = invis_chars
333 self.invisible_chars[name] = invis_chars
332
334
333 def _update_prompt_trait(self, traitname, new_template):
335 def _update_prompt_trait(self, traitname, new_template):
334 name = traitname[:-9] # Cut off '_template'
336 name = traitname[:-9] # Cut off '_template'
335 self.update_prompt(name, new_template)
337 self.update_prompt(name, new_template)
336
338
337 def _render(self, name, color=True, **kwargs):
339 def _render(self, name, color=True, **kwargs):
338 """Render but don't justify, or update the width or txtwidth attributes.
340 """Render but don't justify, or update the width or txtwidth attributes.
339 """
341 """
340 if name == 'rewrite':
342 if name == 'rewrite':
341 return self._render_rewrite(color=color)
343 return self._render_rewrite(color=color)
342
344
343 if color:
345 if color:
344 scheme = self.color_scheme_table.active_colors
346 scheme = self.color_scheme_table.active_colors
345 if name=='out':
347 if name=='out':
346 colors = color_lists['normal']
348 colors = color_lists['normal']
347 colors.number, colors.prompt, colors.normal = \
349 colors.number, colors.prompt, colors.normal = \
348 scheme.out_number, scheme.out_prompt, scheme.normal
350 scheme.out_number, scheme.out_prompt, scheme.normal
349 else:
351 else:
350 colors = color_lists['inp']
352 colors = color_lists['inp']
351 colors.number, colors.prompt, colors.normal = \
353 colors.number, colors.prompt, colors.normal = \
352 scheme.in_number, scheme.in_prompt, scheme.in_normal
354 scheme.in_number, scheme.in_prompt, scheme.in_normal
353 if name=='in2':
355 if name=='in2':
354 colors.prompt = scheme.in_prompt2
356 colors.prompt = scheme.in_prompt2
355 else:
357 else:
356 # No color
358 # No color
357 colors = color_lists['nocolor']
359 colors = color_lists['nocolor']
358 colors.number, colors.prompt, colors.normal = '', '', ''
360 colors.number, colors.prompt, colors.normal = '', '', ''
359
361
360 count = self.shell.execution_count # Shorthand
362 count = self.shell.execution_count # Shorthand
361 # Build the dictionary to be passed to string formatting
363 # Build the dictionary to be passed to string formatting
362 fmtargs = dict(color=colors, count=count,
364 fmtargs = dict(color=colors, count=count,
363 dots="."*len(str(count)), spaces=" "*len(str(count)),
365 dots="."*len(str(count)), spaces=" "*len(str(count)),
364 width=self.width, txtwidth=self.txtwidth)
366 width=self.width, txtwidth=self.txtwidth)
365 fmtargs.update(self.lazy_evaluate_fields)
367 fmtargs.update(self.lazy_evaluate_fields)
366 fmtargs.update(kwargs)
368 fmtargs.update(kwargs)
367
369
368 # Prepare the prompt
370 # Prepare the prompt
369 prompt = colors.prompt + self.templates[name] + colors.normal
371 prompt = colors.prompt + self.templates[name] + colors.normal
370
372
371 # Fill in required fields
373 # Fill in required fields
372 return self._formatter.format(prompt, **fmtargs)
374 return self._formatter.format(prompt, **fmtargs)
373
375
374 def _render_rewrite(self, color=True):
376 def _render_rewrite(self, color=True):
375 """Render the ---> rewrite prompt."""
377 """Render the ---> rewrite prompt."""
376 if color:
378 if color:
377 scheme = self.color_scheme_table.active_colors
379 scheme = self.color_scheme_table.active_colors
378 # We need a non-input version of these escapes
380 # We need a non-input version of these escapes
379 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
381 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
380 color_normal = scheme.normal
382 color_normal = scheme.normal
381 else:
383 else:
382 color_prompt, color_normal = '', ''
384 color_prompt, color_normal = '', ''
383
385
384 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
386 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
385
387
386 def render(self, name, color=True, just=None, **kwargs):
388 def render(self, name, color=True, just=None, **kwargs):
387 """
389 """
388 Render the selected prompt.
390 Render the selected prompt.
389
391
390 Parameters
392 Parameters
391 ----------
393 ----------
392 name : str
394 name : str
393 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
395 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
394 color : bool
396 color : bool
395 If True (default), include ANSI escape sequences for a coloured prompt.
397 If True (default), include ANSI escape sequences for a coloured prompt.
396 just : bool
398 just : bool
397 If True, justify the prompt to the width of the last prompt. The
399 If True, justify the prompt to the width of the last prompt. The
398 default is stored in self.justify.
400 default is stored in self.justify.
399 **kwargs :
401 **kwargs :
400 Additional arguments will be passed to the string formatting operation,
402 Additional arguments will be passed to the string formatting operation,
401 so they can override the values that would otherwise fill in the
403 so they can override the values that would otherwise fill in the
402 template.
404 template.
403
405
404 Returns
406 Returns
405 -------
407 -------
406 A string containing the rendered prompt.
408 A string containing the rendered prompt.
407 """
409 """
408 res = self._render(name, color=color, **kwargs)
410 res = self._render(name, color=color, **kwargs)
409
411
410 # Handle justification of prompt
412 # Handle justification of prompt
411 invis_chars = self.invisible_chars[name] if color else 0
413 invis_chars = self.invisible_chars[name] if color else 0
412 self.txtwidth = _lenlastline(res) - invis_chars
414 self.txtwidth = _lenlastline(res) - invis_chars
413 just = self.justify if (just is None) else just
415 just = self.justify if (just is None) else just
414 # If the prompt spans more than one line, don't try to justify it:
416 # If the prompt spans more than one line, don't try to justify it:
415 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
417 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
416 res = res.rjust(self.width + invis_chars)
418 res = res.rjust(self.width + invis_chars)
417 self.width = _lenlastline(res) - invis_chars
419 self.width = _lenlastline(res) - invis_chars
418 return res
420 return res
@@ -1,463 +1,464 b''
1 """IPython terminal interface using prompt_toolkit in place of readline"""
1 """IPython terminal interface using prompt_toolkit in place of readline"""
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 signal
6 import signal
7 import unicodedata
7 import unicodedata
8 from warnings import warn
8 from warnings import warn
9 from wcwidth import wcwidth
9 from wcwidth import wcwidth
10
10
11 from IPython.core.error import TryNext
11 from IPython.core.error import TryNext
12 from IPython.core.interactiveshell import InteractiveShell
12 from IPython.core.interactiveshell import InteractiveShell
13 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
13 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
14 from IPython.utils.terminal import toggle_set_term_title, set_term_title
14 from IPython.utils.terminal import toggle_set_term_title, set_term_title
15 from IPython.utils.process import abbrev_cwd
15 from IPython.utils.process import abbrev_cwd
16 from traitlets import Bool, CBool, Unicode, Dict, Integer, observe
16 from traitlets import Bool, CBool, Unicode, Dict, Integer, observe
17
17
18 from prompt_toolkit.completion import Completer, Completion
18 from prompt_toolkit.completion import Completer, Completion
19 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
19 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
20 from prompt_toolkit.filters import HasFocus, HasSelection, Condition, ViInsertMode, EmacsInsertMode
20 from prompt_toolkit.filters import HasFocus, HasSelection, Condition, ViInsertMode, EmacsInsertMode
21 from prompt_toolkit.history import InMemoryHistory
21 from prompt_toolkit.history import InMemoryHistory
22 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout
22 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout
23 from prompt_toolkit.interface import CommandLineInterface
23 from prompt_toolkit.interface import CommandLineInterface
24 from prompt_toolkit.key_binding.manager import KeyBindingManager
24 from prompt_toolkit.key_binding.manager import KeyBindingManager
25 from prompt_toolkit.keys import Keys
25 from prompt_toolkit.keys import Keys
26 from prompt_toolkit.layout.lexers import Lexer
26 from prompt_toolkit.layout.lexers import Lexer
27 from prompt_toolkit.layout.lexers import PygmentsLexer
27 from prompt_toolkit.layout.lexers import PygmentsLexer
28 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
28 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
29
29
30 from pygments.styles import get_style_by_name, get_all_styles
30 from pygments.styles import get_style_by_name, get_all_styles
31 from pygments.lexers import Python3Lexer, BashLexer, PythonLexer
31 from pygments.lexers import Python3Lexer, BashLexer, PythonLexer
32 from pygments.token import Token
32 from pygments.token import Token
33
33
34 from .pt_inputhooks import get_inputhook_func
34 from .pt_inputhooks import get_inputhook_func
35 from .interactiveshell import get_default_editor, TerminalMagics
35 from .interactiveshell import get_default_editor, TerminalMagics
36
36
37
37
38 class IPythonPTCompleter(Completer):
38 class IPythonPTCompleter(Completer):
39 """Adaptor to provide IPython completions to prompt_toolkit"""
39 """Adaptor to provide IPython completions to prompt_toolkit"""
40 def __init__(self, ipy_completer):
40 def __init__(self, ipy_completer):
41 self.ipy_completer = ipy_completer
41 self.ipy_completer = ipy_completer
42
42
43 def get_completions(self, document, complete_event):
43 def get_completions(self, document, complete_event):
44 if not document.current_line.strip():
44 if not document.current_line.strip():
45 return
45 return
46
46
47 used, matches = self.ipy_completer.complete(
47 used, matches = self.ipy_completer.complete(
48 line_buffer=document.current_line,
48 line_buffer=document.current_line,
49 cursor_pos=document.cursor_position_col
49 cursor_pos=document.cursor_position_col
50 )
50 )
51 start_pos = -len(used)
51 start_pos = -len(used)
52 for m in matches:
52 for m in matches:
53 m = unicodedata.normalize('NFC', m)
53 m = unicodedata.normalize('NFC', m)
54
54
55 # When the first character of the completion has a zero length,
55 # When the first character of the completion has a zero length,
56 # then it's probably a decomposed unicode character. E.g. caused by
56 # then it's probably a decomposed unicode character. E.g. caused by
57 # the "\dot" completion. Try to compose again with the previous
57 # the "\dot" completion. Try to compose again with the previous
58 # character.
58 # character.
59 if wcwidth(m[0]) == 0:
59 if wcwidth(m[0]) == 0:
60 if document.cursor_position + start_pos > 0:
60 if document.cursor_position + start_pos > 0:
61 char_before = document.text[document.cursor_position + start_pos - 1]
61 char_before = document.text[document.cursor_position + start_pos - 1]
62 m = unicodedata.normalize('NFC', char_before + m)
62 m = unicodedata.normalize('NFC', char_before + m)
63
63
64 # Yield the modified completion instead, if this worked.
64 # Yield the modified completion instead, if this worked.
65 if wcwidth(m[0:1]) == 1:
65 if wcwidth(m[0:1]) == 1:
66 yield Completion(m, start_position=start_pos - 1)
66 yield Completion(m, start_position=start_pos - 1)
67 continue
67 continue
68
68
69 # TODO: Use Jedi to determine meta_text
69 # TODO: Use Jedi to determine meta_text
70 # (Jedi currently has a bug that results in incorrect information.)
70 # (Jedi currently has a bug that results in incorrect information.)
71 # meta_text = ''
71 # meta_text = ''
72 # yield Completion(m, start_position=start_pos,
72 # yield Completion(m, start_position=start_pos,
73 # display_meta=meta_text)
73 # display_meta=meta_text)
74 yield Completion(m, start_position=start_pos)
74 yield Completion(m, start_position=start_pos)
75
75
76 class IPythonPTLexer(Lexer):
76 class IPythonPTLexer(Lexer):
77 """
77 """
78 Wrapper around PythonLexer and BashLexer.
78 Wrapper around PythonLexer and BashLexer.
79 """
79 """
80 def __init__(self):
80 def __init__(self):
81 self.python_lexer = PygmentsLexer(Python3Lexer if PY3 else PythonLexer)
81 self.python_lexer = PygmentsLexer(Python3Lexer if PY3 else PythonLexer)
82 self.shell_lexer = PygmentsLexer(BashLexer)
82 self.shell_lexer = PygmentsLexer(BashLexer)
83
83
84 def lex_document(self, cli, document):
84 def lex_document(self, cli, document):
85 if document.text.startswith('!'):
85 if document.text.startswith('!'):
86 return self.shell_lexer.lex_document(cli, document)
86 return self.shell_lexer.lex_document(cli, document)
87 else:
87 else:
88 return self.python_lexer.lex_document(cli, document)
88 return self.python_lexer.lex_document(cli, document)
89
89
90
90
91 class TerminalInteractiveShell(InteractiveShell):
91 class TerminalInteractiveShell(InteractiveShell):
92 colors_force = True
92 colors_force = True
93
93
94 space_for_menu = Integer(6).tag(config=True, help='Number of line at the bottom of the screen '
94 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
95 'to reserve for the completion menu')
95 'to reserve for the completion menu'
96 ).tag(config=True)
96
97
97 def _space_for_menu_changed(self, old, new):
98 def _space_for_menu_changed(self, old, new):
98 self._update_layout()
99 self._update_layout()
99
100
100 pt_cli = None
101 pt_cli = None
101
102
102 autoedit_syntax = CBool(False).tag(config=True,
103 autoedit_syntax = CBool(False).tag(config=True,
103 help="auto editing of files with syntax errors.")
104 help="auto editing of files with syntax errors.")
104
105
105 confirm_exit = CBool(True).tag(config=True,
106 confirm_exit = CBool(True).tag(config=True,
106 help="""
107 help="""
107 Set to confirm when you try to exit IPython with an EOF (Control-D
108 Set to confirm when you try to exit IPython with an EOF (Control-D
108 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
109 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
109 you can force a direct exit without any confirmation.""",
110 you can force a direct exit without any confirmation.""",
110 )
111 )
111 editing_mode = Unicode('emacs').tag(config=True,
112 editing_mode = Unicode('emacs',
112 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
113 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
113 )
114 ).tag(config=True)
114
115
115 mouse_support = Bool(False).tag(config=True,
116 mouse_support = Bool(False,
116 help="Enable mouse support in the prompt"
117 help="Enable mouse support in the prompt"
117 )
118 ).tag(config=True)
118
119
119 highlighting_style = Unicode('default').tag(config=True,
120 highlighting_style = Unicode('default',
120 help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
121 help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
121 )
122 ).tag(config=True)
122
123
123 def _highlighting_style_changed(self, old, new):
124 def _highlighting_style_changed(self, old, new):
124 self._style = self._make_style_from_name(self.highlighting_style)
125 self._style = self._make_style_from_name(self.highlighting_style)
125
126
126 highlighting_style_overrides = Dict().tag(config=True,
127 highlighting_style_overrides = Dict(
127 help="Override highlighting format for specific tokens"
128 help="Override highlighting format for specific tokens"
128 )
129 ).tag(config=True)
129
130
130 editor = Unicode(get_default_editor()).tag(config=True,
131 editor = Unicode(get_default_editor(),
131 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
132 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
132 )
133 ).tag(config=True)
133
134
134 term_title = Bool(True).tag(config=True,
135 term_title = Bool(True,
135 help="Automatically set the terminal title"
136 help="Automatically set the terminal title"
136 )
137 ).tag(config=True)
137
138
138 display_completions_in_columns = Bool(False).tag(config=True,
139 display_completions_in_columns = Bool(False,
139 help="Display a multi column completion menu.",
140 help="Display a multi column completion menu.",
140 )
141 ).tag(config=True)
141
142
142 @observe('term_title')
143 @observe('term_title')
143 def _term_title_changed(self, change):
144 def _term_title_changed(self, change):
144 self.init_term_title()
145 self.init_term_title()
145
146
146 def init_term_title(self):
147 def init_term_title(self):
147 # Enable or disable the terminal title.
148 # Enable or disable the terminal title.
148 if self.term_title:
149 if self.term_title:
149 toggle_set_term_title(True)
150 toggle_set_term_title(True)
150 set_term_title('IPython: ' + abbrev_cwd())
151 set_term_title('IPython: ' + abbrev_cwd())
151 else:
152 else:
152 toggle_set_term_title(False)
153 toggle_set_term_title(False)
153
154
154 def get_prompt_tokens(self, cli):
155 def get_prompt_tokens(self, cli):
155 return [
156 return [
156 (Token.Prompt, 'In ['),
157 (Token.Prompt, 'In ['),
157 (Token.PromptNum, str(self.execution_count)),
158 (Token.PromptNum, str(self.execution_count)),
158 (Token.Prompt, ']: '),
159 (Token.Prompt, ']: '),
159 ]
160 ]
160
161
161 def get_continuation_tokens(self, cli, width):
162 def get_continuation_tokens(self, cli, width):
162 return [
163 return [
163 (Token.Prompt, (' ' * (width - 5)) + '...: '),
164 (Token.Prompt, (' ' * (width - 5)) + '...: '),
164 ]
165 ]
165
166
166 def init_prompt_toolkit_cli(self):
167 def init_prompt_toolkit_cli(self):
167 if ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or not sys.stdin.isatty():
168 if ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or not sys.stdin.isatty():
168 # Fall back to plain non-interactive output for tests.
169 # Fall back to plain non-interactive output for tests.
169 # This is very limited, and only accepts a single line.
170 # This is very limited, and only accepts a single line.
170 def prompt():
171 def prompt():
171 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
172 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
172 self.prompt_for_code = prompt
173 self.prompt_for_code = prompt
173 return
174 return
174
175
175 kbmanager = KeyBindingManager.for_prompt()
176 kbmanager = KeyBindingManager.for_prompt()
176 insert_mode = ViInsertMode() | EmacsInsertMode()
177 insert_mode = ViInsertMode() | EmacsInsertMode()
177 # Ctrl+J == Enter, seemingly
178 # Ctrl+J == Enter, seemingly
178 @kbmanager.registry.add_binding(Keys.ControlJ,
179 @kbmanager.registry.add_binding(Keys.ControlJ,
179 filter=(HasFocus(DEFAULT_BUFFER)
180 filter=(HasFocus(DEFAULT_BUFFER)
180 & ~HasSelection()
181 & ~HasSelection()
181 & insert_mode
182 & insert_mode
182 ))
183 ))
183 def _(event):
184 def _(event):
184 b = event.current_buffer
185 b = event.current_buffer
185 d = b.document
186 d = b.document
186 if not (d.on_last_line or d.cursor_position_row >= d.line_count
187 if not (d.on_last_line or d.cursor_position_row >= d.line_count
187 - d.empty_line_count_at_the_end()):
188 - d.empty_line_count_at_the_end()):
188 b.newline()
189 b.newline()
189 return
190 return
190
191
191 status, indent = self.input_splitter.check_complete(d.text)
192 status, indent = self.input_splitter.check_complete(d.text)
192
193
193 if (status != 'incomplete') and b.accept_action.is_returnable:
194 if (status != 'incomplete') and b.accept_action.is_returnable:
194 b.accept_action.validate_and_handle(event.cli, b)
195 b.accept_action.validate_and_handle(event.cli, b)
195 else:
196 else:
196 b.insert_text('\n' + (' ' * (indent or 0)))
197 b.insert_text('\n' + (' ' * (indent or 0)))
197
198
198 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
199 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
199 def _reset_buffer(event):
200 def _reset_buffer(event):
200 event.current_buffer.reset()
201 event.current_buffer.reset()
201
202
202 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER))
203 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER))
203 def _reset_search_buffer(event):
204 def _reset_search_buffer(event):
204 if event.current_buffer.document.text:
205 if event.current_buffer.document.text:
205 event.current_buffer.reset()
206 event.current_buffer.reset()
206 else:
207 else:
207 event.cli.push_focus(DEFAULT_BUFFER)
208 event.cli.push_focus(DEFAULT_BUFFER)
208
209
209 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
210 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
210
211
211 @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend)
212 @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend)
212 def _suspend_to_bg(event):
213 def _suspend_to_bg(event):
213 event.cli.suspend_to_background()
214 event.cli.suspend_to_background()
214
215
215 @Condition
216 @Condition
216 def cursor_in_leading_ws(cli):
217 def cursor_in_leading_ws(cli):
217 before = cli.application.buffer.document.current_line_before_cursor
218 before = cli.application.buffer.document.current_line_before_cursor
218 return (not before) or before.isspace()
219 return (not before) or before.isspace()
219
220
220 # Ctrl+I == Tab
221 # Ctrl+I == Tab
221 @kbmanager.registry.add_binding(Keys.ControlI,
222 @kbmanager.registry.add_binding(Keys.ControlI,
222 filter=(HasFocus(DEFAULT_BUFFER)
223 filter=(HasFocus(DEFAULT_BUFFER)
223 & ~HasSelection()
224 & ~HasSelection()
224 & insert_mode
225 & insert_mode
225 & cursor_in_leading_ws
226 & cursor_in_leading_ws
226 ))
227 ))
227 def _indent_buffer(event):
228 def _indent_buffer(event):
228 event.current_buffer.insert_text(' ' * 4)
229 event.current_buffer.insert_text(' ' * 4)
229
230
230 # Pre-populate history from IPython's history database
231 # Pre-populate history from IPython's history database
231 history = InMemoryHistory()
232 history = InMemoryHistory()
232 last_cell = u""
233 last_cell = u""
233 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
234 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
234 include_latest=True):
235 include_latest=True):
235 # Ignore blank lines and consecutive duplicates
236 # Ignore blank lines and consecutive duplicates
236 cell = cell.rstrip()
237 cell = cell.rstrip()
237 if cell and (cell != last_cell):
238 if cell and (cell != last_cell):
238 history.append(cell)
239 history.append(cell)
239
240
240 self._style = self._make_style_from_name(self.highlighting_style)
241 self._style = self._make_style_from_name(self.highlighting_style)
241 style = DynamicStyle(lambda: self._style)
242 style = DynamicStyle(lambda: self._style)
242
243
243 editing_mode = getattr(EditingMode, self.editing_mode.upper())
244 editing_mode = getattr(EditingMode, self.editing_mode.upper())
244
245
245 self._app = create_prompt_application(
246 self._app = create_prompt_application(
246 editing_mode=editing_mode,
247 editing_mode=editing_mode,
247 key_bindings_registry=kbmanager.registry,
248 key_bindings_registry=kbmanager.registry,
248 history=history,
249 history=history,
249 completer=IPythonPTCompleter(self.Completer),
250 completer=IPythonPTCompleter(self.Completer),
250 enable_history_search=True,
251 enable_history_search=True,
251 style=style,
252 style=style,
252 mouse_support=self.mouse_support,
253 mouse_support=self.mouse_support,
253 **self._layout_options()
254 **self._layout_options()
254 )
255 )
255 self.pt_cli = CommandLineInterface(self._app,
256 self.pt_cli = CommandLineInterface(self._app,
256 eventloop=create_eventloop(self.inputhook))
257 eventloop=create_eventloop(self.inputhook))
257
258
258 def _make_style_from_name(self, name):
259 def _make_style_from_name(self, name):
259 """
260 """
260 Small wrapper that make an IPython compatible style from a style name
261 Small wrapper that make an IPython compatible style from a style name
261
262
262 We need that to add style for prompt ... etc.
263 We need that to add style for prompt ... etc.
263 """
264 """
264 style_cls = get_style_by_name(name)
265 style_cls = get_style_by_name(name)
265 style_overrides = {
266 style_overrides = {
266 Token.Prompt: '#009900',
267 Token.Prompt: '#009900',
267 Token.PromptNum: '#00ff00 bold',
268 Token.PromptNum: '#00ff00 bold',
268 }
269 }
269 if name == 'default':
270 if name == 'default':
270 style_cls = get_style_by_name('default')
271 style_cls = get_style_by_name('default')
271 # The default theme needs to be visible on both a dark background
272 # The default theme needs to be visible on both a dark background
272 # and a light background, because we can't tell what the terminal
273 # and a light background, because we can't tell what the terminal
273 # looks like. These tweaks to the default theme help with that.
274 # looks like. These tweaks to the default theme help with that.
274 style_overrides.update({
275 style_overrides.update({
275 Token.Number: '#007700',
276 Token.Number: '#007700',
276 Token.Operator: 'noinherit',
277 Token.Operator: 'noinherit',
277 Token.String: '#BB6622',
278 Token.String: '#BB6622',
278 Token.Name.Function: '#2080D0',
279 Token.Name.Function: '#2080D0',
279 Token.Name.Class: 'bold #2080D0',
280 Token.Name.Class: 'bold #2080D0',
280 Token.Name.Namespace: 'bold #2080D0',
281 Token.Name.Namespace: 'bold #2080D0',
281 })
282 })
282 style_overrides.update(self.highlighting_style_overrides)
283 style_overrides.update(self.highlighting_style_overrides)
283 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
284 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
284 style_dict=style_overrides)
285 style_dict=style_overrides)
285
286
286 return style
287 return style
287
288
288 def _layout_options(self):
289 def _layout_options(self):
289 """
290 """
290 Return the current layout option for the current Terminal InteractiveShell
291 Return the current layout option for the current Terminal InteractiveShell
291 """
292 """
292 return {
293 return {
293 'lexer':IPythonPTLexer(),
294 'lexer':IPythonPTLexer(),
294 'reserve_space_for_menu':self.space_for_menu,
295 'reserve_space_for_menu':self.space_for_menu,
295 'get_prompt_tokens':self.get_prompt_tokens,
296 'get_prompt_tokens':self.get_prompt_tokens,
296 'get_continuation_tokens':self.get_continuation_tokens,
297 'get_continuation_tokens':self.get_continuation_tokens,
297 'multiline':True,
298 'multiline':True,
298 'display_completions_in_columns': self.display_completions_in_columns,
299 'display_completions_in_columns': self.display_completions_in_columns,
299 }
300 }
300
301
301 def _update_layout(self):
302 def _update_layout(self):
302 """
303 """
303 Ask for a re computation of the application layout, if for example ,
304 Ask for a re computation of the application layout, if for example ,
304 some configuration options have changed.
305 some configuration options have changed.
305 """
306 """
306 self._app.layout = create_prompt_layout(**self._layout_options())
307 self._app.layout = create_prompt_layout(**self._layout_options())
307
308
308 def prompt_for_code(self):
309 def prompt_for_code(self):
309 document = self.pt_cli.run(
310 document = self.pt_cli.run(
310 pre_run=self.pre_prompt, reset_current_buffer=True)
311 pre_run=self.pre_prompt, reset_current_buffer=True)
311 return document.text
312 return document.text
312
313
313 def init_io(self):
314 def init_io(self):
314 if sys.platform not in {'win32', 'cli'}:
315 if sys.platform not in {'win32', 'cli'}:
315 return
316 return
316
317
317 import colorama
318 import colorama
318 colorama.init()
319 colorama.init()
319
320
320 # For some reason we make these wrappers around stdout/stderr.
321 # For some reason we make these wrappers around stdout/stderr.
321 # For now, we need to reset them so all output gets coloured.
322 # For now, we need to reset them so all output gets coloured.
322 # https://github.com/ipython/ipython/issues/8669
323 # https://github.com/ipython/ipython/issues/8669
323 from IPython.utils import io
324 from IPython.utils import io
324 io.stdout = io.IOStream(sys.stdout)
325 io.stdout = io.IOStream(sys.stdout)
325 io.stderr = io.IOStream(sys.stderr)
326 io.stderr = io.IOStream(sys.stderr)
326
327
327 def init_magics(self):
328 def init_magics(self):
328 super(TerminalInteractiveShell, self).init_magics()
329 super(TerminalInteractiveShell, self).init_magics()
329 self.register_magics(TerminalMagics)
330 self.register_magics(TerminalMagics)
330
331
331 def init_alias(self):
332 def init_alias(self):
332 # The parent class defines aliases that can be safely used with any
333 # The parent class defines aliases that can be safely used with any
333 # frontend.
334 # frontend.
334 super(TerminalInteractiveShell, self).init_alias()
335 super(TerminalInteractiveShell, self).init_alias()
335
336
336 # Now define aliases that only make sense on the terminal, because they
337 # Now define aliases that only make sense on the terminal, because they
337 # need direct access to the console in a way that we can't emulate in
338 # need direct access to the console in a way that we can't emulate in
338 # GUI or web frontend
339 # GUI or web frontend
339 if os.name == 'posix':
340 if os.name == 'posix':
340 for cmd in ['clear', 'more', 'less', 'man']:
341 for cmd in ['clear', 'more', 'less', 'man']:
341 self.alias_manager.soft_define_alias(cmd, cmd)
342 self.alias_manager.soft_define_alias(cmd, cmd)
342
343
343
344
344 def __init__(self, *args, **kwargs):
345 def __init__(self, *args, **kwargs):
345 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
346 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
346 self.init_prompt_toolkit_cli()
347 self.init_prompt_toolkit_cli()
347 self.init_term_title()
348 self.init_term_title()
348 self.keep_running = True
349 self.keep_running = True
349
350
350 def ask_exit(self):
351 def ask_exit(self):
351 self.keep_running = False
352 self.keep_running = False
352
353
353 rl_next_input = None
354 rl_next_input = None
354
355
355 def pre_prompt(self):
356 def pre_prompt(self):
356 if self.rl_next_input:
357 if self.rl_next_input:
357 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
358 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
358 self.rl_next_input = None
359 self.rl_next_input = None
359
360
360 def interact(self):
361 def interact(self):
361 while self.keep_running:
362 while self.keep_running:
362 print(self.separate_in, end='')
363 print(self.separate_in, end='')
363
364
364 try:
365 try:
365 code = self.prompt_for_code()
366 code = self.prompt_for_code()
366 except EOFError:
367 except EOFError:
367 if (not self.confirm_exit) \
368 if (not self.confirm_exit) \
368 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
369 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
369 self.ask_exit()
370 self.ask_exit()
370
371
371 else:
372 else:
372 if code:
373 if code:
373 self.run_cell(code, store_history=True)
374 self.run_cell(code, store_history=True)
374 if self.autoedit_syntax and self.SyntaxTB.last_syntax_error:
375 if self.autoedit_syntax and self.SyntaxTB.last_syntax_error:
375 self.edit_syntax_error()
376 self.edit_syntax_error()
376
377
377 def mainloop(self):
378 def mainloop(self):
378 # An extra layer of protection in case someone mashing Ctrl-C breaks
379 # An extra layer of protection in case someone mashing Ctrl-C breaks
379 # out of our internal code.
380 # out of our internal code.
380 while True:
381 while True:
381 try:
382 try:
382 self.interact()
383 self.interact()
383 break
384 break
384 except KeyboardInterrupt:
385 except KeyboardInterrupt:
385 print("\nKeyboardInterrupt escaped interact()\n")
386 print("\nKeyboardInterrupt escaped interact()\n")
386
387
387 _inputhook = None
388 _inputhook = None
388 def inputhook(self, context):
389 def inputhook(self, context):
389 if self._inputhook is not None:
390 if self._inputhook is not None:
390 self._inputhook(context)
391 self._inputhook(context)
391
392
392 def enable_gui(self, gui=None):
393 def enable_gui(self, gui=None):
393 if gui:
394 if gui:
394 self._inputhook = get_inputhook_func(gui)
395 self._inputhook = get_inputhook_func(gui)
395 else:
396 else:
396 self._inputhook = None
397 self._inputhook = None
397
398
398 # Methods to support auto-editing of SyntaxErrors:
399 # Methods to support auto-editing of SyntaxErrors:
399
400
400 def edit_syntax_error(self):
401 def edit_syntax_error(self):
401 """The bottom half of the syntax error handler called in the main loop.
402 """The bottom half of the syntax error handler called in the main loop.
402
403
403 Loop until syntax error is fixed or user cancels.
404 Loop until syntax error is fixed or user cancels.
404 """
405 """
405
406
406 while self.SyntaxTB.last_syntax_error:
407 while self.SyntaxTB.last_syntax_error:
407 # copy and clear last_syntax_error
408 # copy and clear last_syntax_error
408 err = self.SyntaxTB.clear_err_state()
409 err = self.SyntaxTB.clear_err_state()
409 if not self._should_recompile(err):
410 if not self._should_recompile(err):
410 return
411 return
411 try:
412 try:
412 # may set last_syntax_error again if a SyntaxError is raised
413 # may set last_syntax_error again if a SyntaxError is raised
413 self.safe_execfile(err.filename, self.user_ns)
414 self.safe_execfile(err.filename, self.user_ns)
414 except:
415 except:
415 self.showtraceback()
416 self.showtraceback()
416 else:
417 else:
417 try:
418 try:
418 with open(err.filename) as f:
419 with open(err.filename) as f:
419 # This should be inside a display_trap block and I
420 # This should be inside a display_trap block and I
420 # think it is.
421 # think it is.
421 sys.displayhook(f.read())
422 sys.displayhook(f.read())
422 except:
423 except:
423 self.showtraceback()
424 self.showtraceback()
424
425
425 def _should_recompile(self, e):
426 def _should_recompile(self, e):
426 """Utility routine for edit_syntax_error"""
427 """Utility routine for edit_syntax_error"""
427
428
428 if e.filename in ('<ipython console>', '<input>', '<string>',
429 if e.filename in ('<ipython console>', '<input>', '<string>',
429 '<console>', '<BackgroundJob compilation>',
430 '<console>', '<BackgroundJob compilation>',
430 None):
431 None):
431 return False
432 return False
432 try:
433 try:
433 if (self.autoedit_syntax and
434 if (self.autoedit_syntax and
434 not self.ask_yes_no(
435 not self.ask_yes_no(
435 'Return to editor to correct syntax error? '
436 'Return to editor to correct syntax error? '
436 '[Y/n] ', 'y')):
437 '[Y/n] ', 'y')):
437 return False
438 return False
438 except EOFError:
439 except EOFError:
439 return False
440 return False
440
441
441 def int0(x):
442 def int0(x):
442 try:
443 try:
443 return int(x)
444 return int(x)
444 except TypeError:
445 except TypeError:
445 return 0
446 return 0
446
447
447 # always pass integer line and offset values to editor hook
448 # always pass integer line and offset values to editor hook
448 try:
449 try:
449 self.hooks.fix_error_editor(e.filename,
450 self.hooks.fix_error_editor(e.filename,
450 int0(e.lineno), int0(e.offset),
451 int0(e.lineno), int0(e.offset),
451 e.msg)
452 e.msg)
452 except TryNext:
453 except TryNext:
453 warn('Could not open editor')
454 warn('Could not open editor')
454 return False
455 return False
455 return True
456 return True
456
457
457 # Run !system commands directly, not through pipes, so terminal programs
458 # Run !system commands directly, not through pipes, so terminal programs
458 # work correctly.
459 # work correctly.
459 system = InteractiveShell.system_raw
460 system = InteractiveShell.system_raw
460
461
461
462
462 if __name__ == '__main__':
463 if __name__ == '__main__':
463 TerminalInteractiveShell.instance().interact()
464 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now