##// END OF EJS Templates
Docs: mention empty histrange behavior of `%save`
Blazej Michalik -
Show More
@@ -1,737 +1,740 b''
1 1 """Implementation of code management magic functions.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (c) 2012 The IPython Development Team.
5 5 #
6 6 # Distributed under the terms of the Modified BSD License.
7 7 #
8 8 # The full license is in the file COPYING.txt, distributed with this software.
9 9 #-----------------------------------------------------------------------------
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Imports
13 13 #-----------------------------------------------------------------------------
14 14
15 15 # Stdlib
16 16 import inspect
17 17 import io
18 18 import os
19 19 import re
20 20 import sys
21 21 import ast
22 22 from itertools import chain
23 23 from urllib.request import Request, urlopen
24 24 from urllib.parse import urlencode
25 25 from pathlib import Path
26 26
27 27 # Our own packages
28 28 from IPython.core.error import TryNext, StdinNotImplementedError, UsageError
29 29 from IPython.core.macro import Macro
30 30 from IPython.core.magic import Magics, magics_class, line_magic
31 31 from IPython.core.oinspect import find_file, find_source_lines
32 32 from IPython.core.release import version
33 33 from IPython.testing.skipdoctest import skip_doctest
34 34 from IPython.utils.contexts import preserve_keys
35 35 from IPython.utils.path import get_py_filename
36 36 from warnings import warn
37 37 from logging import error
38 38 from IPython.utils.text import get_text_list
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Magic implementation classes
42 42 #-----------------------------------------------------------------------------
43 43
44 44 # Used for exception handling in magic_edit
45 45 class MacroToEdit(ValueError): pass
46 46
47 47 ipython_input_pat = re.compile(r"<ipython\-input\-(\d+)-[a-z\d]+>$")
48 48
49 49 # To match, e.g. 8-10 1:5 :10 3-
50 50 range_re = re.compile(r"""
51 51 (?P<start>\d+)?
52 52 ((?P<sep>[\-:])
53 53 (?P<end>\d+)?)?
54 54 $""", re.VERBOSE)
55 55
56 56
57 57 def extract_code_ranges(ranges_str):
58 58 """Turn a string of range for %%load into 2-tuples of (start, stop)
59 59 ready to use as a slice of the content split by lines.
60 60
61 61 Examples
62 62 --------
63 63 list(extract_input_ranges("5-10 2"))
64 64 [(4, 10), (1, 2)]
65 65 """
66 66 for range_str in ranges_str.split():
67 67 rmatch = range_re.match(range_str)
68 68 if not rmatch:
69 69 continue
70 70 sep = rmatch.group("sep")
71 71 start = rmatch.group("start")
72 72 end = rmatch.group("end")
73 73
74 74 if sep == '-':
75 75 start = int(start) - 1 if start else None
76 76 end = int(end) if end else None
77 77 elif sep == ':':
78 78 start = int(start) - 1 if start else None
79 79 end = int(end) - 1 if end else None
80 80 else:
81 81 end = int(start)
82 82 start = int(start) - 1
83 83 yield (start, end)
84 84
85 85
86 86 def extract_symbols(code, symbols):
87 87 """
88 88 Return a tuple (blocks, not_found)
89 89 where ``blocks`` is a list of code fragments
90 90 for each symbol parsed from code, and ``not_found`` are
91 91 symbols not found in the code.
92 92
93 93 For example::
94 94
95 95 In [1]: code = '''a = 10
96 96 ...: def b(): return 42
97 97 ...: class A: pass'''
98 98
99 99 In [2]: extract_symbols(code, 'A,b,z')
100 100 Out[2]: (['class A: pass\\n', 'def b(): return 42\\n'], ['z'])
101 101 """
102 102 symbols = symbols.split(',')
103 103
104 104 # this will raise SyntaxError if code isn't valid Python
105 105 py_code = ast.parse(code)
106 106
107 107 marks = [(getattr(s, 'name', None), s.lineno) for s in py_code.body]
108 108 code = code.split('\n')
109 109
110 110 symbols_lines = {}
111 111
112 112 # we already know the start_lineno of each symbol (marks).
113 113 # To find each end_lineno, we traverse in reverse order until each
114 114 # non-blank line
115 115 end = len(code)
116 116 for name, start in reversed(marks):
117 117 while not code[end - 1].strip():
118 118 end -= 1
119 119 if name:
120 120 symbols_lines[name] = (start - 1, end)
121 121 end = start - 1
122 122
123 123 # Now symbols_lines is a map
124 124 # {'symbol_name': (start_lineno, end_lineno), ...}
125 125
126 126 # fill a list with chunks of codes for each requested symbol
127 127 blocks = []
128 128 not_found = []
129 129 for symbol in symbols:
130 130 if symbol in symbols_lines:
131 131 start, end = symbols_lines[symbol]
132 132 blocks.append('\n'.join(code[start:end]) + '\n')
133 133 else:
134 134 not_found.append(symbol)
135 135
136 136 return blocks, not_found
137 137
138 138 def strip_initial_indent(lines):
139 139 """For %load, strip indent from lines until finding an unindented line.
140 140
141 141 https://github.com/ipython/ipython/issues/9775
142 142 """
143 143 indent_re = re.compile(r'\s+')
144 144
145 145 it = iter(lines)
146 146 first_line = next(it)
147 147 indent_match = indent_re.match(first_line)
148 148
149 149 if indent_match:
150 150 # First line was indented
151 151 indent = indent_match.group()
152 152 yield first_line[len(indent):]
153 153
154 154 for line in it:
155 155 if line.startswith(indent):
156 156 yield line[len(indent):]
157 157 else:
158 158 # Less indented than the first line - stop dedenting
159 159 yield line
160 160 break
161 161 else:
162 162 yield first_line
163 163
164 164 # Pass the remaining lines through without dedenting
165 165 for line in it:
166 166 yield line
167 167
168 168
169 169 class InteractivelyDefined(Exception):
170 170 """Exception for interactively defined variable in magic_edit"""
171 171 def __init__(self, index):
172 172 self.index = index
173 173
174 174
175 175 @magics_class
176 176 class CodeMagics(Magics):
177 177 """Magics related to code management (loading, saving, editing, ...)."""
178 178
179 179 def __init__(self, *args, **kwargs):
180 180 self._knowntemps = set()
181 181 super(CodeMagics, self).__init__(*args, **kwargs)
182 182
183 183 @line_magic
184 184 def save(self, parameter_s=''):
185 185 """Save a set of lines or a macro to a given filename.
186 186
187 187 Usage:\\
188 188 %save [options] filename n1-n2 n3-n4 ... n5 .. n6 ...
189 189
190 190 Options:
191 191
192 192 -r: use 'raw' input. By default, the 'processed' history is used,
193 193 so that magics are loaded in their transformed version to valid
194 194 Python. If this option is given, the raw input as typed as the
195 195 command line is used instead.
196 196
197 197 -f: force overwrite. If file exists, %save will prompt for overwrite
198 198 unless -f is given.
199 199
200 200 -a: append to the file instead of overwriting it.
201 201
202 202 This function uses the same syntax as %history for input ranges,
203 203 then saves the lines to the filename you specify.
204 204
205 If no ranges are specified, saves history of the current session up to
206 this point.
207
205 208 It adds a '.py' extension to the file if you don't do so yourself, and
206 209 it asks for confirmation before overwriting existing files.
207 210
208 211 If `-r` option is used, the default extension is `.ipy`.
209 212 """
210 213
211 214 opts,args = self.parse_options(parameter_s,'fra',mode='list')
212 215 if not args:
213 216 raise UsageError('Missing filename.')
214 217 raw = 'r' in opts
215 218 force = 'f' in opts
216 219 append = 'a' in opts
217 220 mode = 'a' if append else 'w'
218 221 ext = '.ipy' if raw else '.py'
219 222 fname, codefrom = args[0], " ".join(args[1:])
220 223 if not fname.endswith(('.py','.ipy')):
221 224 fname += ext
222 225 file_exists = os.path.isfile(fname)
223 226 if file_exists and not force and not append:
224 227 try:
225 228 overwrite = self.shell.ask_yes_no('File `%s` exists. Overwrite (y/[N])? ' % fname, default='n')
226 229 except StdinNotImplementedError:
227 230 print("File `%s` exists. Use `%%save -f %s` to force overwrite" % (fname, parameter_s))
228 231 return
229 232 if not overwrite :
230 233 print('Operation cancelled.')
231 234 return
232 235 try:
233 236 cmds = self.shell.find_user_code(codefrom,raw)
234 237 except (TypeError, ValueError) as e:
235 238 print(e.args[0])
236 239 return
237 240 with io.open(fname, mode, encoding="utf-8") as f:
238 241 if not file_exists or not append:
239 242 f.write("# coding: utf-8\n")
240 243 f.write(cmds)
241 244 # make sure we end on a newline
242 245 if not cmds.endswith('\n'):
243 246 f.write('\n')
244 247 print('The following commands were written to file `%s`:' % fname)
245 248 print(cmds)
246 249
247 250 @line_magic
248 251 def pastebin(self, parameter_s=''):
249 252 """Upload code to dpaste.com, returning the URL.
250 253
251 254 Usage:\\
252 255 %pastebin [-d "Custom description"] 1-7
253 256
254 257 The argument can be an input history range, a filename, or the name of a
255 258 string or macro.
256 259
257 260 Options:
258 261
259 262 -d: Pass a custom description. The default will say
260 263 "Pasted from IPython".
261 264 """
262 265 opts, args = self.parse_options(parameter_s, 'd:')
263 266
264 267 try:
265 268 code = self.shell.find_user_code(args)
266 269 except (ValueError, TypeError) as e:
267 270 print(e.args[0])
268 271 return
269 272
270 273 post_data = urlencode(
271 274 {
272 275 "title": opts.get("d", "Pasted from IPython"),
273 276 "syntax": "python",
274 277 "content": code,
275 278 }
276 279 ).encode("utf-8")
277 280
278 281 request = Request(
279 282 "http://dpaste.com/api/v2/",
280 283 headers={"User-Agent": "IPython v{}".format(version)},
281 284 )
282 285 response = urlopen(request, post_data)
283 286 return response.headers.get('Location')
284 287
285 288 @line_magic
286 289 def loadpy(self, arg_s):
287 290 """Alias of `%load`
288 291
289 292 `%loadpy` has gained some flexibility and dropped the requirement of a `.py`
290 293 extension. So it has been renamed simply into %load. You can look at
291 294 `%load`'s docstring for more info.
292 295 """
293 296 self.load(arg_s)
294 297
295 298 @line_magic
296 299 def load(self, arg_s):
297 300 """Load code into the current frontend.
298 301
299 302 Usage:\\
300 303 %load [options] source
301 304
302 305 where source can be a filename, URL, input history range, macro, or
303 306 element in the user namespace
304 307
305 308 Options:
306 309
307 310 -r <lines>: Specify lines or ranges of lines to load from the source.
308 311 Ranges could be specified as x-y (x..y) or in python-style x:y
309 312 (x..(y-1)). Both limits x and y can be left blank (meaning the
310 313 beginning and end of the file, respectively).
311 314
312 315 -s <symbols>: Specify function or classes to load from python source.
313 316
314 317 -y : Don't ask confirmation for loading source above 200 000 characters.
315 318
316 319 -n : Include the user's namespace when searching for source code.
317 320
318 321 This magic command can either take a local filename, a URL, an history
319 322 range (see %history) or a macro as argument, it will prompt for
320 323 confirmation before loading source with more than 200 000 characters, unless
321 324 -y flag is passed or if the frontend does not support raw_input::
322 325
323 326 %load myscript.py
324 327 %load 7-27
325 328 %load myMacro
326 329 %load http://www.example.com/myscript.py
327 330 %load -r 5-10 myscript.py
328 331 %load -r 10-20,30,40: foo.py
329 332 %load -s MyClass,wonder_function myscript.py
330 333 %load -n MyClass
331 334 %load -n my_module.wonder_function
332 335 """
333 336 opts,args = self.parse_options(arg_s,'yns:r:')
334 337
335 338 if not args:
336 339 raise UsageError('Missing filename, URL, input history range, '
337 340 'macro, or element in the user namespace.')
338 341
339 342 search_ns = 'n' in opts
340 343
341 344 contents = self.shell.find_user_code(args, search_ns=search_ns)
342 345
343 346 if 's' in opts:
344 347 try:
345 348 blocks, not_found = extract_symbols(contents, opts['s'])
346 349 except SyntaxError:
347 350 # non python code
348 351 error("Unable to parse the input as valid Python code")
349 352 return
350 353
351 354 if len(not_found) == 1:
352 355 warn('The symbol `%s` was not found' % not_found[0])
353 356 elif len(not_found) > 1:
354 357 warn('The symbols %s were not found' % get_text_list(not_found,
355 358 wrap_item_with='`')
356 359 )
357 360
358 361 contents = '\n'.join(blocks)
359 362
360 363 if 'r' in opts:
361 364 ranges = opts['r'].replace(',', ' ')
362 365 lines = contents.split('\n')
363 366 slices = extract_code_ranges(ranges)
364 367 contents = [lines[slice(*slc)] for slc in slices]
365 368 contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents)))
366 369
367 370 l = len(contents)
368 371
369 372 # 200 000 is ~ 2500 full 80 character lines
370 373 # so in average, more than 5000 lines
371 374 if l > 200000 and 'y' not in opts:
372 375 try:
373 376 ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\
374 377 " (%d characters). Continue (y/[N]) ?" % l), default='n' )
375 378 except StdinNotImplementedError:
376 379 #assume yes if raw input not implemented
377 380 ans = True
378 381
379 382 if ans is False :
380 383 print('Operation cancelled.')
381 384 return
382 385
383 386 contents = "# %load {}\n".format(arg_s) + contents
384 387
385 388 self.shell.set_next_input(contents, replace=True)
386 389
387 390 @staticmethod
388 391 def _find_edit_target(shell, args, opts, last_call):
389 392 """Utility method used by magic_edit to find what to edit."""
390 393
391 394 def make_filename(arg):
392 395 "Make a filename from the given args"
393 396 try:
394 397 filename = get_py_filename(arg)
395 398 except IOError:
396 399 # If it ends with .py but doesn't already exist, assume we want
397 400 # a new file.
398 401 if arg.endswith('.py'):
399 402 filename = arg
400 403 else:
401 404 filename = None
402 405 return filename
403 406
404 407 # Set a few locals from the options for convenience:
405 408 opts_prev = 'p' in opts
406 409 opts_raw = 'r' in opts
407 410
408 411 # custom exceptions
409 412 class DataIsObject(Exception): pass
410 413
411 414 # Default line number value
412 415 lineno = opts.get('n',None)
413 416
414 417 if opts_prev:
415 418 args = '_%s' % last_call[0]
416 419 if args not in shell.user_ns:
417 420 args = last_call[1]
418 421
419 422 # by default this is done with temp files, except when the given
420 423 # arg is a filename
421 424 use_temp = True
422 425
423 426 data = ''
424 427
425 428 # First, see if the arguments should be a filename.
426 429 filename = make_filename(args)
427 430 if filename:
428 431 use_temp = False
429 432 elif args:
430 433 # Mode where user specifies ranges of lines, like in %macro.
431 434 data = shell.extract_input_lines(args, opts_raw)
432 435 if not data:
433 436 try:
434 437 # Load the parameter given as a variable. If not a string,
435 438 # process it as an object instead (below)
436 439
437 440 #print '*** args',args,'type',type(args) # dbg
438 441 data = eval(args, shell.user_ns)
439 442 if not isinstance(data, str):
440 443 raise DataIsObject
441 444
442 445 except (NameError,SyntaxError):
443 446 # given argument is not a variable, try as a filename
444 447 filename = make_filename(args)
445 448 if filename is None:
446 449 warn("Argument given (%s) can't be found as a variable "
447 450 "or as a filename." % args)
448 451 return (None, None, None)
449 452 use_temp = False
450 453
451 454 except DataIsObject as e:
452 455 # macros have a special edit function
453 456 if isinstance(data, Macro):
454 457 raise MacroToEdit(data) from e
455 458
456 459 # For objects, try to edit the file where they are defined
457 460 filename = find_file(data)
458 461 if filename:
459 462 if 'fakemodule' in filename.lower() and \
460 463 inspect.isclass(data):
461 464 # class created by %edit? Try to find source
462 465 # by looking for method definitions instead, the
463 466 # __module__ in those classes is FakeModule.
464 467 attrs = [getattr(data, aname) for aname in dir(data)]
465 468 for attr in attrs:
466 469 if not inspect.ismethod(attr):
467 470 continue
468 471 filename = find_file(attr)
469 472 if filename and \
470 473 'fakemodule' not in filename.lower():
471 474 # change the attribute to be the edit
472 475 # target instead
473 476 data = attr
474 477 break
475 478
476 479 m = ipython_input_pat.match(os.path.basename(filename))
477 480 if m:
478 481 raise InteractivelyDefined(int(m.groups()[0])) from e
479 482
480 483 datafile = 1
481 484 if filename is None:
482 485 filename = make_filename(args)
483 486 datafile = 1
484 487 if filename is not None:
485 488 # only warn about this if we get a real name
486 489 warn('Could not find file where `%s` is defined.\n'
487 490 'Opening a file named `%s`' % (args, filename))
488 491 # Now, make sure we can actually read the source (if it was
489 492 # in a temp file it's gone by now).
490 493 if datafile:
491 494 if lineno is None:
492 495 lineno = find_source_lines(data)
493 496 if lineno is None:
494 497 filename = make_filename(args)
495 498 if filename is None:
496 499 warn('The file where `%s` was defined '
497 500 'cannot be read or found.' % data)
498 501 return (None, None, None)
499 502 use_temp = False
500 503
501 504 if use_temp:
502 505 filename = shell.mktempfile(data)
503 506 print('IPython will make a temporary file named:',filename)
504 507
505 508 # use last_call to remember the state of the previous call, but don't
506 509 # let it be clobbered by successive '-p' calls.
507 510 try:
508 511 last_call[0] = shell.displayhook.prompt_count
509 512 if not opts_prev:
510 513 last_call[1] = args
511 514 except:
512 515 pass
513 516
514 517
515 518 return filename, lineno, use_temp
516 519
517 520 def _edit_macro(self,mname,macro):
518 521 """open an editor with the macro data in a file"""
519 522 filename = self.shell.mktempfile(macro.value)
520 523 self.shell.hooks.editor(filename)
521 524
522 525 # and make a new macro object, to replace the old one
523 526 mvalue = Path(filename).read_text()
524 527 self.shell.user_ns[mname] = Macro(mvalue)
525 528
526 529 @skip_doctest
527 530 @line_magic
528 531 def edit(self, parameter_s='',last_call=['','']):
529 532 """Bring up an editor and execute the resulting code.
530 533
531 534 Usage:
532 535 %edit [options] [args]
533 536
534 537 %edit runs IPython's editor hook. The default version of this hook is
535 538 set to call the editor specified by your $EDITOR environment variable.
536 539 If this isn't found, it will default to vi under Linux/Unix and to
537 540 notepad under Windows. See the end of this docstring for how to change
538 541 the editor hook.
539 542
540 543 You can also set the value of this editor via the
541 544 ``TerminalInteractiveShell.editor`` option in your configuration file.
542 545 This is useful if you wish to use a different editor from your typical
543 546 default with IPython (and for Windows users who typically don't set
544 547 environment variables).
545 548
546 549 This command allows you to conveniently edit multi-line code right in
547 550 your IPython session.
548 551
549 552 If called without arguments, %edit opens up an empty editor with a
550 553 temporary file and will execute the contents of this file when you
551 554 close it (don't forget to save it!).
552 555
553 556
554 557 Options:
555 558
556 559 -n <number>: open the editor at a specified line number. By default,
557 560 the IPython editor hook uses the unix syntax 'editor +N filename', but
558 561 you can configure this by providing your own modified hook if your
559 562 favorite editor supports line-number specifications with a different
560 563 syntax.
561 564
562 565 -p: this will call the editor with the same data as the previous time
563 566 it was used, regardless of how long ago (in your current session) it
564 567 was.
565 568
566 569 -r: use 'raw' input. This option only applies to input taken from the
567 570 user's history. By default, the 'processed' history is used, so that
568 571 magics are loaded in their transformed version to valid Python. If
569 572 this option is given, the raw input as typed as the command line is
570 573 used instead. When you exit the editor, it will be executed by
571 574 IPython's own processor.
572 575
573 576 -x: do not execute the edited code immediately upon exit. This is
574 577 mainly useful if you are editing programs which need to be called with
575 578 command line arguments, which you can then do using %run.
576 579
577 580
578 581 Arguments:
579 582
580 583 If arguments are given, the following possibilities exist:
581 584
582 585 - If the argument is a filename, IPython will load that into the
583 586 editor. It will execute its contents with execfile() when you exit,
584 587 loading any code in the file into your interactive namespace.
585 588
586 589 - The arguments are ranges of input history, e.g. "7 ~1/4-6".
587 590 The syntax is the same as in the %history magic.
588 591
589 592 - If the argument is a string variable, its contents are loaded
590 593 into the editor. You can thus edit any string which contains
591 594 python code (including the result of previous edits).
592 595
593 596 - If the argument is the name of an object (other than a string),
594 597 IPython will try to locate the file where it was defined and open the
595 598 editor at the point where it is defined. You can use `%edit function`
596 599 to load an editor exactly at the point where 'function' is defined,
597 600 edit it and have the file be executed automatically.
598 601
599 602 - If the object is a macro (see %macro for details), this opens up your
600 603 specified editor with a temporary file containing the macro's data.
601 604 Upon exit, the macro is reloaded with the contents of the file.
602 605
603 606 Note: opening at an exact line is only supported under Unix, and some
604 607 editors (like kedit and gedit up to Gnome 2.8) do not understand the
605 608 '+NUMBER' parameter necessary for this feature. Good editors like
606 609 (X)Emacs, vi, jed, pico and joe all do.
607 610
608 611 After executing your code, %edit will return as output the code you
609 612 typed in the editor (except when it was an existing file). This way
610 613 you can reload the code in further invocations of %edit as a variable,
611 614 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
612 615 the output.
613 616
614 617 Note that %edit is also available through the alias %ed.
615 618
616 619 This is an example of creating a simple function inside the editor and
617 620 then modifying it. First, start up the editor::
618 621
619 622 In [1]: edit
620 623 Editing... done. Executing edited code...
621 624 Out[1]: 'def foo():\\n print "foo() was defined in an editing
622 625 session"\\n'
623 626
624 627 We can then call the function foo()::
625 628
626 629 In [2]: foo()
627 630 foo() was defined in an editing session
628 631
629 632 Now we edit foo. IPython automatically loads the editor with the
630 633 (temporary) file where foo() was previously defined::
631 634
632 635 In [3]: edit foo
633 636 Editing... done. Executing edited code...
634 637
635 638 And if we call foo() again we get the modified version::
636 639
637 640 In [4]: foo()
638 641 foo() has now been changed!
639 642
640 643 Here is an example of how to edit a code snippet successive
641 644 times. First we call the editor::
642 645
643 646 In [5]: edit
644 647 Editing... done. Executing edited code...
645 648 hello
646 649 Out[5]: "print 'hello'\\n"
647 650
648 651 Now we call it again with the previous output (stored in _)::
649 652
650 653 In [6]: edit _
651 654 Editing... done. Executing edited code...
652 655 hello world
653 656 Out[6]: "print 'hello world'\\n"
654 657
655 658 Now we call it with the output #8 (stored in _8, also as Out[8])::
656 659
657 660 In [7]: edit _8
658 661 Editing... done. Executing edited code...
659 662 hello again
660 663 Out[7]: "print 'hello again'\\n"
661 664
662 665
663 666 Changing the default editor hook:
664 667
665 668 If you wish to write your own editor hook, you can put it in a
666 669 configuration file which you load at startup time. The default hook
667 670 is defined in the IPython.core.hooks module, and you can use that as a
668 671 starting example for further modifications. That file also has
669 672 general instructions on how to set a new hook for use once you've
670 673 defined it."""
671 674 opts,args = self.parse_options(parameter_s,'prxn:')
672 675
673 676 try:
674 677 filename, lineno, is_temp = self._find_edit_target(self.shell,
675 678 args, opts, last_call)
676 679 except MacroToEdit as e:
677 680 self._edit_macro(args, e.args[0])
678 681 return
679 682 except InteractivelyDefined as e:
680 683 print("Editing In[%i]" % e.index)
681 684 args = str(e.index)
682 685 filename, lineno, is_temp = self._find_edit_target(self.shell,
683 686 args, opts, last_call)
684 687 if filename is None:
685 688 # nothing was found, warnings have already been issued,
686 689 # just give up.
687 690 return
688 691
689 692 if is_temp:
690 693 self._knowntemps.add(filename)
691 694 elif (filename in self._knowntemps):
692 695 is_temp = True
693 696
694 697
695 698 # do actual editing here
696 699 print('Editing...', end=' ')
697 700 sys.stdout.flush()
698 701 filepath = Path(filename)
699 702 try:
700 703 # Quote filenames that may have spaces in them when opening
701 704 # the editor
702 705 quoted = filename = str(filepath.absolute())
703 706 if " " in quoted:
704 707 quoted = "'%s'" % quoted
705 708 self.shell.hooks.editor(quoted, lineno)
706 709 except TryNext:
707 710 warn('Could not open editor')
708 711 return
709 712
710 713 # XXX TODO: should this be generalized for all string vars?
711 714 # For now, this is special-cased to blocks created by cpaste
712 715 if args.strip() == "pasted_block":
713 716 self.shell.user_ns["pasted_block"] = filepath.read_text()
714 717
715 718 if 'x' in opts: # -x prevents actual execution
716 719 print()
717 720 else:
718 721 print('done. Executing edited code...')
719 722 with preserve_keys(self.shell.user_ns, '__file__'):
720 723 if not is_temp:
721 724 self.shell.user_ns['__file__'] = filename
722 725 if 'r' in opts: # Untranslated IPython code
723 726 source = filepath.read_text()
724 727 self.shell.run_cell(source, store_history=False)
725 728 else:
726 729 self.shell.safe_execfile(filename, self.shell.user_ns,
727 730 self.shell.user_ns)
728 731
729 732 if is_temp:
730 733 try:
731 734 return filepath.read_text()
732 735 except IOError as msg:
733 736 if Path(msg.filename) == filepath:
734 737 warn('File not found. Did you forget to save?')
735 738 return
736 739 else:
737 740 self.shell.showtraceback()
General Comments 0
You need to be logged in to leave comments. Login now