AndrewRadev / sideways.vim

A Vim plugin to move function arguments (and other delimited-by-something items) left and right.
http://www.vim.org/scripts/script.php?script_id=4171
MIT License
479 stars 9 forks source link

space as separator #36

Open tim2CF opened 4 years ago

tim2CF commented 4 years ago

Is it possible to provide some config with additional separators? In Haskell we are using space as separator between function and argument, example:

add x y = x + y

atm if I'm trying to apply :SidewaysRight to x - it's not working

AndrewRadev commented 4 years ago

Haskell is tricky. Let me start by explaining how the plugin works in general. The easiest example is something like this:

function_call(x, y, z)

Suppose your cursor is on y. In that case, the plugin starts by trying to find the start of the expression it'll be working in. In this case, that would be the opening bracket, so the specific configuration it'll be working with is this:

\   {
\     'start':     '(\_s*',
\     'end':       ')',
\     'delimiter': ',\_s*',
\     'brackets':  ['([{''"', ')]}''"'],
\   },

So, all it does is search backwards to the start, and then go forward, character by character, skipping nested brackets, until it gets to the end.

Ruby is one example where it's not necessary to have brackets:

function_call x, y, z

So, instead of looking for a (, we can look for something like "a keyword (the function name), followed by a space, followed by something that could be the first argument of the function". The definition for that looks like this:

\   {
\     'start':       '\k\{1,}[?!]\= \ze\s*[^=,*/%<>+-]',
\     'end':         '\s*\%(\<do\>\|#\)',
\     'delimiter':   ',\s*',
\     'brackets':    ['([{''"', ')]}''"'],
\   },

The pattern is a bit more complicated, but it mostly describes what I put above. The "end" is either a do block or a comment, or the plugin could terminate the parsing for other reasons (unbalanced closing bracket, for instance, indicating the function is within something else, or a newline without a delimiter).


Now, haskell functions are similar to ruby ones in that they don't have brackets, but the problem is that the delimiter and the function call look the same way. So, in the case of add x y, if I do the same thing as I do with ruby and just set the delimiter to be space, that would decide that x is the function call, which wouldn't be very useful.

The key really is finding some way to indicate what the start of the list/function call is. In the particular case you point out, it's actually quite simple, because it's a function definition and not a function call. So I can say, "the first keyword on the line is the start":

\   {
\     'start':       '^\s*\k\{1,} \ze\s*[^=,*/%<>+-]',
\     'end':         '\s*\%(=\|--\)',
\     'brackets':    ['([{"', ')]}"'],
\     'single_line': 1,
\     'delimited_by_whitespace': 1,
\   },

(The delimited_by_whitespace key is the same as putting \s\+ in there, but there's some extra logic needed for some stuff.)

This definition is adapted from ruby, but I've added a ^\s* before the start so it detects the first keyword on the line as the function call. The stuff after \ze (end of the match) is there so that the first character after the function call is not an operator to avoid cases like x + y for instance. The end pattern is either a = where the body of the function will be or a comment, which doesn't really make sense in this case, I guess, and I might remove it.

If we wanted to get the function calls working, I don't think there's a 100% reliable way. We'd need to figure out the right "start pattern" to determine where the start of a call is. For instance, I think it's common to do something like (add 3 4) with haskell calls, so it would be sensible to match (add as the start, check for a starting round bracket. Also, starting with = might be a good check to catch let x = add 3 4. But this is just heuristics, and I'm not sure if and when they'd work. Here's a pattern that tries to catch round brackets and an equals sign:

\   {
\     'start':       '\%(^\s*\|[=(]\s*\)\k\{1,} \ze\s*[^=,*/%<>+-]',
\     'end':         '\s*\%(=\|--\)',
\     'brackets':    ['([{"', ')]}"'],
\     'single_line': 1,
\     'delimited_by_whitespace': 1,
\   },

Unfortunately, it gets confused in this case:

distance (Point x y) (Point x' y') = sqrt $ dx + dy
    where dx = (x - x') ** 2
          dy = (y - y') ** 2

Trying to move the second argument of the definition, (Point x' y') around with the above definition fails, because it finds (Point as the start of the "function". It's the problem with relying on brackets for this kind of thing, I can't consider nesting at the initial start search. It might be theoretically possible with a different option, like starts_with_brackets: or something. I'll do some thinking.


This is probably a lot of info and I'm not sure how much of it you understand re: vimscript, but I've created a branch with the simpler definition logic and you could try it out: https://github.com/AndrewRadev/sideways.vim/pull/37. I'd like to provide at least some support, and I'd appreciate ideas on how to detect the start of a function more reliably. But it's regexes and bracket-counting (which, surprisingly, has worked really, really well so far), so I hope you can manage your expectations on the completeness of the solution :).