Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 97429bd

Browse files
committed
Merge pull request #9487 from takluyver/ptk-ipdb
Ipdb with prompt_toolkit
2 parents 8185987 + 94631e5 commit 97429bd

7 files changed

Lines changed: 166 additions & 97 deletions

File tree

IPython/core/completer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,9 @@ def __init__(self, shell=None, namespace=None, global_namespace=None,
652652
self.dict_key_matches,
653653
]
654654

655+
# This is set externally by InteractiveShell
656+
self.custom_completers = None
657+
655658
def all_completions(self, text):
656659
"""
657660
Wrapper around the complete method for the benefit of emacs.
@@ -1072,6 +1075,9 @@ def latex_matches(self, text):
10721075
return u'', []
10731076

10741077
def dispatch_custom_completer(self, text):
1078+
if not self.custom_completers:
1079+
return
1080+
10751081
line = self.line_buffer
10761082
if not line.strip():
10771083
return None

IPython/core/debugger.py

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from IPython.core.excolors import exception_colors
3838
from IPython.testing.skipdoctest import skip_doctest
3939

40+
4041
prompt = 'ipdb> '
4142

4243
#We have to check this directly from sys.argv, config struct not yet available
@@ -120,12 +121,6 @@ def __init__(self, colors=None):
120121
sys.excepthook = functools.partial(BdbQuit_excepthook,
121122
excepthook=sys.excepthook)
122123
def_colors = 'NoColor'
123-
try:
124-
# Limited tab completion support
125-
import readline
126-
readline.parse_and_bind('tab: complete')
127-
except ImportError:
128-
pass
129124
else:
130125
# In ipython, we use its custom exception handler mechanism
131126
def_colors = ip.colors
@@ -205,7 +200,7 @@ def __init__(self,color_scheme='NoColor',completekey=None,
205200
except (TypeError, ValueError):
206201
raise ValueError("Context must be a positive integer")
207202

208-
OldPdb.__init__(self,completekey,stdin,stdout)
203+
OldPdb.__init__(self, completekey, stdin, stdout)
209204

210205
# IPython changes...
211206
self.shell = get_ipython()
@@ -245,31 +240,17 @@ def __init__(self,color_scheme='NoColor',completekey=None,
245240
self.parser = PyColorize.Parser()
246241

247242
# Set the prompt - the default prompt is '(Pdb)'
248-
Colors = cst.active_colors
249-
if color_scheme == 'NoColor':
250-
self.prompt = prompt
251-
else:
252-
# The colour markers are wrapped by bytes 01 and 02 so that readline
253-
# can calculate the width.
254-
self.prompt = u'\x01%s\x02%s\x01%s\x02' % (Colors.prompt, prompt, Colors.Normal)
243+
self.prompt = prompt
255244

256245
def set_colors(self, scheme):
257246
"""Shorthand access to the color table scheme selector method."""
258247
self.color_scheme_table.set_active_scheme(scheme)
259248

260249
def interaction(self, frame, traceback):
261-
self.shell.set_completer_frame(frame)
262-
while True:
263-
try:
264-
OldPdb.interaction(self, frame, traceback)
265-
break
266-
except KeyboardInterrupt:
267-
self.shell.write('\n' + self.shell.get_exception_only())
268-
break
269-
finally:
270-
# Pdb sets readline delimiters, so set them back to our own
271-
if self.shell.readline is not None:
272-
self.shell.readline.set_completer_delims(self.shell.readline_delims)
250+
try:
251+
OldPdb.interaction(self, frame, traceback)
252+
except KeyboardInterrupt:
253+
sys.stdout.write('\n' + self.shell.get_exception_only())
273254

274255
def parseline(self, line):
275256
if line.startswith("!!"):
@@ -284,18 +265,15 @@ def parseline(self, line):
284265

285266
def new_do_up(self, arg):
286267
OldPdb.do_up(self, arg)
287-
self.shell.set_completer_frame(self.curframe)
288268
do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
289269

290270
def new_do_down(self, arg):
291271
OldPdb.do_down(self, arg)
292-
self.shell.set_completer_frame(self.curframe)
293272

294273
do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
295274

296275
def new_do_frame(self, arg):
297276
OldPdb.do_frame(self, arg)
298-
self.shell.set_completer_frame(self.curframe)
299277

300278
def new_do_quit(self, arg):
301279

@@ -312,9 +290,6 @@ def new_do_restart(self, arg):
312290
self.msg("Restart doesn't make sense here. Using 'quit' instead.")
313291
return self.do_quit(arg)
314292

315-
def postloop(self):
316-
self.shell.set_completer_frame(None)
317-
318293
def print_stack_trace(self, context=None):
319294
if context is None:
320295
context = self.context

IPython/core/interactiveshell.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from IPython.core.builtin_trap import BuiltinTrap
4343
from IPython.core.events import EventManager, available_events
4444
from IPython.core.compilerop import CachingCompiler, check_linecache_ipython
45+
from IPython.core.debugger import Pdb
4546
from IPython.core.display_trap import DisplayTrap
4647
from IPython.core.displayhook import DisplayHook
4748
from IPython.core.displaypub import DisplayPublisher
@@ -1584,6 +1585,8 @@ def init_history(self):
15841585
# Things related to exception handling and tracebacks (not debugging)
15851586
#-------------------------------------------------------------------------
15861587

1588+
debugger_cls = Pdb
1589+
15871590
def init_traceback_handlers(self, custom_exceptions):
15881591
# Syntax error handler.
15891592
self.SyntaxTB = ultratb.SyntaxTB(color_scheme='NoColor')
@@ -1594,7 +1597,8 @@ def init_traceback_handlers(self, custom_exceptions):
15941597
self.InteractiveTB = ultratb.AutoFormattedTB(mode = 'Plain',
15951598
color_scheme='NoColor',
15961599
tb_offset = 1,
1597-
check_cache=check_linecache_ipython)
1600+
check_cache=check_linecache_ipython,
1601+
debugger_cls=self.debugger_cls)
15981602

15991603
# The instance will store a pointer to the system-wide exception hook,
16001604
# so that runtime code (such as magics) can access it. This is because

IPython/core/ultratb.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ class VerboseTB(TBTools):
809809

810810
def __init__(self, color_scheme='Linux', call_pdb=False, ostream=None,
811811
tb_offset=0, long_header=False, include_vars=True,
812-
check_cache=None):
812+
check_cache=None, debugger_cls = None):
813813
"""Specify traceback offset, headers and color scheme.
814814
815815
Define how many frames to drop from the tracebacks. Calling it with
@@ -830,6 +830,8 @@ def __init__(self, color_scheme='Linux', call_pdb=False, ostream=None,
830830
check_cache = linecache.checkcache
831831
self.check_cache = check_cache
832832

833+
self.debugger_cls = debugger_cls or debugger.Pdb
834+
833835
def format_records(self, records, last_unique, recursion_repeat):
834836
"""Format the stack frames of the traceback"""
835837
frames = []
@@ -1217,7 +1219,7 @@ def debugger(self, force=False):
12171219

12181220
if force or self.call_pdb:
12191221
if self.pdb is None:
1220-
self.pdb = debugger.Pdb(
1222+
self.pdb = self.debugger_cls(
12211223
self.color_scheme_table.active_scheme_name)
12221224
# the system displayhook may have changed, restore the original
12231225
# for pdb
@@ -1278,7 +1280,7 @@ class FormattedTB(VerboseTB, ListTB):
12781280
def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
12791281
ostream=None,
12801282
tb_offset=0, long_header=False, include_vars=False,
1281-
check_cache=None):
1283+
check_cache=None, debugger_cls=None):
12821284

12831285
# NEVER change the order of this list. Put new modes at the end:
12841286
self.valid_modes = ['Plain', 'Context', 'Verbose']
@@ -1287,7 +1289,7 @@ def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
12871289
VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
12881290
ostream=ostream, tb_offset=tb_offset,
12891291
long_header=long_header, include_vars=include_vars,
1290-
check_cache=check_cache)
1292+
check_cache=check_cache, debugger_cls=debugger_cls)
12911293

12921294
# Different types of tracebacks are joined with different separators to
12931295
# form a single string. They are taken from this dict

IPython/terminal/debugger.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from IPython.core.debugger import Pdb
2+
3+
from IPython.core.completer import IPCompleter
4+
from .ptutils import IPythonPTCompleter
5+
6+
from prompt_toolkit.token import Token
7+
from prompt_toolkit.shortcuts import create_prompt_application
8+
from prompt_toolkit.interface import CommandLineInterface
9+
from prompt_toolkit.enums import EditingMode
10+
11+
class TerminalPdb(Pdb):
12+
def __init__(self, *args, **kwargs):
13+
Pdb.__init__(self, *args, **kwargs)
14+
self._ptcomp = None
15+
self.pt_init()
16+
17+
def pt_init(self):
18+
def get_prompt_tokens(cli):
19+
return [(Token.Prompt, self.prompt)]
20+
21+
if self._ptcomp is None:
22+
compl = IPCompleter(shell=self.shell,
23+
namespace={},
24+
global_namespace={},
25+
use_readline=False,
26+
parent=self.shell,
27+
)
28+
self._ptcomp = IPythonPTCompleter(compl)
29+
30+
self._pt_app = create_prompt_application(
31+
editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
32+
history=self.shell.debugger_history,
33+
completer= self._ptcomp,
34+
enable_history_search=True,
35+
mouse_support=self.shell.mouse_support,
36+
get_prompt_tokens=get_prompt_tokens
37+
)
38+
self.pt_cli = CommandLineInterface(self._pt_app, eventloop=self.shell._eventloop)
39+
40+
def cmdloop(self, intro=None):
41+
"""Repeatedly issue a prompt, accept input, parse an initial prefix
42+
off the received input, and dispatch to action methods, passing them
43+
the remainder of the line as argument.
44+
45+
override the same methods from cmd.Cmd to provide prompt toolkit replacement.
46+
"""
47+
if not self.use_rawinput:
48+
raise ValueError('Sorry ipdb does not support use_rawinput=False')
49+
50+
self.preloop()
51+
52+
try:
53+
if intro is not None:
54+
self.intro = intro
55+
if self.intro:
56+
self.stdout.write(str(self.intro)+"\n")
57+
stop = None
58+
while not stop:
59+
if self.cmdqueue:
60+
line = self.cmdqueue.pop(0)
61+
else:
62+
self._ptcomp.ipy_completer.namespace = self.curframe_locals
63+
self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
64+
try:
65+
line = self.pt_cli.run(reset_current_buffer=True).text
66+
except EOFError:
67+
line = 'EOF'
68+
line = self.precmd(line)
69+
stop = self.onecmd(line)
70+
stop = self.postcmd(stop, line)
71+
self.postloop()
72+
except Exception:
73+
raise

IPython/terminal/ptshell.py

Lines changed: 7 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,89 +4,32 @@
44
import os
55
import sys
66
import signal
7-
import unicodedata
87
from warnings import warn
9-
from wcwidth import wcwidth
108

119
from IPython.core.error import TryNext
1210
from IPython.core.interactiveshell import InteractiveShell
13-
from IPython.utils.py3compat import PY3, cast_unicode_py2, input
11+
from IPython.utils.py3compat import cast_unicode_py2, input
1412
from IPython.utils.terminal import toggle_set_term_title, set_term_title
1513
from IPython.utils.process import abbrev_cwd
1614
from traitlets import Bool, Unicode, Dict, Integer, observe
1715

18-
from prompt_toolkit.completion import Completer, Completion
1916
from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
2017
from prompt_toolkit.filters import HasFocus, HasSelection, Condition, ViInsertMode, EmacsInsertMode, IsDone
2118
from prompt_toolkit.history import InMemoryHistory
2219
from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout
2320
from prompt_toolkit.interface import CommandLineInterface
2421
from prompt_toolkit.key_binding.manager import KeyBindingManager
2522
from prompt_toolkit.keys import Keys
26-
from prompt_toolkit.layout.lexers import Lexer
27-
from prompt_toolkit.layout.lexers import PygmentsLexer
2823
from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
2924
from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
3025

3126
from pygments.styles import get_style_by_name, get_all_styles
32-
from pygments.lexers import Python3Lexer, BashLexer, PythonLexer
3327
from pygments.token import Token
3428

29+
from .debugger import TerminalPdb
3530
from .pt_inputhooks import get_inputhook_func
3631
from .interactiveshell import get_default_editor, TerminalMagics
37-
38-
39-
class IPythonPTCompleter(Completer):
40-
"""Adaptor to provide IPython completions to prompt_toolkit"""
41-
def __init__(self, ipy_completer):
42-
self.ipy_completer = ipy_completer
43-
44-
def get_completions(self, document, complete_event):
45-
if not document.current_line.strip():
46-
return
47-
48-
used, matches = self.ipy_completer.complete(
49-
line_buffer=document.current_line,
50-
cursor_pos=document.cursor_position_col
51-
)
52-
start_pos = -len(used)
53-
for m in matches:
54-
m = unicodedata.normalize('NFC', m)
55-
56-
# When the first character of the completion has a zero length,
57-
# then it's probably a decomposed unicode character. E.g. caused by
58-
# the "\dot" completion. Try to compose again with the previous
59-
# character.
60-
if wcwidth(m[0]) == 0:
61-
if document.cursor_position + start_pos > 0:
62-
char_before = document.text[document.cursor_position + start_pos - 1]
63-
m = unicodedata.normalize('NFC', char_before + m)
64-
65-
# Yield the modified completion instead, if this worked.
66-
if wcwidth(m[0:1]) == 1:
67-
yield Completion(m, start_position=start_pos - 1)
68-
continue
69-
70-
# TODO: Use Jedi to determine meta_text
71-
# (Jedi currently has a bug that results in incorrect information.)
72-
# meta_text = ''
73-
# yield Completion(m, start_position=start_pos,
74-
# display_meta=meta_text)
75-
yield Completion(m, start_position=start_pos)
76-
77-
class IPythonPTLexer(Lexer):
78-
"""
79-
Wrapper around PythonLexer and BashLexer.
80-
"""
81-
def __init__(self):
82-
self.python_lexer = PygmentsLexer(Python3Lexer if PY3 else PythonLexer)
83-
self.shell_lexer = PygmentsLexer(BashLexer)
84-
85-
def lex_document(self, cli, document):
86-
if document.text.startswith('!'):
87-
return self.shell_lexer.lex_document(cli, document)
88-
else:
89-
return self.python_lexer.lex_document(cli, document)
32+
from .ptutils import IPythonPTCompleter, IPythonPTLexer
9033

9134

9235
class TerminalInteractiveShell(InteractiveShell):
@@ -100,6 +43,8 @@ def _space_for_menu_changed(self, old, new):
10043
self._update_layout()
10144

10245
pt_cli = None
46+
debugger_history = None
47+
debugger_cls = TerminalPdb
10348

10449
autoedit_syntax = Bool(False,
10550
help="auto editing of files with syntax errors.",
@@ -362,6 +307,8 @@ def __init__(self, *args, **kwargs):
362307
self.init_term_title()
363308
self.keep_running = True
364309

310+
self.debugger_history = InMemoryHistory()
311+
365312
def ask_exit(self):
366313
self.keep_running = False
367314

0 commit comments

Comments
 (0)