Permalink
Browse files

Version 0.1.0

- Ruby methods without brackets
- Go lists
- Argument text object
- Various bugfixes
  • Loading branch information...
AndrewRadev authored and vim-scripts committed Jan 18, 2014
1 parent ec2f8ed commit badb2c876dc7dc966956708e938bd845bc5e1a03
Showing with 255 additions and 85 deletions.
  1. +1 −1 README
  2. +32 −2 autoload/sideways.vim
  3. +80 −54 autoload/sideways/parsing.vim
  4. +27 −0 autoload/sideways/textobj.vim
  5. +27 −15 autoload/sideways/util.vim
  6. +36 −9 doc/sideways.txt
  7. +52 −4 plugin/sideways.vim
View
2 README
@@ -37,5 +37,5 @@ It's highly suggested to map the two commands to convenient keys. For example, m
nnoremap <c-h> :SidewaysLeft<cr>
nnoremap <c-l> :SidewaysRight<cr>
-The plugin is intended to be highly customizable, eventually. In the future, it should be able to work with ruby function arguments and it may also contain an "argument" text object (since the machinery to detect arguments is already there).
+The plugin is intended to be customizable, though at this point you'd need to look at the source to do this.
View
@@ -21,7 +21,6 @@ function! sideways#Left(definitions)
endif
call s:Swap(first, second, new_cursor_column)
- silent! call repeat#set(":call sideways#Left()\<cr>")
return 1
endfunction
@@ -48,10 +47,41 @@ function! sideways#Right(definitions)
endif
call s:Swap(first, second, new_cursor_column)
- silent! call repeat#set(":call sideways#Right()\<cr>")
return 1
endfunction
+function! sideways#AroundCursor(definitions)
+ let items = sideways#parsing#Parse(a:definitions)
+ if empty(items)
+ return []
+ end
+
+ let current_index = s:FindActiveItem(items)
+ let current = items[current_index]
+
+ if current_index == 0
+ let previous = []
+ else
+ let previous = items[current_index - 1]
+ endif
+
+ if current_index == len(items) - 1
+ let next = []
+ else
+ let next = items[current_index + 1]
+ endif
+
+ return [previous, current, next]
+endfunction
+
+function! sideways#Definitions()
+ if exists('b:sideways_definitions')
+ return extend(copy(g:sideways_definitions), b:sideways_definitions)
+ else
+ return g:sideways_definitions
+ endif
+endfunction
+
" Swaps the a:first and a:second items in the buffer. Both first arguments are
" expected to be pairs of start and end columns. The last argument is a
" number, the new column to position the cursor on.
@@ -12,83 +12,109 @@
" [ [14, 16], [19, 21] ]
"
function! sideways#parsing#Parse(definitions)
+ call sideways#util#PushCursor()
+
let definitions = a:definitions
let items = []
- for definition in definitions
- let start_pattern = definition.start
- let end_pattern = definition.end
- let delimiter_pattern = definition.delimiter
- let skip_pattern = definition.skip
+ let definition = s:LocateBestDefinition(definitions)
- let [opening_brackets, closing_brackets] = definition.brackets
+ if empty(definition)
+ call sideways#util#PopCursor()
+ return []
+ endif
- silent! normal! zO
- call sideways#util#PushCursor()
+ let start_pattern = definition.start
+ let end_pattern = definition.end
+ let delimiter_pattern = definition.delimiter
+ let skip_pattern = definition.skip
- if searchpair(start_pattern, '', end_pattern, 'bW', '', line('.')) <= 0
- call sideways#util#PopCursor()
- continue
- else
- call search(start_pattern, 'Wce', line('.'))
- endif
+ let [opening_brackets, closing_brackets] = definition.brackets
- normal! l
+ let current_item = [col('.'), -1]
+ let remainder_of_line = s:RemainderOfLine()
- let current_item = [col('.'), -1]
+ " TODO (2012-09-07) Figure out how to work with RemainderOfLine
+ while s:RemainderOfLine() !~ '^'.end_pattern
let remainder_of_line = s:RemainderOfLine()
+ let bracket_match = s:BracketMatch(remainder_of_line, opening_brackets)
- " TODO (2012-09-07) Figure out how to work with RemainderOfLine
- while s:RemainderOfLine() !~ '^'.end_pattern
- let remainder_of_line = s:RemainderOfLine()
- let bracket_match = s:BracketMatch(remainder_of_line, opening_brackets)
+ if col('.') == col('$') - 1
+ " then we're at the end of the line, finish up
+ let current_item[1] = col('$') - 1
+ break
+ elseif bracket_match >= 0
+ " then try to jump to the closing bracket
+ let opening_bracket = opening_brackets[bracket_match]
+ let closing_bracket = closing_brackets[bracket_match]
+
+ call searchpair('\V'.opening_bracket, '', '\V'.closing_bracket, 'W', '', line('.'))
+ " move rightwards regardless of the result
+ normal! l
+ elseif remainder_of_line =~ delimiter_pattern
+ " then store the current item
+ let current_item[1] = col('.') - 1
+ call add(items, current_item)
- if bracket_match >= 0
- " then try to jump to the closing bracket
- let opening_bracket = opening_brackets[bracket_match]
- let closing_bracket = closing_brackets[bracket_match]
+ let match = matchstr(remainder_of_line, delimiter_pattern)
+ exe 'normal! '.len(match).'l'
- call searchpair('\V'.opening_bracket, '', '\V'.closing_bracket, 'W', '', line('.'))
- " move rightwards regardless of the result
+ " skip some whitespace TODO consider removing
+ while s:RemainderOfLine() =~ skip_pattern
normal! l
- elseif remainder_of_line =~ delimiter_pattern
- " then store the current item
- let current_item[1] = col('.') - 1
- call add(items, current_item)
+ endwhile
- normal! l
+ " initialize a new "current item"
+ let current_item = [col('.'), -1]
+ else
+ " move rightwards
+ normal! l
+ endif
+ endwhile
- " skip some whitespace TODO consider removing
- while s:RemainderOfLine() =~ skip_pattern
- normal! l
- endwhile
-
- " initialize a new "current item"
- let current_item = [col('.'), -1]
- elseif col('.') == col('$') - 1
- " then we're at the end of the line, finish up
- let current_item[1] = col('$') - 1
- break
- else
- " move rightwards
- normal! l
- endif
- endwhile
+ if current_item[1] < 0
+ let current_item[1] = col('.') - 1
+ endif
+ call add(items, current_item)
- if current_item[1] < 0
- let current_item[1] = col('.') - 1
- endif
- call add(items, current_item)
+ call sideways#util#PopCursor()
+ return items
+endfunction
+
+function! s:LocateBestDefinition(definitions)
+ silent! normal! zO
+
+ let best_definition = {}
+ let best_definition_col = 0
+
+ for definition in a:definitions
+ let start_pattern = definition.start
+ let end_pattern = definition.end
+
+ call sideways#util#PushCursor()
- if !empty(items)
+ if searchpair(start_pattern, '', end_pattern, 'bW', '', line('.')) <= 0
call sideways#util#PopCursor()
- break
+ continue
+ else
+ call search(start_pattern, 'Wce', line('.'))
+ normal! l
+
+ if best_definition_col < col('.')
+ let best_definition_col = col('.')
+ let best_definition = definition
+ endif
endif
call sideways#util#PopCursor()
endfor
- return items
+ if best_definition_col > 0
+ call sideways#util#SetCol(best_definition_col)
+ return best_definition
+ else
+ return {}
+ endif
endfunction
function! s:BracketMatch(text, brackets)
@@ -0,0 +1,27 @@
+function! sideways#textobj#Argument(mode)
+ let coordinates = sideways#AroundCursor(sideways#Definitions())
+ if empty(coordinates)
+ return
+ endif
+
+ let [previous, current, next] = coordinates
+
+ if a:mode == 'i'
+ call s:MarkCols(current[0], current[1])
+ elseif a:mode == 'a'
+ if empty(next)
+ call s:MarkCols(previous[1] + 1, current[1])
+ else
+ call s:MarkCols(current[0], next[0] - 1)
+ endif
+ endif
+endfunction
+
+function! s:MarkCols(start_col, end_col)
+ let line_offset = line2byte('.')
+ let start_byte = line_offset + a:start_col - 1
+ let end_byte = line_offset + a:end_col - 1
+
+ exe 'normal! '.start_byte.'gov'.end_byte.'go'
+ return 1
+endfunction
View
@@ -33,6 +33,17 @@ function! sideways#util#PopCursor()
call setpos('.', remove(b:cursor_position_stack, -1))
endfunction
+" function! sideways#util#DropCursor() {{{2
+"
+" Drops the last cursor location from the stack.
+function! sideways#util#DropCursor()
+ if !exists('b:cursor_position_stack')
+ let b:cursor_position_stack = []
+ endif
+
+ call remove(b:cursor_position_stack, -1)
+endfunction
+
" function! sideways#util#PeekCursor() {{{2
"
" Returns the last saved cursor position from the cursor stack.
@@ -73,23 +84,12 @@ endfunction
"
" Replace the area defined by the 'start' and 'end' columns on the current
" line with 'text'
-"
-" TODO Multibyte characters break it
function! sideways#util#ReplaceCols(start, end, text)
- let start = a:start - 1
- let interval = a:end - a:start
-
- if start > 0 && interval > 0
- let motion = '0'.start.'lv'.interval.'l'
- elseif start > 0
- let motion = '0'.start.'lv'
- elseif interval > 0
- let motion = '0v'.interval.'l'
- else
- return 0
- endif
+ let line_offset = line2byte('.')
+ let start_byte = line_offset + a:start - 1
+ let end_byte = line_offset + a:end - 1
- call sideways#util#ReplaceMotion(motion, a:text)
+ call sideways#util#ReplaceMotion(start_byte.'gov'.end_byte.'go', a:text)
return 1
endfunction
@@ -125,3 +125,15 @@ endfunction
function! sideways#util#GetCols(start, end)
return strpart(getline('.'), a:start - 1, a:end - a:start + 1)
endfunction
+
+" Positioning the cursor {{{1
+"
+
+" function! sideways#util#SetCol(col) {{{2
+"
+" Positions the cursor at the given column.
+function! sideways#util#SetCol(col)
+ let position = getpos('.')
+ let position[2] = a:col
+ call setpos('.', position)
+endfunction
View
@@ -1,3 +1,5 @@
+*sideways.txt* Move an item in a delimiter-separated list left or right
+
==============================================================================
CONTENTS *sideways* *sideways-contents*
@@ -46,15 +48,21 @@ various other cases and it's intended to make the process configurable. While
this particular example is in python, this should work for arguments in many
different languages that use round braces to denote function calls.
+For ruby and eruby, it detects method calls without braces as well:
+>
+ link_to user_registration_path, 'Something'
+ # changes to:
+ link_to 'Something', user_registration_path
+<
Apart from functions, it works for square-bracket lists in dynamic languages:
>
list = [one, [two, four, five], three]
<
-Notice that, if you experiment with this example, you'll find that you can
-move the entire second list around. The plugin takes into consideration nested
-structures.
+If you experiment with this example, you'll find that you can move the entire
+second list around, as long as the cursor is on one of the inner brackets. The
+plugin takes into consideration nested structures.
-Apart from functions, it works for lists in CSS declarations:
+It works for lists in CSS declarations:
>
border-radius: 20px 0 0 20px;
<
@@ -71,16 +79,35 @@ And, it also works for cucumber tables:
| 0 | 40 | add | 40 |
<
-It's highly suggested to map the two commands to convenient keys. For example,
-mapping them to <c-h> and <c-l> would look like this:
+It's highly recommended to map the two commands to convenient keys. For
+example, mapping them to <c-h> and <c-l> would look like this:
>
nnoremap <c-h> :SidewaysLeft<cr>
nnoremap <c-l> :SidewaysRight<cr>
<
-The plugin is intended to be highly customizable. In the future, it should be
-able to work with ruby function arguments and it may also contain an "argument"
-text object (since the machinery to detect arguments is already there).
+The plugin is intended to be customizable, though at this point you'd need to
+look at the source to do this.
+
+Bonus functionality: ~
+
+The plugin's machinery makes it easy to implement an "argument" text object.
+There are two mappings provided:
+>
+ <Plug>SidewaysArgumentTextobjA
+ <Plug>SidewaysArgumentTextobjI
+<
+These are the outer and inner text objects, respectively. To use them, you
+need to create mappings in your configuration files. Something like this:
+>
+ omap aa <Plug>SidewaysArgumentTextobjA
+ xmap aa <Plug>SidewaysArgumentTextobjA
+ omap ia <Plug>SidewaysArgumentTextobjI
+ xmap ia <Plug>SidewaysArgumentTextobjI
+<
+This will map the "a" text object to operate on an "argument". So, you can
+perform `daa` to delete an argument, `cia` to change an argument, and so on.
+See |text-objects| for more information.
==============================================================================
ISSUES *sideways-issues*
Oops, something went wrong.

0 comments on commit badb2c8

Please sign in to comment.