@@ -46,7 +46,7 @@ def plugin_loaded():
4646# Constants #
4747#############
4848
49- bp_regex = r"^[\t ]*import [\w.; ]+set_trace\(\) # breakpoint ([a-f0-9]{8}) //"
49+ bp_regex = r"^[\t ]*import [\w.; ]+set_trace\(\) # breakpoint ([a-f0-9]{8})([a-z]?) //"
5050bp_re = re .compile (bp_regex , re .DOTALL )
5151
5252EXPR_PRE = ['class' , 'def' , 'if' , 'for' , 'try' , 'while' , 'with' ]
@@ -56,17 +56,22 @@ def plugin_loaded():
5656expr_re1 = re .compile (r"^[\t ]*({tokens})[: ]" .format (tokens = '|' .join (EXPR_PRE + EXPR_PST )))
5757expr_re2 = re .compile (r"^[\t ]*({tokens})[: ]" .format (tokens = '|' .join (EXPR_PST )))
5858
59+ TRY_STR = 'try:'
60+ EXCEPT_STR = 'except Exception as exc:'
61+
5962
6063class Breakpoint (object ):
6164 """
6265 Breakpoint object with its UID
6366 """
64- def __init__ (self , from_text = None ):
67+ def __init__ (self , from_text = None , in_block = False ):
6568 self .uid = None
69+ self .in_block = in_block
6670 if from_text is not None :
6771 m = bp_re .match (from_text )
6872 if m :
6973 self .uid = m .groups ()[0 ]
74+ self .in_block = m .groups ()[1 ] == 'x'
7075 else :
7176 self .uid = str (uuid .uuid4 ())[- 8 :]
7277
@@ -82,8 +87,8 @@ def as_string(self, indent):
8287 format breakpoint string
8388 """
8489 debugger = settings .get ('debugger' , 'pdb' )
85- return "{indent}import {debugger }; {debugger }.set_trace() # breakpoint {uid} //\n " .format (
86- indent = ' ' * indent , debugger = debugger , uid = self .uid )
90+ return "{indent}import {dbg }; {dbg }.set_trace() # breakpoint {uid}{mark } //\n " .format (
91+ indent = ' ' * indent , dbg = debugger , uid = self .uid , mark = 'x' if self . in_block else '' )
8792
8893 def highlight (self , view , rg ):
8994 """
@@ -108,6 +113,13 @@ def save_file(view):
108113 view .run_command ('save' )
109114
110115
116+ def get_indent (s ):
117+ """
118+ return number of spaces left of the first non-whitespace character in s
119+ """
120+ return len (s ) - len (s .lstrip ())
121+
122+
111123def get_line_number (view , rg ):
112124 """
113125 line number from region
@@ -161,10 +173,9 @@ def calc_indent(view, rg):
161173 next_line = view .substr (lines [ln + 1 ])
162174
163175 # calculate indent of current, previous and next lines
164- _indent = lambda x : len (x ) - len (x .lstrip ())
165- curr_indent = _indent (curr_line )
166- prev_indent = _indent (prev_line )
167- next_indent = _indent (next_line )
176+ curr_indent = get_indent (curr_line )
177+ prev_indent = get_indent (prev_line )
178+ next_indent = get_indent (next_line )
168179 debug ('indent p' , prev_indent , 'c' , curr_indent , 'n' , next_indent )
169180
170181 def _result (msg , indent ):
@@ -212,7 +223,7 @@ def find_breakpoint(view):
212223 return position of the 1st breakpoint, or None
213224 """
214225 rg = view .find (bp_regex , 0 )
215- if rg : return rg .end ()
226+ return rg .end () if rg else None
216227
217228
218229def remove_breakpoint (edit , view , rg ):
@@ -228,19 +239,79 @@ def remove_breakpoint(edit, view, rg):
228239 if bp .uid :
229240 view .erase (edit , view .full_line (line ))
230241 view .erase_regions (bp .region_id )
242+ if bp .in_block :
243+ # remove the try-except block
244+ indent = None
245+ for pline in reversed (lines [:ln ]):
246+ if pline == line :
247+ continue
248+
249+ s = view .substr (pline )
250+
251+ # first, find the "except:" block
252+ if indent is None :
253+ if s .strip ().startswith (EXCEPT_STR ):
254+ indent = get_indent (s )
255+ view .erase (edit , view .full_line (pline ))
256+ continue
257+
258+ # it's a "try:" at the same indent level, finish
259+ if s .startswith (' ' * indent + TRY_STR ):
260+ view .erase (edit , view .full_line (pline ))
261+ break
262+
263+ # else outdent each line within the try-except block
264+ indent_rg = sublime .Region (pline .begin (), pline .begin () + tab_size )
265+ view .erase (edit , indent_rg )
231266 return True
232267 return False
233268
234269
235270def insert_breakpoint (edit , view , rg ):
271+ indent = calc_indent (view , rg )
272+ if indent is None :
273+ return
274+
236275 bp = Breakpoint ()
237276 rg_a = rg .begin ()
238- indent = calc_indent (view , rg )
239- if indent is not None :
240- bp_rg_sz = view .insert (edit , rg_a , bp .as_string (indent ))
241- color_rg = sublime .Region (rg_a , rg_a + bp_rg_sz )
242- bp .highlight (view , color_rg )
243- goto_position (view , rg_a + indent )
277+ bp_rg_sz = view .insert (edit , rg_a , bp .as_string (indent ))
278+ color_rg = sublime .Region (rg_a , rg_a + bp_rg_sz )
279+ bp .highlight (view , color_rg )
280+ goto_position (view , rg_a + indent )
281+
282+
283+ def insert_try_except_breakpoint (edit , view , rgs ):
284+ # find first non-empty non-comment line in the selection and get its indent
285+ indent = None
286+ for rg in rgs :
287+ line = view .substr (rg ).strip ()
288+ if line and not line .startswith ('#' ):
289+ indent = get_indent (view .substr (rg ))
290+ break
291+
292+ if indent is None : # nothing worthwhile in the selection
293+ return
294+
295+ rg_a = rgs [0 ].begin ()
296+ rg_b = view .full_line (rgs [- 1 ]).end ()
297+ indent_str = ' ' * indent
298+
299+ # insert "try:" at the same indent level
300+ offset = view .insert (edit , rg_a , "{}{}\n " .format (indent_str , TRY_STR ))
301+
302+ # add one level of indentation to every line in the block
303+ for rg in rgs :
304+ offset += view .insert (edit , rg .begin () + offset , ' ' * tab_size )
305+
306+ offset += view .insert (edit , rg_b + offset , "{}{}\n " .format (indent_str , EXCEPT_STR ))
307+
308+ # insert the breakpoint
309+ bp = Breakpoint (in_block = True )
310+ rg_a = rg_b + offset
311+ bp_rg_sz = view .insert (edit , rg_a , bp .as_string (indent + tab_size ))
312+ color_rg = sublime .Region (rg_a , rg_a + bp_rg_sz )
313+ bp .highlight (view , color_rg )
314+ goto_position (view , rg_a + indent + tab_size )
244315
245316
246317###############
@@ -252,15 +323,22 @@ class ToggleBreakpointCommand(sublime_plugin.TextCommand):
252323 def run (self , edit ):
253324 view = self .view
254325
255- # don't handle non-Python and selected text
256- if not ( is_python (view ) and view . sel ()[ 0 ]. empty () ):
326+ # don't handle non-Python
327+ if not is_python (view ):
257328 return
258329
259- # remove/insert the breakpoint
260- rg = view .line (view .sel ()[0 ])
261- if not remove_breakpoint (edit , view , rg ):
262- insert_breakpoint (edit , view , rg )
263- save_file (view )
330+ # is there a selected text?
331+ if view .sel ()[0 ].empty ():
332+ # remove/insert a one-line breakpoint
333+ rg = view .line (view .sel ()[0 ])
334+ if not remove_breakpoint (edit , view , rg ):
335+ insert_breakpoint (edit , view , rg )
336+ save_file (view )
337+ else :
338+ # wrap the selected text in try-except
339+ rgs = view .lines (view .sel ()[0 ])
340+ insert_try_except_breakpoint (edit , view , rgs )
341+ save_file (view )
264342
265343
266344class GotoBreakpointCommand (sublime_plugin .TextCommand ):
@@ -290,7 +368,7 @@ def run(self, edit):
290368 if bp_re .match (s ):
291369 s = s [s .find ('# breakpoint' ) + 2 :]
292370
293- items [i ].append ('%d: %s' % (lnn , s ))
371+ items [i ].append ('{}: {}' . format (lnn , s ))
294372 if len (items [i ]) > 2 :
295373 break
296374
0 commit comments