1"""
2The :class:`~traitlets.config.application.Application` object for the command
3line :command:`ipython` program.
4"""
5
6# Copyright (c) IPython Development Team.
7# Distributed under the terms of the Modified BSD License.
8
9
10import logging
11import os
12import sys
13import warnings
14
15from traitlets.config.loader import Config
16from traitlets.config.application import boolean_flag, catch_config_error
17from IPython.core import release
18from IPython.core import usage
19from IPython.core.completer import IPCompleter
20from IPython.core.crashhandler import CrashHandler
21from IPython.core.formatters import PlainTextFormatter
22from IPython.core.history import HistoryManager
23from IPython.core.application import (
24 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
25)
26from IPython.core.magic import MagicsManager
27from IPython.core.magics import (
28 ScriptMagics, LoggingMagics
29)
30from IPython.core.shellapp import (
31 InteractiveShellApp, shell_flags, shell_aliases
32)
33from IPython.extensions.storemagic import StoreMagics
34from .interactiveshell import TerminalInteractiveShell
35from IPython.paths import get_ipython_dir
36from traitlets import (
37 Bool, List, default, observe, Type
38)
39
40#-----------------------------------------------------------------------------
41# Globals, utilities and helpers
42#-----------------------------------------------------------------------------
43
44_examples = """
45ipython --matplotlib # enable matplotlib integration
46ipython --matplotlib=qt # enable matplotlib integration with qt4 backend
47
48ipython --log-level=DEBUG # set logging to DEBUG
49ipython --profile=foo # start with profile foo
50
51ipython profile create foo # create profile foo w/ default config files
52ipython help profile # show the help for the profile subcmd
53
54ipython locate # print the path to the IPython directory
55ipython locate profile foo # print the path to the directory for profile `foo`
56"""
57
58#-----------------------------------------------------------------------------
59# Crash handler for this application
60#-----------------------------------------------------------------------------
61
62class IPAppCrashHandler(CrashHandler):
63 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
64
65 def __init__(self, app):
66 contact_name = release.author
67 contact_email = release.author_email
68 bug_tracker = 'https://github.com/ipython/ipython/issues'
69 super().__init__(
70 app, contact_name, contact_email, bug_tracker
71 )
72
73 def make_report(self,traceback):
74 """Return a string containing a crash report."""
75
76 sec_sep = self.section_sep
77 # Start with parent report
78 report = [super().make_report(traceback)]
79 # Add interactive-specific info we may have
80 rpt_add = report.append
81 try:
82 rpt_add(sec_sep+"History of session input:")
83 for line in self.app.shell.user_ns['_ih']:
84 rpt_add(line)
85 rpt_add('\n*** Last line of input (may not be in above history):\n')
86 rpt_add(self.app.shell._last_input_line+'\n')
87 except Exception:
88 pass
89
90 return ''.join(report)
91
92#-----------------------------------------------------------------------------
93# Aliases and Flags
94#-----------------------------------------------------------------------------
95flags = dict(base_flags)
96flags.update(shell_flags)
97frontend_flags = {}
98addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
99addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
100 'Turn on auto editing of files with syntax errors.',
101 'Turn off auto editing of files with syntax errors.'
102)
103addflag('simple-prompt', 'TerminalInteractiveShell.simple_prompt',
104 "Force simple minimal prompt using `raw_input`",
105 "Use a rich interactive prompt with prompt_toolkit",
106)
107
108addflag('banner', 'TerminalIPythonApp.display_banner',
109 "Display a banner upon starting IPython.",
110 "Don't display a banner upon starting IPython."
111)
112addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
113 """Set to confirm when you try to exit IPython with an EOF (Control-D
114 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
115 you can force a direct exit without any confirmation.""",
116 "Don't prompt the user when exiting."
117)
118addflag(
119 "tip",
120 "TerminalInteractiveShell.enable_tip",
121 """Shows a tip when IPython starts.""",
122 "Don't show tip when IPython starts.",
123)
124addflag('term-title', 'TerminalInteractiveShell.term_title',
125 "Enable auto setting the terminal title.",
126 "Disable auto setting the terminal title."
127)
128classic_config = Config()
129classic_config.InteractiveShell.cache_size = 0
130classic_config.PlainTextFormatter.pprint = False
131classic_config.TerminalInteractiveShell.prompts_class = (
132 "IPython.terminal.prompts.ClassicPrompts"
133)
134classic_config.InteractiveShell.separate_in = ""
135classic_config.InteractiveShell.separate_out = ""
136classic_config.InteractiveShell.separate_out2 = ""
137classic_config.InteractiveShell.colors = "nocolor"
138classic_config.InteractiveShell.xmode = "Plain"
139
140frontend_flags['classic']=(
141 classic_config,
142 "Gives IPython a similar feel to the classic Python prompt."
143)
144# # log doesn't make so much sense this way anymore
145# paa('--log','-l',
146# action='store_true', dest='InteractiveShell.logstart',
147# help="Start logging to the default log file (./ipython_log.py).")
148#
149# # quick is harder to implement
150frontend_flags['quick']=(
151 {'TerminalIPythonApp' : {'quick' : True}},
152 "Enable quick startup with no config files."
153)
154
155frontend_flags['i'] = (
156 {'TerminalIPythonApp' : {'force_interact' : True}},
157 """If running code from the command line, become interactive afterwards.
158 It is often useful to follow this with `--` to treat remaining flags as
159 script arguments.
160 """
161)
162flags.update(frontend_flags)
163
164aliases = dict(base_aliases)
165aliases.update(shell_aliases) # type: ignore[arg-type]
166
167#-----------------------------------------------------------------------------
168# Main classes and functions
169#-----------------------------------------------------------------------------
170
171
172class LocateIPythonApp(BaseIPythonApplication):
173 description = """print the path to the IPython dir"""
174 subcommands = dict(
175 profile=('IPython.core.profileapp.ProfileLocate',
176 "print the path to an IPython profile directory",
177 ),
178 )
179 def start(self):
180 if self.subapp is not None:
181 return self.subapp.start()
182 else:
183 print(self.ipython_dir)
184
185
186class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
187 name = "ipython"
188 description = usage.cl_usage
189 crash_handler_class = IPAppCrashHandler # typing: ignore[assignment]
190 examples = _examples
191
192 flags = flags
193 aliases = aliases
194 classes = List()
195
196 interactive_shell_class = Type(
197 klass=object, # use default_value otherwise which only allow subclasses.
198 default_value=TerminalInteractiveShell,
199 help="Class to use to instantiate the TerminalInteractiveShell object. Useful for custom Frontends"
200 ).tag(config=True)
201
202 @default('classes')
203 def _classes_default(self):
204 """This has to be in a method, for TerminalIPythonApp to be available."""
205 return [
206 InteractiveShellApp, # ShellApp comes before TerminalApp, because
207 self.__class__, # it will also affect subclasses (e.g. QtConsole)
208 TerminalInteractiveShell,
209 HistoryManager,
210 MagicsManager,
211 ProfileDir,
212 PlainTextFormatter,
213 IPCompleter,
214 ScriptMagics,
215 LoggingMagics,
216 StoreMagics,
217 ]
218
219 subcommands = dict(
220 profile = ("IPython.core.profileapp.ProfileApp",
221 "Create and manage IPython profiles."
222 ),
223 kernel = ("ipykernel.kernelapp.IPKernelApp",
224 "Start a kernel without an attached frontend."
225 ),
226 locate=('IPython.terminal.ipapp.LocateIPythonApp',
227 LocateIPythonApp.description
228 ),
229 history=('IPython.core.historyapp.HistoryApp',
230 "Manage the IPython history database."
231 ),
232 )
233
234 # *do* autocreate requested profile, but don't create the config file.
235 auto_create = Bool(True).tag(config=True)
236
237 # configurables
238 quick = Bool(False,
239 help="""Start IPython quickly by skipping the loading of config files."""
240 ).tag(config=True)
241 @observe('quick')
242 def _quick_changed(self, change):
243 if change['new']:
244 self.load_config_file = lambda *a, **kw: None
245
246 display_banner = Bool(True,
247 help="Whether to display a banner upon starting IPython."
248 ).tag(config=True)
249
250 # if there is code of files to run from the cmd line, don't interact
251 # unless the --i flag (App.force_interact) is true.
252 force_interact = Bool(False,
253 help="""If a command or file is given via the command-line,
254 e.g. 'ipython foo.py', start an interactive shell after executing the
255 file or command."""
256 ).tag(config=True)
257 @observe('force_interact')
258 def _force_interact_changed(self, change):
259 if change['new']:
260 self.interact = True
261
262 @observe('file_to_run', 'code_to_run', 'module_to_run')
263 def _file_to_run_changed(self, change):
264 new = change['new']
265 if new:
266 self.something_to_run = True
267 if new and not self.force_interact:
268 self.interact = False
269
270 # internal, not-configurable
271 something_to_run=Bool(False)
272
273 @catch_config_error
274 def initialize(self, argv=None):
275 """Do actions after construct, but before starting the app."""
276 super().initialize(argv)
277 if self.subapp is not None:
278 # don't bother initializing further, starting subapp
279 return
280 # print(self.extra_args)
281 if self.extra_args and not self.something_to_run:
282 self.file_to_run = self.extra_args[0]
283 self.init_path()
284 # create the shell
285 self.init_shell()
286 # and draw the banner
287 self.init_banner()
288 # Now a variety of things that happen after the banner is printed.
289 self.init_gui_pylab()
290 self.init_extensions()
291 self.init_code()
292
293 def init_shell(self):
294 """initialize the InteractiveShell instance"""
295 # Create an InteractiveShell instance.
296 # shell.display_banner should always be False for the terminal
297 # based app, because we call shell.show_banner() by hand below
298 # so the banner shows *before* all extension loading stuff.
299 self.shell = self.interactive_shell_class.instance(parent=self,
300 profile_dir=self.profile_dir,
301 ipython_dir=self.ipython_dir, user_ns=self.user_ns)
302 self.shell.configurables.append(self)
303
304 def init_banner(self):
305 """optionally display the banner"""
306 if self.display_banner and self.interact:
307 self.shell.show_banner()
308 # Make sure there is a space below the banner.
309 if self.log_level <= logging.INFO: print()
310
311 @observe("pylab")
312 def _pylab_changed(self, change):
313 """Replace --pylab='inline' with --pylab='auto'"""
314 if change["new"] == "inline":
315 warnings.warn(
316 "'inline' not available as pylab backend, using 'auto' instead."
317 )
318 self.pylab = "auto"
319
320 def start(self):
321 if self.subapp is not None:
322 return self.subapp.start()
323 # perform any prexec steps:
324 if self.interact:
325 self.log.debug("Starting IPython's mainloop...")
326 self.shell.mainloop()
327 else:
328 self.log.debug("IPython not interactive...")
329 self.shell.restore_term_title()
330 if not self.shell.last_execution_succeeded:
331 sys.exit(1)
332
333def load_default_config(ipython_dir=None):
334 """Load the default config file from the default ipython_dir.
335
336 This is useful for embedded shells.
337 """
338 if ipython_dir is None:
339 ipython_dir = get_ipython_dir()
340
341 profile_dir = os.path.join(ipython_dir, 'profile_default')
342 app = TerminalIPythonApp()
343 app.config_file_paths.append(profile_dir)
344 app.load_config_file()
345 return app.config
346
347launch_new_instance = TerminalIPythonApp.launch_instance