diff --git a/autoload/ember_tools.vim b/autoload/ember_tools.vim index c27aeb5..9c94179 100644 --- a/autoload/ember_tools.vim +++ b/autoload/ember_tools.vim @@ -18,6 +18,9 @@ function! ember_tools#Init() setlocal includeexpr=ember_tools#Includeexpr() command! -count=0 -nargs=1 -buffer Extract call ember_tools#extract#Run(, , ) + + command! -buffer Unpack call ember_tools#unpack#Run() + command! -buffer Inline call ember_tools#unpack#Reverse() endfunction function! ember_tools#Includeexpr() diff --git a/autoload/ember_tools/unpack.vim b/autoload/ember_tools/unpack.vim new file mode 100644 index 0000000..d397251 --- /dev/null +++ b/autoload/ember_tools/unpack.vim @@ -0,0 +1,101 @@ +function! ember_tools#unpack#Run() + " TODO (2016-08-07) Multiline imports (look for the closing ; of the line?) + " TODO (2016-07-06) Nested unpacking: + " const { computed, Controller, inject: { service }, observer } = Ember; + + let saved_view = winsaveview() + + if !search('\%(\k\|\.\)\+', 'bc', line('.')) + return + endif + + let namespace = expand('') + normal! "_df. + let member = expand('') + + " Look for an existing unpacking + if search('const {.*'.member.'.*}\s\+=\s\+'.namespace, 'n') + " this member of the namespace is already unpacked, nothing to do + return + endif + + if search('const {.*}\s\+=\s\+'.namespace) + " we found an existing unpacking without this member, unpack it here + let unpacking = getline('.') + let unpacking = substitute(unpacking, + \ '\s*}\(\s\+=\s\+'.namespace.'\)', + \ ', '.member.' } = '.namespace, + \ 'g') + call setline('.', unpacking) + + call winrestview(saved_view) + silent! call repeat#set(":call ember_tools#unpack#Run(0)\") + return + endif + + " if we're here, there's no existing unpacking + if search('^const {', 'bW') + " we can add it after the last unpacking + call append(line('.'), ['']) + normal! j + elseif search('^import', 'bW') + " we can add it after the last import + call append(line('.'), ['', '']) + normal! jj + else + " just add it at the top of the file + call append(0, ['', '']) + normal! gg + endif + + call setline('.', 'const { '.member.' } = '.namespace.';') + call winrestview(saved_view) + silent! call repeat#set(":call ember_tools#unpack#Run()\") +endfunction + +function! ember_tools#unpack#Reverse() + let saved_view = winsaveview() + let variable = expand('') + + if searchpair('const {', '', '}\s*=\s*\zs\k\+', 'W') <= 0 + return + endif + + let prefix = expand('') + + call search('\<'.variable.'\>', 'bW') + + " Remove variable from const line + exe 's/,\s*\%#'.variable.'//e' + exe 's/\%#'.variable.',\=\s*\ze\%(\k\| }\)//e' + + " Handle empty const blocks + if getline('.') =~ '^const {\s*} =' + let next_lineno = nextnonblank(line('.') + 1) + + if getline(next_lineno) !~ '^const' + " it's something other than another const line, let's delete all the + " whitespace up until that point + exe line('.').','.(next_lineno - 1).'delete _' + else + " just delete this line + delete _ + endif + endif + + " Add prefix everywhere + normal! G$ + let search_flags = "w" + let variable_pattern = '\%('.prefix.'\.\)\@' + + while search(variable_pattern, search_flags) > 0 + if synIDattr(synID(line('.'), col('.'), 1), 'name') !~ 'String\|Comment' + exe 'normal! i'.prefix.'.' + " go back to the search + call search(variable_pattern) + endif + let search_flags = "W" + endwhile + + call winrestview(saved_view) +endfunction diff --git a/doc/ember_tools.txt b/doc/ember_tools.txt index 709292e..6cf8ae8 100644 --- a/doc/ember_tools.txt +++ b/doc/ember_tools.txt @@ -197,6 +197,81 @@ emblem template, but if you'd like to specify explicitly what templates you prefer, set the |g:ember_tools_default_logic_filetype| and/or |g:ember_tools_default_template_filetype| configuration variables. + *ember_tools-:Unpack* +:Unpack ~ + +The `:Unpack` command helps you unpack an imported variable into its component +pieces. An example might looks something like this: + +> + import Ember from 'ember'; + + export default Ember.Controller.extend({ + foo: Ember.computed.equal('bar', 'baz') + }); +< +Running the `:Unpack` command with the cursor on "Ember.Controller" would lead +to the following result: +> + import Ember from 'ember'; + + const { Controller } = Ember; + + export default Controller.extend({ + foo: Ember.computed.equal('bar', 'baz') + }); +< +The command creates a `const { ... } = ` line that unpacks the `Ember` +variable's `Controller` component into its own variable. + +You can continue to run `:Unpack` on, for instance, "Ember.computed.equal", +and then once again on the remaining "computed.equal" (if you have repeat.vim +installed, you can just trigger the |.| mapping) to get: +> + import Ember from 'ember'; + + const { Controller, computed } = Ember; + const { equal } = computed; + + export default Controller.extend({ + foo: equal('bar', 'baz') + }); +< +The command adds new entries to the end of the list. If you'd like to sort +them in some way afterwards, you can try using a different plugin of mine, +sideways (https://github.com/AndrewRadev/sideways.vim). + + *ember_tools-:Inline* +:Inline ~ + +The `:Inline` command inlines an "unpacked" variable. If you have code like +this: +> + import Ember from 'ember'; + + const { computed, Controller } = Ember; + + export default Controller.extend({ + foo: computed.equal('bar', 'baz') + bar: computed.equal('baz', 'qux') + }); +< +Running `:Inline` on "computed" within the `const { ... }` definition will +remove it from that list and replace it across the file (ignoring strings and +comments) with "Ember.computed". +> + import Ember from 'ember'; + + const { Controller } = Ember; + + export default Controller.extend({ + foo: Ember.computed.equal('bar', 'baz') + bar: Ember.computed.equal('baz', 'qux') + }); +< +For now, this is simply a reversal of the `:Unpack` command. In the future, +the `:Inline` command might also inline other kinds of constructs, like local +variables or properties. ============================================================================== diff --git a/spec/plugin/inline_spec.rb b/spec/plugin/inline_spec.rb new file mode 100644 index 0000000..d7785d8 --- /dev/null +++ b/spec/plugin/inline_spec.rb @@ -0,0 +1,118 @@ +require 'spec_helper' + +describe ":Inline" do + describe "const unpacking" do + specify "deletes a const line if nothing is left of it after inlining" do + edit_file 'test.js', <<-EOF + const { Controller } = Ember; + + export default Controller.extend({}); + EOF + + vim.search 'const { \zsController' + vim.command 'Inline' + vim.write + + expect_file_contents current_file, <<-EOF + export default Ember.Controller.extend({}); + EOF + end + + specify "deletes an const line with another const line following" do + edit_file 'test.js', <<-EOF + const { Controller } = Ember; + const { foo } = bar; + + export default Controller.extend({}); + EOF + + vim.search 'const { \zsController' + vim.command 'Inline' + vim.write + + expect_file_contents current_file, <<-EOF + const { foo } = bar; + + export default Ember.Controller.extend({}); + EOF + end + + specify "inlines entries from the beginning" do + edit_file 'test.js', <<-EOF + import Ember from 'ember'; + + const { computed, Controller, isPresent } = Ember; + + export default Controller.extend({ + foo: Ember.computed.equal('bar', 'baz') + }); + EOF + + vim.search 'const.*\zscomputed' + vim.command 'Inline' + vim.write + + expect_file_contents current_file, <<-EOF + import Ember from 'ember'; + + const { Controller, isPresent } = Ember; + + export default Controller.extend({ + foo: Ember.computed.equal('bar', 'baz') + }); + EOF + end + + specify "inlines entries in the middle" do + edit_file 'test.js', <<-EOF + import Ember from 'ember'; + + const { computed, Controller, isPresent } = Ember; + + export default Controller.extend({ + foo: Ember.computed.equal('bar', 'baz') + }); + EOF + + vim.search 'const.*\zsController' + vim.command 'Inline' + vim.write + + expect_file_contents current_file, <<-EOF + import Ember from 'ember'; + + const { computed, isPresent } = Ember; + + export default Ember.Controller.extend({ + foo: Ember.computed.equal('bar', 'baz') + }); + EOF + end + + specify "inlines entries at the end" do + edit_file 'test.js', <<-EOF + import Ember from 'ember'; + + const { computed, Controller, isPresent } = Ember; + + export default Controller.extend({ + foo: Ember.computed.equal('bar', 'baz') + }); + EOF + + vim.search 'const.*\zsisPresent' + vim.command 'Inline' + vim.write + + expect_file_contents current_file, <<-EOF + import Ember from 'ember'; + + const { computed, Controller } = Ember; + + export default Controller.extend({ + foo: Ember.computed.equal('bar', 'baz') + }); + EOF + end + end +end diff --git a/spec/plugin/unpack_spec.rb b/spec/plugin/unpack_spec.rb new file mode 100644 index 0000000..f65ca14 --- /dev/null +++ b/spec/plugin/unpack_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe ":Unpack" do + specify "creates a const line if there is none, at the top of the file" do + edit_file 'test.js', <<-EOF + export default Ember.Controller.extend({}); + EOF + + vim.search 'Ember\.Controller' + vim.command 'Unpack' + vim.write + + expect_file_contents current_file, <<-EOF + const { Controller } = Ember; + + export default Controller.extend({}); + EOF + end + + specify "creates a const line if there is none, after the import lines" do + edit_file 'test.js', <<-EOF + import Ember from 'ember'; + + export default Ember.Controller.extend({}); + EOF + + vim.search 'Ember\.Controller' + vim.command 'Unpack' + vim.write + + expect_file_contents current_file, <<-EOF + import Ember from 'ember'; + + const { Controller } = Ember; + + export default Controller.extend({}); + EOF + end + + specify "adds entries to the const line if it exists" do + edit_file 'test.js', <<-EOF + import Ember from 'ember'; + + const { Controller } = Ember; + + export default Controller.extend({ + foo: Ember.computed.equal('bar', 'baz') + }); + EOF + + vim.search 'Ember\.computed' + vim.command 'Unpack' + vim.write + + expect_file_contents current_file, <<-EOF + import Ember from 'ember'; + + const { Controller, computed } = Ember; + + export default Controller.extend({ + foo: computed.equal('bar', 'baz') + }); + EOF + end + + specify "adds a new const line for nested unpacking" do + edit_file 'test.js', <<-EOF + import Ember from 'ember'; + + const { Controller } = Ember; + + export default Controller.extend({}); + EOF + + vim.search 'Controller\.extend' + vim.command 'Unpack' + vim.write + + expect_file_contents current_file, <<-EOF + import Ember from 'ember'; + + const { Controller } = Ember; + const { extend } = Controller; + + export default extend({}); + EOF + end +end