##// END OF EJS Templates
Update a few observer and .tag(config=True) to match new traitlets API.
Matthias Bussonnier -
Show More
@@ -1,311 +1,314 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for managing IPython profiles.
4 4
5 5 To be invoked as the `ipython profile` subcommand.
6 6
7 7 Authors:
8 8
9 9 * Min RK
10 10
11 11 """
12 12 from __future__ import print_function
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2008 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-----------------------------------------------------------------------------
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Imports
23 23 #-----------------------------------------------------------------------------
24 24
25 25 import os
26 26
27 27 from traitlets.config.application import Application
28 28 from IPython.core.application import (
29 29 BaseIPythonApplication, base_flags
30 30 )
31 31 from IPython.core.profiledir import ProfileDir
32 32 from IPython.utils.importstring import import_item
33 33 from IPython.paths import get_ipython_dir, get_ipython_package_dir
34 34 from IPython.utils import py3compat
35 from traitlets import Unicode, Bool, Dict
35 from traitlets import Unicode, Bool, Dict, observe
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Constants
39 39 #-----------------------------------------------------------------------------
40 40
41 41 create_help = """Create an IPython profile by name
42 42
43 43 Create an ipython profile directory by its name or
44 44 profile directory path. Profile directories contain
45 45 configuration, log and security related files and are named
46 46 using the convention 'profile_<name>'. By default they are
47 47 located in your ipython directory. Once created, you will
48 48 can edit the configuration files in the profile
49 49 directory to configure IPython. Most users will create a
50 50 profile directory by name,
51 51 `ipython profile create myprofile`, which will put the directory
52 52 in `<ipython_dir>/profile_myprofile`.
53 53 """
54 54 list_help = """List available IPython profiles
55 55
56 56 List all available profiles, by profile location, that can
57 57 be found in the current working directly or in the ipython
58 58 directory. Profile directories are named using the convention
59 59 'profile_<profile>'.
60 60 """
61 61 profile_help = """Manage IPython profiles
62 62
63 63 Profile directories contain
64 64 configuration, log and security related files and are named
65 65 using the convention 'profile_<name>'. By default they are
66 66 located in your ipython directory. You can create profiles
67 67 with `ipython profile create <name>`, or see the profiles you
68 68 already have with `ipython profile list`
69 69
70 70 To get started configuring IPython, simply do:
71 71
72 72 $> ipython profile create
73 73
74 74 and IPython will create the default profile in <ipython_dir>/profile_default,
75 75 where you can edit ipython_config.py to start configuring IPython.
76 76
77 77 """
78 78
79 79 _list_examples = "ipython profile list # list all profiles"
80 80
81 81 _create_examples = """
82 82 ipython profile create foo # create profile foo w/ default config files
83 83 ipython profile create foo --reset # restage default config files over current
84 84 ipython profile create foo --parallel # also stage parallel config files
85 85 """
86 86
87 87 _main_examples = """
88 88 ipython profile create -h # show the help string for the create subcommand
89 89 ipython profile list -h # show the help string for the list subcommand
90 90
91 91 ipython locate profile foo # print the path to the directory for profile 'foo'
92 92 """
93 93
94 94 #-----------------------------------------------------------------------------
95 95 # Profile Application Class (for `ipython profile` subcommand)
96 96 #-----------------------------------------------------------------------------
97 97
98 98
99 99 def list_profiles_in(path):
100 100 """list profiles in a given root directory"""
101 101 files = os.listdir(path)
102 102 profiles = []
103 103 for f in files:
104 104 try:
105 105 full_path = os.path.join(path, f)
106 106 except UnicodeError:
107 107 continue
108 108 if os.path.isdir(full_path) and f.startswith('profile_'):
109 109 profiles.append(f.split('_',1)[-1])
110 110 return profiles
111 111
112 112
113 113 def list_bundled_profiles():
114 114 """list profiles that are bundled with IPython."""
115 115 path = os.path.join(get_ipython_package_dir(), u'core', u'profile')
116 116 files = os.listdir(path)
117 117 profiles = []
118 118 for profile in files:
119 119 full_path = os.path.join(path, profile)
120 120 if os.path.isdir(full_path) and profile != "__pycache__":
121 121 profiles.append(profile)
122 122 return profiles
123 123
124 124
125 125 class ProfileLocate(BaseIPythonApplication):
126 126 description = """print the path to an IPython profile dir"""
127 127
128 128 def parse_command_line(self, argv=None):
129 129 super(ProfileLocate, self).parse_command_line(argv)
130 130 if self.extra_args:
131 131 self.profile = self.extra_args[0]
132 132
133 133 def start(self):
134 134 print(self.profile_dir.location)
135 135
136 136
137 137 class ProfileList(Application):
138 138 name = u'ipython-profile'
139 139 description = list_help
140 140 examples = _list_examples
141 141
142 142 aliases = Dict({
143 143 'ipython-dir' : 'ProfileList.ipython_dir',
144 144 'log-level' : 'Application.log_level',
145 145 })
146 146 flags = Dict(dict(
147 147 debug = ({'Application' : {'log_level' : 0}},
148 148 "Set Application.log_level to 0, maximizing log output."
149 149 )
150 150 ))
151 151
152 ipython_dir = Unicode(get_ipython_dir(), config=True,
152 ipython_dir = Unicode(get_ipython_dir(),
153 153 help="""
154 154 The name of the IPython directory. This directory is used for logging
155 155 configuration (through profiles), history storage, etc. The default
156 156 is usually $HOME/.ipython. This options can also be specified through
157 157 the environment variable IPYTHONDIR.
158 158 """
159 )
159 ).tag(config=True)
160 160
161 161
162 162 def _print_profiles(self, profiles):
163 163 """print list of profiles, indented."""
164 164 for profile in profiles:
165 165 print(' %s' % profile)
166 166
167 167 def list_profile_dirs(self):
168 168 profiles = list_bundled_profiles()
169 169 if profiles:
170 170 print()
171 171 print("Available profiles in IPython:")
172 172 self._print_profiles(profiles)
173 173 print()
174 174 print(" The first request for a bundled profile will copy it")
175 175 print(" into your IPython directory (%s)," % self.ipython_dir)
176 176 print(" where you can customize it.")
177 177
178 178 profiles = list_profiles_in(self.ipython_dir)
179 179 if profiles:
180 180 print()
181 181 print("Available profiles in %s:" % self.ipython_dir)
182 182 self._print_profiles(profiles)
183 183
184 184 profiles = list_profiles_in(py3compat.getcwd())
185 185 if profiles:
186 186 print()
187 187 print("Available profiles in current directory (%s):" % py3compat.getcwd())
188 188 self._print_profiles(profiles)
189 189
190 190 print()
191 191 print("To use any of the above profiles, start IPython with:")
192 192 print(" ipython --profile=<name>")
193 193 print()
194 194
195 195 def start(self):
196 196 self.list_profile_dirs()
197 197
198 198
199 199 create_flags = {}
200 200 create_flags.update(base_flags)
201 201 # don't include '--init' flag, which implies running profile create in other apps
202 202 create_flags.pop('init')
203 203 create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
204 204 "reset config files in this profile to the defaults.")
205 205 create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
206 206 "Include the config files for parallel "
207 207 "computing apps (ipengine, ipcontroller, etc.)")
208 208
209 209
210 210 class ProfileCreate(BaseIPythonApplication):
211 211 name = u'ipython-profile'
212 212 description = create_help
213 213 examples = _create_examples
214 auto_create = Bool(True, config=False)
214 auto_create = Bool(True)
215 215 def _log_format_default(self):
216 216 return "[%(name)s] %(message)s"
217 217
218 218 def _copy_config_files_default(self):
219 219 return True
220 220
221 parallel = Bool(False, config=True,
222 help="whether to include parallel computing config files")
221 parallel = Bool(False,
222 help="whether to include parallel computing config files"
223 ).tag(config=True)
224
225 @observe('parallel')
223 226 def _parallel_changed(self, name, old, new):
224 227 parallel_files = [ 'ipcontroller_config.py',
225 228 'ipengine_config.py',
226 229 'ipcluster_config.py'
227 230 ]
228 231 if new:
229 232 for cf in parallel_files:
230 233 self.config_files.append(cf)
231 234 else:
232 235 for cf in parallel_files:
233 236 if cf in self.config_files:
234 237 self.config_files.remove(cf)
235 238
236 239 def parse_command_line(self, argv):
237 240 super(ProfileCreate, self).parse_command_line(argv)
238 241 # accept positional arg as profile name
239 242 if self.extra_args:
240 243 self.profile = self.extra_args[0]
241 244
242 245 flags = Dict(create_flags)
243 246
244 247 classes = [ProfileDir]
245 248
246 249 def _import_app(self, app_path):
247 250 """import an app class"""
248 251 app = None
249 252 name = app_path.rsplit('.', 1)[-1]
250 253 try:
251 254 app = import_item(app_path)
252 255 except ImportError:
253 256 self.log.info("Couldn't import %s, config file will be excluded", name)
254 257 except Exception:
255 258 self.log.warning('Unexpected error importing %s', name, exc_info=True)
256 259 return app
257 260
258 261 def init_config_files(self):
259 262 super(ProfileCreate, self).init_config_files()
260 263 # use local imports, since these classes may import from here
261 264 from IPython.terminal.ipapp import TerminalIPythonApp
262 265 apps = [TerminalIPythonApp]
263 266 for app_path in (
264 267 'ipykernel.kernelapp.IPKernelApp',
265 268 ):
266 269 app = self._import_app(app_path)
267 270 if app is not None:
268 271 apps.append(app)
269 272 if self.parallel:
270 273 from ipyparallel.apps.ipcontrollerapp import IPControllerApp
271 274 from ipyparallel.apps.ipengineapp import IPEngineApp
272 275 from ipyparallel.apps.ipclusterapp import IPClusterStart
273 276 apps.extend([
274 277 IPControllerApp,
275 278 IPEngineApp,
276 279 IPClusterStart,
277 280 ])
278 281 for App in apps:
279 282 app = App()
280 283 app.config.update(self.config)
281 284 app.log = self.log
282 285 app.overwrite = self.overwrite
283 286 app.copy_config_files=True
284 287 app.ipython_dir=self.ipython_dir
285 288 app.profile_dir=self.profile_dir
286 289 app.init_config_files()
287 290
288 291 def stage_default_config_file(self):
289 292 pass
290 293
291 294
292 295 class ProfileApp(Application):
293 296 name = u'ipython profile'
294 297 description = profile_help
295 298 examples = _main_examples
296 299
297 300 subcommands = Dict(dict(
298 301 create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
299 302 list = (ProfileList, ProfileList.description.splitlines()[0]),
300 303 locate = (ProfileLocate, ProfileLocate.description.splitlines()[0]),
301 304 ))
302 305
303 306 def start(self):
304 307 if self.subapp is None:
305 308 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
306 309 print()
307 310 self.print_description()
308 311 self.print_subcommands()
309 312 self.exit(1)
310 313 else:
311 314 return self.subapp.start()
@@ -1,409 +1,412 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Classes for handling input/output prompts."""
3 3
4 4 # Copyright (c) 2001-2007 Fernando Perez <fperez@colorado.edu>
5 5 # Copyright (c) IPython Development Team.
6 6 # Distributed under the terms of the Modified BSD License.
7 7
8 8 import os
9 9 import re
10 10 import socket
11 11 import sys
12 12 import time
13 13
14 14 from string import Formatter
15 15
16 16 from traitlets.config.configurable import Configurable
17 17 from IPython.core import release
18 18 from IPython.utils import coloransi, py3compat
19 19 from traitlets import Unicode, Instance, Dict, Bool, Int, observe, default
20 20
21 21 from IPython.utils.PyColorize import LightBGColors, LinuxColors, NoColor
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Color schemes for prompts
25 25 #-----------------------------------------------------------------------------
26 26
27 27 InputColors = coloransi.InputTermColors # just a shorthand
28 28 Colors = coloransi.TermColors # just a shorthand
29 29
30 30 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Utilities
34 34 #-----------------------------------------------------------------------------
35 35
36 36 class LazyEvaluate(object):
37 37 """This is used for formatting strings with values that need to be updated
38 38 at that time, such as the current time or working directory."""
39 39 def __init__(self, func, *args, **kwargs):
40 40 self.func = func
41 41 self.args = args
42 42 self.kwargs = kwargs
43 43
44 44 def __call__(self, **kwargs):
45 45 self.kwargs.update(kwargs)
46 46 return self.func(*self.args, **self.kwargs)
47 47
48 48 def __str__(self):
49 49 return str(self())
50 50
51 51 def __unicode__(self):
52 52 return py3compat.unicode_type(self())
53 53
54 54 def __format__(self, format_spec):
55 55 return format(self(), format_spec)
56 56
57 57 def multiple_replace(dict, text):
58 58 """ Replace in 'text' all occurrences of any key in the given
59 59 dictionary by its corresponding value. Returns the new string."""
60 60
61 61 # Function by Xavier Defrang, originally found at:
62 62 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
63 63
64 64 # Create a regular expression from the dictionary keys
65 65 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
66 66 # For each match, look-up corresponding value in dictionary
67 67 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
68 68
69 69 #-----------------------------------------------------------------------------
70 70 # Special characters that can be used in prompt templates, mainly bash-like
71 71 #-----------------------------------------------------------------------------
72 72
73 73 # If $HOME isn't defined (Windows), make it an absurd string so that it can
74 74 # never be expanded out into '~'. Basically anything which can never be a
75 75 # reasonable directory name will do, we just want the $HOME -> '~' operation
76 76 # to become a no-op. We pre-compute $HOME here so it's not done on every
77 77 # prompt call.
78 78
79 79 # FIXME:
80 80
81 81 # - This should be turned into a class which does proper namespace management,
82 82 # since the prompt specials need to be evaluated in a certain namespace.
83 83 # Currently it's just globals, which need to be managed manually by code
84 84 # below.
85 85
86 86 # - I also need to split up the color schemes from the prompt specials
87 87 # somehow. I don't have a clean design for that quite yet.
88 88
89 89 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
90 90
91 91 # This is needed on FreeBSD, and maybe other systems which symlink /home to
92 92 # /usr/home, but retain the $HOME variable as pointing to /home
93 93 HOME = os.path.realpath(HOME)
94 94
95 95 # We precompute a few more strings here for the prompt_specials, which are
96 96 # fixed once ipython starts. This reduces the runtime overhead of computing
97 97 # prompt strings.
98 98 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
99 99 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
100 100 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
101 101
102 102 # IronPython doesn't currently have os.getuid() even if
103 103 # os.name == 'posix'; 2/8/2014
104 104 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
105 105
106 106 prompt_abbreviations = {
107 107 # Prompt/history count
108 108 '%n' : '{color.number}' '{count}' '{color.prompt}',
109 109 r'\#': '{color.number}' '{count}' '{color.prompt}',
110 110 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
111 111 # can get numbers displayed in whatever color they want.
112 112 r'\N': '{count}',
113 113
114 114 # Prompt/history count, with the actual digits replaced by dots or
115 115 # spaces. Used mainly in continuation prompts (prompt_in2).
116 116 r'\D': '{dots}',
117 117 r'\S': '{spaces}',
118 118
119 119 # Current time
120 120 r'\T' : '{time}',
121 121 # Current working directory
122 122 r'\w': '{cwd}',
123 123 # Basename of current working directory.
124 124 # (use os.sep to make this portable across OSes)
125 125 r'\W' : '{cwd_last}',
126 126 # These X<N> are an extension to the normal bash prompts. They return
127 127 # N terms of the path, after replacing $HOME with '~'
128 128 r'\X0': '{cwd_x[0]}',
129 129 r'\X1': '{cwd_x[1]}',
130 130 r'\X2': '{cwd_x[2]}',
131 131 r'\X3': '{cwd_x[3]}',
132 132 r'\X4': '{cwd_x[4]}',
133 133 r'\X5': '{cwd_x[5]}',
134 134 # Y<N> are similar to X<N>, but they show '~' if it's the directory
135 135 # N+1 in the list. Somewhat like %cN in tcsh.
136 136 r'\Y0': '{cwd_y[0]}',
137 137 r'\Y1': '{cwd_y[1]}',
138 138 r'\Y2': '{cwd_y[2]}',
139 139 r'\Y3': '{cwd_y[3]}',
140 140 r'\Y4': '{cwd_y[4]}',
141 141 r'\Y5': '{cwd_y[5]}',
142 142 # Hostname up to first .
143 143 r'\h': HOSTNAME_SHORT,
144 144 # Full hostname
145 145 r'\H': HOSTNAME,
146 146 # Username of current user
147 147 r'\u': USER,
148 148 # Escaped '\'
149 149 '\\\\': '\\',
150 150 # Newline
151 151 r'\n': '\n',
152 152 # Carriage return
153 153 r'\r': '\r',
154 154 # Release version
155 155 r'\v': release.version,
156 156 # Root symbol ($ or #)
157 157 r'\$': ROOT_SYMBOL,
158 158 }
159 159
160 160 #-----------------------------------------------------------------------------
161 161 # More utilities
162 162 #-----------------------------------------------------------------------------
163 163
164 164 def cwd_filt(depth):
165 165 """Return the last depth elements of the current working directory.
166 166
167 167 $HOME is always replaced with '~'.
168 168 If depth==0, the full path is returned."""
169 169
170 170 cwd = py3compat.getcwd().replace(HOME,"~")
171 171 out = os.sep.join(cwd.split(os.sep)[-depth:])
172 172 return out or os.sep
173 173
174 174 def cwd_filt2(depth):
175 175 """Return the last depth elements of the current working directory.
176 176
177 177 $HOME is always replaced with '~'.
178 178 If depth==0, the full path is returned."""
179 179
180 180 full_cwd = py3compat.getcwd()
181 181 cwd = full_cwd.replace(HOME,"~").split(os.sep)
182 182 if '~' in cwd and len(cwd) == depth+1:
183 183 depth += 1
184 184 drivepart = ''
185 185 if sys.platform == 'win32' and len(cwd) > depth:
186 186 drivepart = os.path.splitdrive(full_cwd)[0]
187 187 out = drivepart + '/'.join(cwd[-depth:])
188 188
189 189 return out or os.sep
190 190
191 191 #-----------------------------------------------------------------------------
192 192 # Prompt classes
193 193 #-----------------------------------------------------------------------------
194 194
195 195 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
196 196 'cwd': LazyEvaluate(py3compat.getcwd),
197 197 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
198 198 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
199 199 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
200 200 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
201 201 }
202 202
203 203 def _lenlastline(s):
204 204 """Get the length of the last line. More intelligent than
205 205 len(s.splitlines()[-1]).
206 206 """
207 207 if not s or s.endswith(('\n', '\r')):
208 208 return 0
209 209 return len(s.splitlines()[-1])
210 210
211 211
212 212 invisible_chars_re = re.compile('\001[^\001\002]*\002')
213 213 def _invisible_characters(s):
214 214 """
215 215 Get the number of invisible ANSI characters in s. Invisible characters
216 216 must be delimited by \001 and \002.
217 217 """
218 218 return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s))
219 219
220 220 class UserNSFormatter(Formatter):
221 221 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
222 222 def __init__(self, shell):
223 223 self.shell = shell
224 224
225 225 def get_value(self, key, args, kwargs):
226 226 # try regular formatting first:
227 227 try:
228 228 return Formatter.get_value(self, key, args, kwargs)
229 229 except Exception:
230 230 pass
231 231 # next, look in user_ns and builtins:
232 232 for container in (self.shell.user_ns, __builtins__):
233 233 if key in container:
234 234 return container[key]
235 235 # nothing found, put error message in its place
236 236 return "<ERROR: '%s' not found>" % key
237 237
238 238
239 239 class PromptManager(Configurable):
240 240 """This is the primary interface for producing IPython's prompts."""
241 241 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
242 242
243 243 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
244 244 color_scheme = Unicode('Linux').tag(config=True)
245 245
246 246 @observe('color_scheme')
247 247 def _color_scheme_changed(self, change):
248 248 self.color_scheme_table.set_active_scheme(change['new'])
249 249 for pname in ['in', 'in2', 'out', 'rewrite']:
250 250 # We need to recalculate the number of invisible characters
251 251 self.update_prompt(pname)
252 252
253 253 lazy_evaluate_fields = Dict(help="""
254 254 This maps field names used in the prompt templates to functions which
255 255 will be called when the prompt is rendered. This allows us to include
256 256 things like the current time in the prompts. Functions are only called
257 257 if they are used in the prompt.
258 258 """)
259 259
260 260 in_template = Unicode('In [\\#]: ',
261 261 help="Input prompt. '\\#' will be transformed to the prompt number"
262 262 ).tag(config=True)
263 263 in2_template = Unicode(' .\\D.: ',
264 264 help="Continuation prompt.").tag(config=True)
265 265 out_template = Unicode('Out[\\#]: ',
266 266 help="Output prompt. '\\#' will be transformed to the prompt number"
267 267 ).tag(config=True)
268 268
269 269 @default('lazy_evaluate_fields')
270 270 def _lazy_evaluate_fields_default(self):
271 271 return lazily_evaluate.copy()
272 272
273 273 justify = Bool(True, help="""
274 274 If True (default), each prompt will be right-aligned with the
275 275 preceding one.
276 276 """).tag(config=True)
277 277
278 278 # We actually store the expanded templates here:
279 279 templates = Dict()
280 280
281 281 # The number of characters in the last prompt rendered, not including
282 282 # colour characters.
283 283 width = Int()
284 284 txtwidth = Int() # Not including right-justification
285 285
286 286 # The number of characters in each prompt which don't contribute to width
287 287 invisible_chars = Dict()
288
288 289 @default('invisible_chars')
289 290 def _invisible_chars_default(self):
290 291 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
291 292
292 293 def __init__(self, shell, **kwargs):
293 294 super(PromptManager, self).__init__(shell=shell, **kwargs)
294 295
295 296 # Prepare colour scheme table
296 297 self.color_scheme_table = coloransi.ColorSchemeTable([NoColor,
297 298 LinuxColors, LightBGColors], self.color_scheme)
298 299
299 300 self._formatter = UserNSFormatter(shell)
300 301 # Prepare templates & numbers of invisible characters
301 302 self.update_prompt('in', self.in_template)
302 303 self.update_prompt('in2', self.in2_template)
303 304 self.update_prompt('out', self.out_template)
304 305 self.update_prompt('rewrite')
305 306 self.observe(self._update_prompt_trait,
306 307 names=['in_template', 'in2_template', 'out_template'])
307 308
308 309 def update_prompt(self, name, new_template=None):
309 310 """This is called when a prompt template is updated. It processes
310 311 abbreviations used in the prompt template (like \#) and calculates how
311 312 many invisible characters (ANSI colour escapes) the resulting prompt
312 313 contains.
313 314
314 315 It is also called for each prompt on changing the colour scheme. In both
315 316 cases, traitlets should take care of calling this automatically.
316 317 """
317 318 if new_template is not None:
318 319 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
319 320 # We count invisible characters (colour escapes) on the last line of the
320 321 # prompt, to calculate the width for lining up subsequent prompts.
321 322 invis_chars = _invisible_characters(self._render(name, color=True))
322 323 self.invisible_chars[name] = invis_chars
323 324
324 def _update_prompt_trait(self, traitname, new_template):
325 def _update_prompt_trait(self, changes):
326 traitname = changes['name']
327 new_template = changes['new']
325 328 name = traitname[:-9] # Cut off '_template'
326 329 self.update_prompt(name, new_template)
327 330
328 331 def _render(self, name, color=True, **kwargs):
329 332 """Render but don't justify, or update the width or txtwidth attributes.
330 333 """
331 334 if name == 'rewrite':
332 335 return self._render_rewrite(color=color)
333 336
334 337 if color:
335 338 scheme = self.color_scheme_table.active_colors
336 339 if name=='out':
337 340 colors = color_lists['normal']
338 341 colors.number, colors.prompt, colors.normal = \
339 342 scheme.out_number, scheme.out_prompt, scheme.normal
340 343 else:
341 344 colors = color_lists['inp']
342 345 colors.number, colors.prompt, colors.normal = \
343 346 scheme.in_number, scheme.in_prompt, scheme.in_normal
344 347 if name=='in2':
345 348 colors.prompt = scheme.in_prompt2
346 349 else:
347 350 # No color
348 351 colors = color_lists['nocolor']
349 352 colors.number, colors.prompt, colors.normal = '', '', ''
350 353
351 354 count = self.shell.execution_count # Shorthand
352 355 # Build the dictionary to be passed to string formatting
353 356 fmtargs = dict(color=colors, count=count,
354 357 dots="."*len(str(count)), spaces=" "*len(str(count)),
355 358 width=self.width, txtwidth=self.txtwidth)
356 359 fmtargs.update(self.lazy_evaluate_fields)
357 360 fmtargs.update(kwargs)
358 361
359 362 # Prepare the prompt
360 363 prompt = colors.prompt + self.templates[name] + colors.normal
361 364
362 365 # Fill in required fields
363 366 return self._formatter.format(prompt, **fmtargs)
364 367
365 368 def _render_rewrite(self, color=True):
366 369 """Render the ---> rewrite prompt."""
367 370 if color:
368 371 scheme = self.color_scheme_table.active_colors
369 372 # We need a non-input version of these escapes
370 373 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
371 374 color_normal = scheme.normal
372 375 else:
373 376 color_prompt, color_normal = '', ''
374 377
375 378 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
376 379
377 380 def render(self, name, color=True, just=None, **kwargs):
378 381 """
379 382 Render the selected prompt.
380 383
381 384 Parameters
382 385 ----------
383 386 name : str
384 387 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
385 388 color : bool
386 389 If True (default), include ANSI escape sequences for a coloured prompt.
387 390 just : bool
388 391 If True, justify the prompt to the width of the last prompt. The
389 392 default is stored in self.justify.
390 393 **kwargs :
391 394 Additional arguments will be passed to the string formatting operation,
392 395 so they can override the values that would otherwise fill in the
393 396 template.
394 397
395 398 Returns
396 399 -------
397 400 A string containing the rendered prompt.
398 401 """
399 402 res = self._render(name, color=color, **kwargs)
400 403
401 404 # Handle justification of prompt
402 405 invis_chars = self.invisible_chars[name] if color else 0
403 406 self.txtwidth = _lenlastline(res) - invis_chars
404 407 just = self.justify if (just is None) else just
405 408 # If the prompt spans more than one line, don't try to justify it:
406 409 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
407 410 res = res.rjust(self.width + invis_chars)
408 411 self.width = _lenlastline(res) - invis_chars
409 412 return res
General Comments 0
You need to be logged in to leave comments. Login now