Skip to content
This repository

Fix for prompts containing newlines. #1105

Merged
merged 2 commits into from over 2 years ago

4 participants

Thomas Kluyver Robert Kern Min RK Fernando Perez
Thomas Kluyver
Collaborator

@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

Robert Kern
Collaborator

Works for me.

Thomas Kluyver
Collaborator

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

Min RK
Owner

works fine for me, thanks.

Fernando Perez
Owner

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.

Thomas Kluyver
Collaborator
Fernando Perez
Owner

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.

Thomas Kluyver
Collaborator

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

Min RK
Owner

nice, looks good to me.

Thomas Kluyver
Collaborator

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

Min RK
Owner

please do, thanks!

Thomas Kluyver takluyver merged commit 96e8539 into from December 08, 2011
Thomas Kluyver takluyver closed this December 08, 2011
Thomas Kluyver
Collaborator

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
This page is out of date. Refresh to see the latest.
22  IPython/core/prompts.py
@@ -232,7 +232,14 @@ def cwd_filt2(depth):
232 232
                             [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
233 233
                    'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
234 234
                    }
235  
-        
  235
+
  236
+def _lenlastline(s):
  237
+    """Get the length of the last line. More intelligent than
  238
+    len(s.splitlines()[-1]).
  239
+    """
  240
+    if not s or s.endswith(('\n', '\r')):
  241
+        return 0
  242
+    return len(s.splitlines()[-1])
236 243
 
237 244
 class PromptManager(Configurable):
238 245
     """This is the primary interface for producing IPython's prompts."""
@@ -305,8 +312,10 @@ def update_prompt(self, name, new_template=None):
305 312
         """
306 313
         if new_template is not None:
307 314
             self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
308  
-        invis_chars = len(self._render(name, color=True)) - \
309  
-                            len(self._render(name, color=False))
  315
+        # We count invisible characters (colour escapes) on the last line of the
  316
+        # prompt, to calculate the width for lining up subsequent prompts.
  317
+        invis_chars = _lenlastline(self._render(name, color=True)) - \
  318
+                        _lenlastline(self._render(name, color=False))
310 319
         self.invisible_chars[name] = invis_chars
311 320
     
312 321
     def _update_prompt_trait(self, traitname, new_template):
@@ -388,9 +397,10 @@ def render(self, name, color=True, just=None, **kwargs):
388 397
         
389 398
         # Handle justification of prompt
390 399
         invis_chars = self.invisible_chars[name] if color else 0
391  
-        self.txtwidth = len(res) - invis_chars
  400
+        self.txtwidth = _lenlastline(res) - invis_chars
392 401
         just = self.justify if (just is None) else just
393  
-        if just:
  402
+        # If the prompt spans more than one line, don't try to justify it:
  403
+        if just and ('\n' not in res) and ('\r' not in res):
394 404
             res = res.rjust(self.width + invis_chars)
395  
-        self.width = len(res) - invis_chars
  405
+        self.width = _lenlastline(res) - invis_chars
396 406
         return res
43  IPython/core/tests/test_prompts.py
... ...
@@ -0,0 +1,43 @@
  1
+"""Tests for prompt generation."""
  2
+
  3
+import unittest
  4
+
  5
+import nose.tools as nt
  6
+
  7
+from IPython.testing import tools as tt, decorators as dec
  8
+from IPython.core.prompts import PromptManager
  9
+from IPython.testing.globalipapp import get_ipython
  10
+
  11
+ip = get_ipython()
  12
+
  13
+
  14
+class PromptTests(unittest.TestCase):
  15
+    def setUp(self):
  16
+        self.pm = PromptManager(shell=ip, config=ip.config)
  17
+    
  18
+    def test_multiline_prompt(self):
  19
+        self.pm.in_template = "[In]\n>>>"
  20
+        self.pm.render('in')
  21
+        self.assertEqual(self.pm.width, 3)
  22
+        self.assertEqual(self.pm.txtwidth, 3)
  23
+        
  24
+        self.pm.in_template = '[In]\n'
  25
+        self.pm.render('in')
  26
+        self.assertEqual(self.pm.width, 0)
  27
+        self.assertEqual(self.pm.txtwidth, 0)
  28
+    
  29
+    def test_translate_abbreviations(self):
  30
+        def do_translate(template):
  31
+            self.pm.in_template = template
  32
+            return self.pm.templates['in']
  33
+        
  34
+        pairs = [(r'%n>', '{color.number}{count}{color.prompt}>'),
  35
+                 (r'\T', '{time}'),
  36
+                 (r'\n', '\n')
  37
+                ]
  38
+    
  39
+        tt.check_pairs(do_translate, pairs)
  40
+    
  41
+    def test_render(self):
  42
+        self.pm.in_template = r'\#>'
  43
+        self.assertEqual(self.pm.render('in',color=False), '%d>' % ip.execution_count)
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.