##// END OF EJS Templates
Fix 'interactive' tests using pipes to a subprocess
Thomas Kluyver -
Show More
@@ -1,68 +1,70 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for shellapp module.
3 3
4 4 Authors
5 5 -------
6 6 * Bradley Froehle
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2012 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18 import unittest
19 19
20 20 from IPython.testing import decorators as dec
21 21 from IPython.testing import tools as tt
22 22 from IPython.utils.py3compat import PY3
23 23
24 24 sqlite_err_maybe = dec.module_not_available('sqlite3')
25 25 SQLITE_NOT_AVAILABLE_ERROR = ('WARNING: IPython History requires SQLite,'
26 26 ' your history will not be saved\n')
27 27
28 28 class TestFileToRun(unittest.TestCase, tt.TempFileMixin):
29 29 """Test the behavior of the file_to_run parameter."""
30 30
31 31 def test_py_script_file_attribute(self):
32 32 """Test that `__file__` is set when running `ipython file.py`"""
33 33 src = "print(__file__)\n"
34 34 self.mktmp(src)
35 35
36 36 err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None
37 37 tt.ipexec_validate(self.fname, self.fname, err)
38 38
39 39 def test_ipy_script_file_attribute(self):
40 40 """Test that `__file__` is set when running `ipython file.ipy`"""
41 41 src = "print(__file__)\n"
42 42 self.mktmp(src, ext='.ipy')
43 43
44 44 err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None
45 45 tt.ipexec_validate(self.fname, self.fname, err)
46 46
47 47 # The commands option to ipexec_validate doesn't work on Windows, and it
48 48 # doesn't seem worth fixing
49 49 @dec.skip_win32
50 50 def test_py_script_file_attribute_interactively(self):
51 51 """Test that `__file__` is not set after `ipython -i file.py`"""
52 52 src = "True\n"
53 53 self.mktmp(src)
54 54
55 out = 'In [1]: False\n\nIn [2]:'
55 56 err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None
56 tt.ipexec_validate(self.fname, 'False', err, options=['-i'],
57 tt.ipexec_validate(self.fname, out, err, options=['-i'],
57 58 commands=['"__file__" in globals()', 'exit()'])
58 59
59 60 @dec.skip_win32
60 61 @dec.skipif(PY3)
61 62 def test_py_script_file_compiler_directive(self):
62 63 """Test `__future__` compiler directives with `ipython -i file.py`"""
63 64 src = "from __future__ import division\n"
64 65 self.mktmp(src)
65 66
67 out = 'In [1]: float\n\nIn [2]:'
66 68 err = SQLITE_NOT_AVAILABLE_ERROR if sqlite_err_maybe else None
67 tt.ipexec_validate(self.fname, 'float', err, options=['-i'],
69 tt.ipexec_validate(self.fname, out, err, options=['-i'],
68 70 commands=['type(1/2)', 'exit()'])
@@ -1,228 +1,240 b''
1 1 """IPython terminal interface using prompt_toolkit in place of readline"""
2 2 from __future__ import print_function
3 3
4 4 import sys
5 5
6 6 from IPython.core.interactiveshell import InteractiveShell
7 from IPython.utils.py3compat import PY3, cast_unicode_py2
7 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
8 8 from traitlets import Bool, Unicode, Dict
9 9
10 10 from prompt_toolkit.completion import Completer, Completion
11 11 from prompt_toolkit.enums import DEFAULT_BUFFER
12 12 from prompt_toolkit.filters import HasFocus, HasSelection
13 13 from prompt_toolkit.history import InMemoryHistory
14 14 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop
15 15 from prompt_toolkit.interface import CommandLineInterface
16 16 from prompt_toolkit.key_binding.manager import KeyBindingManager
17 17 from prompt_toolkit.key_binding.vi_state import InputMode
18 18 from prompt_toolkit.key_binding.bindings.vi import ViStateFilter
19 19 from prompt_toolkit.keys import Keys
20 20 from prompt_toolkit.layout.lexers import PygmentsLexer
21 21 from prompt_toolkit.styles import PygmentsStyle
22 22
23 23 from pygments.styles import get_style_by_name
24 24 from pygments.lexers import Python3Lexer, PythonLexer
25 25 from pygments.token import Token
26 26
27 27 from .pt_inputhooks import get_inputhook_func
28 28 from .interactiveshell import get_default_editor
29 29
30 30
31 31 class IPythonPTCompleter(Completer):
32 32 """Adaptor to provide IPython completions to prompt_toolkit"""
33 33 def __init__(self, ipy_completer):
34 34 self.ipy_completer = ipy_completer
35 35
36 36 def get_completions(self, document, complete_event):
37 37 if not document.current_line.strip():
38 38 return
39 39
40 40 used, matches = self.ipy_completer.complete(
41 41 line_buffer=document.current_line,
42 42 cursor_pos=document.cursor_position_col
43 43 )
44 44 start_pos = -len(used)
45 45 for m in matches:
46 46 yield Completion(m, start_position=start_pos)
47 47
48 48 class PTInteractiveShell(InteractiveShell):
49 49 colors_force = True
50 50
51 51 pt_cli = None
52 52
53 53 vi_mode = Bool(False, config=True,
54 54 help="Use vi style keybindings at the prompt",
55 55 )
56 56
57 57 mouse_support = Bool(False, config=True,
58 58 help="Enable mouse support in the prompt"
59 59 )
60 60
61 61 highlighting_style = Unicode('', config=True,
62 62 help="The name of a Pygments style to use for syntax highlighting"
63 63 )
64 64
65 65 highlighting_style_overrides = Dict(config=True,
66 66 help="Override highlighting format for specific tokens"
67 67 )
68 68
69 69 editor = Unicode(get_default_editor(), config=True,
70 70 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
71 71 )
72 72
73 73 def get_prompt_tokens(self, cli):
74 74 return [
75 75 (Token.Prompt, 'In ['),
76 76 (Token.PromptNum, str(self.execution_count)),
77 77 (Token.Prompt, ']: '),
78 78 ]
79 79
80 80 def get_continuation_tokens(self, cli, width):
81 81 return [
82 82 (Token.Prompt, (' ' * (width - 2)) + ': '),
83 83 ]
84 84
85 85 def init_prompt_toolkit_cli(self):
86 if not sys.stdin.isatty():
87 # Piped input - e.g. for tests. Fall back to plain non-interactive
88 # output. This is very limited, and only accepts a single line.
89 def prompt():
90 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
91 self.prompt_for_code = prompt
92 return
93
86 94 kbmanager = KeyBindingManager.for_prompt(enable_vi_mode=self.vi_mode)
87 95 insert_mode = ViStateFilter(kbmanager.get_vi_state, InputMode.INSERT)
88 96 # Ctrl+J == Enter, seemingly
89 97 @kbmanager.registry.add_binding(Keys.ControlJ,
90 98 filter=(HasFocus(DEFAULT_BUFFER)
91 99 & ~HasSelection()
92 100 & insert_mode
93 101 ))
94 102 def _(event):
95 103 b = event.current_buffer
96 104 d = b.document
97 105 if not (d.on_last_line or d.cursor_position_row >= d.line_count
98 106 - d.empty_line_count_at_the_end()):
99 107 b.newline()
100 108 return
101 109
102 110 status, indent = self.input_splitter.check_complete(d.text)
103 111
104 112 if (status != 'incomplete') and b.accept_action.is_returnable:
105 113 b.accept_action.validate_and_handle(event.cli, b)
106 114 else:
107 115 b.insert_text('\n' + (' ' * (indent or 0)))
108 116
109 117 @kbmanager.registry.add_binding(Keys.ControlC)
110 118 def _(event):
111 119 event.current_buffer.reset()
112 120
113 121 # Pre-populate history from IPython's history database
114 122 history = InMemoryHistory()
115 123 last_cell = u""
116 124 for _, _, cell in self.history_manager.get_tail(self.history_load_length,
117 125 include_latest=True):
118 126 # Ignore blank lines and consecutive duplicates
119 127 cell = cell.rstrip()
120 128 if cell and (cell != last_cell):
121 129 history.append(cell)
122 130
123 131 style_overrides = {
124 132 Token.Prompt: '#009900',
125 133 Token.PromptNum: '#00ff00 bold',
126 134 }
127 135 if self.highlighting_style:
128 136 style_cls = get_style_by_name(self.highlighting_style)
129 137 else:
130 138 style_cls = get_style_by_name('default')
131 139 # The default theme needs to be visible on both a dark background
132 140 # and a light background, because we can't tell what the terminal
133 141 # looks like. These tweaks to the default theme help with that.
134 142 style_overrides.update({
135 143 Token.Number: '#007700',
136 144 Token.Operator: 'noinherit',
137 145 Token.String: '#BB6622',
138 146 Token.Name.Function: '#2080D0',
139 147 Token.Name.Class: 'bold #2080D0',
140 148 Token.Name.Namespace: 'bold #2080D0',
141 149 })
142 150 style_overrides.update(self.highlighting_style_overrides)
143 151 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
144 152 style_dict=style_overrides)
145 153
146 154 app = create_prompt_application(multiline=True,
147 155 lexer=PygmentsLexer(Python3Lexer if PY3 else PythonLexer),
148 156 get_prompt_tokens=self.get_prompt_tokens,
149 157 # The line below is waiting for a new release of
150 158 # prompt_toolkit (> 0.57)
151 159 #get_continuation_tokens=self.get_continuation_tokens,
152 160 key_bindings_registry=kbmanager.registry,
153 161 history=history,
154 162 completer=IPythonPTCompleter(self.Completer),
155 163 enable_history_search=True,
156 164 style=style,
157 165 mouse_support=self.mouse_support,
158 166 )
159 167
160 168 self.pt_cli = CommandLineInterface(app,
161 169 eventloop=create_eventloop(self.inputhook))
162 170
171 def prompt_for_code(self):
172 document = self.pt_cli.run(pre_run=self.pre_prompt)
173 return document.text
174
163 175 def init_io(self):
164 176 if sys.platform not in {'win32', 'cli'}:
165 177 return
166 178
167 179 import colorama
168 180 colorama.init()
169 181
170 182 # For some reason we make these wrappers around stdout/stderr.
171 183 # For now, we need to reset them so all output gets coloured.
172 184 # https://github.com/ipython/ipython/issues/8669
173 185 from IPython.utils import io
174 186 io.stdout = io.IOStream(sys.stdout)
175 187 io.stderr = io.IOStream(sys.stderr)
176 188
177 189 def __init__(self, *args, **kwargs):
178 190 super(PTInteractiveShell, self).__init__(*args, **kwargs)
179 191 self.init_prompt_toolkit_cli()
180 192 self.keep_running = True
181 193
182 194 def ask_exit(self):
183 195 self.keep_running = False
184 196
185 197 rl_next_input = None
186 198
187 199 def pre_prompt(self):
188 200 if self.rl_next_input:
189 201 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
190 202 self.rl_next_input = None
191 203
192 204 def interact(self):
193 205 while self.keep_running:
194 206 print(self.separate_in, end='')
195 207
196 208 try:
197 document = self.pt_cli.run(pre_run=self.pre_prompt)
209 code = self.prompt_for_code()
198 210 except EOFError:
199 211 if self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
200 212 self.ask_exit()
201 213
202 214 else:
203 if document:
204 self.run_cell(document.text, store_history=True)
215 if code:
216 self.run_cell(code, store_history=True)
205 217
206 218 def mainloop(self):
207 219 # An extra layer of protection in case someone mashing Ctrl-C breaks
208 220 # out of our internal code.
209 221 while True:
210 222 try:
211 223 self.interact()
212 224 break
213 225 except KeyboardInterrupt:
214 226 print("\nKeyboardInterrupt escaped interact()\n")
215 227
216 228 _inputhook = None
217 229 def inputhook(self, context):
218 230 if self._inputhook is not None:
219 231 self._inputhook(context)
220 232
221 233 def enable_gui(self, gui=None):
222 234 if gui:
223 235 self._inputhook = get_inputhook_func(gui)
224 236 else:
225 237 self._inputhook = None
226 238
227 239 if __name__ == '__main__':
228 240 PTInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now