AndrewRadev/switch.vim

Idea: Multiple switch alternatives

unphased opened this issue · 3 comments

I find that this plugin has a lot of powerful possibilities, and it makes me want to do a lot of things but we quickly run into ambiguities.

In order to address those ambiguities, the simple choices that we have in terms of controlling priority, although they are quite good and simple, reveal to me that "horizontal scaling" could be very useful. What I mean by this is that I think it would be powerful for me to be able to

  • Define an arbitrary number of binds that trigger Switching
  • For each of them specify a definition list, and a priority scheme (by order in list, or by applying the smallest match -- actually I can suggest a few more possible options for this, how about next nearest match, seems useful to go along with https://github.com/AndrewRadev/switch.vim/wiki/Switch-next-in-current-line )

I think that the situation that I ran into is a good example that this proposal can address: as discussed in my issue #80 I have explored making Switch handle two very expansive classes of switching:

  1. The provided example which cycles between PascalCase, snake_case, CAPS_CASE, kebab-case, and camelCase, although this is provided as an extreme and silly example, I've found it to be both really cool and actually useful.
  2. Toggling a prefix and suffix to a symbol together (the example is const and & for C++, used to change argument types between a value and a const ref)

Got them both working. However, it's not possible to define ahead of time whether I want to prioritize one over the other. It seems possible to derive intent with more precision than is possible now by making further use of the cursor position, but it wouldn't make much of a difference because we want to minimize the required precision of cursor positioning anyway.

So I think it would be great if I could make one map to do one set of switch custom definitions and another to do another. And I think it would be highly intuitive to organize these separate definitions along semantic lines or whatever makes sense.

I think that it is possible to do this external to the plugin by simply reassigning the g:switch_custom_definitions when hitting these different bindings prior to executing Switch. So there doesn't seem to be any need to really add any abstraction layers in the plugin config for this kind of usage. What do you think?

So I think it would be great if I could make one map to do one set of switch custom definitions and another to do another.
I think that it is possible to do this external to the plugin by simply reassigning the g:switch_custom_definitions when hitting these different bindings prior to executing Switch.

There's no need to reassign the definitions. You can create the mappings yourself by passing along the definition dictionaries:

switch.vim/doc/switch.txt

Lines 363 to 384 in 900c5d3

Separate mappings ~
While there's a default mapping for |:Switch|, you could actually define
several mappings with your own custom definitions:
>
let g:variable_style_switch_definitions = [
\ {
\ '\<[a-z0-9]\+_\k\+\>': {
\ '_\(.\)': '\U\1'
\ },
\ '\<[a-z0-9]\+[A-Z]\k\+\>': {
\ '\([A-Z]\)': '_\l\1'
\ },
\ }
\ ]
nnoremap + :call switch#Switch({'definitions': g:variable_style_switch_definitions})<cr>
nnoremap - :Switch<cr>
<
With this, typing "-" would invoke the built-in switch definitions, while
typing "+" would switch between camelcase and underscored variable styles.
This may be particularly useful if you have several clashing switches on
patterns that match similar things.

You can choose whatever combination of built-ins you'd like to use for any given mapping, from this list:

let g:switch_builtins =
\ {
\ 'ampersands': ['&&', '||'],
\ 'capital_true_false': {
\ '\C\<True\>': 'False',
\ '\C\<False\>': 'True',
\ },
\ 'true_false': {
\ '\C\<true\>': 'false',
\ '\C\<false\>': 'true',
\ },
\ 'ruby_hash_style': {
\ ':\(\k\+\)\s*=>\s*': '\1: ',
\ '\<\(\k\+\): ': ':\1 => ',
\ },
\ 'ruby_oneline_hash': {
\ '\v\{(\s*:(\k|["''])+\s*\=\>\s*[^,]+\s*,?)*}': {
\ ':\(\%\(\k\|["'']\)\+\)\s*=>': '\1:',
\ },
\ '\v\{(\s*(\k|["''])+:\s*[^,]+,?)*\s*}': {
\ '\(\%\(\k\|["'']\)\+\):': ':\1 =>',
\ },
\ },
\ 'ruby_lambda': {
\ 'lambda\s*{\s*|\([^|]\+\)|': '->(\1) {',
\ '->\s*(\([^)]\+\))\s*{': 'lambda { |\1|',
\ 'lambda\s*{': '-> {',
\ '->\s*{': 'lambda {'
\ },
\ 'ruby_if_clause': {
\ 'if true or (\(.*\))': 'if false and (\1)',
\ 'if false and (\(.*\))': 'if \1',
\ 'if \%(true\|false\)\@!\(.*\)': 'if true or (\1)',
\ },
\ 'ruby_string': {
\ '"\(\k\+\%([?!]\)\=\)"': '''\1''',
\ '''\(\k\+\%([?!]\)\=\)''': ':\1',
\ ':\(\k\+\%([?!]\)\=\)\@>\%(\s*=>\)\@!': '"\1"\2',
\ },
\ 'ruby_short_blocks': {
\ '\(\k\+\)(&:\(\k\+[!?]\=\))': '\1 { |x| x\.\2 }',
\ '\(\k\+\)\s\={\s*|\(\k\+\)|\s*\2.\(\S\+\)\s*}': '\1(&:\3)',
\ },
\ 'ruby_array_shorthand': {
\ '\v\[\s*%((["''])%([^"'']\s@!)+\1,?\s*)*]': {
\ '\[': '%w(',
\ '\v\s*(["''])(%([^"'']\s@!)+)\1,?(\s)*': '\2\3',
\ '\s*]': ')',
\ },
\ '\v\%w\(\s*%([^"'',]\s*)+\)': {
\ '%w(\s*': '[''',
\ '\v(\s+)@>\)@!': ''', ''',
\ '\s*)': ''']',
\ },
\ '\v\[\s*%(:\@{0,2}\k+,?\s*)+\]': {
\ '\[': '%i(',
\ '\v\s*:(\@{0,2}\k+),?(\s)*': '\1\2',
\ '\s*]': ')',
\ },
\ '\v\%i\(\s*%(\@{0,2}\k+\s*)+\)': {
\ '\v\%i\((\s*)@>': '[:',
\ '\v(\s+)@>\)@!': ', :',
\ '\s*)': ']',
\ },
\ },
\ 'ruby_fetch': {
\ '\v(%(\%@<!\k)+)\[(.{-})\]': '\1.fetch(\2)',
\ '\v(\k+)\.fetch\((.{-})\)': '\1[\2]',
\ },
\ 'ruby_assert_nil': {
\ 'assert_equal nil,': 'assert_nil',
\ 'assert_nil': 'assert_equal nil,',
\ },
\ 'rspec_should': ['should ', 'should_not '],
\ 'rspec_expect': {
\ '\(expect(.*)\)\.to ': '\1.not_to ',
\ '\(expect(.*)\)\.to_not ': '\1.to ',
\ '\(expect(.*)\)\.not_to ': '\1.to ',
\ },
\ 'rspec_to': {
\ '\.to ': '.not_to ',
\ '\.not_to ': '.to ',
\ '\.to_not ': '.to ',
\ },
\ 'rspec_be_truthy_falsey': ['be_truthy', 'be_falsey'],
\ 'eruby_if_clause': {
\ '<% if true or (\(.*\)) %>': '<% if false and (\1) %>',
\ '<% if false and (\(.*\)) %>': '<% if \1 %>',
\ '<% if \%(true\|false\)\@!\(.*\) %>': '<% if true or (\1) %>',
\ },
\ 'eruby_tag_type': {
\ '<%= \(.*\) %>': '<% \1 %>',
\ '<% \(.*\) -\?%>': '<%# \1 %>',
\ '<%# \(.*\) %>': '<%= \1 %>',
\ },
\ 'php_echo': {
\ '<?php echo \(.\{-}\) ?>': '<?php \1 ?>',
\ '<?php \%(echo\)\@!\(.\{-}\) ?>': '<?php echo \1 ?>',
\ },
\ 'cpp_pointer': {
\ '\(\k\+\)\.': '\1->',
\ '\(\k\+\)->': '\1.',
\ },
\ 'javascript_function': {
\ '\(async \)\?function\s*\(\k\+\)\s*()\s*{': 'const \2 = \1() => {',
\ '\(async \)\?function\s*\(\k\+\)\s*(\([^()]\{-},[^()]\{-}\))\s*{': 'const \2 = \1(\3) => {',
\ '\(async \)\?function\s*\(\k\+\)\s*(\(\k\+\))\s*{': 'const \2 = \1\3 => {',
\ '\%(var \|let \|const \)\?\(\k\+\)\s*=\s*\(async \)\?function\s*(': '\2function \1(',
\ '\%(var \|let \|const \)\?\(\k\+\)\s*=\s*\(async \)\?(\([^()]\{-}\))\s*=>\s*{': '\2function \1(\3) {',
\ '\%(var \|let \|const \)\?\(\k\+\)\s*=\s*\(async \)\?\(\k\+\)\s*=>\s*{': '\2function \1(\3) {',
\ },
\ 'javascript_arrow_function': {
\ 'function\s*()\s*{': '() => {',
\ 'function\s*(\([^()]\{-},[^()]\{-}\))\s*{': '(\1) => {',
\ 'function\s*(\(\k\+\))\s*{': '\1 => {',
\ '(\([^()]\{-}\))\s*=>\s*{': 'function(\1) {',
\ '\(\k\+\)\s*=>\s*{': 'function(\1) {',
\ },
\ 'javascript_es6_declarations': {
\ '\<var\s\+': 'let ',
\ '\<let\s\+': 'const ',
\ '\<const\s\+': 'let ',
\ },
\ 'coffee_arrow': {
\ '^\(.*\)->': '\1=>',
\ '^\(.*\)=>': '\1->',
\ },
\ 'coffee_dictionary_shorthand': {
\ '\([{,]\_s*\)\@<=\(\k\+\)\(\s*[},]\)': '\2: \2\3',
\ '\([{,]\_s*\)\@<=\(\k\+\): \?\2\(\s*[},]\)': '\2\3',
\ },
\ 'clojure_string': {
\ '"\(\k\+\)"': '''\1',
\ '''\(\k\+\)': ':\1',
\ ':\(\k\+\)': '"\1"\2',
\ },
\ 'clojure_if_clause': {
\ '(\(if\|if-not\|when\|when-not\) (or true \(.*\))': '(\1 (and false \2)',
\ '(\(if\|if-not\|when\|when-not\) (and false \(.*\))': '(\1 \2',
\ '(\(if\|if-not\|when\|when-not\) (\@!\(.*\)': '(\1 (or true \2)',
\ },
\ 'scala_string': {
\ '[sf"]\@<!"\(\%(\\.\|.\)\{-}\)""\@!': 's"\1"',
\ 's"\(\%(\\.\|.\)\{-}\)"': 'f"\1"',
\ 'f"\(\%(\\.\|.\)\{-}\)"': '"""\1"""',
\ '[sf"]\@<!"""\(.\{-}\)"""': 's"""\1"""',
\ 's"""\(.\{-}\)"""': 'f"""\1"""',
\ 'f"""\(.\{-}\)"""': '"\1"',
\ },
\ 'elixir_list_shorthand': {
\ '\[\%(\k\|[''", ]\)\+\]': {
\ '\[': '\~w(',
\ '[''"]\(\k\+\)[''"],\=': '\1',
\ ']': ')',
\ },
\ '\~w(\%(\k\|\s\)\+)a': {
\ '\~w(': '[',
\ '\(\k\+\) ': ':\1, ',
\ '\(\k\+\))a': ':\1]',
\ },
\ '\~w(\%(\k\|\s\)\+)a\@!': {
\ '\~w(': '[',
\ '\(\k\+\) ': '"\1", ',
\ '\(\k\+\))': '"\1"]',
\ },
\ '\[\%(\k\|[:, ]\)\+\]': {
\ '\[': '\~w(',
\ ':\(\k\+\),\=': '\1',
\ ']': ')a',
\ },
\ },
\ 'rust_void_typecheck': {
\ '\(let\s*\%(mut\s*\)\=\k\+\) = ': '\1: () = ',
\ '\(let\s*\%(mut\s*\)\=\k\+\): () = ': '\1 = ',
\ },
\ 'rust_turbofish': {
\ '\(\k\+\)(': '\1::<Todo>(',
\ '\(\k\+\)::<\%(\k\|\s\|[<>,]\)\+>(': '\1(',
\ },
\ 'rust_string': {
\ '"\([^"]*\)"': 'r"\1"',
\ 'r"\([^"]*\)"': 'r#"\1"#',
\ 'r#"\([^"]*\)"#': '"\1"',
\ },
\ 'rust_is_some': {
\ '\<is_some\>': 'is_none',
\ '\<is_none\>': 'is_some',
\ },
\ 'rust_assert': {
\ '\<assert_eq!': 'assert_ne!',
\ '\<assert_ne!': 'assert_eq!',
\ },
\ 'cargo_dependency_version': {
\ '^\s*\([[:keyword:]-]\+\)\s*=\s*\(["''].\{-}["'']\)': '\1 = { version = \2 }',
\ '^\s*\([[:keyword:]-]\+\)\s*=\s*{\s*version\s*=\s*\(["''].\{-}["'']\)\s*}': '\1 = \2',
\ },
\ 'vim_script_local_function': {
\ '\<s:\(\h\w\+\)(': '<SID>\1(',
\ '<SID>\(\h\w\+\)(': 's:\1(',
\ }
\ }

Would this be enough to do what you're looking for?

Yeah I should have looked more carefully in the doc, it would have obviated the need for this issue. thank you!

No worries, lots of settings and options in there to read :)