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