Skip to content
This repository
Browse code

Merge pull request #1083 from takluyver/prompts

Fixes that finish up the recent PromptManager work:

* InteractiveShell.prompt_foo traits show deprecation warning, and map to new PromptManager traits
* PromptManager properly added to IPython App, so it will show up in config
* add helpstrings to PromptManager traits.
* Docs / embed references to Shell.prompt_foo also updated
* Prompt rewriting in autocall scenarios is now controlled by a boolean, `show_rewritten_input`, attribute of the InteractiveShell.

Closes #1075.
  • Loading branch information...
commit 4b8920a558ddf77639c1eaf5753308442785b6fe 2 parents c309789 + ff4cb84
Fernando Perez fperez authored
42 IPython/core/interactiveshell.py
@@ -315,10 +315,41 @@ def _exiter_default(self):
315 315 help="Save multi-line entries as one entry in readline history"
316 316 )
317 317
318   - prompt_in1 = Unicode('In [\\#]: ', config=True)
319   - prompt_in2 = Unicode(' .\\D.: ', config=True)
320   - prompt_out = Unicode('Out[\\#]: ', config=True)
321   - prompts_pad_left = CBool(True, config=True)
  318 + # deprecated prompt traits:
  319 +
  320 + prompt_in1 = Unicode('In [\\#]: ', config=True,
  321 + help="Deprecated, use PromptManager.in_template")
  322 + prompt_in2 = Unicode(' .\\D.: ', config=True,
  323 + help="Deprecated, use PromptManager.in2_template")
  324 + prompt_out = Unicode('Out[\\#]: ', config=True,
  325 + help="Deprecated, use PromptManager.out_template")
  326 + prompts_pad_left = CBool(True, config=True,
  327 + help="Deprecated, use PromptManager.justify")
  328 +
  329 + def _prompt_trait_changed(self, name, old, new):
  330 + table = {
  331 + 'prompt_in1' : 'in_template',
  332 + 'prompt_in2' : 'in2_template',
  333 + 'prompt_out' : 'out_template',
  334 + 'prompts_pad_left' : 'justify',
  335 + }
  336 + warn("InteractiveShell.{name} is deprecated, use PromptManager.{newname}\n".format(
  337 + name=name, newname=table[name])
  338 + )
  339 + # protect against weird cases where self.config may not exist:
  340 + if self.config is not None:
  341 + # propagate to corresponding PromptManager trait
  342 + setattr(self.config.PromptManager, table[name], new)
  343 +
  344 + _prompt_in1_changed = _prompt_trait_changed
  345 + _prompt_in2_changed = _prompt_trait_changed
  346 + _prompt_out_changed = _prompt_trait_changed
  347 + _prompt_pad_left_changed = _prompt_trait_changed
  348 +
  349 + show_rewritten_input = CBool(True, config=True,
  350 + help="Show rewritten input, e.g. for autocall."
  351 + )
  352 +
322 353 quiet = CBool(False, config=True)
323 354
324 355 history_length = Integer(10000, config=True)
@@ -2141,6 +2172,9 @@ def auto_rewrite_input(self, cmd):
2141 2172 after the user's input prompt. This helps the user understand that the
2142 2173 input line was transformed automatically by IPython.
2143 2174 """
  2175 + if not self.show_rewritten_input:
  2176 + return
  2177 +
2144 2178 rw = self.prompt_manager.render('rewrite') + cmd
2145 2179
2146 2180 try:
96 IPython/core/prompts.py
@@ -254,10 +254,12 @@ def _color_scheme_changed(self, name, new_value):
254 254 """)
255 255 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
256 256
257   - in_template = Unicode('In [\\#]: ', config=True)
258   - in2_template = Unicode(' .\\D.: ', config=True)
259   - out_template = Unicode('Out[\\#]: ', config=True)
260   - rewrite_template = Unicode("------> ", config=True)
  257 + in_template = Unicode('In [\\#]: ', config=True,
  258 + help="Input prompt. '\\#' will be transformed to the prompt number")
  259 + in2_template = Unicode(' .\\D.: ', config=True,
  260 + help="Continuation prompt.")
  261 + out_template = Unicode('Out[\\#]: ', config=True,
  262 + help="Output prompt. '\\#' will be transformed to the prompt number")
261 263
262 264 justify = Bool(True, config=True, help="""
263 265 If True (default), each prompt will be right-aligned with the
@@ -270,11 +272,12 @@ def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
270 272 # The number of characters in the last prompt rendered, not including
271 273 # colour characters.
272 274 width = Int()
  275 + txtwidth = Int() # Not including right-justification
273 276
274 277 # The number of characters in each prompt which don't contribute to width
275 278 invisible_chars = Dict()
276 279 def _invisible_chars_default(self):
277   - return {'in': 0, 'in2': 0, 'out': 0, 'rewrite': 0}
  280 + return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
278 281
279 282 def __init__(self, shell, config=None):
280 283 super(PromptManager, self).__init__(shell=shell, config=config)
@@ -283,13 +286,13 @@ def __init__(self, shell, config=None):
283 286 self.color_scheme_table = coloransi.ColorSchemeTable([PColNoColors,
284 287 PColLinux, PColLightBG], self.color_scheme)
285 288
286   - # Prepare templates
  289 + # Prepare templates & numbers of invisible characters
287 290 self.update_prompt('in', self.in_template)
288 291 self.update_prompt('in2', self.in2_template)
289 292 self.update_prompt('out', self.out_template)
290   - self.update_prompt('rewrite', self.rewrite_template)
  293 + self.update_prompt('rewrite')
291 294 self.on_trait_change(self._update_prompt_trait, ['in_template',
292   - 'in2_template', 'out_template', 'rewrite_template'])
  295 + 'in2_template', 'out_template'])
293 296
294 297 def update_prompt(self, name, new_template=None):
295 298 """This is called when a prompt template is updated. It processes
@@ -302,48 +305,26 @@ def update_prompt(self, name, new_template=None):
302 305 """
303 306 if new_template is not None:
304 307 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
305   - invis_chars = len(self.render(name, color=True, just=False)) - \
306   - len(self.render(name, color=False, just=False))
  308 + invis_chars = len(self._render(name, color=True)) - \
  309 + len(self._render(name, color=False))
307 310 self.invisible_chars[name] = invis_chars
308 311
309 312 def _update_prompt_trait(self, traitname, new_template):
310 313 name = traitname[:-9] # Cut off '_template'
311 314 self.update_prompt(name, new_template)
312 315
313   - def render(self, name, color=True, just=None, **kwargs):
  316 + def _render(self, name, color=True, **kwargs):
  317 + """Render but don't justify, or update the width or txtwidth attributes.
314 318 """
315   - Render the selected prompt.
  319 + if name == 'rewrite':
  320 + return self._render_rewrite(color=color)
316 321
317   - Parameters
318   - ----------
319   - name : str
320   - Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
321   - color : bool
322   - If True (default), include ANSI escape sequences for a coloured prompt.
323   - just : bool
324   - If True, justify the prompt to the width of the last prompt. The
325   - default is stored in self.justify.
326   - **kwargs :
327   - Additional arguments will be passed to the string formatting operation,
328   - so they can override the values that would otherwise fill in the
329   - template.
330   -
331   - Returns
332   - -------
333   - A string containing the rendered prompt.
334   - """
335 322 if color:
336 323 scheme = self.color_scheme_table.active_colors
337 324 if name=='out':
338 325 colors = color_lists['normal']
339 326 colors.number, colors.prompt, colors.normal = \
340 327 scheme.out_number, scheme.out_prompt, scheme.normal
341   - elif name=='rewrite':
342   - colors = color_lists['normal']
343   - # We need a non-input version of these escapes
344   - colors.number = scheme.in_number.replace("\001","").replace("\002","")
345   - colors.prompt = scheme.in_prompt.replace("\001","").replace("\002","")
346   - colors.normal = scheme.normal
347 328 else:
348 329 colors = color_lists['inp']
349 330 colors.number, colors.prompt, colors.normal = \
@@ -358,7 +339,8 @@ def render(self, name, color=True, just=None, **kwargs):
358 339 count = self.shell.execution_count # Shorthand
359 340 # Build the dictionary to be passed to string formatting
360 341 fmtargs = dict(color=colors, count=count,
361   - dots="."*len(str(count)) )
  342 + dots="."*len(str(count)),
  343 + width=self.width, txtwidth=self.txtwidth )
362 344 fmtargs.update(self.lazy_evaluate_fields)
363 345 fmtargs.update(kwargs)
364 346
@@ -366,13 +348,49 @@ def render(self, name, color=True, just=None, **kwargs):
366 348 prompt = colors.prompt + self.templates[name] + colors.normal
367 349
368 350 # Fill in required fields
369   - res = prompt.format(**fmtargs)
  351 + return prompt.format(**fmtargs)
  352 +
  353 + def _render_rewrite(self, color=True):
  354 + """Render the ---> rewrite prompt."""
  355 + if color:
  356 + scheme = self.color_scheme_table.active_colors
  357 + # We need a non-input version of these escapes
  358 + color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
  359 + color_normal = scheme.normal
  360 + else:
  361 + color_prompt, color_normal = '', ''
  362 +
  363 + return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
  364 +
  365 + def render(self, name, color=True, just=None, **kwargs):
  366 + """
  367 + Render the selected prompt.
  368 +
  369 + Parameters
  370 + ----------
  371 + name : str
  372 + Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
  373 + color : bool
  374 + If True (default), include ANSI escape sequences for a coloured prompt.
  375 + just : bool
  376 + If True, justify the prompt to the width of the last prompt. The
  377 + default is stored in self.justify.
  378 + **kwargs :
  379 + Additional arguments will be passed to the string formatting operation,
  380 + so they can override the values that would otherwise fill in the
  381 + template.
  382 +
  383 + Returns
  384 + -------
  385 + A string containing the rendered prompt.
  386 + """
  387 + res = self._render(name, color=color, **kwargs)
370 388
371 389 # Handle justification of prompt
372 390 invis_chars = self.invisible_chars[name] if color else 0
  391 + self.txtwidth = len(res) - invis_chars
373 392 just = self.justify if (just is None) else just
374 393 if just:
375 394 res = res.rjust(self.width + invis_chars)
376 395 self.width = len(res) - invis_chars
377 396 return res
378   -
8 IPython/frontend/terminal/ipapp.py
@@ -38,6 +38,7 @@
38 38 from IPython.core.completer import IPCompleter
39 39 from IPython.core.crashhandler import CrashHandler
40 40 from IPython.core.formatters import PlainTextFormatter
  41 +from IPython.core.prompts import PromptManager
41 42 from IPython.core.application import (
42 43 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
43 44 )
@@ -133,9 +134,9 @@ def make_report(self,traceback):
133 134 classic_config = Config()
134 135 classic_config.InteractiveShell.cache_size = 0
135 136 classic_config.PlainTextFormatter.pprint = False
136   -classic_config.InteractiveShell.prompt_in1 = '>>> '
137   -classic_config.InteractiveShell.prompt_in2 = '... '
138   -classic_config.InteractiveShell.prompt_out = ''
  137 +classic_config.PromptManager.in_template = '>>> '
  138 +classic_config.PromptManager.in2_template = '... '
  139 +classic_config.PromptManager.out_template = ''
139 140 classic_config.InteractiveShell.separate_in = ''
140 141 classic_config.InteractiveShell.separate_out = ''
141 142 classic_config.InteractiveShell.separate_out2 = ''
@@ -197,6 +198,7 @@ def _classes_default(self):
197 198 InteractiveShellApp, # ShellApp comes before TerminalApp, because
198 199 self.__class__, # it will also affect subclasses (e.g. QtConsole)
199 200 TerminalInteractiveShell,
  201 + PromptManager,
200 202 ProfileDir,
201 203 PlainTextFormatter,
202 204 IPCompleter,
6 IPython/testing/tools.py
@@ -202,9 +202,9 @@ def ipexec(fname, options=None):
202 202
203 203 # For these subprocess calls, eliminate all prompt printing so we only see
204 204 # output from script execution
205   - prompt_opts = [ '--InteractiveShell.prompt_in1=""',
206   - '--InteractiveShell.prompt_in2=""',
207   - '--InteractiveShell.prompt_out=""'
  205 + prompt_opts = [ '--PromptManager.in_template=""',
  206 + '--PromptManager.in2_template=""',
  207 + '--PromptManager.out_template=""'
208 208 ]
209 209 cmdargs = ' '.join(default_argv() + prompt_opts + options)
210 210
18 docs/examples/core/example-embed.py
@@ -23,10 +23,10 @@
23 23 except NameError:
24 24 nested = 0
25 25 cfg = Config()
26   - shell_config = cfg.InteractiveShellEmbed
27   - shell_config.prompt_in1 = 'In <\\#>: '
28   - shell_config.prompt_in2 = ' .\\D.: '
29   - shell_config.prompt_out = 'Out<\\#>: '
  26 + prompt_config = cfg.PromptManager
  27 + prompt_config.in_template = 'In <\\#>: '
  28 + prompt_config.in2_template = ' .\\D.: '
  29 + prompt_config.out_template = 'Out<\\#>: '
30 30 else:
31 31 print "Running nested copies of IPython."
32 32 print "The prompts for the nested copy have been modified"
@@ -46,12 +46,12 @@
46 46
47 47 # Make a second instance, you can have as many as you want.
48 48 cfg2 = cfg.copy()
49   -shell_config = cfg2.InteractiveShellEmbed
50   -shell_config.prompt_in1 = 'In2<\\#>: '
  49 +prompt_config = cfg2.PromptManager
  50 +prompt_config.in_template = 'In2<\\#>: '
51 51 if not nested:
52   - shell_config.prompt_in1 = 'In2<\\#>: '
53   - shell_config.prompt_in2 = ' .\\D.: '
54   - shell_config.prompt_out = 'Out<\\#>: '
  52 + prompt_config.in_template = 'In2<\\#>: '
  53 + prompt_config.in2_template = ' .\\D.: '
  54 + prompt_config.out_template = 'Out<\\#>: '
55 55 ipshell2 = InteractiveShellEmbed(config=cfg,
56 56 banner1 = 'Second IPython instance.')
57 57
8 docs/source/interactive/reference.txt
@@ -227,7 +227,7 @@ All options with a [no] prepended can be specified in negated form
227 227 circular file inclusions, IPython will stop if it reaches 15
228 228 recursive inclusions.
229 229
230   - ``InteractiveShell.prompt_in1=<string>``
  230 + ``PromptManager.in_template=<string>``
231 231
232 232 Specify the string used for input prompts. Note that if you are using
233 233 numbered prompts, the number is represented with a '\#' in the
@@ -236,7 +236,7 @@ All options with a [no] prepended can be specified in negated form
236 236 discusses in detail all the available escapes to customize your
237 237 prompts.
238 238
239   - ``InteractiveShell.prompt_in2=<string>``
  239 + ``PromptManager.in2_template=<string>``
240 240 Similar to the previous option, but used for the continuation
241 241 prompts. The special sequence '\D' is similar to '\#', but
242 242 with all digits replaced dots (so you can have your
@@ -244,9 +244,9 @@ All options with a [no] prepended can be specified in negated form
244 244 ' .\D.:' (note three spaces at the start for alignment with
245 245 'In [\#]').
246 246
247   - ``InteractiveShell.prompt_out=<string>``
  247 + ``PromptManager.out_template=<string>``
248 248 String used for output prompts, also uses numbers like
249   - prompt_in1. Default: 'Out[\#]:'
  249 + in_template. Default: 'Out[\#]:'
250 250
251 251 ``--quick``
252 252 start in bare bones mode (no config file loaded).
5 docs/source/interactive/shell.txt
@@ -155,8 +155,9 @@ Prompt customization
155 155
156 156 The sh profile uses the following prompt configurations::
157 157
158   - o.prompt_in1= r'\C_LightBlue[\C_LightCyan\Y2\C_LightBlue]\C_Green|\#>'
159   - o.prompt_in2= r'\C_Green|\C_LightGreen\D\C_Green>'
  158 + c.PromptManager.in_template = r'{color.LightGreen}\u@\h{color.LightBlue}[{color.LightCyan}\Y1{color.LightBlue}]{color.Green}|\#> '
  159 + c.PromptManager.in2_template = r'{color.Green}|{color.LightGreen}\D{color.Green}> '
  160 + c.PromptManager.out_template = r'<\#> '
160 161
161 162 You can change the prompt configuration to your liking by editing
162 163 ipython_config.py.

0 comments on commit 4b8920a

Please sign in to comment.
Something went wrong with that request. Please try again.