##// END OF EJS Templates
Fix a couple of edge cases, and update tests...
Matthias Bussonnier -
Show More
@@ -1,113 +1,114 b''
1 # http://travis-ci.org/#!/ipython/ipython
1 # http://travis-ci.org/#!/ipython/ipython
2 language: python
2 language: python
3 os: linux
3 os: linux
4
4
5 addons:
5 addons:
6 apt:
6 apt:
7 packages:
7 packages:
8 - graphviz
8 - graphviz
9
9
10 python:
10 python:
11 - 3.6
11 - 3.6
12
12
13 sudo: false
13 sudo: false
14
14
15 env:
15 env:
16 global:
16 global:
17 - PATH=$TRAVIS_BUILD_DIR/pandoc:$PATH
17 - PATH=$TRAVIS_BUILD_DIR/pandoc:$PATH
18
18
19 group: edge
19 group: edge
20
20
21 before_install:
21 before_install:
22 - |
22 - |
23 # install Python on macOS
23 # install Python on macOS
24 if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
24 if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
25 env | sort
25 env | sort
26 if ! which python$TRAVIS_PYTHON_VERSION; then
26 if ! which python$TRAVIS_PYTHON_VERSION; then
27 HOMEBREW_NO_AUTO_UPDATE=1 brew tap minrk/homebrew-python-frameworks
27 HOMEBREW_NO_AUTO_UPDATE=1 brew tap minrk/homebrew-python-frameworks
28 HOMEBREW_NO_AUTO_UPDATE=1 brew cask install python-framework-${TRAVIS_PYTHON_VERSION/./}
28 HOMEBREW_NO_AUTO_UPDATE=1 brew cask install python-framework-${TRAVIS_PYTHON_VERSION/./}
29 fi
29 fi
30 python3 -m pip install virtualenv
30 python3 -m pip install virtualenv
31 python3 -m virtualenv -p $(which python$TRAVIS_PYTHON_VERSION) ~/travis-env
31 python3 -m virtualenv -p $(which python$TRAVIS_PYTHON_VERSION) ~/travis-env
32 source ~/travis-env/bin/activate
32 source ~/travis-env/bin/activate
33 fi
33 fi
34 - python --version
34 - python --version
35
35
36 install:
36 install:
37 - pip install pip --upgrade
37 - pip install pip --upgrade
38 - pip install setuptools --upgrade
38 - pip install setuptools --upgrade
39 - pip install -e file://$PWD#egg=ipython[test] --upgrade
39 - pip install -e file://$PWD#egg=ipython[test] --upgrade
40 - pip install trio curio --upgrade --upgrade-strategy eager
40 - pip install trio curio --upgrade --upgrade-strategy eager
41 - pip install pytest 'matplotlib !=3.2.0'
41 - pip install pytest 'matplotlib !=3.2.0' mypy
42 - pip install codecov check-manifest --upgrade
42 - pip install codecov check-manifest --upgrade
43
43
44 script:
44 script:
45 - check-manifest
45 - check-manifest
46 - |
46 - |
47 if [[ "$TRAVIS_PYTHON_VERSION" == "nightly" ]]; then
47 if [[ "$TRAVIS_PYTHON_VERSION" == "nightly" ]]; then
48 # on nightly fake parso known the grammar
48 # on nightly fake parso known the grammar
49 cp /home/travis/virtualenv/python3.8-dev/lib/python3.8/site-packages/parso/python/grammar37.txt /home/travis/virtualenv/python3.8-dev/lib/python3.8/site-packages/parso/python/grammar38.txt
49 cp /home/travis/virtualenv/python3.8-dev/lib/python3.8/site-packages/parso/python/grammar37.txt /home/travis/virtualenv/python3.8-dev/lib/python3.8/site-packages/parso/python/grammar38.txt
50 fi
50 fi
51 - cd /tmp && iptest --coverage xml && cd -
51 - cd /tmp && iptest --coverage xml && cd -
52 - pytest IPython
52 - pytest IPython
53 - mypy --ignore-missing-imports -m IPython.terminal.ptutils
53 # On the latest Python (on Linux) only, make sure that the docs build.
54 # On the latest Python (on Linux) only, make sure that the docs build.
54 - |
55 - |
55 if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
56 if [[ "$TRAVIS_PYTHON_VERSION" == "3.7" ]] && [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
56 pip install -r docs/requirements.txt
57 pip install -r docs/requirements.txt
57 python tools/fixup_whats_new_pr.py
58 python tools/fixup_whats_new_pr.py
58 make -C docs/ html SPHINXOPTS="-W"
59 make -C docs/ html SPHINXOPTS="-W"
59 fi
60 fi
60
61
61 after_success:
62 after_success:
62 - cp /tmp/ipy_coverage.xml ./
63 - cp /tmp/ipy_coverage.xml ./
63 - cp /tmp/.coverage ./
64 - cp /tmp/.coverage ./
64 - codecov
65 - codecov
65
66
66 matrix:
67 matrix:
67 include:
68 include:
68 - arch: amd64
69 - arch: amd64
69 python: "3.7"
70 python: "3.7"
70 dist: xenial
71 dist: xenial
71 sudo: true
72 sudo: true
72 - arch: amd64
73 - arch: amd64
73 python: "3.8"
74 python: "3.8"
74 dist: xenial
75 dist: xenial
75 sudo: true
76 sudo: true
76 - arch: amd64
77 - arch: amd64
77 python: "nightly"
78 python: "nightly"
78 dist: xenial
79 dist: xenial
79 sudo: true
80 sudo: true
80 - arch: arm64
81 - arch: arm64
81 python: "nightly"
82 python: "nightly"
82 dist: bionic
83 dist: bionic
83 env: ARM64=True
84 env: ARM64=True
84 sudo: true
85 sudo: true
85 - os: osx
86 - os: osx
86 language: generic
87 language: generic
87 python: 3.6
88 python: 3.6
88 env: TRAVIS_PYTHON_VERSION=3.6
89 env: TRAVIS_PYTHON_VERSION=3.6
89 - os: osx
90 - os: osx
90 language: generic
91 language: generic
91 python: 3.7
92 python: 3.7
92 env: TRAVIS_PYTHON_VERSION=3.7
93 env: TRAVIS_PYTHON_VERSION=3.7
93 allow_failures:
94 allow_failures:
94 - python: nightly
95 - python: nightly
95
96
96 before_deploy:
97 before_deploy:
97 - rm -rf dist/
98 - rm -rf dist/
98 - python setup.py sdist
99 - python setup.py sdist
99 - python setup.py bdist_wheel
100 - python setup.py bdist_wheel
100
101
101 deploy:
102 deploy:
102 provider: releases
103 provider: releases
103 api_key:
104 api_key:
104 secure: Y/Ae9tYs5aoBU8bDjN2YrwGG6tCbezj/h3Lcmtx8HQavSbBgXnhnZVRb2snOKD7auqnqjfT/7QMm4ZyKvaOEgyggGktKqEKYHC8KOZ7yp8I5/UMDtk6j9TnXpSqqBxPiud4MDV76SfRYEQiaDoG4tGGvSfPJ9KcNjKrNvSyyxns=
105 secure: Y/Ae9tYs5aoBU8bDjN2YrwGG6tCbezj/h3Lcmtx8HQavSbBgXnhnZVRb2snOKD7auqnqjfT/7QMm4ZyKvaOEgyggGktKqEKYHC8KOZ7yp8I5/UMDtk6j9TnXpSqqBxPiud4MDV76SfRYEQiaDoG4tGGvSfPJ9KcNjKrNvSyyxns=
105 file: dist/*
106 file: dist/*
106 file_glob: true
107 file_glob: true
107 skip_cleanup: true
108 skip_cleanup: true
108 on:
109 on:
109 repo: ipython/ipython
110 repo: ipython/ipython
110 all_branches: true # Backports are released from e.g. 5.x branch
111 all_branches: true # Backports are released from e.g. 5.x branch
111 tags: true
112 tags: true
112 python: 3.6 # Any version should work, but we only need one
113 python: 3.6 # Any version should work, but we only need one
113 condition: $TRAVIS_OS_NAME = "linux"
114 condition: $TRAVIS_OS_NAME = "linux"
@@ -1,190 +1,192 b''
1 """prompt-toolkit utilities
1 """prompt-toolkit utilities
2
2
3 Everything in this module is a private API,
3 Everything in this module is a private API,
4 not to be used outside IPython.
4 not to be used outside IPython.
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 import unicodedata
10 import unicodedata
11 from wcwidth import wcwidth
11 from wcwidth import wcwidth
12
12
13 from IPython.core.completer import (
13 from IPython.core.completer import (
14 provisionalcompleter, cursor_to_position,
14 provisionalcompleter, cursor_to_position,
15 _deduplicate_completions)
15 _deduplicate_completions)
16 from prompt_toolkit.completion import Completer, Completion
16 from prompt_toolkit.completion import Completer, Completion
17 from prompt_toolkit.lexers import Lexer
17 from prompt_toolkit.lexers import Lexer
18 from prompt_toolkit.lexers import PygmentsLexer
18 from prompt_toolkit.lexers import PygmentsLexer
19 from prompt_toolkit.patch_stdout import patch_stdout
19 from prompt_toolkit.patch_stdout import patch_stdout
20
20
21 import pygments.lexers as pygments_lexers
21 import pygments.lexers as pygments_lexers
22 import os
22 import os
23
23
24 _completion_sentinel = object()
24 _completion_sentinel = object()
25
25
26 def _elide_point(string, *, min_elide=30):
26 def _elide_point(string:str, *, min_elide=30)->str:
27 """
27 """
28 If a string is long enough, and has at least 3 dots,
28 If a string is long enough, and has at least 3 dots,
29 replace the middle part with ellipses.
29 replace the middle part with ellipses.
30
30
31 If a string naming a file is long enough, and has at least 3 slashes,
31 If a string naming a file is long enough, and has at least 3 slashes,
32 replace the middle part with ellipses.
32 replace the middle part with ellipses.
33
33
34 If three consecutive dots, or two consecutive dots are encountered these are
34 If three consecutive dots, or two consecutive dots are encountered these are
35 replaced by the equivalents HORIZONTAL ELLIPSIS or TWO DOT LEADER unicode
35 replaced by the equivalents HORIZONTAL ELLIPSIS or TWO DOT LEADER unicode
36 equivalents
36 equivalents
37 """
37 """
38 string = string.replace('...','\N{HORIZONTAL ELLIPSIS}')
38 string = string.replace('...','\N{HORIZONTAL ELLIPSIS}')
39 string = string.replace('..','\N{TWO DOT LEADER}')
39 string = string.replace('..','\N{TWO DOT LEADER}')
40 if len(string) < min_elide:
40 if len(string) < min_elide:
41 return string
41 return string
42
42
43 object_parts = string.split('.')
43 object_parts = string.split('.')
44 file_parts = string.split(os.sep)
44 file_parts = string.split(os.sep)
45 if file_parts[-1] == '':
45 if file_parts[-1] == '':
46 file_parts.pop()
46 file_parts.pop()
47
47
48 if len(object_parts) > 3:
48 if len(object_parts) > 3:
49 return '{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}'.format(object_parts[0], object_parts[1][0], object_parts[-2][-1], object_parts[-1])
49 return '{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}'.format(object_parts[0], object_parts[1][0], object_parts[-2][-1], object_parts[-1])
50
50
51 elif len(file_parts) > 3:
51 elif len(file_parts) > 3:
52 return ('{}' + os.sep + '{}\N{HORIZONTAL ELLIPSIS}{}' + os.sep + '{}').format(file_parts[0], file_parts[1][0], file_parts[-2][-1], file_parts[-1])
52 return ('{}' + os.sep + '{}\N{HORIZONTAL ELLIPSIS}{}' + os.sep + '{}').format(file_parts[0], file_parts[1][0], file_parts[-2][-1], file_parts[-1])
53
53
54 return string
54 return string
55
55
56 def _elide_typed(string, typed, *, min_elide=30):
56 def _elide_typed(string:str, typed:str, *, min_elide:int=30)->str:
57 """
57 """
58 Elide the middle of a long string if the beginning has already been typed.
58 Elide the middle of a long string if the beginning has already been typed.
59 """
59 """
60
60
61 if len(string) < min_elide:
61 if len(string) < min_elide:
62 return string
62 return string
63 cut_how_much = len(typed)-3
63 cut_how_much = len(typed)-3
64 if cut_how_much < 7:
65 return string
64 if string.startswith(typed) and len(string)> len(typed):
66 if string.startswith(typed) and len(string)> len(typed):
65 return f"{string[:3]}\N{HORIZONTAL ELLIPSIS}{string[cut_how_much:]}"
67 return f"{string[:3]}\N{HORIZONTAL ELLIPSIS}{string[cut_how_much:]}"
66 return string
68 return string
67
69
68 def _elide(string, typed, min_elide=30):
70 def _elide(string:str, typed:str, min_elide=30)->str:
69 return _elide_typed(
71 return _elide_typed(
70 _elide_point(string, min_elide=min_elide),
72 _elide_point(string, min_elide=min_elide),
71 typed, min_elide=min_elide)
73 typed, min_elide=min_elide)
72
74
73
75
74
76
75 def _adjust_completion_text_based_on_context(text, body, offset):
77 def _adjust_completion_text_based_on_context(text, body, offset):
76 if text.endswith('=') and len(body) > offset and body[offset] == '=':
78 if text.endswith('=') and len(body) > offset and body[offset] == '=':
77 return text[:-1]
79 return text[:-1]
78 else:
80 else:
79 return text
81 return text
80
82
81
83
82 class IPythonPTCompleter(Completer):
84 class IPythonPTCompleter(Completer):
83 """Adaptor to provide IPython completions to prompt_toolkit"""
85 """Adaptor to provide IPython completions to prompt_toolkit"""
84 def __init__(self, ipy_completer=None, shell=None):
86 def __init__(self, ipy_completer=None, shell=None):
85 if shell is None and ipy_completer is None:
87 if shell is None and ipy_completer is None:
86 raise TypeError("Please pass shell=an InteractiveShell instance.")
88 raise TypeError("Please pass shell=an InteractiveShell instance.")
87 self._ipy_completer = ipy_completer
89 self._ipy_completer = ipy_completer
88 self.shell = shell
90 self.shell = shell
89
91
90 @property
92 @property
91 def ipy_completer(self):
93 def ipy_completer(self):
92 if self._ipy_completer:
94 if self._ipy_completer:
93 return self._ipy_completer
95 return self._ipy_completer
94 else:
96 else:
95 return self.shell.Completer
97 return self.shell.Completer
96
98
97 def get_completions(self, document, complete_event):
99 def get_completions(self, document, complete_event):
98 if not document.current_line.strip():
100 if not document.current_line.strip():
99 return
101 return
100 # Some bits of our completion system may print stuff (e.g. if a module
102 # Some bits of our completion system may print stuff (e.g. if a module
101 # is imported). This context manager ensures that doesn't interfere with
103 # is imported). This context manager ensures that doesn't interfere with
102 # the prompt.
104 # the prompt.
103
105
104 with patch_stdout(), provisionalcompleter():
106 with patch_stdout(), provisionalcompleter():
105 body = document.text
107 body = document.text
106 cursor_row = document.cursor_position_row
108 cursor_row = document.cursor_position_row
107 cursor_col = document.cursor_position_col
109 cursor_col = document.cursor_position_col
108 cursor_position = document.cursor_position
110 cursor_position = document.cursor_position
109 offset = cursor_to_position(body, cursor_row, cursor_col)
111 offset = cursor_to_position(body, cursor_row, cursor_col)
110 try:
112 try:
111 yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
113 yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
112 except Exception as e:
114 except Exception as e:
113 from traceback import print_tb
115 from traceback import print_tb
114 print_tb(e)
116 print_tb(e)
115
117
116 @staticmethod
118 @staticmethod
117 def _get_completions(body, offset, cursor_position, ipyc):
119 def _get_completions(body, offset, cursor_position, ipyc):
118 """
120 """
119 Private equivalent of get_completions() use only for unit_testing.
121 Private equivalent of get_completions() use only for unit_testing.
120 """
122 """
121 debug = getattr(ipyc, 'debug', False)
123 debug = getattr(ipyc, 'debug', False)
122 completions = _deduplicate_completions(
124 completions = _deduplicate_completions(
123 body, ipyc.completions(body, offset))
125 body, ipyc.completions(body, offset))
124 for c in completions:
126 for c in completions:
125 if not c.text:
127 if not c.text:
126 # Guard against completion machinery giving us an empty string.
128 # Guard against completion machinery giving us an empty string.
127 continue
129 continue
128 text = unicodedata.normalize('NFC', c.text)
130 text = unicodedata.normalize('NFC', c.text)
129 # When the first character of the completion has a zero length,
131 # When the first character of the completion has a zero length,
130 # then it's probably a decomposed unicode character. E.g. caused by
132 # then it's probably a decomposed unicode character. E.g. caused by
131 # the "\dot" completion. Try to compose again with the previous
133 # the "\dot" completion. Try to compose again with the previous
132 # character.
134 # character.
133 if wcwidth(text[0]) == 0:
135 if wcwidth(text[0]) == 0:
134 if cursor_position + c.start > 0:
136 if cursor_position + c.start > 0:
135 char_before = body[c.start - 1]
137 char_before = body[c.start - 1]
136 fixed_text = unicodedata.normalize(
138 fixed_text = unicodedata.normalize(
137 'NFC', char_before + text)
139 'NFC', char_before + text)
138
140
139 # Yield the modified completion instead, if this worked.
141 # Yield the modified completion instead, if this worked.
140 if wcwidth(text[0:1]) == 1:
142 if wcwidth(text[0:1]) == 1:
141 yield Completion(fixed_text, start_position=c.start - offset - 1)
143 yield Completion(fixed_text, start_position=c.start - offset - 1)
142 continue
144 continue
143
145
144 # TODO: Use Jedi to determine meta_text
146 # TODO: Use Jedi to determine meta_text
145 # (Jedi currently has a bug that results in incorrect information.)
147 # (Jedi currently has a bug that results in incorrect information.)
146 # meta_text = ''
148 # meta_text = ''
147 # yield Completion(m, start_position=start_pos,
149 # yield Completion(m, start_position=start_pos,
148 # display_meta=meta_text)
150 # display_meta=meta_text)
149 display_text = c.text
151 display_text = c.text
150
152
151 adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset)
153 adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset)
152 if c.type == 'function':
154 if c.type == 'function':
153 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()', body[c.start:c.end]), display_meta=c.type+c.signature)
155 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()', body[c.start:c.end]), display_meta=c.type+c.signature)
154 else:
156 else:
155 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text, body[c.start:c.end]), display_meta=c.type)
157 yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text, body[c.start:c.end]), display_meta=c.type)
156
158
157 class IPythonPTLexer(Lexer):
159 class IPythonPTLexer(Lexer):
158 """
160 """
159 Wrapper around PythonLexer and BashLexer.
161 Wrapper around PythonLexer and BashLexer.
160 """
162 """
161 def __init__(self):
163 def __init__(self):
162 l = pygments_lexers
164 l = pygments_lexers
163 self.python_lexer = PygmentsLexer(l.Python3Lexer)
165 self.python_lexer = PygmentsLexer(l.Python3Lexer)
164 self.shell_lexer = PygmentsLexer(l.BashLexer)
166 self.shell_lexer = PygmentsLexer(l.BashLexer)
165
167
166 self.magic_lexers = {
168 self.magic_lexers = {
167 'HTML': PygmentsLexer(l.HtmlLexer),
169 'HTML': PygmentsLexer(l.HtmlLexer),
168 'html': PygmentsLexer(l.HtmlLexer),
170 'html': PygmentsLexer(l.HtmlLexer),
169 'javascript': PygmentsLexer(l.JavascriptLexer),
171 'javascript': PygmentsLexer(l.JavascriptLexer),
170 'js': PygmentsLexer(l.JavascriptLexer),
172 'js': PygmentsLexer(l.JavascriptLexer),
171 'perl': PygmentsLexer(l.PerlLexer),
173 'perl': PygmentsLexer(l.PerlLexer),
172 'ruby': PygmentsLexer(l.RubyLexer),
174 'ruby': PygmentsLexer(l.RubyLexer),
173 'latex': PygmentsLexer(l.TexLexer),
175 'latex': PygmentsLexer(l.TexLexer),
174 }
176 }
175
177
176 def lex_document(self, document):
178 def lex_document(self, document):
177 text = document.text.lstrip()
179 text = document.text.lstrip()
178
180
179 lexer = self.python_lexer
181 lexer = self.python_lexer
180
182
181 if text.startswith('!') or text.startswith('%%bash'):
183 if text.startswith('!') or text.startswith('%%bash'):
182 lexer = self.shell_lexer
184 lexer = self.shell_lexer
183
185
184 elif text.startswith('%%'):
186 elif text.startswith('%%'):
185 for magic, l in self.magic_lexers.items():
187 for magic, l in self.magic_lexers.items():
186 if text.startswith('%%' + magic):
188 if text.startswith('%%' + magic):
187 lexer = l
189 lexer = l
188 break
190 break
189
191
190 return lexer.lex_document(document)
192 return lexer.lex_document(document)
@@ -1,175 +1,193 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the TerminalInteractiveShell and related pieces."""
2 """Tests for the TerminalInteractiveShell and related pieces."""
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import sys
6 import sys
7 import unittest
7 import unittest
8 import os
8 import os
9
9
10 from IPython.core.inputtransformer import InputTransformer
10 from IPython.core.inputtransformer import InputTransformer
11 from IPython.testing import tools as tt
11 from IPython.testing import tools as tt
12 from IPython.utils.capture import capture_output
12 from IPython.utils.capture import capture_output
13
13
14 from IPython.terminal.ptutils import _elide, _adjust_completion_text_based_on_context
14 from IPython.terminal.ptutils import _elide, _adjust_completion_text_based_on_context
15 import nose.tools as nt
15 import nose.tools as nt
16
16
17 class TestElide(unittest.TestCase):
17 class TestElide(unittest.TestCase):
18
18
19 def test_elide(self):
19 def test_elide(self):
20 _elide('concatenate((a1, a2, ...), axis') # do not raise
20 _elide('concatenate((a1, a2, ...), axis', '') # do not raise
21 _elide('concatenate((a1, a2, ..), . axis') # do not raise
21 _elide('concatenate((a1, a2, ..), . axis', '') # do not raise
22 nt.assert_equal(_elide('aaaa.bbbb.ccccc.dddddd.eeeee.fffff.gggggg.hhhhhh'), 'aaaa.b…g.hhhhhh')
22 nt.assert_equal(_elide('aaaa.bbbb.ccccc.dddddd.eeeee.fffff.gggggg.hhhhhh',''), 'aaaa.b…g.hhhhhh')
23
23
24 test_string = os.sep.join(['', 10*'a', 10*'b', 10*'c', ''])
24 test_string = os.sep.join(['', 10*'a', 10*'b', 10*'c', ''])
25 expect_stirng = os.sep + 'a' + '\N{HORIZONTAL ELLIPSIS}' + 'b' + os.sep + 10*'c'
25 expect_stirng = os.sep + 'a' + '\N{HORIZONTAL ELLIPSIS}' + 'b' + os.sep + 10*'c'
26 nt.assert_equal(_elide(test_string), expect_stirng)
26 nt.assert_equal(_elide(test_string, ''), expect_stirng)
27
27
28 def test_elide_typed_normal(self):
29 nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the quick brown fox', min_elide=10), 'the…fox jumped over the lazy dog')
30
31
32 def test_elide_typed_short_match(self):
33 """
34 if the match is too short we don't elide.
35 avoid the "the...the"
36 """
37 nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the', min_elide=10), 'the quick brown fox jumped over the lazy dog')
38
39 def test_elide_typed_no_match(self):
40 """
41 if the match is too short we don't elide.
42 avoid the "the...the"
43 """
44 # here we typed red instead of brown
45 nt.assert_equal(_elide('the quick brown fox jumped over the lazy dog', 'the quick red fox', min_elide=10), 'the quick brown fox jumped over the lazy dog')
28
46
29 class TestContextAwareCompletion(unittest.TestCase):
47 class TestContextAwareCompletion(unittest.TestCase):
30
48
31 def test_adjust_completion_text_based_on_context(self):
49 def test_adjust_completion_text_based_on_context(self):
32 # Adjusted case
50 # Adjusted case
33 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a=)', 7), 'arg1')
51 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a=)', 7), 'arg1')
34
52
35 # Untouched cases
53 # Untouched cases
36 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a)', 7), 'arg1=')
54 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a)', 7), 'arg1=')
37 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a', 7), 'arg1=')
55 nt.assert_equal(_adjust_completion_text_based_on_context('arg1=', 'func1(a', 7), 'arg1=')
38 nt.assert_equal(_adjust_completion_text_based_on_context('%magic', 'func1(a=)', 7), '%magic')
56 nt.assert_equal(_adjust_completion_text_based_on_context('%magic', 'func1(a=)', 7), '%magic')
39 nt.assert_equal(_adjust_completion_text_based_on_context('func2', 'func1(a=)', 7), 'func2')
57 nt.assert_equal(_adjust_completion_text_based_on_context('func2', 'func1(a=)', 7), 'func2')
40
58
41 # Decorator for interaction loop tests -----------------------------------------
59 # Decorator for interaction loop tests -----------------------------------------
42
60
43 class mock_input_helper(object):
61 class mock_input_helper(object):
44 """Machinery for tests of the main interact loop.
62 """Machinery for tests of the main interact loop.
45
63
46 Used by the mock_input decorator.
64 Used by the mock_input decorator.
47 """
65 """
48 def __init__(self, testgen):
66 def __init__(self, testgen):
49 self.testgen = testgen
67 self.testgen = testgen
50 self.exception = None
68 self.exception = None
51 self.ip = get_ipython()
69 self.ip = get_ipython()
52
70
53 def __enter__(self):
71 def __enter__(self):
54 self.orig_prompt_for_code = self.ip.prompt_for_code
72 self.orig_prompt_for_code = self.ip.prompt_for_code
55 self.ip.prompt_for_code = self.fake_input
73 self.ip.prompt_for_code = self.fake_input
56 return self
74 return self
57
75
58 def __exit__(self, etype, value, tb):
76 def __exit__(self, etype, value, tb):
59 self.ip.prompt_for_code = self.orig_prompt_for_code
77 self.ip.prompt_for_code = self.orig_prompt_for_code
60
78
61 def fake_input(self):
79 def fake_input(self):
62 try:
80 try:
63 return next(self.testgen)
81 return next(self.testgen)
64 except StopIteration:
82 except StopIteration:
65 self.ip.keep_running = False
83 self.ip.keep_running = False
66 return u''
84 return u''
67 except:
85 except:
68 self.exception = sys.exc_info()
86 self.exception = sys.exc_info()
69 self.ip.keep_running = False
87 self.ip.keep_running = False
70 return u''
88 return u''
71
89
72 def mock_input(testfunc):
90 def mock_input(testfunc):
73 """Decorator for tests of the main interact loop.
91 """Decorator for tests of the main interact loop.
74
92
75 Write the test as a generator, yield-ing the input strings, which IPython
93 Write the test as a generator, yield-ing the input strings, which IPython
76 will see as if they were typed in at the prompt.
94 will see as if they were typed in at the prompt.
77 """
95 """
78 def test_method(self):
96 def test_method(self):
79 testgen = testfunc(self)
97 testgen = testfunc(self)
80 with mock_input_helper(testgen) as mih:
98 with mock_input_helper(testgen) as mih:
81 mih.ip.interact()
99 mih.ip.interact()
82
100
83 if mih.exception is not None:
101 if mih.exception is not None:
84 # Re-raise captured exception
102 # Re-raise captured exception
85 etype, value, tb = mih.exception
103 etype, value, tb = mih.exception
86 import traceback
104 import traceback
87 traceback.print_tb(tb, file=sys.stdout)
105 traceback.print_tb(tb, file=sys.stdout)
88 del tb # Avoid reference loop
106 del tb # Avoid reference loop
89 raise value
107 raise value
90
108
91 return test_method
109 return test_method
92
110
93 # Test classes -----------------------------------------------------------------
111 # Test classes -----------------------------------------------------------------
94
112
95 class InteractiveShellTestCase(unittest.TestCase):
113 class InteractiveShellTestCase(unittest.TestCase):
96 def rl_hist_entries(self, rl, n):
114 def rl_hist_entries(self, rl, n):
97 """Get last n readline history entries as a list"""
115 """Get last n readline history entries as a list"""
98 return [rl.get_history_item(rl.get_current_history_length() - x)
116 return [rl.get_history_item(rl.get_current_history_length() - x)
99 for x in range(n - 1, -1, -1)]
117 for x in range(n - 1, -1, -1)]
100
118
101 @mock_input
119 @mock_input
102 def test_inputtransformer_syntaxerror(self):
120 def test_inputtransformer_syntaxerror(self):
103 ip = get_ipython()
121 ip = get_ipython()
104 ip.input_transformers_post.append(syntax_error_transformer)
122 ip.input_transformers_post.append(syntax_error_transformer)
105
123
106 try:
124 try:
107 #raise Exception
125 #raise Exception
108 with tt.AssertPrints('4', suppress=False):
126 with tt.AssertPrints('4', suppress=False):
109 yield u'print(2*2)'
127 yield u'print(2*2)'
110
128
111 with tt.AssertPrints('SyntaxError: input contains', suppress=False):
129 with tt.AssertPrints('SyntaxError: input contains', suppress=False):
112 yield u'print(2345) # syntaxerror'
130 yield u'print(2345) # syntaxerror'
113
131
114 with tt.AssertPrints('16', suppress=False):
132 with tt.AssertPrints('16', suppress=False):
115 yield u'print(4*4)'
133 yield u'print(4*4)'
116
134
117 finally:
135 finally:
118 ip.input_transformers_post.remove(syntax_error_transformer)
136 ip.input_transformers_post.remove(syntax_error_transformer)
119
137
120 def test_plain_text_only(self):
138 def test_plain_text_only(self):
121 ip = get_ipython()
139 ip = get_ipython()
122 formatter = ip.display_formatter
140 formatter = ip.display_formatter
123 assert formatter.active_types == ['text/plain']
141 assert formatter.active_types == ['text/plain']
124 assert not formatter.ipython_display_formatter.enabled
142 assert not formatter.ipython_display_formatter.enabled
125
143
126 class Test(object):
144 class Test(object):
127 def __repr__(self):
145 def __repr__(self):
128 return "<Test %i>" % id(self)
146 return "<Test %i>" % id(self)
129
147
130 def _repr_html_(self):
148 def _repr_html_(self):
131 return '<html>'
149 return '<html>'
132
150
133 # verify that HTML repr isn't computed
151 # verify that HTML repr isn't computed
134 obj = Test()
152 obj = Test()
135 data, _ = formatter.format(obj)
153 data, _ = formatter.format(obj)
136 self.assertEqual(data, {'text/plain': repr(obj)})
154 self.assertEqual(data, {'text/plain': repr(obj)})
137
155
138 class Test2(Test):
156 class Test2(Test):
139 def _ipython_display_(self):
157 def _ipython_display_(self):
140 from IPython.display import display
158 from IPython.display import display
141 display('<custom>')
159 display('<custom>')
142
160
143 # verify that _ipython_display_ shortcut isn't called
161 # verify that _ipython_display_ shortcut isn't called
144 obj = Test2()
162 obj = Test2()
145 with capture_output() as captured:
163 with capture_output() as captured:
146 data, _ = formatter.format(obj)
164 data, _ = formatter.format(obj)
147
165
148 self.assertEqual(data, {'text/plain': repr(obj)})
166 self.assertEqual(data, {'text/plain': repr(obj)})
149 assert captured.stdout == ''
167 assert captured.stdout == ''
150
168
151 def syntax_error_transformer(lines):
169 def syntax_error_transformer(lines):
152 """Transformer that throws SyntaxError if 'syntaxerror' is in the code."""
170 """Transformer that throws SyntaxError if 'syntaxerror' is in the code."""
153 for line in lines:
171 for line in lines:
154 pos = line.find('syntaxerror')
172 pos = line.find('syntaxerror')
155 if pos >= 0:
173 if pos >= 0:
156 e = SyntaxError('input contains "syntaxerror"')
174 e = SyntaxError('input contains "syntaxerror"')
157 e.text = line
175 e.text = line
158 e.offset = pos + 1
176 e.offset = pos + 1
159 raise e
177 raise e
160 return lines
178 return lines
161
179
162
180
163 class TerminalMagicsTestCase(unittest.TestCase):
181 class TerminalMagicsTestCase(unittest.TestCase):
164 def test_paste_magics_blankline(self):
182 def test_paste_magics_blankline(self):
165 """Test that code with a blank line doesn't get split (gh-3246)."""
183 """Test that code with a blank line doesn't get split (gh-3246)."""
166 ip = get_ipython()
184 ip = get_ipython()
167 s = ('def pasted_func(a):\n'
185 s = ('def pasted_func(a):\n'
168 ' b = a+1\n'
186 ' b = a+1\n'
169 '\n'
187 '\n'
170 ' return b')
188 ' return b')
171
189
172 tm = ip.magics_manager.registry['TerminalMagics']
190 tm = ip.magics_manager.registry['TerminalMagics']
173 tm.store_or_execute(s, name=None)
191 tm.store_or_execute(s, name=None)
174
192
175 self.assertEqual(ip.user_ns['pasted_func'](54), 55)
193 self.assertEqual(ip.user_ns['pasted_func'](54), 55)
General Comments 0
You need to be logged in to leave comments. Login now