Skip to content

Commit 8a92561

Browse files
committed
add wrapping code selection in a try-except block
1 parent 6bc5567 commit 8a92561

File tree

4 files changed

+116
-24
lines changed

4 files changed

+116
-24
lines changed

PythonBreakpoints.py

Lines changed: 101 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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]?) //"
5050
bp_re = re.compile(bp_regex, re.DOTALL)
5151

5252
EXPR_PRE = ['class', 'def', 'if', 'for', 'try', 'while', 'with']
@@ -56,17 +56,22 @@ def plugin_loaded():
5656
expr_re1 = re.compile(r"^[\t ]*({tokens})[: ]".format(tokens='|'.join(EXPR_PRE + EXPR_PST)))
5757
expr_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

6063
class 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+
111123
def 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

218229
def 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

235270
def 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

266344
class 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

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ From GitHub: Clone this repository into your version/platform specific Packages
3333
* `Goto` a selected breakpoint (or `ctrl+shift+g`, or `Menu` > `Goto` > `Goto Python Breakpoint...`)
3434
* `Clear All` breakpoints in current file (or `Menu` > `Tools` > `Breakpoints` > `Clear All Python Breakpoints`)
3535

36+
If one or multiple lines of code are selected, the Toggle command will wrap the selected lines in a try-except statement and a new breakpoint will be added under the except: section.
37+
3638
## Settings
3739

3840
`Preferences` > `Package Settings` > `Python Breakpoints`

messages.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"0.3.2": "messages/0.3.2.txt"
2+
"0.3.2": "messages/0.3.2.txt",
3+
"0.4.0": "messages/0.4.0.txt"
34
}

messages/0.4.0.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Python Breakpoints 0.4.0 Changelog:
2+
3+
4+
New features:
5+
6+
- Now if you select a block of code (one or multiple lines), and press Toggle Breakpoint,
7+
the selected lines will be wrapped in a try-except statement and a new breakpoint will
8+
be added under the except: section.
9+
10+
To undo this construct simply place the cursor on the breakpoint and press Toggle, as
11+
you would for a regular breakpoint.

0 commit comments

Comments
 (0)