Skip to content


Subversion checkout URL

You can clone with
Download ZIP


Fix for prompts containing newlines. #1105

merged 2 commits into from

4 participants


@rkern, I've tried this with the config example you supplied, and it seems to be working OK.

Specifically, we now only justify prompts based on the last line of the preceding prompt, and we don't attempt to justify multiline prompts at all.

Closes gh-1104


Works for me.


Great, thanks. I'll give the others a day or two to make any comments, then merge it.


works fine for me, thanks.


Glad to hear the fixes work correctly. I thought for a bit about what kind of automated test we could add to ensure this doesn't return to bite us later, but I can't quite seem to see a quick way to make one. I figured we could do one involving creating an irunner with custom prompts, running a session through it and validating back the output, but it sounds like a fair amount of hassle and I'm not sure the effort/payoff is worth it.

If you can think of a way to test this that isn't too painful to implement, go ahead and do it, otherwise merge as-is.


Sure :) We've made good progress on the other PRs today, so we can sit on this one for a few days before the 0.12 rc.


I've added a few simple tests, and checked that they're passing.


nice, looks good to me.


Great. I'll merge it later today unless anyone objects.


please do, thanks!

@takluyver takluyver merged commit 96e8539 into ipython:master

Rebased to avoid a merge, and pushed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 8, 2011
  1. @takluyver
  2. @takluyver
This page is out of date. Refresh to see the latest.
Showing with 59 additions and 6 deletions.
  1. +16 −6 IPython/core/
  2. +43 −0 IPython/core/tests/
22 IPython/core/
@@ -232,7 +232,14 @@ def cwd_filt2(depth):
[LazyEvaluate(cwd_filt, x) for x in range(1,6)],
'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
+def _lenlastline(s):
+ """Get the length of the last line. More intelligent than
+ len(s.splitlines()[-1]).
+ """
+ if not s or s.endswith(('\n', '\r')):
+ return 0
+ return len(s.splitlines()[-1])
class PromptManager(Configurable):
"""This is the primary interface for producing IPython's prompts."""
@@ -305,8 +312,10 @@ def update_prompt(self, name, new_template=None):
if new_template is not None:
self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
- invis_chars = len(self._render(name, color=True)) - \
- len(self._render(name, color=False))
+ # We count invisible characters (colour escapes) on the last line of the
+ # prompt, to calculate the width for lining up subsequent prompts.
+ invis_chars = _lenlastline(self._render(name, color=True)) - \
+ _lenlastline(self._render(name, color=False))
self.invisible_chars[name] = invis_chars
def _update_prompt_trait(self, traitname, new_template):
@@ -388,9 +397,10 @@ def render(self, name, color=True, just=None, **kwargs):
# Handle justification of prompt
invis_chars = self.invisible_chars[name] if color else 0
- self.txtwidth = len(res) - invis_chars
+ self.txtwidth = _lenlastline(res) - invis_chars
just = self.justify if (just is None) else just
- if just:
+ # If the prompt spans more than one line, don't try to justify it:
+ if just and ('\n' not in res) and ('\r' not in res):
res = res.rjust(self.width + invis_chars)
- self.width = len(res) - invis_chars
+ self.width = _lenlastline(res) - invis_chars
return res
43 IPython/core/tests/
@@ -0,0 +1,43 @@
+"""Tests for prompt generation."""
+import unittest
+import as nt
+from IPython.testing import tools as tt, decorators as dec
+from IPython.core.prompts import PromptManager
+from IPython.testing.globalipapp import get_ipython
+ip = get_ipython()
+class PromptTests(unittest.TestCase):
+ def setUp(self):
+ = PromptManager(shell=ip, config=ip.config)
+ def test_multiline_prompt(self):
+ = "[In]\n>>>"
+ self.assertEqual(, 3)
+ self.assertEqual(, 3)
+ = '[In]\n'
+ self.assertEqual(, 0)
+ self.assertEqual(, 0)
+ def test_translate_abbreviations(self):
+ def do_translate(template):
+ = template
+ return['in']
+ pairs = [(r'%n>', '{color.number}{count}{color.prompt}>'),
+ (r'\T', '{time}'),
+ (r'\n', '\n')
+ ]
+ tt.check_pairs(do_translate, pairs)
+ def test_render(self):
+ = r'\#>'
+ self.assertEqual('in',color=False), '%d>' % ip.execution_count)
Something went wrong with that request. Please try again.