diff --git a/autoload/textobj/python.vim b/autoload/textobj/python.vim index e23804b..ee3a474 100644 --- a/autoload/textobj/python.vim +++ b/autoload/textobj/python.vim @@ -22,153 +22,197 @@ " SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. " }}} -function! textobj#python#move_cursor_to_starting_line() - " Start at a nonblank line +function! s:skip_pair(start, end, ...) abort + let l:flag = (a:000 + [''])[0] let l:cur_pos = getpos('.') - let l:cur_line = getline('.') - if l:cur_line =~# '^\s*$' - call cursor(prevnonblank(l:cur_pos[1]), 0) + if l:flag =~# 'b' + call search(a:end, 'bcW', l:cur_pos[1]) + else + call search(a:start, 'cW', l:cur_pos[1]) + endif + if searchpair(a:start, '', a:end, l:flag.'rW') <= 0 + call cursor(l:cur_pos[1:]) + return 0 endif + return 1 endfunction -function! textobj#python#find_defn_line(kwd) +function! s:next_non_blank_or_comment(linenr) abort + let l:linenr = a:linenr + while 1 + let l:linenr = nextnonblank(l:linenr) + if l:linenr is# 0 || getline(l:linenr) !~# '^\s*#' + return l:linenr + endif + let l:linenr += 1 + endwhile +endfunction + +function! s:prev_non_blank_or_comment(linenr) abort + let l:linenr = a:linenr + while 1 + let l:linenr = prevnonblank(l:linenr) + if l:linenr is# 0 || getline(l:linenr) !~# '^\s*#' + return l:linenr + endif + let l:linenr -= 1 + endwhile +endfunction + +" Find the start position of the block at given defn pattern +" Return 0, if there is none. +function! textobj#python#find_defn_pos(pattern) + let l:save_pos = getpos('.') + + " Skip parens backward + call s:skip_pair('\[', '\]', 'b') + call s:skip_pair('(', ')', 'b') " Skip decorators - while getline('.') !~# '^\s*'.a:kwd.' ' - " Avoid skipping decorator under the cursor - normal! $ - let l:cur_pos = getpos('.') - let l:decorator = search('^\s*@.*(\?', 'bW') - if l:decorator == 0 - break - endif - " Match r-parent if any - normal! 0 - normal! % - normal! j - let l:new_pos = getpos('.') - if l:new_pos[1] <= l:cur_pos[1] - call setpos('.', l:cur_pos) - break - endif + while getline('.') =~# '^\s*@' + call s:skip_pair('(', ')') + let l:linenr = s:next_non_blank_or_comment(line('.') + 1) + if l:linenr is# 0 + " EOF + call cursor(l:save_pos[1:]) + return 0 + endif + call cursor(l:linenr, 1) endwhile - let l:cur_line = getline('.') + " If current line is defn, then return + call cursor(0, 1) + if search('^\s*\zs'.a:pattern.' ', 'c', line('.')) + return getpos('.') + endif + " Find a defn backward let l:cur_pos = getpos('.') - if l:cur_line =~# '^\s*'.a:kwd.' ' - let l:defn_pos = l:cur_pos - else - let l:cur_indent = indent(l:cur_pos[1]) - while 1 - if search('^\s*'.a:kwd.' ', 'bW') - let l:defn_pos = getpos('.') - let l:defn_indent = indent(l:defn_pos[1]) - if l:defn_indent >= l:cur_indent - " This is a defn at the same level or deeper, keep searching - continue - else - " Found a defn, make sure there aren't any statements at a - " shallower indent level in between - for l:l in range(l:defn_pos[1] + 1, l:cur_pos[1]) - if getline(l:l) !~# '^\s*$' && indent(l:l) <= l:defn_indent - throw "defn-not-found" - endif - endfor - break - endif - else - " We didn't find a suitable defn - throw "defn-not-found" - endif - endwhile - endif - call cursor(defn_pos) + let l:cur_indent = indent('.') + while 1 + if !search('^\s*\zs'.a:pattern.' ', 'bW') + " We didn't find a suitable defn + call cursor(l:save_pos[1:]) + return 0 + endif + let l:defn_pos = getpos('.') + let l:defn_indent = indent(l:defn_pos[1]) + if l:defn_indent < l:cur_indent + break + endif + " This is a defn at the same level or deeper, keep searching + endwhile + + " Skip multiline arguments and typings + call s:skip_pair('(', ')') + call s:skip_pair('\[', '\]') + + " Found a defn, make sure there aren't any statements at a + " shallower indent level in between + for l:l in range(line('.') + 1, l:cur_pos[1]) + if getline(l:l) !~# '^\s*\%(#.*\)\?$' && indent(l:l) <= l:defn_indent + call cursor(l:save_pos[1:]) + return 0 + endif + endfor + + call cursor(l:defn_pos[1:]) return l:defn_pos endfunction -function! textobj#python#find_prev_decorators(l) - " Find the line with the first (valid) decorator above `line`, return the - " current line, if there is none. - let l:linenr = a:l - let l:line_ident = indent(l:linenr) +" Find the position with the first (valid) decorator above defn position. +" Return the defn position, if there is none. +function! textobj#python#find_prev_decorators_pos(defn_pos) + let l:last_pos = a:defn_pos[:] + let l:linenr = a:defn_pos[1] + let l:defn_indent = indent(l:linenr) while 1 " Get the first not blank line - let l:prev = prevnonblank(l:linenr - 1) - if l:prev == 0 + let l:linenr = s:prev_non_blank_or_comment(l:linenr - 1) + if l:linenr is# 0 " There is not above current one. break endif - " Move cursor - let l:prev_pos = getpos('.') - let l:prev_pos[1] = l:prev - call setpos('.', l:prev_pos) + " Skip parens backward + call cursor(l:linenr, 0) + call s:skip_pair('(', ')', 'b') + let l:linenr = line('.') - let l:prev_indent = indent(l:prev_pos[1]) - if l:prev_indent < l:line_ident - " Indentention isn't valid for a decorator - break + " The decorator should be in the same level as defn + if search('^\s*\zs@', 'bcW', l:linenr) && indent(line('.')) == l:defn_indent + let l:last_pos = getpos('.') + continue endif - " Match l-parent if any - normal! $ - normal! % + " There is not a (valid) decorator + break + endwhile + call cursor(l:last_pos[1:]) + return l:last_pos +endfunction + +" Find the start position of the block inner at given defn position. +function! textobj#python#find_defn_inner_pos(defn_pos) + " Put the cursor on the def line + call cursor(a:defn_pos[1:]) - let l:prev_pos = getpos('.') + " Skip multiline arguments and typings + call s:skip_pair('(', ')') + call s:skip_pair('\[', '\]') - " The decorator should be in the same level - " as the original class/function. - if getline(l:prev_pos[1])[l:line_ident] != "@" - break - endif - let l:linenr = l:prev_pos[1] - endwhile - return l:linenr + if search(':\s*\zs#\@!\S.*$', 'cz', line('.')) + " It is a one-liner + else + " Start from the beginning of the next line + call cursor(line('.') + 1, 1) + endif + + return getpos('.') endfunction -function! textobj#python#find_last_line(kwd, defn_pos, indent_level) - " Find the last line of the block at given indent level - let l:cur_pos = getpos('.') - let l:end_pos = l:cur_pos - while 1 - " Is this a one-liner? - if getline('.') =~# '^\s*'.a:kwd.'\[^:\]\+:\s*\[^#\]' - return a:defn_pos - endif - " This isn't a one-liner, so skip the def line - if line('.') == a:defn_pos[1] - normal! j - continue - endif - if getline('.') !~# '^\s*$' - if indent('.') > a:indent_level - let l:end_pos = getpos('.') - else +" Find the last position of the block at given defn position. +function! textobj#python#find_last_pos(defn_pos) + " Put the cursor on the def inner position + let l:end_pos = textobj#python#find_defn_inner_pos(a:defn_pos) + + if l:end_pos[2] > 1 + " It is a one-liner + else + " Find the last line of deeper indent lines + let l:defn_indent = indent(a:defn_pos[1]) + let l:linenr = l:end_pos[1] + while 1 + let l:linenr = nextnonblank(l:linenr + 1) + if l:linenr is# 0 + " EOF break endif - endif - if line('.') == line('$') - break - else - normal! j - endif - endwhile - call cursor(l:cur_pos[1], l:cur_pos[2]) + if indent(l:linenr) <= l:defn_indent + " de-indented + if getline(l:linenr) =~# '^\s*#' + " Skip de-indented commend + continue + endif + break + endif + let l:end_pos[1] = l:linenr + endwhile + endif + + " Put the cursor on the end of line + let l:end_pos[2] = col([l:end_pos[1], '$']) + call cursor(l:end_pos[1:]) return l:end_pos endfunction -function! s:find_defn(kwd) - call textobj#python#move_cursor_to_starting_line() - - try - let l:defn_pos = textobj#python#find_defn_line(a:kwd) - catch /defn-not-found/ +function! s:find_defn_selection(pattern) + call cursor(s:next_non_blank_or_comment(line('.')), 0) + let l:defn_pos = textobj#python#find_defn_pos(a:pattern) + if empty(l:defn_pos) return 0 - endtry - let l:defn_indent_level = indent(l:defn_pos[1]) - - let l:end_pos = textobj#python#find_last_line(a:kwd, l:defn_pos, l:defn_indent_level) + endif + let l:end_pos = textobj#python#find_last_pos(l:defn_pos) return ['V', l:defn_pos, l:end_pos] endfunction @@ -182,45 +226,38 @@ function! s:select_surrounding_blank_lines(pos) if l:next_block_linenr != 0 if l:current_block_indent_level != 0 && l:next_block_indent_level == 0 - let l:desired_blanks = 2 - let l:desired_blanks = max([0, l:desired_blanks - l:blanks_on_start]) + let l:desired_blanks = 2 + let l:desired_blanks = max([0, l:desired_blanks - l:blanks_on_start]) let l:defn_pos[2][1] = l:next_block_linenr - 1 - l:desired_blanks - else + else let l:defn_pos[2][1] = l:next_block_linenr - 1 - endif + endif else let l:defn_pos[1][1] = prevnonblank(l:defn_pos[1][1] - 1) + 1 endif return l:defn_pos endfunction -function! textobj#python#select_a(kwd) - let l:defn_pos = s:find_defn(a:kwd) - if type(l:defn_pos) == type([]) - let l:defn_pos[1][1] = textobj#python#find_prev_decorators(l:defn_pos[1][1]) - let l:defn_pos = s:select_surrounding_blank_lines(l:defn_pos) - return l:defn_pos +function! textobj#python#select_a(pattern) + let l:cur_pos = getpos('.') + let l:defn_sel = s:find_defn_selection(a:pattern) + if !empty(l:defn_sel) + let l:defn_sel[1] = textobj#python#find_prev_decorators_pos(l:defn_sel[1]) + let l:defn_sel = s:select_surrounding_blank_lines(l:defn_sel) + return l:defn_sel endif return 0 endfunction -function! textobj#python#select_i(kwd) - let l:a_pos = s:find_defn(a:kwd) - if type(l:a_pos) == type([]) - if l:a_pos[1][1] == l:a_pos[2][1] - " This is a one-liner, treat it like af - " TODO Maybe change this to a 'v'-mode selection and only get the - " statement from the one-liner? - return l:a_pos +function! textobj#python#select_i(pattern) + let l:defn_sel = s:find_defn_selection(a:pattern) + if !empty(l:defn_sel) + let l:defn_sel[1] = textobj#python#find_defn_inner_pos(l:defn_sel[1]) + if l:defn_sel[1][1] is# l:defn_sel[2][1] + " It is a one-liner + let l:defn_sel[0] = 'v' endif - " Put the cursor on the def line - call cursor(l:a_pos[1][1], l:a_pos[1][2]) - " Get to the closing parenthesis if it exists - normal! ^f(% - " Start from the beginning of the next line - normal! j0 - let l:start_pos = getpos('.') - return ['V', l:start_pos, l:a_pos[2]] + return l:defn_sel endif return 0 endfunction diff --git a/test/test-typing.py b/test/test-typing.py new file mode 100644 index 0000000..ff3448d --- /dev/null +++ b/test/test-typing.py @@ -0,0 +1,95 @@ +from typing import Generic, List, Tuple, TypeVar + +T = TypeVar('T') +S = TypeVar('S') + + +class ClassWithTyping(Generic[T]): + pass + + +class ClassWithTypings(Generic[T, S]): + # ClassWithTyping + pass + + +def function_with_typing(foo: str) -> None: + pass + + +def function_with_typings(foo: str, bar: int) -> None: + # function_with_typings + pass + + +def oneliner_with_typing(foo: str) -> None: pass + + +def function_with_multiline_typings( + foo: str, + bar: List[int], + baz: T, qux: S, +) -> Tuple[ + str, # comment + Tuple[ + int, + int, + ], + # comment + T, S, +]: + # function_with_multiline_typings + pass + + +def oneliner_with_multiline_typings( + foo: str, + bar: List[int], + baz: T, qux: S, +) -> Tuple[ + str, # comment + Tuple[ + int, + int, + ], + # comment + T, S, +]: pass + + +class ClassWithMultilineTypings(Generic[ + T, # comment + # comment + S, +]): + # ClassWithMultilineTypings + pass + + +class RegularClass(): + def foo(self): + pass + + def method_with_typing(self, bar: str) -> None: + pass + + def method_with_multiline_typings( + self, + foo: str, + bar: List[int], + baz: T, qux: S, + ) -> Tuple[ + str, # comment + Tuple[ + int, + int, + ], + # comment + T, S, + ]: + # method_with_multiline_typings + pass + + +def at_end_of_file(): + pass diff --git a/test/textobj-python-typing.vader b/test/textobj-python-typing.vader new file mode 100644 index 0000000..155ac97 --- /dev/null +++ b/test/textobj-python-typing.vader @@ -0,0 +1,224 @@ +Before (Read in test-typing.py): + execute "read " . globpath(fnamemodify(g:vader_file, ":h"), "test-typing.py") + setf python + normal 1Gdd + +Execute (Typing: function): + /^def function_with_typing( + let linenr = getpos(".")[1] + norm vaf + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 3 + +Execute (Typing: inner function): + /^def function_with_typing( + let linenr = getpos(".")[1] + norm vif + AssertEqual getpos("'<")[1], linenr + 1 + AssertEqual getpos("'>")[1], linenr + 1 + +Execute (Typings: function): + /^def function_with_typings( + let linenr = getpos(".")[1] + norm vaf + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 4 + +Execute (Typings: function from typing): + /^def function_with_typings(foo:\zs + let linenr = getpos(".")[1] + norm vaf + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 4 + +Execute (Typings: inner function): + /^def function_with_typings( + norm vif + AssertEqual getline("."), "# function_with_typings" + AssertEqual getline("'>"), "pass" + +Execute (Typings: inner function from typing): + /^def function_with_typings(foo:\zs + norm vif + AssertEqual getline("."), "# function_with_typings" + AssertEqual getline("'>"), "pass" + +Execute (Typing: One-liner inner function v-mode): + /^def oneliner_with_typing( + let linenr = getpos(".")[1] + normal vif + AssertEqual [linenr, 45], getpos("'<")[1:2] + AssertEqual [linenr, 49], getpos("'>")[1:2] + +Execute (Typings: inner function from typings with multiple lines): + /^def function_with_multiline_typings( + norm jvif + AssertEqual getline("."), "# function_with_multiline_typings" + AssertEqual getline("'>"), "pass" + +Execute (Typings: inner function from typings with multiple lines (2)): + /^def function_with_multiline_typings( + norm 4jvif + AssertEqual getline("."), "# function_with_multiline_typings" + AssertEqual getline("'>"), "pass" + +Execute (Typings: inner function from typings with multiple lines (3)): + /^def function_with_multiline_typings( + norm 5jvif + AssertEqual getline("."), "# function_with_multiline_typings" + AssertEqual getline("'>"), "pass" + +Execute (Typings: inner function from typings with multiple lines (4)): + /^def function_with_multiline_typings( + norm 6jvif + AssertEqual getline("."), "# function_with_multiline_typings" + AssertEqual getline("'>"), "pass" + +Execute (Typings: inner function from typings with multiple lines (5)): + /^def function_with_multiline_typings( + norm 7jvif + AssertEqual getline("."), "# function_with_multiline_typings" + AssertEqual getline("'>"), "pass" + +Execute (Typings: inner function from typings with multiple lines (6)): + /^def function_with_multiline_typings( + norm 10jvif + AssertEqual getline("."), "# function_with_multiline_typings" + AssertEqual getline("'>"), "pass" + +Execute (Typings: function with typings with multiple lines): + /^def function_with_multiline_typings( + let linenr = getpos(".")[1] + norm jvaf + AssertEqual getline("."), "def function_with_multiline_typings(" + AssertEqual getpos("'>")[1], linenr + 16 + +Execute (Typings: One-liner inner function from typings with multiple lines): + /^def oneliner_with_multiline_typings( + let linenr = getpos(".")[1] + norm jvif + AssertEqual [linenr + 12, 4], getpos("'<")[1:2] + AssertEqual [linenr + 12, 8], getpos("'>")[1:2] + +Execute (Typings: One-liner inner function from typings with multiple lines (2)): + /^def oneliner_with_multiline_typings( + let linenr = getpos(".")[1] + norm 5jvif + AssertEqual [linenr + 12, 4], getpos("'<")[1:2] + AssertEqual [linenr + 12, 8], getpos("'>")[1:2] + +Execute (Typing: class): + /^class ClassWithTyping( + let linenr = getpos(".")[1] + norm vac + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 3 + +Execute (Typings: class): + /^class ClassWithTypings( + let linenr = getpos(".")[1] + norm vac + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 4 + +Execute (Typings: class from typing): + /^class ClassWithTypings(Generic\[\zs + let linenr = getpos(".")[1] + norm vac + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 4 + +Execute (Typings: inner class from typings with multiple lines): + /^class ClassWithMultilineTypings( + norm vic + AssertEqual getline("."), "# ClassWithMultilineTypings" + AssertEqual getline("'>"), "pass" + +Execute (Typings: inner class from typings with multiple lines (2)): + /^class ClassWithMultilineTypings( + norm jvic + AssertEqual getline("."), "# ClassWithMultilineTypings" + AssertEqual getline("'>"), "pass" + +Execute (Typings: inner class from typings with multiple lines (3)): + /^class ClassWithMultilineTypings( + norm 3jvic + AssertEqual getline("."), "# ClassWithMultilineTypings" + AssertEqual getline("'>"), "pass" + +Execute (Typings: class with typings with multiple lines): + /^class ClassWithMultilineTypings( + let linenr = getpos(".")[1] + norm jvac + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 8 + +Execute (Typing: method with typing): + /^ def method_with_typing( + let linenr = getpos(".")[1] + norm vaf + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 2 + +Execute (Typings: method with typings with multiple lines): + /^ def method_with_multiline_typings( + let linenr = getpos(".")[1] + norm vaf + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 16 + +Execute (Typings: method from typings with multiple lines): + /^ def method_with_multiline_typings( + let linenr = getpos(".")[1] + norm jvaf + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 16 + +Execute (Typings: method from typings with multiple lines (2)): + /^ def method_with_multiline_typings( + let linenr = getpos(".")[1] + norm 5jvaf + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 16 + +Execute (Typings: method from typings with multiple lines (3)): + /^ def method_with_multiline_typings( + let linenr = getpos(".")[1] + norm 6jvaf + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 16 + +Execute (Typings: method from typings with multiple lines (4)): + /^ def method_with_multiline_typings( + let linenr = getpos(".")[1] + norm 8jvaf + AssertEqual getpos("'<")[1], linenr + AssertEqual getpos("'>")[1], linenr + 16 + +Execute (Typings: inner method from typings with multiple lines): + /^ def method_with_multiline_typings( + let linenr = getpos(".")[1] + norm jvif + AssertEqual getline("."), " # method_with_multiline_typings" + AssertEqual getline("'>"), " pass" + +Execute (Typings: inner method from typings with multiple lines (2)): + /^ def method_with_multiline_typings( + let linenr = getpos(".")[1] + norm 5jvif + AssertEqual getline("."), " # method_with_multiline_typings" + AssertEqual getline("'>"), " pass" + +Execute (Typings: inner method from typings with multiple lines (3)): + /^ def method_with_multiline_typings( + let linenr = getpos(".")[1] + norm 6jvif + AssertEqual getline("."), " # method_with_multiline_typings" + AssertEqual getline("'>"), " pass" + +Execute (Typings: inner method from typings with multiple lines (4)): + /^ def method_with_multiline_typings( + let linenr = getpos(".")[1] + norm 8jvif + AssertEqual getline("."), " # method_with_multiline_typings" + AssertEqual getline("'>"), " pass" diff --git a/test/textobj-python.vader b/test/textobj-python.vader index 22d34c7..dde078a 100644 --- a/test/textobj-python.vader +++ b/test/textobj-python.vader @@ -27,11 +27,11 @@ Execute (Multiline defn a function): AssertEqual 13, getpos("'<")[1] AssertEqual 20, getpos("'>")[1] -Execute (One-liner inner function): +Execute (One-liner inner function v-mode): call cursor(21, 12) normal vif - AssertEqual 21, getpos("'<")[1] - AssertEqual 21, getpos("'>")[1] + AssertEqual [21, 17], getpos("'<")[1:2] + AssertEqual [21, 21], getpos("'>")[1:2] Execute (One-liner a function): call cursor(21, 12) @@ -39,10 +39,10 @@ Execute (One-liner a function): AssertEqual 21, getpos("'<")[1] AssertEqual 23, getpos("'>")[1] -Execute (FIXME Multiline defn one-liner inner function): +Execute (Multiline defn one-liner inner function v-mode): call cursor(24, 5) normal vif - AssertEqual 24, getpos("'<")[1] + AssertEqual 25, getpos("'<")[1] AssertEqual 25, getpos("'>")[1] Execute (Multiline defn one-liner a function): @@ -177,7 +177,7 @@ Execute (Don't cross scope for defn): let linenr = getpos(".")[1] norm vaf AssertEqual getpos(".")[1], linenr, "The line should not have changed." - AssertThrows textobj#python#find_defn_line("def") + AssertEqual 0, textobj#python#find_defn_pos("def") Execute (Method with decorator): /^ def method_with_decorator( @@ -193,14 +193,15 @@ Execute (At end of file, select prior whitespace): Execute (Decorator: function): /^def function_with_decorator( - let linenr = getpos(".")[1] -" debug call textobj#python#find_prev_decorators(linenr) - AssertEqual linenr-1, textobj#python#find_prev_decorators(linenr) + let pos = getpos(".") + let expected = [pos[0], pos[1] - 1] + pos[2:] +" debug call textobj#python#find_prev_decorators_pos(pos) + AssertEqual expected, textobj#python#find_prev_decorators_pos(pos) " Move cursor back /^def function_with_decorator( norm jvaf AssertEqual getline("."), "@decorator" - AssertEqual getpos("'>")[1], linenr + 3 + AssertEqual getpos("'>")[1], pos[1] + 3 Execute (Decorators: function): /^def function_with_decorators( @@ -273,13 +274,14 @@ Execute (Decorators: a function from decorator): Execute (Decorator: class): /^class ClassWithDecorator: - let linenr = getpos(".")[1] - AssertEqual linenr-1, textobj#python#find_prev_decorators(linenr) + let pos = getpos(".") + let expected = [pos[0], pos[1] - 1] + pos[2:] + AssertEqual expected, textobj#python#find_prev_decorators_pos(pos) " Move cursor back /^class ClassWithDecorator: norm jvac AssertEqual getline("."), "@decorator" - AssertEqual getpos("'>")[1], linenr + 3 + AssertEqual getpos("'>")[1], pos[1] + 3 Execute (Decorators: class): /^class ClassWithDecorators: