From de0760cef786ebfd95e7e9287939a8f32087317b Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 27 Jun 2024 18:10:36 +0800 Subject: [PATCH] chore(bundle): use bundle splitjoin --- autoload/SpaceVim/layers/edit.vim | 2 +- bundle/splitjoin.vim/.circleci/config.yml | 38 + .../.github/workflows/mirror.yml | 20 + bundle/splitjoin.vim/.gitignore | 4 + bundle/splitjoin.vim/.gitmodules | 18 + bundle/splitjoin.vim/.rspec | 2 + bundle/splitjoin.vim/CODE_OF_CONDUCT.md | 74 + bundle/splitjoin.vim/CONTRIBUTING.md | 44 + bundle/splitjoin.vim/Gemfile | 9 + bundle/splitjoin.vim/Gemfile.lock | 67 + bundle/splitjoin.vim/Guardfile | 4 + bundle/splitjoin.vim/LICENSE | 17 + bundle/splitjoin.vim/README.md | 108 + bundle/splitjoin.vim/Rakefile | 8 + bundle/splitjoin.vim/autoload/sj.vim | 881 +++++++ .../autoload/sj/argparser/clojure.vim | 27 + .../autoload/sj/argparser/common.vim | 206 ++ .../autoload/sj/argparser/elixir.vim | 34 + .../autoload/sj/argparser/go_vars.vim | 33 + .../autoload/sj/argparser/html_args.vim | 33 + .../autoload/sj/argparser/js.vim | 27 + .../autoload/sj/argparser/json.vim | 29 + .../autoload/sj/argparser/ruby.vim | 156 ++ .../autoload/sj/argparser/rust_list.vim | 42 + .../autoload/sj/argparser/rust_struct.vim | 105 + .../autoload/sj/argparser/sh.vim | 27 + bundle/splitjoin.vim/autoload/sj/c.vim | 68 + bundle/splitjoin.vim/autoload/sj/clojure.vim | 49 + bundle/splitjoin.vim/autoload/sj/coffee.vim | 298 +++ bundle/splitjoin.vim/autoload/sj/css.vim | 122 + bundle/splitjoin.vim/autoload/sj/cue.vim | 143 ++ bundle/splitjoin.vim/autoload/sj/elixir.vim | 288 +++ bundle/splitjoin.vim/autoload/sj/elm.vim | 249 ++ bundle/splitjoin.vim/autoload/sj/eruby.vim | 55 + bundle/splitjoin.vim/autoload/sj/go.vim | 405 ++++ bundle/splitjoin.vim/autoload/sj/haml.vim | 78 + .../splitjoin.vim/autoload/sj/handlebars.vim | 105 + bundle/splitjoin.vim/autoload/sj/html.vim | 144 ++ bundle/splitjoin.vim/autoload/sj/java.vim | 183 ++ bundle/splitjoin.vim/autoload/sj/js.vim | 267 +++ bundle/splitjoin.vim/autoload/sj/jsx.vim | 133 ++ bundle/splitjoin.vim/autoload/sj/lua.vim | 131 + bundle/splitjoin.vim/autoload/sj/perl.vim | 241 ++ bundle/splitjoin.vim/autoload/sj/php.vim | 254 ++ bundle/splitjoin.vim/autoload/sj/python.vim | 422 ++++ bundle/splitjoin.vim/autoload/sj/r.vim | 196 ++ bundle/splitjoin.vim/autoload/sj/ruby.vim | 1314 ++++++++++ bundle/splitjoin.vim/autoload/sj/rust.vim | 935 ++++++++ bundle/splitjoin.vim/autoload/sj/scss.vim | 68 + bundle/splitjoin.vim/autoload/sj/settings.vim | 37 + bundle/splitjoin.vim/autoload/sj/sh.vim | 40 + bundle/splitjoin.vim/autoload/sj/tex.vim | 69 + bundle/splitjoin.vim/autoload/sj/vim.vim | 110 + bundle/splitjoin.vim/autoload/sj/vue.vim | 32 + bundle/splitjoin.vim/autoload/sj/yaml.vim | 541 +++++ bundle/splitjoin.vim/doc/splitjoin.txt | 2127 +++++++++++++++++ bundle/splitjoin.vim/ftplugin/c/splitjoin.vim | 13 + .../ftplugin/clojure/splitjoin.vim | 11 + .../ftplugin/coffee/splitjoin.vim | 20 + .../splitjoin.vim/ftplugin/cs/splitjoin.vim | 15 + .../splitjoin.vim/ftplugin/css/splitjoin.vim | 13 + .../splitjoin.vim/ftplugin/cue/splitjoin.vim | 16 + .../ftplugin/elixir/splitjoin.vim | 12 + .../splitjoin.vim/ftplugin/elm/splitjoin.vim | 11 + .../ftplugin/eruby/splitjoin.vim | 13 + .../splitjoin.vim/ftplugin/go/splitjoin.vim | 17 + .../splitjoin.vim/ftplugin/haml/splitjoin.vim | 9 + .../ftplugin/handlebars/splitjoin.vim | 13 + .../splitjoin.vim/ftplugin/html/splitjoin.vim | 13 + .../ftplugin/htmldjango/splitjoin.vim | 13 + .../splitjoin.vim/ftplugin/java/splitjoin.vim | 17 + .../ftplugin/javascript/splitjoin.vim | 21 + .../ftplugin/javascriptreact/splitjoin.vim | 1 + .../splitjoin.vim/ftplugin/json/splitjoin.vim | 12 + .../splitjoin.vim/ftplugin/jsx/splitjoin.vim | 24 + .../splitjoin.vim/ftplugin/less/splitjoin.vim | 15 + .../splitjoin.vim/ftplugin/lua/splitjoin.vim | 13 + .../splitjoin.vim/ftplugin/perl/splitjoin.vim | 22 + .../splitjoin.vim/ftplugin/php/splitjoin.vim | 19 + .../ftplugin/python/splitjoin.vim | 24 + bundle/splitjoin.vim/ftplugin/r/splitjoin.vim | 12 + .../splitjoin.vim/ftplugin/ruby/splitjoin.vim | 35 + .../splitjoin.vim/ftplugin/rust/splitjoin.vim | 24 + .../splitjoin.vim/ftplugin/scss/splitjoin.vim | 15 + .../splitjoin.vim/ftplugin/sh/splitjoin.vim | 9 + .../ftplugin/snakemake/splitjoin.vim | 9 + .../ftplugin/svelte/splitjoin.vim | 1 + .../splitjoin.vim/ftplugin/tex/splitjoin.vim | 7 + .../splitjoin.vim/ftplugin/tsx/splitjoin.vim | 24 + .../ftplugin/typescript/splitjoin.vim | 1 + .../ftplugin/typescriptreact/splitjoin.vim | 1 + .../splitjoin.vim/ftplugin/vim/splitjoin.vim | 13 + .../splitjoin.vim/ftplugin/vue/splitjoin.vim | 25 + .../splitjoin.vim/ftplugin/xml/splitjoin.vim | 13 + .../splitjoin.vim/ftplugin/yaml/splitjoin.vim | 13 + .../splitjoin.vim/ftplugin/zsh/splitjoin.vim | 7 + bundle/splitjoin.vim/plugin/splitjoin.vim | 91 + bundle/splitjoin.vim/spec/plugin/c_spec.rb | 63 + .../splitjoin.vim/spec/plugin/clojure_spec.rb | 76 + .../splitjoin.vim/spec/plugin/coffee_spec.rb | 187 ++ bundle/splitjoin.vim/spec/plugin/css_spec.rb | 86 + .../splitjoin.vim/spec/plugin/elixir_spec.rb | 720 ++++++ bundle/splitjoin.vim/spec/plugin/elm_spec.rb | 533 +++++ .../splitjoin.vim/spec/plugin/eruby_spec.rb | 39 + bundle/splitjoin.vim/spec/plugin/go_spec.rb | 462 ++++ bundle/splitjoin.vim/spec/plugin/haml_spec.rb | 28 + .../spec/plugin/handlebars_spec.rb | 70 + bundle/splitjoin.vim/spec/plugin/html_spec.rb | 140 ++ bundle/splitjoin.vim/spec/plugin/java_spec.rb | 200 ++ bundle/splitjoin.vim/spec/plugin/js_spec.rb | 347 +++ bundle/splitjoin.vim/spec/plugin/jsx_spec.rb | 212 ++ bundle/splitjoin.vim/spec/plugin/lua_spec.rb | 78 + bundle/splitjoin.vim/spec/plugin/perl_spec.rb | 172 ++ bundle/splitjoin.vim/spec/plugin/php_spec.rb | 266 +++ .../splitjoin.vim/spec/plugin/python_spec.rb | 274 +++ bundle/splitjoin.vim/spec/plugin/r_spec.rb | 86 + bundle/splitjoin.vim/spec/plugin/ruby_spec.rb | 1905 +++++++++++++++ bundle/splitjoin.vim/spec/plugin/rust_spec.rb | 1460 +++++++++++ bundle/splitjoin.vim/spec/plugin/scss_spec.rb | 145 ++ .../spec/plugin/settings_spec.rb | 109 + bundle/splitjoin.vim/spec/plugin/sh_spec.rb | 101 + bundle/splitjoin.vim/spec/plugin/tex_spec.rb | 94 + bundle/splitjoin.vim/spec/plugin/vim_spec.rb | 70 + bundle/splitjoin.vim/spec/plugin/yaml_spec.rb | 709 ++++++ bundle/splitjoin.vim/spec/spec_helper.rb | 44 + bundle/splitjoin.vim/spec/support/vim.rb | 27 + 126 files changed, 20478 insertions(+), 1 deletion(-) create mode 100644 bundle/splitjoin.vim/.circleci/config.yml create mode 100644 bundle/splitjoin.vim/.github/workflows/mirror.yml create mode 100644 bundle/splitjoin.vim/.gitignore create mode 100644 bundle/splitjoin.vim/.gitmodules create mode 100644 bundle/splitjoin.vim/.rspec create mode 100644 bundle/splitjoin.vim/CODE_OF_CONDUCT.md create mode 100644 bundle/splitjoin.vim/CONTRIBUTING.md create mode 100644 bundle/splitjoin.vim/Gemfile create mode 100644 bundle/splitjoin.vim/Gemfile.lock create mode 100644 bundle/splitjoin.vim/Guardfile create mode 100644 bundle/splitjoin.vim/LICENSE create mode 100644 bundle/splitjoin.vim/README.md create mode 100644 bundle/splitjoin.vim/Rakefile create mode 100644 bundle/splitjoin.vim/autoload/sj.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/clojure.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/common.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/elixir.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/go_vars.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/html_args.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/js.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/json.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/ruby.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/rust_list.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/rust_struct.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/argparser/sh.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/c.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/clojure.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/coffee.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/css.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/cue.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/elixir.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/elm.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/eruby.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/go.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/haml.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/handlebars.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/html.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/java.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/js.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/jsx.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/lua.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/perl.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/php.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/python.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/r.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/ruby.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/rust.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/scss.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/settings.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/sh.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/tex.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/vim.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/vue.vim create mode 100644 bundle/splitjoin.vim/autoload/sj/yaml.vim create mode 100644 bundle/splitjoin.vim/doc/splitjoin.txt create mode 100644 bundle/splitjoin.vim/ftplugin/c/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/clojure/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/coffee/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/cs/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/css/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/cue/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/elixir/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/elm/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/eruby/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/go/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/haml/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/handlebars/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/html/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/htmldjango/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/java/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/javascript/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/javascriptreact/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/json/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/jsx/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/less/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/lua/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/perl/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/php/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/python/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/r/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/ruby/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/rust/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/scss/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/sh/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/snakemake/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/svelte/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/tex/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/tsx/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/typescript/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/typescriptreact/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/vim/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/vue/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/xml/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/yaml/splitjoin.vim create mode 100644 bundle/splitjoin.vim/ftplugin/zsh/splitjoin.vim create mode 100644 bundle/splitjoin.vim/plugin/splitjoin.vim create mode 100644 bundle/splitjoin.vim/spec/plugin/c_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/clojure_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/coffee_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/css_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/elixir_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/elm_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/eruby_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/go_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/haml_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/handlebars_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/html_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/java_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/js_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/jsx_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/lua_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/perl_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/php_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/python_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/r_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/ruby_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/rust_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/scss_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/settings_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/sh_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/tex_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/vim_spec.rb create mode 100644 bundle/splitjoin.vim/spec/plugin/yaml_spec.rb create mode 100644 bundle/splitjoin.vim/spec/spec_helper.rb create mode 100644 bundle/splitjoin.vim/spec/support/vim.rb diff --git a/autoload/SpaceVim/layers/edit.vim b/autoload/SpaceVim/layers/edit.vim index 793e17fe1..f3bda41d6 100644 --- a/autoload/SpaceVim/layers/edit.vim +++ b/autoload/SpaceVim/layers/edit.vim @@ -112,7 +112,7 @@ function! SpaceVim#layers#edit#plugins() abort \ [g:_spacevim_root_dir . 'bundle/editorconfig-vim', { 'merged' : 0, 'if' : has('python') || has('python3')}], \ [g:_spacevim_root_dir . 'bundle/vim-jplus', { 'on_map' : '(jplus' }], \ [g:_spacevim_root_dir . 'bundle/tabular', { 'merged' : 0, 'on_cmd' : ['Tabularize']}], - \ ['andrewradev/splitjoin.vim',{ 'on_cmd':['SplitjoinJoin', 'SplitjoinSplit'],'merged' : 0, 'loadconf' : 1}], + \ [g:_spacevim_root_dir . 'bundle/splitjoin.vim',{ 'on_cmd':['SplitjoinJoin', 'SplitjoinSplit'],'merged' : 0, 'loadconf' : 1}], \ ] if has('nvim-0.8.0') call add(plugins,[g:_spacevim_root_dir . 'bundle/nvim-surround', { 'merged' : 0, 'loadconf' : 1, 'on_event' : ['BufReadPost']}]) diff --git a/bundle/splitjoin.vim/.circleci/config.yml b/bundle/splitjoin.vim/.circleci/config.yml new file mode 100644 index 000000000..a34ffdfda --- /dev/null +++ b/bundle/splitjoin.vim/.circleci/config.yml @@ -0,0 +1,38 @@ +version: 2.1 + +orbs: + ruby: circleci/ruby@2.0.0 + +jobs: + test: + docker: + - image: cimg/ruby:3.2.1 + executor: ruby/default + steps: + - checkout + - run: + name: Install submodules + command: | + git submodule init + git submodule update + - run: + name: Install bundler + command: gem install bundler:2.1.4 + - ruby/install-deps + + - run: + name: Install dependencies + command: | + sudo apt-get update + sudo apt-get -y install xvfb vim-gtk3 + - run: + name: Which (g)vim? + command: xvfb-run gvim --version + + - run: + name: Run tests + command: xvfb-run bundle exec rspec -fd spec + +workflows: + test: + jobs: [test] diff --git a/bundle/splitjoin.vim/.github/workflows/mirror.yml b/bundle/splitjoin.vim/.github/workflows/mirror.yml new file mode 100644 index 000000000..d1fa789f3 --- /dev/null +++ b/bundle/splitjoin.vim/.github/workflows/mirror.yml @@ -0,0 +1,20 @@ +# Originally forked from: https://github.com/google/mirror-branch-action +# Current repo: https://github.com/AndrewRadev/mirror-branch-action + +on: + push: + branches: + - 'main' + +jobs: + mirror_job: + runs-on: ubuntu-latest + name: Mirror main branch to master branch + steps: + - name: Mirror action step + id: mirror + uses: AndrewRadev/mirror-branch-action@main + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + source: 'main' + dest: 'master' diff --git a/bundle/splitjoin.vim/.gitignore b/bundle/splitjoin.vim/.gitignore new file mode 100644 index 000000000..e6da2bdd0 --- /dev/null +++ b/bundle/splitjoin.vim/.gitignore @@ -0,0 +1,4 @@ +_project.vim +tags +doc/tags +tmp/ diff --git a/bundle/splitjoin.vim/.gitmodules b/bundle/splitjoin.vim/.gitmodules new file mode 100644 index 000000000..cde56e2f7 --- /dev/null +++ b/bundle/splitjoin.vim/.gitmodules @@ -0,0 +1,18 @@ +[submodule "spec/support/rust.vim"] + path = spec/support/rust.vim + url = https://github.com/rust-lang/rust.vim +[submodule "spec/support/vim-javascript"] + path = spec/support/vim-javascript + url = https://github.com/pangloss/vim-javascript +[submodule "spec/support/tabular"] + path = spec/support/tabular + url = https://github.com/godlygeek/tabular +[submodule "spec/support/vim-elm-syntax"] + path = spec/support/vim-elm-syntax + url = https://github.com/andys8/vim-elm-syntax +[submodule "spec/support/vim-elixir"] + path = spec/support/vim-elixir + url = https://github.com/elixir-editors/vim-elixir +[submodule "spec/support/R-Vim-runtime"] + path = spec/support/R-Vim-runtime + url = https://github.com/jalvesaq/R-Vim-runtime diff --git a/bundle/splitjoin.vim/.rspec b/bundle/splitjoin.vim/.rspec new file mode 100644 index 000000000..d6b64a7c4 --- /dev/null +++ b/bundle/splitjoin.vim/.rspec @@ -0,0 +1,2 @@ +--color +--exclude-pattern="./spec/support/**/*" diff --git a/bundle/splitjoin.vim/CODE_OF_CONDUCT.md b/bundle/splitjoin.vim/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..3de9cb517 --- /dev/null +++ b/bundle/splitjoin.vim/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at andrey.radev@gmail.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/bundle/splitjoin.vim/CONTRIBUTING.md b/bundle/splitjoin.vim/CONTRIBUTING.md new file mode 100644 index 000000000..b12aa7a2f --- /dev/null +++ b/bundle/splitjoin.vim/CONTRIBUTING.md @@ -0,0 +1,44 @@ +# Contributing + +If you'd like to contribute to the project, you can use the usual github pull-request flow: + +1. Fork the project +2. Make your change/addition, preferably in a separate branch. +3. Test the new behaviour and make sure all existing tests pass (optional, see below for more information). +4. Issue a pull request with a description of your feature/bugfix. + +## Testing + +This project uses [rspec](http://rspec.info/) and [vimrunner](https://github.com/AndrewRadev/vimrunner) to test its behaviour. Testing vimscript this way does a great job of catching regressions, since it launches a real Vim instance and drives it (almost) as if it's a real user. Tests are written in the ruby programming language, so if you're familiar with it, you should (I hope) find the tests fairly understandable and easy to get into. + +If you're not familiar with ruby, it's okay to skip them. I'd definitely appreciate it if you could take a look at the tests and attempt to write something that describes your change. Even if you don't, TravisCI should run the tests on every pull request, so we'll know right away if there's a regression. In that case, I'll work on the tests myself and see what I can do. + +To run the test suite, you need to first make sure you've got git submodules checked out: + +``` +$ git submodule init +$ git submodule update +``` + +Then, provided you have ruby installed, you need bundler: + +``` +$ gem install bundler +``` + +If you already have the `bundle` command (check it out with `which bundle`), you don't need this step. Afterwards, it should be as simple as: + +``` +$ bundle install +$ bundle exec rspec spec +``` + +Instead of running `rspec` by hand you can also use: + +``` +$ bundle exec guard +``` + +This will trigger `rspec` automatically every time you make a change either to a spec file like `spec/plugin/coffee_spec.rb` or one of sj's autoload files like `autoload/sj/coffee.vim`. This has the additional benefit of only running the specs for the file you are currently working one, which shortens your feedback loop considerably. E.g. when you work on `autoload/sj/sh.vim` only shell specs will be run. + +Depending on what kind of Vim you have installed, this may spawn a GUI Vim instance, or even several. You can read up on [vimrunner's README](https://github.com/AndrewRadev/vimrunner/blob/master/README.md) to understand how that works. diff --git a/bundle/splitjoin.vim/Gemfile b/bundle/splitjoin.vim/Gemfile new file mode 100644 index 000000000..8e5330894 --- /dev/null +++ b/bundle/splitjoin.vim/Gemfile @@ -0,0 +1,9 @@ +source 'http://rubygems.org' + +gem 'rake' +gem 'rspec' +gem 'vimrunner' +gem 'pry' + +gem 'guard' +gem 'guard-rspec' diff --git a/bundle/splitjoin.vim/Gemfile.lock b/bundle/splitjoin.vim/Gemfile.lock new file mode 100644 index 000000000..68bdbeec6 --- /dev/null +++ b/bundle/splitjoin.vim/Gemfile.lock @@ -0,0 +1,67 @@ +GEM + remote: http://rubygems.org/ + specs: + coderay (1.1.3) + diff-lcs (1.5.0) + ffi (1.16.3) + formatador (1.1.0) + guard (2.18.1) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.13.0) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-rspec (4.7.3) + guard (~> 2.1) + guard-compat (~> 1.1) + rspec (>= 2.99.0, < 4.0) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + lumberjack (1.2.10) + method_source (1.0.0) + nenv (0.3.0) + notiffany (0.1.3) + nenv (~> 0.1) + shellany (~> 0.0) + pry (0.14.2) + coderay (~> 1.1) + method_source (~> 1.0) + rake (13.1.0) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + rspec (3.12.0) + rspec-core (~> 3.12.0) + rspec-expectations (~> 3.12.0) + rspec-mocks (~> 3.12.0) + rspec-core (3.12.2) + rspec-support (~> 3.12.0) + rspec-expectations (3.12.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-mocks (3.12.6) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.12.0) + rspec-support (3.12.1) + shellany (0.0.1) + thor (1.3.0) + vimrunner (0.3.5) + +PLATFORMS + ruby + +DEPENDENCIES + guard + guard-rspec + pry + rake + rspec + vimrunner + +BUNDLED WITH + 2.1.4 diff --git a/bundle/splitjoin.vim/Guardfile b/bundle/splitjoin.vim/Guardfile new file mode 100644 index 000000000..8b983054c --- /dev/null +++ b/bundle/splitjoin.vim/Guardfile @@ -0,0 +1,4 @@ +guard 'rspec', cmd: 'bundle exec rspec' do + watch(%r{autoload/sj/(.*)\.vim}) { |m| "spec/plugin/#{m[1]}_spec.rb"} + watch(%r{spec/plugin/(.*)_spec.rb}) +end diff --git a/bundle/splitjoin.vim/LICENSE b/bundle/splitjoin.vim/LICENSE new file mode 100644 index 000000000..f85e365de --- /dev/null +++ b/bundle/splitjoin.vim/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bundle/splitjoin.vim/README.md b/bundle/splitjoin.vim/README.md new file mode 100644 index 000000000..1d85f20a5 --- /dev/null +++ b/bundle/splitjoin.vim/README.md @@ -0,0 +1,108 @@ +[![GitHub version](https://badge.fury.io/gh/andrewradev%2Fsplitjoin.vim.svg)](https://badge.fury.io/gh/andrewradev%2Fsplitjoin.vim) +[![Build Status](https://circleci.com/gh/AndrewRadev/splitjoin.vim/tree/main.svg?style=shield)](https://circleci.com/gh/AndrewRadev/splitjoin.vim?branch=main) + +## Usage + +This plugin is meant to simplify a task I've found too common in my workflow: switching between a single-line statement and a multi-line one. It offers the following default keybindings, which can be customized: + +* `gS` to split a one-liner into multiple lines +* `gJ` (with the cursor on the first line of a block) to join a block into a single-line statement. + +![Demo](http://i.andrewradev.com/2fcc9f013816ec744c54e57476afac32.gif) + +I usually work with ruby and a lot of expressions can be written very concisely on a single line. A good example is the "if" statement: + +``` ruby +puts "foo" if bar? +``` + +This is a great feature of the language, but when you need to add more statements to the body of the "if", you need to rewrite it: + +``` ruby +if bar? + puts "foo" + puts "baz" +end +``` + +The idea of this plugin is to introduce a single key binding (default: `gS`) for transforming a line like this: + +``` html +
bar
+``` + +Into this: + +``` html +
+ bar +
+``` + +And another binding (default: `gJ`) for the opposite transformation. + +This currently works for various constructs in the following languages: + +- C +- CSS +- Clojure +- Coffeescript +- Elixir +- Elm +- Eruby +- Go +- HAML +- HTML (and HTML-like markup) +- Handlebars +- JSON +- Java +- Javascript (within JSX, TSX, Vue.js templates as well) +- Lua +- PHP +- Perl +- Python +- R +- Ruby +- Rust +- SCSS and Less +- Shell (sh, bash, zsh) +- Tex +- Vimscript +- YAML + +For more information, including examples for all of those languages, try `:help +splitjoin`, or take a look at the full help file online at +[doc/splitjoin.txt](https://github.com/AndrewRadev/splitjoin.vim/blob/master/doc/splitjoin.txt) + +## Installation + +The easiest way to install the plugin is with a plugin manager: + +- vim-plug: https://github.com/junegunn/vim-plug +- Vundle: https://github.com/VundleVim/Vundle.vim + +If you use one, just follow the instructions in its documentation. + +You can install the plugin yourself using Vim's "packages" functionality by cloning the project (or adding it as a submodule) under `~/.vim/pack//start/`. For example: + +``` +git clone https://github.com/AndrewRadev/splitjoin.vim ~/.vim/pack/_/start/splitjoin +``` + +This should automatically load the plugin for you on Vim start. Alternatively, you can add it to `~/.vim/pack//opt/` instead and load it in your .vimrc manually with: + +``` vim +packadd splitjoin +``` + +If you'd rather not use git, you can download the files from the "releases" tab and unzip them in the relevant directory: https://github.com/AndrewRadev/splitjoin.vim/releases. + +## Contributing + +If you'd like to hack on the plugin, please see +[CONTRIBUTING.md](https://github.com/AndrewRadev/splitjoin.vim/blob/master/CONTRIBUTING.md) first. + +## Issues + +Any issues and suggestions are very welcome on the +[github bugtracker](https://github.com/AndrewRadev/splitjoin.vim/issues). diff --git a/bundle/splitjoin.vim/Rakefile b/bundle/splitjoin.vim/Rakefile new file mode 100644 index 000000000..2c8c2e13b --- /dev/null +++ b/bundle/splitjoin.vim/Rakefile @@ -0,0 +1,8 @@ +task :default do + sh 'rspec spec' +end + +desc "Prepare archive for deployment" +task :archive do + sh 'zip -r ~/splitjoin.zip autoload/ doc/splitjoin.txt ftplugin/ plugin/' +end diff --git a/bundle/splitjoin.vim/autoload/sj.vim b/bundle/splitjoin.vim/autoload/sj.vim new file mode 100644 index 000000000..bb0e6bc59 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj.vim @@ -0,0 +1,881 @@ +" vim: foldmethod=marker + +" Main entry points {{{1 +" +" The two main functions that loop through callbacks and execute them. +" +" function! sj#Split() {{{2 +" +function! sj#Split() + if !exists('b:splitjoin_split_callbacks') + return + end + + " expand any folds under the cursor, or we might replace the wrong area + silent! foldopen + + let disabled_callbacks = sj#settings#Read('disabled_split_callbacks') + + let saved_view = winsaveview() + let saved_whichwrap = &whichwrap + set whichwrap-=l + + if !sj#settings#Read('quiet') | echo "Splitjoin: Working..." | endif + for callback in b:splitjoin_split_callbacks + if index(disabled_callbacks, callback) >= 0 + continue + endif + + try + call sj#PushCursor() + + if call(callback, []) + silent! call repeat#set("\SplitjoinSplit") + let &whichwrap = saved_whichwrap + if !sj#settings#Read('quiet') + " clear progress message + redraw | echo "" + endif + return 1 + endif + + finally + call sj#PopCursor() + endtry + endfor + + call winrestview(saved_view) + let &whichwrap = saved_whichwrap + if !sj#settings#Read('quiet') + " clear progress message + redraw | echo "" + endif + return 0 +endfunction + +" function! sj#Join() {{{2 +" +function! sj#Join() + if !exists('b:splitjoin_join_callbacks') + return + end + + " expand any folds under the cursor, or we might replace the wrong area + silent! foldopen + + let disabled_callbacks = sj#settings#Read('disabled_join_callbacks') + + let saved_view = winsaveview() + let saved_whichwrap = &whichwrap + set whichwrap-=l + + if !sj#settings#Read('quiet') | echo "Splitjoin: Working..." | endif + for callback in b:splitjoin_join_callbacks + if index(disabled_callbacks, callback) >= 0 + continue + endif + + try + call sj#PushCursor() + + if call(callback, []) + silent! call repeat#set("\SplitjoinJoin") + let &whichwrap = saved_whichwrap + if !sj#settings#Read('quiet') + " clear progress message + redraw | echo "" + endif + return 1 + endif + + finally + call sj#PopCursor() + endtry + endfor + + call winrestview(saved_view) + let &whichwrap = saved_whichwrap + if !sj#settings#Read('quiet') + " clear progress message + redraw | echo "" + endif + return 0 +endfunction + +" Cursor stack manipulation {{{1 +" +" In order to make the pattern of saving the cursor and restoring it +" afterwards easier, these functions implement a simple cursor stack. The +" basic usage is: +" +" call sj#PushCursor() +" " Do stuff that move the cursor around +" call sj#PopCursor() +" +" function! sj#PushCursor() {{{2 +" +" Adds the current cursor position to the cursor stack. +function! sj#PushCursor() + if !exists('b:cursor_position_stack') + let b:cursor_position_stack = [] + endif + + call add(b:cursor_position_stack, winsaveview()) +endfunction + +" function! sj#PopCursor() {{{2 +" +" Restores the cursor to the latest position in the cursor stack, as added +" from the sj#PushCursor function. Removes the position from the stack. +function! sj#PopCursor() + call winrestview(remove(b:cursor_position_stack, -1)) +endfunction + +" function! sj#DropCursor() {{{2 +" +" Discards the last saved cursor position from the cursor stack. +" Note that if the cursor hasn't been saved at all, this will raise an error. +function! sj#DropCursor() + call remove(b:cursor_position_stack, -1) +endfunction + +" Indenting {{{1 +" +" Some languages don't have built-in support, and some languages have semantic +" indentation. In such cases, code blocks might need to be reindented +" manually. +" + +" function! sj#SetIndent(start_lineno, end_lineno, indent) {{{2 +" function! sj#SetIndent(lineno, indent) +" +" Sets the indent of the given line numbers to "indent" amount of whitespace. +" +function! sj#SetIndent(...) + if a:0 == 3 + let start_lineno = a:1 + let end_lineno = a:2 + let indent = a:3 + elseif a:0 == 2 + let start_lineno = a:1 + let end_lineno = a:1 + let indent = a:2 + endif + + let is_tabs = &l:expandtab + let shift = shiftwidth() + + if is_tabs == 0 + if shift > 0 + let indent = indent / shift + endif + + let whitespace = repeat('\t', indent) + else + let whitespace = repeat(' ', indent) + endif + + exe start_lineno.','.end_lineno.'s/^\s*/'.whitespace + + " Don't leave a history entry + call histdel('search', -1) + let @/ = histget('search', -1) +endfunction + +" function! sj#PeekCursor() {{{2 +" +" Returns the last saved cursor position from the cursor stack. +" Note that if the cursor hasn't been saved at all, this will raise an error. +function! sj#PeekCursor() + return b:cursor_position_stack[-1] +endfunction + +" Text replacement {{{1 +" +" Vim doesn't seem to have a whole lot of functions to aid in text replacement +" within a buffer. The ":normal!" command usually works just fine, but it +" could be difficult to maintain sometimes. These functions encapsulate a few +" common patterns for this. + +" function! sj#ReplaceMotion(motion, text) {{{2 +" +" Replace the normal mode "motion" with "text". This is mostly just a wrapper +" for a normal! command with a paste, but doesn't pollute any registers. +" +" Examples: +" call sj#ReplaceMotion('Va{', 'some text') +" call sj#ReplaceMotion('V', 'replacement line') +" +" Note that the motion needs to include a visual mode key, like "V", "v" or +" "gv" +function! sj#ReplaceMotion(motion, text) + " reset clipboard to avoid problems with 'unnamed' and 'autoselect' + let saved_clipboard = &clipboard + set clipboard= + let saved_selection = &selection + let &selection = "inclusive" + + let saved_register_text = getreg('"', 1) + let saved_register_type = getregtype('"') + let saved_opening_visual = getpos("'<") + let saved_closing_visual = getpos("'>") + + call setreg('"', a:text, 'v') + exec 'silent noautocmd normal! '.a:motion.'p' + + " TODO (2021-02-22) Not a good idea to rely on reindent here + silent normal! gv= + + call setreg('"', saved_register_text, saved_register_type) + call setpos("'<", saved_opening_visual) + call setpos("'>", saved_closing_visual) + + let &clipboard = saved_clipboard + let &selection = saved_selection +endfunction + +" function! sj#ReplaceLines(start, end, text) {{{2 +" +" Replace the area defined by the 'start' and 'end' lines with 'text'. +function! sj#ReplaceLines(start, end, text) + let interval = a:end - a:start + + if interval == 0 + return sj#ReplaceMotion(a:start.'GV', a:text) + else + return sj#ReplaceMotion(a:start.'GV'.interval.'j', a:text) + endif +endfunction + +" function! sj#ReplaceCols(start, end, text) {{{2 +" +" Replace the area defined by the 'start' and 'end' columns on the current +" line with 'text' +function! sj#ReplaceCols(start, end, text) + let start_position = getpos('.') + let end_position = getpos('.') + + let start_position[2] = a:start + let end_position[2] = a:end + + return sj#ReplaceByPosition(start_position, end_position, a:text) +endfunction + +" function! sj#ReplaceByPosition(start, end, text) {{{2 +" +" Replace the area defined by the 'start' and 'end' positions with 'text'. The +" positions should be compatible with the results of getpos(): +" +" [bufnum, lnum, col, off] +" +function! sj#ReplaceByPosition(start, end, text) + let saved_z_pos = getpos("'z") + + try + call setpos('.', a:start) + call setpos("'z", a:end) + + return sj#ReplaceMotion('v`z', a:text) + finally + call setpos("'z", saved_z_pos) + endtry +endfunction + +" Text retrieval {{{1 +" +" These functions are similar to the text replacement functions, only retrieve +" the text instead. +" +" function! sj#GetMotion(motion) {{{2 +" +" Execute the normal mode motion "motion" and return the text it marks. +" +" Note that the motion needs to include a visual mode key, like "V", "v" or +" "gv" +function! sj#GetMotion(motion) + call sj#PushCursor() + + let saved_selection = &selection + let &selection = "inclusive" + let saved_register_text = getreg('z', 1) + let saved_register_type = getregtype('z') + let saved_opening_visual = getpos("'<") + let saved_closing_visual = getpos("'>") + + let @z = '' + exec 'silent noautocmd normal! '.a:motion.'"zy' + let text = @z + + if text == '' + " nothing got selected, so we might still be in visual mode + exe "normal! \" + endif + + call setreg('z', saved_register_text, saved_register_type) + call setpos("'<", saved_opening_visual) + call setpos("'>", saved_closing_visual) + let &selection = saved_selection + + call sj#PopCursor() + + return text +endfunction + +" function! sj#GetLines(start, end) {{{2 +" +" Retrieve the lines from "start" to "end" and return them as a list. This is +" simply a wrapper for getbufline for the moment. +function! sj#GetLines(start, end) + return getbufline('%', a:start, a:end) +endfunction + +" function! sj#GetCols(start, end) {{{2 +" +" Retrieve the text from columns "start" to "end" on the current line. +function! sj#GetCols(start, end) + return strpart(getline('.'), a:start - 1, a:end - a:start + 1) +endfunction + +" function! sj#GetByPosition(start, end) {{{2 +" +" Fetch the area defined by the 'start' and 'end' positions. The positions +" should be compatible with the results of getpos(): +" +" [bufnum, lnum, col, off] +" +function! sj#GetByPosition(start, end) + let saved_z_pos = getpos("'z") + + try + call setpos('.', a:start) + call setpos("'z", a:end) + + return sj#GetMotion('v`z') + finally + call setpos("'z", saved_z_pos) + endtry +endfunction + +" String functions {{{1 +" Various string manipulation utility functions +function! sj#BlankString(s) + return (a:s =~ '^\s*$') +endfunction + +" Surprisingly, Vim doesn't seem to have a "trim" function. In any case, these +" should be fairly obvious. +function! sj#Ltrim(s) + return substitute(a:s, '^\_s\+', '', '') +endfunction +function! sj#Rtrim(s) + return substitute(a:s, '\_s\+$', '', '') +endfunction +function! sj#Trim(s) + return sj#Rtrim(sj#Ltrim(a:s)) +endfunction + +" Execute sj#Trim on each item of a List +function! sj#TrimList(list) + return map(a:list, 'sj#Trim(v:val)') +endfunction + +" Remove blank strings from the List +function! sj#RemoveBlanks(list) + return filter(a:list, 'v:val !~ "^\\s*$"') +endfunction + +" Searching for patterns {{{1 +" +" function! sj#SearchUnderCursor(pattern, flags, skip) {{{2 +" +" Searches for a match for the given pattern under the cursor. Returns the +" result of the |search()| call if a match was found, 0 otherwise. +" +" Moves the cursor unless the 'n' flag is given. +" +" The a:flags parameter can include one of "e", "p", "s", "n", which work the +" same way as the built-in |search()| call. Any other flags will be ignored. +" +function! sj#SearchUnderCursor(pattern, ...) + let [match_start, match_end] = call('sj#SearchColsUnderCursor', [a:pattern] + a:000) + if match_start > 0 + return match_start + else + return 0 + endif +endfunction + +" function! sj#SearchColsUnderCursor(pattern, flags, skip) {{{2 +" +" Searches for a match for the given pattern under the cursor. Returns the +" start and (end + 1) column positions of the match. If nothing was found, +" returns [0, 0]. +" +" Moves the cursor unless the 'n' flag is given. +" +" Respects the skip expression if it's given. +" +" See sj#SearchUnderCursor for the behaviour of a:flags +" +function! sj#SearchColsUnderCursor(pattern, ...) + if a:0 >= 1 + let given_flags = a:1 + else + let given_flags = '' + endif + + if a:0 >= 2 + let skip = a:2 + else + let skip = '' + endif + + let lnum = line('.') + let col = col('.') + let pattern = a:pattern + let extra_flags = '' + + " handle any extra flags provided by the user + for char in ['e', 'p', 's'] + if stridx(given_flags, char) >= 0 + let extra_flags .= char + endif + endfor + + call sj#PushCursor() + + " find the start of the pattern + call search(pattern, 'bcW', lnum) + let search_result = sj#SearchSkip(pattern, skip, 'cW'.extra_flags, lnum) + if search_result <= 0 + call sj#PopCursor() + return [0, 0] + endif + + call sj#PushCursor() + + " find the end of the pattern + if stridx(extra_flags, 'e') >= 0 + let match_end = col('.') + + call sj#PushCursor() + call sj#SearchSkip(pattern, skip, 'cWb', lnum) + let match_start = col('.') + call sj#PopCursor() + else + let match_start = col('.') + call sj#SearchSkip(pattern, skip, 'cWe', lnum) + let match_end = col('.') + end + + " set the end of the pattern to the next character, or EOL. Extra logic + " is for multibyte characters. + normal! l + if col('.') == match_end + " no movement, we must be at the end + let match_end = col('$') + else + let match_end = col('.') + endif + call sj#PopCursor() + + if !sj#ColBetween(col, match_start, match_end) + " then the cursor is not in the pattern + call sj#PopCursor() + return [0, 0] + else + " a match has been found + if stridx(given_flags, 'n') >= 0 + call sj#PopCursor() + else + call sj#DropCursor() + endif + + return [match_start, match_end] + endif +endfunction + +" function! sj#SearchSkip(pattern, skip, ...) {{{2 +" A partial replacement to search() that consults a skip pattern when +" performing a search, just like searchpair(). +" +" Note that it doesn't accept the "n" and "c" flags due to implementation +" difficulties. +function! sj#SearchSkip(pattern, skip, ...) + " collect all of our arguments + let pattern = a:pattern + let skip = a:skip + + if a:0 >= 1 + let flags = a:1 + else + let flags = '' + endif + + if stridx(flags, 'n') > -1 + echoerr "Doesn't work with 'n' flag, was given: ".flags + return + endif + + let stopline = (a:0 >= 2) ? a:2 : 0 + let timeout = (a:0 >= 3) ? a:3 : 0 + + " Note: Native search() seems to hit a bug with one of the HTML tests + " (because of \zs?) + if skip == '' + " no skip, can delegate to native search() + return search(pattern, flags, stopline, timeout) + " elseif has('patch-8.2.915') + " " the native search() function can do this now: + " return search(pattern, flags, stopline, timeout, skip) + endif + + " search for the pattern, skipping a match if necessary + let skip_match = 1 + while skip_match + let match = search(pattern, flags, stopline, timeout) + + " remove 'c' flag for any run after the first + let flags = substitute(flags, 'c', '', 'g') + + if match && eval(skip) + let skip_match = 1 + else + let skip_match = 0 + endif + endwhile + + return match +endfunction + +function! sj#SkipSyntax(syntax_groups) + let syntax_groups = a:syntax_groups + let skip_pattern = '\%('.join(syntax_groups, '\|').'\)' + + return "synIDattr(synID(line('.'),col('.'),1),'name') =~ '".skip_pattern."'" +endfunction + +function! sj#IncludeSyntax(syntax_groups) + let syntax_groups = a:syntax_groups + let include_pattern = '\%('.join(syntax_groups, '\|').'\)' + + return "synIDattr(synID(line('.'),col('.'),1),'name') !~ '".include_pattern."'" +endfunction + +" Checks if the current position of the cursor is within the given limits. +" +function! sj#CursorBetween(start, end) + return sj#ColBetween(col('.'), a:start, a:end) +endfunction + +" Checks if the given column is within the given limits. +" +function! sj#ColBetween(col, start, end) + return a:start <= a:col && a:end > a:col +endfunction + +" Regex helpers {{{1 +" +" function! sj#ExtractRx(expr, pat, sub) {{{2 +" +" Extract a regex match from a string. Ordinarily, substitute() would be used +" for this, but it's a bit too cumbersome for extracting a particular grouped +" match. Example usage: +" +" sj#ExtractRx('foo:bar:baz', ':\(.*\):', '\1') == 'bar' +" +function! sj#ExtractRx(expr, pat, sub) + let rx = a:pat + + if stridx(a:pat, '^') != 0 + let rx = '^.*'.rx + endif + + if strridx(a:pat, '$') + 1 != strlen(a:pat) + let rx = rx.'.*$' + endif + + return substitute(a:expr, rx, a:sub, '') +endfunction + +" Compatibility {{{1 +" +" Functionality that is present in newer versions of Vim, but needs a +" compatibility layer for older ones. +" +" function! sj#Keeppatterns(command) {{{2 +" +" Executes the given command, but attempts to keep search patterns as they +" were. +" +function! sj#Keeppatterns(command) + if exists(':keeppatterns') + exe 'keeppatterns '.a:command + else + let histnr = histnr('search') + + exe a:command + + if histnr != histnr('search') + call histdel('search', -1) + let @/ = histget('search', -1) + endif + endif +endfunction + +" Splitjoin-specific helpers {{{1 + +" These functions are not general-purpose, but can be used all around the +" plugin disregarding filetype, so they have no place in the specific autoload +" files. + +function! sj#Align(from, to, type) + if a:from >= a:to + return + endif + + if exists('g:tabular_loaded') + call s:Tabularize(a:from, a:to, a:type) + elseif exists('g:loaded_AlignPlugin') + call s:Align(a:from, a:to, a:type) + endif +endfunction + +function! s:Tabularize(from, to, type) + if a:type == 'hashrocket' + let pattern = '^[^=>]*\zs=>' + elseif a:type == 'css_declaration' || a:type == 'json_object' + let pattern = '^[^:]*:\s*\zs\s/l0' + elseif a:type == 'lua_table' + let pattern = '^[^=]*\zs=' + elseif a:type == 'when_then' + let pattern = 'then' + elseif a:type == 'equals' + let pattern = '=' + else + return + endif + + exe a:from.",".a:to."Tabularize/".pattern +endfunction + +function! s:Align(from, to, type) + if a:type == 'hashrocket' + let pattern = 'l: =>' + elseif a:type == 'css_declaration' || a:type == 'json_object' + let pattern = 'lp0W0 :\s*\zs' + elseif a:type == 'when_then' + let pattern = 'l: then' + elseif a:type == 'equals' + let pattern = '=' + else + return + endif + + exe a:from.",".a:to."Align! ".pattern +endfunction + +" Returns a pair with the column positions of the closest opening and closing +" braces on the current line. The a:open and a:close parameters are the +" opening and closing brace characters to look for. +" +" The optional parameter is the list of syntax groups to skip while searching. +" +" If a pair is not found on the line, returns [-1, -1] +" +" Examples: +" +" let [start, end] = sj#LocateBracesOnLine('{', '}') +" let [start, end] = sj#LocateBracesOnLine('{', '}', ['rubyString']) +" let [start, end] = sj#LocateBracesOnLine('[', ']') +" +function! sj#LocateBracesOnLine(open, close, ...) + let [_bufnum, line, col, _off] = getpos('.') + let search_pattern = '\V'.a:open.'\m.*\V'.a:close + let current_line = line('.') + + " bail early if there's obviously no match + if getline('.') !~ search_pattern + return [-1, -1] + endif + + " optional skip parameter + if a:0 > 0 + let skip = sj#SkipSyntax(a:1) + else + let skip = '' + endif + + " try looking backwards, then forwards + let found = searchpair('\V'.a:open, '', '\V'.a:close, 'cb', skip, line('.')) + if found <= 0 + let found = sj#SearchSkip(search_pattern, skip, '', line('.')) + endif + + if found > 0 + let from = col('.') + + normal! % + if line('.') != current_line + return [-1, -1] + endif + + let to = col('.') + + return [from, to] + else + return [-1, -1] + endif +endfunction + +" Returns a pair with the column positions of the closest opening and closing +" braces on the current line, but only if the cursor is between them. +" +" The optional parameter is the list of syntax groups to skip while searching. +" +" If a pair is not found around the cursor, returns [-1, -1] +" +" Examples: +" +" let [start, end] = sj#LocateBracesAroundCursor('{', '}') +" let [start, end] = sj#LocateBracesAroundCursor('{', '}', ['rubyString']) +" let [start, end] = sj#LocateBracesAroundCursor('[', ']') +" +function! sj#LocateBracesAroundCursor(open, close, ...) + let args = [a:open, a:close] + if a:0 > 0 + call extend(args, a:000) + endif + + call sj#PushCursor() + let [start, end] = call('sj#LocateBracesOnLine', args) + call sj#PopCursor() + + if sj#CursorBetween(start, end) + return [start, end] + else + return [-1, -1] + endif +endfunction + +" Removes all extra whitespace on the current line. Such is often left when +" joining lines that have been aligned. +" +" Example: +" +" var one = { one: "two", three: "four" }; +" " turns into: +" var one = { one: "two", three: "four" }; +" +function! sj#CompressWhitespaceOnLine() + call sj#PushCursor() + call sj#Keeppatterns('s/\S\zs \+/ /g') + call sj#PopCursor() +endfunction + +" Parses a JSON-like object and returns a list of its components +" (comma-separated parts). +" +" Note that a:from and a:to are the start and end of the body, not the curly +" braces that usually define a JSON object. This makes it possible to use the +" function for parsing an argument list into separate arguments, knowing their +" start and end. +" +" Different languages have different rules for delimiters, so it might be a +" better idea to write a specific parser. See autoload/sj/argparser/json.vim +" for inspiration. +" +function! sj#ParseJsonObjectBody(from, to) + " Just use js object parser + let parser = sj#argparser#json#Construct(a:from, a:to, getline('.')) + call parser.Process() + return parser.args +endfunction + +" Jumps over nested brackets until it reaches the given pattern. +" +" Special handling for "<" for Rust (for now), but this only matters if it's +" provided in the `a:opening_brackets` +" +function! sj#JumpBracketsTill(end_pattern, brackets) + let opening_brackets = a:brackets['opening'] + let closing_brackets = a:brackets['closing'] + + try + " ensure we can't go to the next line: + let saved_whichwrap = &whichwrap + set whichwrap-=l + " ensure we can go to the very end of the line + let saved_virtualedit = &virtualedit + set virtualedit=onemore + + let remainder_of_line = s:RemainderOfLine() + while remainder_of_line !~ '^'.a:end_pattern + \ && remainder_of_line !~ '^\s*$' + let [opening_bracket_match, offset] = s:BracketMatch(remainder_of_line, opening_brackets) + let [closing_bracket_match, _] = s:BracketMatch(remainder_of_line, closing_brackets) + + if opening_bracket_match < 0 && closing_bracket_match >= 0 + let closing_bracket = closing_brackets[closing_bracket_match] + + if closing_bracket == '>' + " an unmatched > in this context means comparison, so do nothing + else + " there's an extra closing bracket from outside the list, bail out + break + endif + elseif opening_bracket_match >= 0 + " then try to jump to the closing bracket + let opening_bracket = opening_brackets[opening_bracket_match] + let closing_bracket = closing_brackets[opening_bracket_match] + + " first, go to the opening bracket + if offset > 0 + exe "normal! ".offset."l" + end + + if opening_bracket == closing_bracket + " same bracket (quote), search for it, unless it's escaped + call search('\\\@ 0 + call self.PushArg() + endif +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/argparser/common.vim b/bundle/splitjoin.vim/autoload/sj/argparser/common.vim new file mode 100644 index 000000000..0918b3c60 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/argparser/common.vim @@ -0,0 +1,206 @@ +" Constructor: +" ============ + +function! sj#argparser#common#Construct(start_column, end_column, line) + let parser = { + \ 'args': [], + \ 'opts': [], + \ 'body': a:line, + \ 'index': a:start_column - 1, + \ 'current_arg': '', + \ 'current_arg_type': 'normal', + \ + \ 'Process': function('sj#argparser#common#Process'), + \ 'PushArg': function('sj#argparser#common#PushArg'), + \ 'PushChar': function('sj#argparser#common#PushChar'), + \ 'Next': function('sj#argparser#common#Next'), + \ 'JumpPair': function('sj#argparser#common#JumpPair'), + \ 'AtFunctionEnd': function('sj#argparser#common#AtFunctionEnd'), + \ 'Finished': function('sj#argparser#common#Finished'), + \ } + + if a:start_column > 1 + let parser.body = strpart(parser.body, a:start_column - 1) + endif + if a:end_column > 1 + let parser.body = strpart(parser.body, 0, (a:end_column - a:start_column) + 1) + endif + + return parser +endfunction + +" Methods: +" ======== + +function! sj#argparser#common#Process() dict + throw "Not implemented" + + " " Implementation might go something like this: + " + " while !self.Finished() + " if self.body[0] == ',' + " call self.PushArg() + " call self.Next() + " continue + " elseif self.AtFunctionEnd() + " break + " else + " " ... + " endif + + " call self.PushChar() + " endwhile + + " if len(self.current_arg) > 0 + " call self.PushArg() + " endif +endfunction + +" Pushes the current argument to the args and initializes a new one. +function! sj#argparser#common#PushArg() dict + call add(self.args, sj#Trim(self.current_arg)) + + let self.current_arg = '' + let self.current_arg_type = 'normal' +endfunction + +" Moves the parser to the next char and consumes the current +function! sj#argparser#common#PushChar() dict + let self.current_arg .= self.body[0] + call self.Next() +endfunction + +" Moves the parser to the next char without consuming it. +function! sj#argparser#common#Next() dict + let self.body = strpart(self.body, 1) + let self.index = self.index + 1 +endfunction + +" Finds the current char in a:start_chars and jumps to its match in a:end_chars. +" +" Example: +" call parser.JumpPair("([", ")]") +" +" This will parse matching round and square brackets. +" +" Note: nesting doesn't work properly if there's a string containing unmatched +" braces within the pair. +function! sj#argparser#common#JumpPair(start_chars, end_chars) dict + let char_index = stridx(a:start_chars, self.body[0]) + let start_char = a:start_chars[char_index] + let target_char = a:end_chars[char_index] + + " prepare a stack for nested braces and the like + let stack = 1 + let n = 0 + let limit = len(self.body) + + " Note: if the start and end chars are the same (quotes, for example), this + " will still work, because we're checking for the target_char before the + " start_char + while stack > 0 && n < limit + let n = n + 1 + + if self.body[n] == target_char + let stack = stack - 1 + elseif self.body[n] == start_char + let stack = stack + 1 + endif + endwhile + + let self.current_arg .= strpart(self.body, 0, n) + + let self.body = strpart(self.body, n) + let self.index = self.index + n +endfunction + +" Returns true if the parser has finished parsing the arguments. +function! sj#argparser#common#Finished() dict + return len(self.body) <= 0 +endfunction + +" Returns true if the parser is at the function's end +function! sj#argparser#common#AtFunctionEnd() dict + return (self.body[0] == ')') +endfunction + +" Public functions: +" ================= + +" This code was originally created for ruby functions, but seems to be useful +" for elixir as well, with some modifications. +" +function! sj#argparser#common#LocateRubylikeFunction(keyword_pattern, syntax_groups) + call sj#PushCursor() + let start_col = col('.') + let skip = sj#SkipSyntax(a:syntax_groups) + + " The first pattern matches functions with brackets and consists of the + " following: + " + " - a keyword + " - an opening round bracket + " - something that's not a comma and doesn't look like an operator + " (to avoid a few edge cases) + " + let pattern = '\v(^|\s|\.|::)\m' . a:keyword_pattern . '\v\(\s*[^,=<>+-/*^%})\]]' + let found = sj#SearchSkip(pattern, skip, 'bcW', line('.')) + if found <= 0 + " try searching forward + let found = sj#SearchSkip(pattern, skip, 'cW', line('.')) + endif + if found > 0 + " first, figure out the function name + call search('\k\+', 'cW', line('.')) + let function_name = expand('') + + " go to the end of the matching pattern + call search(pattern, 'cWe', line('.')) + " look for the starting bracket + if sj#SearchSkip(a:keyword_pattern . '\s*\zs(\s*\%#', skip, 'bcW', line('.')) + let function_type = 'with_round_braces' + let from = col('.') + 1 + normal! h%h + let to = col('.') + + if sj#ColBetween(start_col, from - 1, to + 1) + return [function_name, from, to, function_type] + endif + endif + endif + + call sj#PopCursor() + + " The second pattern matches functions without brackets: + " + " - a keyword + " - at least one space + " - something that's not a comma and doesn't look like an operator + " (to avoid a few edge cases) + " + let pattern = '\v(^|\s|\.|::)\m' . a:keyword_pattern . '\v\s+[^ ,=<>+-/*^%})\]]' + let found = sj#SearchSkip(pattern, skip, 'bcW', line('.')) + if found <= 0 + " try searching forward + let found = sj#SearchSkip(pattern, skip, 'cW', line('.')) + endif + if found > 0 + " first, figure out the function name + call search('\k\+', 'cW', line('.')) + let function_name = expand('') + let function_start_col = col('.') + + " go to the end of the matching pattern + call search(pattern, 'cWe', line('.')) + + let function_type = 'with_spaces' + let from = col('.') + let to = -1 " we're not sure about the end + + if sj#ColBetween(start_col, function_start_col - 1, col('$')) + return [function_name, from, to, function_type] + endif + endif + + return ['', -1, -1, 'none'] +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/argparser/elixir.vim b/bundle/splitjoin.vim/autoload/sj/argparser/elixir.vim new file mode 100644 index 000000000..1fae390cb --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/argparser/elixir.vim @@ -0,0 +1,34 @@ +function! sj#argparser#elixir#LocateFunction() + return sj#argparser#common#LocateRubylikeFunction( + \ '\k\+[?!]\=', + \ ['elixirString', 'elixirAtom'] + \ ) +endfunction + +function! sj#argparser#elixir#Construct(start_index, end_index, line) + let parser = sj#argparser#common#Construct(a:start_index, a:end_index, a:line) + + call extend(parser, { + \ 'Process': function('sj#argparser#elixir#Process'), + \ }) + + return parser +endfunction + +function! sj#argparser#elixir#Process() dict + while !self.Finished() + if self.body[0] == ',' + call self.PushArg() + call self.Next() + continue + elseif self.body[0] =~ "[\"'{\[(/]" + call self.JumpPair("\"'{[(/", "\"'}])/") + endif + + call self.PushChar() + endwhile + + if len(sj#Trim(self.current_arg)) > 0 + call self.PushArg() + endif +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/argparser/go_vars.vim b/bundle/splitjoin.vim/autoload/sj/argparser/go_vars.vim new file mode 100644 index 000000000..11e3d9781 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/argparser/go_vars.vim @@ -0,0 +1,33 @@ +function! sj#argparser#go_vars#Construct(line) + " 0, 0 for start, end, since we're giving it the full body + let parser = sj#argparser#common#Construct(0, 0, a:line) + + call extend(parser, { + \ 'comment': '', + \ + \ 'Process': function('sj#argparser#go_vars#Process'), + \ }) + + return parser +endfunction + +function! sj#argparser#go_vars#Process() dict + while !self.Finished() + if self.body[0] == ',' + call self.PushArg() + call self.Next() + continue + elseif self.body[0] =~ "[\"'{\[(]" + call self.JumpPair("\"'{[(<", "\"'}])>") + elseif self.body =~ '^\s*\/\/' + let self.comment = sj#Trim(self.body) + break + endif + + call self.PushChar() + endwhile + + if len(sj#Trim(self.current_arg)) > 0 + call self.PushArg() + endif +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/argparser/html_args.vim b/bundle/splitjoin.vim/autoload/sj/argparser/html_args.vim new file mode 100644 index 000000000..9c4fd777d --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/argparser/html_args.vim @@ -0,0 +1,33 @@ +function! sj#argparser#html_args#Construct(start_index, end_index, line) + let parser = sj#argparser#common#Construct(a:start_index, a:end_index, a:line) + + call extend(parser, { + \ 'Process': function('sj#argparser#html_args#Process'), + \ }) + + return parser +endfunction + +function! sj#argparser#html_args#Process() dict + while !self.Finished() + if self.body =~ '^\s*/\=>' + " end of the tag, push it onto the last argument + let self.current_arg .= self.body + break + elseif self.body[0] == ' ' + if self.current_arg != '' + call self.PushArg() + endif + call self.Next() + continue + elseif self.body[0] =~ '["''{]' + call self.JumpPair('"''{', '"''}') + endif + + call self.PushChar() + endwhile + + if len(self.current_arg) > 0 + call self.PushArg() + endif +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/argparser/js.vim b/bundle/splitjoin.vim/autoload/sj/argparser/js.vim new file mode 100644 index 000000000..7aac17229 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/argparser/js.vim @@ -0,0 +1,27 @@ +function! sj#argparser#js#Construct(start_index, end_index, line) + let parser = sj#argparser#common#Construct(a:start_index, a:end_index, a:line) + + call extend(parser, { + \ 'Process': function('sj#argparser#js#Process'), + \ }) + + return parser +endfunction + +function! sj#argparser#js#Process() dict + while !self.Finished() + if self.body[0] == ',' + call self.PushArg() + call self.Next() + continue + elseif self.body[0] =~ "[\"'{\[(/]" + call self.JumpPair("\"'{[(/", "\"'}])/") + endif + + call self.PushChar() + endwhile + + if len(sj#Trim(self.current_arg)) > 0 + call self.PushArg() + endif +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/argparser/json.vim b/bundle/splitjoin.vim/autoload/sj/argparser/json.vim new file mode 100644 index 000000000..631aaed69 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/argparser/json.vim @@ -0,0 +1,29 @@ +function! sj#argparser#json#Construct(start_index, end_index, line) + let parser = sj#argparser#common#Construct(a:start_index, a:end_index, a:line) + + call extend(parser, { + \ 'Process': function('sj#argparser#json#Process'), + \ }) + + return parser +endfunction + +" Note: Differs from "js" parser by the fact that JSON doesn't have /regexes/ +" to skip through. +function! sj#argparser#json#Process() dict + while !self.Finished() + if self.body[0] == ',' + call self.PushArg() + call self.Next() + continue + elseif self.body[0] =~ "[\"'{\[(]" + call self.JumpPair("\"'{[(", "\"'}])") + endif + + call self.PushChar() + endwhile + + if len(sj#Trim(self.current_arg)) > 0 + call self.PushArg() + endif +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/argparser/ruby.vim b/bundle/splitjoin.vim/autoload/sj/argparser/ruby.vim new file mode 100644 index 000000000..bf7d67c67 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/argparser/ruby.vim @@ -0,0 +1,156 @@ +" Constructor: +" ============ + +function! sj#argparser#ruby#Construct(start_index, end_index, line) + let parser = sj#argparser#common#Construct(a:start_index, a:end_index, a:line) + + call extend(parser, { + \ 'hash_type': '', + \ 'cursor_arg': 0, + \ 'cursor_col': col('.'), + \ + \ 'Process': function('sj#argparser#ruby#Process'), + \ 'PushArg': function('sj#argparser#ruby#PushArg'), + \ 'AtFunctionEnd': function('sj#argparser#ruby#AtFunctionEnd'), + \ 'ExpandOptionHash': function('sj#argparser#ruby#ExpandOptionHash'), + \ 'MarkOptionArg': function('sj#argparser#ruby#MarkOptionArg'), + \ }) + + return parser +endfunction + +" Methods: +" ======== + +function! sj#argparser#ruby#Process() dict + while !self.Finished() + if self.body[0] == ',' + call self.PushArg() + call self.Next() + continue + elseif self.AtFunctionEnd() + break + elseif self.body[0] =~ '[''"]' + call self.JumpPair("'\"", "'\"") + + " Example: 'some key': value + if len(self.body) > 1 && self.body[1] == ':' + call self.MarkOptionArg('new') + endif + elseif self.body[0] =~ "[{\[`(/]" + call self.JumpPair("{[`(/", "}]`)/") + elseif self.body[0] == '%' + call self.PushChar() + if self.body[0] =~ '[qQrswWx]' + call self.PushChar() + endif + let delimiter = self.body[0] + if delimiter =~ '[[({<]' + call self.JumpPair('[({<', '])}>') + else + call self.JumpPair(delimiter, delimiter) + endif + elseif self.body =~ '^=>' + " Example: 'some key' => value + call self.MarkOptionArg('classic') + elseif self.body =~ '^\(\k\|[?!]\):[^:]' + " Example: some_key: value + call self.MarkOptionArg('new') + endif + + call self.PushChar() + endwhile + + if len(self.current_arg) > 0 + call self.PushArg() + endif +endfunction + +" Pushes the current argument either to the args or opts stack and initializes +" a new one. +function! sj#argparser#ruby#PushArg() dict + if self.current_arg_type == 'option' + call add(self.opts, sj#Trim(self.current_arg)) + else + call add(self.args, sj#Trim(self.current_arg)) + endif + + let self.current_arg = '' + let self.current_arg_type = 'normal' + + if self.cursor_col > self.index + 1 + " cursor is after the current argument + let self.cursor_arg += 1 + endif +endfunction + +" If the last argument is a hash and no options have been parsed, splits the +" last argument and fills the options with it. +function! sj#argparser#ruby#ExpandOptionHash() dict + if len(self.opts) <= 0 && len(self.args) > 0 + " then try parsing the last parameter + let last = self.args[-1] + let hash_pattern = '^{\(.*\(=>\|\k:\|[''"]:\).*\)}$' + + if last =~ hash_pattern + " then it seems to be a hash, expand it + call remove(self.args, -1) + + let hash = sj#ExtractRx(last, hash_pattern, '\1') + + let [_from, _to, _args, opts, hash_type, _cursor_arg] = + \ sj#argparser#ruby#ParseArguments(0, -1, hash, { 'expand_options': 1 }) + call extend(self.opts, opts) + let self.hash_type = hash_type + endif + endif +endfunction + +" Returns true if the parser is at the function's end, either because of a +" closing brace, a "do" clause or a "%>". +function! sj#argparser#ruby#AtFunctionEnd() dict + if self.body[0] == ')' + return 1 + elseif self.body =~ '\v^\s*do(\s*\|.*\|)?(\s*-?\%\>\s*)?$' + return 1 + elseif self.body =~ '^\s*-\?%>' + return 1 + endif + + return 0 +endfunction + +" Public functions: +" ================= + +function! sj#argparser#ruby#LocateFunction() + return sj#argparser#common#LocateRubylikeFunction( + \ '\k\+[?!]\=', + \ ['rubyInterpolationDelimiter', 'rubyString'] + \ ) +endfunction + +function! sj#argparser#ruby#MarkOptionArg(type) dict + let self.current_arg_type = 'option' + + if a:type == 'new' && sj#BlankString(self.hash_type) + let self.hash_type = 'new' + elseif a:type == 'classic' && sj#BlankString(self.hash_type) + let self.hash_type = 'classic' + elseif a:type != self.hash_type + let self.hash_type = 'mixed' + endif +endfunction + +function! sj#argparser#ruby#LocateHash() + return sj#LocateBracesOnLine('{', '}', ['rubyInterpolationDelimiter', 'rubyString']) +endfunction + +function! sj#argparser#ruby#ParseArguments(start_index, end_index, line, options) + let parser = sj#argparser#ruby#Construct(a:start_index, a:end_index, a:line) + call parser.Process() + if a:options.expand_options + call parser.ExpandOptionHash() + endif + return [ a:start_index, parser.index, parser.args, parser.opts, parser.hash_type, parser.cursor_arg ] +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/argparser/rust_list.vim b/bundle/splitjoin.vim/autoload/sj/argparser/rust_list.vim new file mode 100644 index 000000000..f9491f97b --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/argparser/rust_list.vim @@ -0,0 +1,42 @@ +function! sj#argparser#rust_list#Construct(type, start_index, end_index, line) + let parser = sj#argparser#common#Construct(a:start_index, a:end_index, a:line) + + call extend(parser, { + \ 'type': a:type, + \ + \ 'Process': function('sj#argparser#rust_list#Process'), + \ }) + + return parser +endfunction + +" Note: Differs from "json" parser by and 'lifetimes +function! sj#argparser#rust_list#Process() dict + if self.type == 'list' + let bracket_regex = "[\"'{\[(<]" + let opening_brackets = "\"'{[(<" + let closing_brackets = "\"'}])>" + elseif self.type == 'fn' + let bracket_regex = "[\"{\[(<]" + let opening_brackets = "\"{[(<" + let closing_brackets = "\"}])>" + else + throw "Unknown rust_list parse type: " . self.type + endif + + while !self.Finished() + if self.body[0] == ',' + call self.PushArg() + call self.Next() + continue + elseif self.body[0] =~ bracket_regex + call self.JumpPair(opening_brackets, closing_brackets) + endif + + call self.PushChar() + endwhile + + if len(sj#Trim(self.current_arg)) > 0 + call self.PushArg() + endif +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/argparser/rust_struct.vim b/bundle/splitjoin.vim/autoload/sj/argparser/rust_struct.vim new file mode 100644 index 000000000..79d70304a --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/argparser/rust_struct.vim @@ -0,0 +1,105 @@ +function! sj#argparser#rust_struct#Construct(start_index, end_index, line) + let parser = sj#argparser#common#Construct(a:start_index, a:end_index, a:line) + + call extend(parser, { + \ 'current_arg_attributes': [], + \ + \ 'PushArg': function('sj#argparser#rust_struct#PushArg'), + \ 'Process': function('sj#argparser#rust_struct#Process'), + \ + \ 'IsValidStruct': function('sj#argparser#rust_struct#IsValidStruct'), + \ 'IsOnlyStructPairs': function('sj#argparser#rust_struct#IsOnlyStructPairs'), + \ }) + + return parser +endfunction + +function! sj#argparser#rust_struct#Process() dict + while !self.Finished() + if self.body[0] == ',' + call self.PushArg() + call self.Next() + continue + elseif self.body =~ '^#[' + " It's an #[attribute], so the "current arg" is going to be temporarily + " reset so we can parse it + let real_current_arg = self.current_arg + let self.current_arg = '' + + call self.PushChar() + call self.JumpPair('[', ']') + call self.PushChar() + call add(self.current_arg_attributes, self.current_arg) + + let self.current_arg = real_current_arg + elseif self.body =~ '^''\\\=.''' + " then it's a char, not a lifetime, we can jump it + call self.JumpPair("'", "'") + elseif self.body[0] =~ '["{\[(|]' + call self.JumpPair('"{[(|', '"}])|') + endif + + call self.PushChar() + endwhile + + if len(sj#Trim(self.current_arg)) > 0 + call self.PushArg() + endif +endfunction + +" Pushes the current argument to the args and initializes a new one. Special +" handling for attributes +function! sj#argparser#rust_struct#PushArg() dict + call add(self.args, { + \ "attributes": self.current_arg_attributes, + \ "argument": sj#Trim(self.current_arg) + \ }) + + let self.current_arg = '' + let self.current_arg_type = 'normal' + let self.current_arg_attributes = [] +endfunction + +" Expects self.Process() to have been run +" +" Possibilities: +" StructName { key: value }, or +" StructName { prop1, prop2 }, or +" StructName { prop1, ..Foo }, or +" StructName { #[cfg] prop1, ..Foo } +" +function! sj#argparser#rust_struct#IsValidStruct() dict + let visibility = '\%(pub\%((crate)\)\=\s*\)\=' + + for entry in self.args + if entry.argument !~ '^'.visibility.'\k\+$' && + \ entry.argument !~ '^'.visibility.'\k\+:' && + \ entry.argument !~ '^'.visibility.'\.\.\k' + return 0 + endif + endfor + + return 1 +endfunction + +" Expects self.Process() to have been run +" +" Possibilities: +" StructName { key: value, other_key: expression() } +" +function! sj#argparser#rust_struct#IsOnlyStructPairs() dict + let visibility = '\%(pub\%((crate)\)\=\s*\)\=' + + for entry in self.args + if len(entry.attributes) > 0 + " then it's not just pairs, there's also an attribute + return 0 + endif + + if entry.argument !~ '^'.visibility.'\k\+:' + return 0 + endif + endfor + + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/argparser/sh.vim b/bundle/splitjoin.vim/autoload/sj/argparser/sh.vim new file mode 100644 index 000000000..0855cef70 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/argparser/sh.vim @@ -0,0 +1,27 @@ +function! sj#argparser#sh#Construct(start_index, end_index, line) + let parser = sj#argparser#common#Construct(a:start_index, a:end_index, a:line) + + call extend(parser, { + \ 'Process': function('sj#argparser#sh#Process'), + \ }) + + return parser +endfunction + +function! sj#argparser#sh#Process() dict + while !self.Finished() + if self.body[0] == ';' + call self.PushArg() + call self.Next() + continue + elseif self.body[0] =~ "[\"'\[(/]" + call self.JumpPair("\"'[(/", "\"'])/") + endif + + call self.PushChar() + endwhile + + if len(self.current_arg) > 0 + call self.PushArg() + endif +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/c.vim b/bundle/splitjoin.vim/autoload/sj/c.vim new file mode 100644 index 000000000..398e48450 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/c.vim @@ -0,0 +1,68 @@ +" Only real syntax that's interesting is cParen and cConditional +let s:skip = sj#SkipSyntax(['cComment', 'cCommentL', 'cString', 'cCppString', 'cBlock']) + +function! sj#c#SplitFuncall() + if sj#SearchUnderCursor('(.\{-})', '', s:skip) <= 0 + return 0 + endif + + call sj#PushCursor() + + normal! l + let start = col('.') + normal! h%h + let end = col('.') + + let items = sj#ParseJsonObjectBody(start, end) + + let body = "(" + if sj#settings#Read('c_argument_split_first_newline') + let body = "(\n" + endif + + let body .= join(items, ",\n") + + if sj#settings#Read('c_argument_split_last_newline') + let body .= "\n)" + else + let body .= ")" + endif + + call sj#PopCursor() + + call sj#ReplaceMotion('va(', body) + return 1 +endfunction + +function! sj#c#SplitIfClause() + if sj#SearchUnderCursor('if\s*(.\{-})', '', s:skip) <= 0 + return 0 + endif + + let items = sj#TrimList(split(getline('.'), '\ze\(&&\|||\)')) + let body = join(items, "\n") + + call sj#ReplaceMotion('V', body) + return 1 +endfunction + +function! sj#c#JoinFuncall() + if sj#SearchUnderCursor('([^)]*\s*$', '', s:skip) <= 0 + return 0 + endif + + normal! va(J + return 1 +endfunction + +function! sj#c#JoinIfClause() + if sj#SearchUnderCursor('if\s*([^)]*\s*$', '', s:skip) <= 0 + return 0 + endif + + call sj#PushCursor() + normal! f( + normal! va(J + call sj#PopCursor() + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/clojure.vim b/bundle/splitjoin.vim/autoload/sj/clojure.vim new file mode 100644 index 000000000..3c02ddd13 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/clojure.vim @@ -0,0 +1,49 @@ +let s:skip_syntax = sj#SkipSyntax(['String', 'Comment']) + +function! sj#clojure#SplitList() + if sj#SearchSkip('[([{]', s:skip_syntax, 'bW', line('.'), ) <= 0 + return 0 + endif + + call sj#PushCursor() + let start_col = col('.') + let bracket = getline('.')[start_col - 1] + + if bracket == '(' + let closing_bracket = ')' + elseif bracket == '[' + let closing_bracket = '\]' + elseif bracket == '{' + let closing_bracket = '}' + else + throw "Unknown bracket: " . bracket + endif + + if searchpair(bracket, '', closing_bracket, 'W', s:skip_syntax, line('.')) <= 0 + call sj#PopCursor() + return 0 + endif + + let end_col = col('.') + + let parser = sj#argparser#clojure#Construct(start_col + 1, end_col - 1, getline('.')) + call parser.Process() + if len(parser.args) <= 0 + call sj#PopCursor() + return 0 + endif + + call sj#PopCursor() + call sj#ReplaceMotion('vi' . bracket, join(parser.args, "\n")) + return 1 +endfunction + +function! sj#clojure#JoinList() + if sj#SearchSkip('[([{]', s:skip_syntax, 'bW') <= 0 + return 0 + endif + + let bracket = getline('.')[col('.') - 1] + exe 'normal! va'.bracket.'J' + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/coffee.vim b/bundle/splitjoin.vim/autoload/sj/coffee.vim new file mode 100644 index 000000000..a6eb102a4 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/coffee.vim @@ -0,0 +1,298 @@ +function! sj#coffee#SplitFunction() + let lineno = line('.') + let line = getline('.') + let indent = indent(lineno) + + if line !~ '[-=]>' + return 0 + else + call sj#Keeppatterns('s/\([-=]\)>\s*/\1>\r/') + call sj#SetIndent(lineno, indent) + call sj#SetIndent(lineno + 1, indent + &sw) + return 1 + endif +endfunction + +function! sj#coffee#JoinFunction() + let line = getline('.') + + if line !~ '[-=]>' + return 0 + else + call sj#Keeppatterns('s/\([-=]\)>\_s\+/\1> /') + return 1 + endif +endfunction + +function! sj#coffee#SplitIfClause() + let line = getline('.') + let base_indent = indent('.') + let suffix_pattern = '\v(.*\S.*) (if|unless|while|until|for) (.*)' + let postfix_pattern = '\v(if|unless|while|until) (.*) then (.*)' + + if line =~ suffix_pattern + call sj#ReplaceMotion('V', substitute(line, suffix_pattern, '\2 \3\n\1', '')) + call sj#SetIndent(line('.'), base_indent) + call sj#SetIndent(line('.') + 1, base_indent + &sw) + return 1 + elseif line =~ postfix_pattern + call sj#ReplaceMotion('V', substitute(line, postfix_pattern, '\1 \2\n\3', '')) + call sj#SetIndent(line('.'), base_indent) + call sj#SetIndent(line('.') + 1, base_indent + &sw) + return 1 + else + return 0 + endif +endfunction + +function! sj#coffee#JoinIfElseClause() + let if_lineno = line('.') + let else_lineno = line('.') + 2 + let if_line = getline(if_lineno) + let else_line = getline(else_lineno) + let base_indent = indent('.') + let if_pattern = '\v^\s*(if|unless|while|until|for)\s' + let else_pattern = '\v^\s*else$' + + if if_line !~ if_pattern || else_line !~ else_pattern + return 0 + endif + + if indent(if_lineno) != indent(else_lineno) + return 0 + endif + + let if_clause = sj#Trim(if_line) + let true_body = sj#Trim(getline(line('.') + 1)) + let else_clause = sj#Trim(else_line) + let false_body = sj#Trim(getline(line('.') + 3)) + + let assignment_pattern = '^\(\w\+\)\s*=\s*' + if true_body =~ assignment_pattern && false_body =~ assignment_pattern + " it might start with the assignment of a single variable, let's see + let match_index = matchend(true_body, assignment_pattern) + if strpart(true_body, 0, match_index) == strpart(false_body, 0, match_index) + " assignment, change the components a bit + let if_clause = strpart(true_body, 0, match_index).if_clause + let true_body = strpart(true_body, match_index) + let false_body = strpart(false_body, match_index) + endif + endif + + call sj#ReplaceMotion('Vjjj', if_clause.' then '.true_body.' else '.false_body) + call sj#SetIndent(line('.'), base_indent) + + return 1 +endfunction + +function! sj#coffee#JoinIfClause() + let line = getline('.') + let base_indent = indent('.') + let pattern = '\v^\s*(if|unless|while|until|for)\ze\s' + + if line !~ pattern + return 0 + endif + + let if_clause = sj#Trim(getline('.')) + let body = sj#Trim(getline(line('.') + 1)) + + if sj#settings#Read('coffee_suffix_if_clause') + call sj#ReplaceMotion('Vj', body.' '.if_clause) + else + call sj#ReplaceMotion('Vj', if_clause.' then '.body) + endif + call sj#SetIndent(line('.'), base_indent) + + return 1 +endfunction + +function! sj#coffee#SplitTernaryClause() + let lineno = line('.') + let indent = indent('.') + let line = getline('.') + let pattern = '\v^(\s*)(.*)if (.*) then (.*) else ([^)]*)(.*)$' + + if line =~ pattern + let body_when_true = sj#ExtractRx(line, pattern, '\4') + let body_when_false = sj#ExtractRx(line, pattern, '\5') + let replacement = "if \\3\r\\2".body_when_true."\\6\relse\r\\2".body_when_false."\\6" + exe 's/'.pattern.'/'.escape(replacement, '/') + + call sj#SetIndent(lineno, lineno + 3, indent) + normal! >>kk>> + + return 1 + else + return 0 + endif +endfunction + +function! sj#coffee#SplitObjectLiteral() + let [from, to] = sj#LocateBracesOnLine('{', '}') + let bracket = '{' + + if from < 0 && to < 0 + let [from, to] = sj#LocateBracesOnLine('(', ')') + let bracket = '(' + endif + + if from < 0 && to < 0 + return 0 + endif + + let lineno = line('.') + let indent = indent(lineno) + let pairs = sj#ParseJsonObjectBody(from + 1, to - 1) + + " Some are arguments, some are real pairs + let arguments = [] + while len(pairs) > 0 + let item = pairs[0] + + if item =~ '^\k\+:' + " we've reached the pairs, stop here + break + else + call add(arguments, remove(pairs, 0)) + endif + endwhile + + if len(pairs) == 0 + " nothing to split + return 0 + endif + + if len(arguments) > 0 + let argument_list = ' '.join(arguments, ', ').', ' + else + let argument_list = '' + endif + + let body = argument_list."\n".join(pairs, "\n") + call sj#ReplaceMotion('Va'.bracket, body) + + " clean the remaining whitespace + call sj#Keeppatterns('s/\s\+$//e') + + call sj#SetIndent(lineno + 1, lineno + len(pairs), indent + &sw) + + if sj#settings#Read('align') + let body_start = lineno + 1 + let body_end = body_start + len(pairs) - 1 + call sj#Align(body_start, body_end, 'json_object') + endif + + return 1 +endfunction + +function! sj#coffee#JoinObjectLiteral() + if line('.') == line('$') + return 0 + endif + + let [start_line, end_line] = s:IndentedLinesBelow('.') + + if start_line == -1 + return 0 + endif + + let lines = sj#GetLines(start_line, end_line) + let lines = sj#TrimList(lines) + let lines = map(lines, 'sj#Trim(v:val)') + if sj#settings#Read('normalize_whitespace') + let lines = map(lines, 'substitute(v:val, ":\\s\\+", ": ", "")') + endif + let body = getline('.').' { '.join(lines, ', ').' }' + call sj#ReplaceLines(start_line - 1, end_line, body) + + return 1 +endfunction + +function! sj#coffee#SplitTripleString() + if search('["'']\{3}.\{-}["'']\{3}\s*$', 'Wbc', line('.')) <= 0 + return 0 + endif + + let start_col = col('.') + let quote = getline('.')[col('.') - 1] + let double_quote = repeat(quote, 2) + let triple_quote = repeat(quote, 3) + + normal! lll + if search(double_quote.'\zs'.quote, 'W', line('.')) <= 0 + return 0 + endif + let end_col = col('.') + + let body = sj#GetCols(start_col, end_col) + let new_body = substitute(body, '^'.triple_quote.'\(.*\)'.triple_quote.'$', '\1', '') + let new_body = triple_quote."\n".new_body."\n".triple_quote + + call sj#ReplaceCols(start_col, end_col, new_body) + normal! j>>j<< + + return 1 +endfunction + +function! sj#coffee#SplitString() + if search('["''].\{-}["'']\s*$', 'Wbc', line('.')) <= 0 + return 0 + endif + + let quote = getline('.')[col('.') - 1] + let multi_quote = repeat(quote, 2) " Note: only two quotes + + let body = sj#GetMotion('vi'.quote) + let new_body = substitute(body, '\\'.quote, quote, 'g') + let new_body = multi_quote."\n".new_body."\n".multi_quote + + call sj#ReplaceMotion('vi'.quote, new_body) + normal! j>> + + return 1 +endfunction + +function! sj#coffee#JoinString() + if search('\%("""\|''''''\)\s*$', 'Wbc') <= 0 + return 0 + endif + let start = getpos('.') + let multi_quote = expand('') + let quote = multi_quote[0] + + normal! j + + if search(multi_quote, 'Wce') <= 0 + return 0 + endif + let end = getpos('.') + + let body = sj#GetByPosition(start, end) + let new_body = substitute(body, '^'.multi_quote.'\_s*\(.*\)\_s*'.multi_quote.'$', '\1', 'g') + let new_body = substitute(new_body, quote, '\\'.quote, 'g') + let new_body = sj#Trim(new_body) + let new_body = quote.new_body.quote + + call sj#ReplaceByPosition(start, end, new_body) + + return 1 +endfunction + +function! s:IndentedLinesBelow(line) + let current_line = line(a:line) + let first_line = nextnonblank(current_line + 1) + let next_line = first_line + let base_indent = indent(current_line) + + if indent(first_line) <= base_indent + return [-1, -1] + endif + + while next_line <= line('$') && indent(next_line) > base_indent + let current_line = next_line + let next_line = nextnonblank(current_line + 1) + endwhile + + return [first_line, current_line] +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/css.vim b/bundle/splitjoin.vim/autoload/sj/css.vim new file mode 100644 index 000000000..e355f1812 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/css.vim @@ -0,0 +1,122 @@ +function! sj#css#SplitDefinition() + if !s:LocateDefinition() + return 0 + endif + + if getline('.') !~ '{.*}' " then there's nothing to split + return 0 + endif + + if getline('.')[col('.') - 1 : col('.')] == '{}' + " nothing in the body + let body = '' + else + let body = sj#GetMotion('vi{') + endif + + let lines = split(body, ";\s*") + let lines = sj#TrimList(lines) + let lines = filter(lines, '!sj#BlankString(v:val)') + + let body = join(lines, ";\n") + if !sj#BlankString(body) + let body .= ";" + endif + + call sj#ReplaceMotion('va{', "{\n".body."\n}") + + if sj#settings#Read('align') + let alignment_start = line('.') + 1 + let alignment_end = alignment_start + len(lines) - 1 + call sj#Align(alignment_start, alignment_end, 'css_declaration') + endif + + return 1 +endfunction + +function! sj#css#JoinDefinition() + if !s:LocateDefinition() + return 0 + endif + + if getline('.') =~ '{.*}' " then there's nothing to join + return 0 + endif + + normal! 0 + call search('{', 'Wc', line('.')) + + if getline(line('.') + 1) =~ '^}' + " nothing in the body + let body = '' + else + let body = sj#GetMotion('Vi{') + endif + + let lines = split(body, ";\\?\s*\n") + let lines = sj#TrimList(lines) + let lines = filter(lines, 'v:val !~ "^\s*$"') + if sj#settings#Read('normalize_whitespace') + let lines = map(lines, "substitute(v:val, '\\s*:\\s\\+', ': ', '')") + endif + + let body = join(lines, "; ") + let body = substitute(body, '\S.*\zs;\?$', ';', '') + let body = substitute(body, '{;', '{', '') " for SCSS + + if body == '' + call sj#ReplaceMotion('va{', '{}') + else + call sj#ReplaceMotion('va{', '{ '.body.' }') + endif + + return 1 +endfunction + +function! sj#css#JoinMultilineSelector() + let line = getline('.') + + let start_line = line('.') + let end_line = start_line + let col = col('.') + let limit_line = line('$') + + while !sj#BlankString(line) && line !~ '{\s*$' && end_line < limit_line + call cursor(end_line + 1, col) + let end_line = line('.') + let line = getline('.') + endwhile + + if start_line == end_line + return 0 + else + if line =~ '^\s*{\s*$' + let end_line = end_line - 1 + endif + + exe start_line.','.end_line.'join' + return 1 + endif +endfunction + +function! sj#css#SplitMultilineSelector() + if getline('.') !~ '.*,.*{\s*$' + " then there is nothing to split + return 0 + endif + + let definition = getline('.') + let replacement = substitute(definition, ',\s*', ",\n", 'g') + + call sj#ReplaceMotion('V', replacement) + + return 1 +endfunction + +function! s:LocateDefinition() + if search('{', 'bcW', line('.')) <= 0 && search('{', 'cW', line('.')) <= 0 + return 0 + else + return 1 + endif +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/cue.vim b/bundle/splitjoin.vim/autoload/sj/cue.vim new file mode 100644 index 000000000..dd6984152 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/cue.vim @@ -0,0 +1,143 @@ +function! sj#cue#SplitImports() + let pattern = '^import\s\+\(\%(\k\+\s\+\)\=\%(".*"\)\)$' + + if getline('.') =~ pattern + call sj#Keeppatterns('s/' . pattern . '/import (\r\1\r)/') + normal! k== + return 1 + else + return 0 + endif +endfunction + +function! sj#cue#SplitStructLiteral() + let [from, to] = sj#LocateBracesOnLine('{', '}') + + if from < 0 && to < 0 + return 0 + endif + + let pairs = sj#ParseJsonObjectBody(from + 1, to - 1) + let body = join(pairs, "\n") + let body = "{\n".body."\n}" + call sj#ReplaceMotion('Va{', body) + + if sj#settings#Read('align') + let body_start = line('.') + 1 + let body_end = body_start + len(pairs) - 1 + call sj#Align(body_start, body_end, 'json_object') + endif + + return 1 +endfunction + +function! sj#cue#JoinStructLiteral() + let line = getline('.') + + if line =~ '{\s*$' + call search('{', 'c', line('.')) + let body = sj#GetMotion('Vi{') + + let lines = split(body, "\n") + let lines = sj#TrimList(lines) + if sj#settings#Read('normalize_whitespace') + let lines = map(lines, 'substitute(v:val, ":\\s\\+", ": ", "")') + endif + + let body = join(lines, ', ') + let body = substitute(body, ',$', '', '') + + if sj#settings#Read('curly_brace_padding') + let body = '{ '.body.' }' + else + let body = '{'.body.'}' + endif + + call sj#ReplaceMotion('Va{', body) + + return 1 + else + return 0 + endif +endfunction + +function! sj#cue#SplitArray() + return s:SplitList(['[', ']'], 'cursor_on_line') +endfunction + +function! sj#cue#JoinArray() + return s:JoinList(['[', ']'], 'padding') +endfunction + +function! sj#cue#SplitArgs() + return s:SplitList(['(', ')'], 'cursor_inside') +endfunction + +function! sj#cue#JoinArgs() + return s:JoinList(['(', ')'], 'no_padding') +endfunction + +function! s:SplitList(delimiter, cursor_position) + let start = a:delimiter[0] + let end = a:delimiter[1] + + let lineno = line('.') + let indent = indent('.') + + if a:cursor_position == 'cursor_inside' + let [from, to] = sj#LocateBracesAroundCursor(start, end) + elseif a:cursor_position == 'cursor_on_line' + let [from, to] = sj#LocateBracesOnLine(start, end) + else + echoerr "Invalid value for a:cursor_position: ".a:cursor_position + return + endif + + if from < 0 && to < 0 + return 0 + endif + + let items = sj#ParseJsonObjectBody(from + 1, to - 1) + if empty(items) + return 0 + endif + + let body = start."\n".join(items, ",\n")."\n".end + + call sj#ReplaceMotion('Va'.start, body) + + let end_line = lineno + len(items) + 1 + call sj#SetIndent(end_line, indent) + + return 1 +endfunction + +function! s:JoinList(delimiter, delimiter_padding) + let start = a:delimiter[0] + let end = a:delimiter[1] + + let line = getline('.') + + if line !~ start . '\s*$' + return 0 + endif + + call search(start, 'c', line('.')) + let body = sj#GetMotion('Vi'.start) + + let lines = split(body, "\n") + let lines = sj#TrimList(lines) + let body = sj#Trim(join(lines, ' ')) + let body = substitute(body, ',\s*$', '', '') + + if a:delimiter_padding == 'padding' + let body = start.' '.body.' '.end + else + let body = start.body.end + endif + + call sj#ReplaceMotion('Va'.start, body) + + return 1 +endfunction + diff --git a/bundle/splitjoin.vim/autoload/sj/elixir.vim b/bundle/splitjoin.vim/autoload/sj/elixir.vim new file mode 100644 index 000000000..d2cb337cd --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/elixir.vim @@ -0,0 +1,288 @@ +function! sj#elixir#SplitDoBlock() + let [function_name, function_start, function_end, function_type] = + \ sj#argparser#elixir#LocateFunction() + if function_start < 0 + return 0 + endif + + let is_if = function_name == 'if' || function_name == 'unless' + + let parser = sj#argparser#elixir#Construct(function_start, function_end, getline('.')) + call parser.Process() + + let do_body = '' + let else_body = '' + let args = [] + + for arg in parser.args + if arg =~ '^do:' && do_body == '' + let do_body = substitute(arg, '^do:\s*', '', '') + elseif arg =~ '^else:' && is_if && else_body == '' + let else_body = substitute(arg, '^else:\s*', '', '') + else + call add(args, arg) + endif + endfor + + if do_body == '' + return 0 + endif + + let line = getline('.') + + if is_if && function_type == 'with_round_braces' + " skip the round brackets before the if-clause + let new_line = strpart(line, 0, function_start - 2) . ' ' . join(args, ', ') + else + let new_line = strpart(line, 0, function_start - 1) . join(args, ', ') + endif + + if function_end > 0 + if is_if && function_type == 'with_round_braces' + " skip the round brackets after the if-clause + let new_line .= strpart(line, function_end + 1) + else + let new_line .= strpart(line, function_end) + end + else + " we didn't detect an end, so it goes on to the end of the line + endif + + if else_body != '' + let do_block = " do\n" . do_body . "\nelse\n" . else_body . "\nend" + else + let do_block = " do\n" . do_body . "\nend" + endif + + call sj#ReplaceLines(line('.'), line('.'), new_line . do_block) + return 1 +endfunction + +function! sj#elixir#JoinDoBlock() + let do_pattern = '\s*do\s*\%(#.*\)\=$' + let def_lineno = line('.') + let def_line = getline(def_lineno) + + if def_line !~ do_pattern + return 0 + endif + + let [function_name, function_start, function_end, function_type] = + \ sj#argparser#elixir#LocateFunction() + if function_start < 0 + return 0 + endif + + let is_if = function_name == 'if' || function_name == 'unless' + let body_lineno = line('.') + 1 + let body_line = getline(body_lineno) + + if is_if && getline(line('.') + 2) =~ '^\s*else\>' + let else_lineno = line('.') + 2 + let else_line = getline(else_lineno) + + let else_body_lineno = line('.') + 3 + let else_body_line = getline(else_body_lineno) + + let end_lineno = line('.') + 4 + let end_line = getline(end_lineno) + else + let else_line = '' + + let end_lineno = line('.') + 2 + let end_line = getline(end_lineno) + endif + + if end_line !~ '^\s*end$' + return 0 + endif + + exe 'keeppatterns s/'.do_pattern.'//' + if function_end < 0 + let function_end = col('$') - 1 + endif + + let args = sj#GetCols(function_start, function_end) + let joined_args = ', do: '.sj#Trim(body_line) + if else_line != '' + let joined_args .= ', else: '.sj#Trim(else_body_line) + endif + + call sj#ReplaceCols(function_start, function_end, args . joined_args) + exe body_lineno.','.end_lineno.'delete _' + return 1 +endfunction + +function! sj#elixir#JoinCommaDelimitedItems() + if getline('.') !~ ',\s*$' + return 0 + endif + + let start_lineno = line('.') + let end_lineno = start_lineno + let lineno = nextnonblank(start_lineno + 1) + let line = getline(lineno) + + while lineno <= line('$') && line =~ ',\s*$' + let end_lineno = lineno + let lineno = nextnonblank(lineno + 1) + let line = getline(lineno) + endwhile + + let end_lineno = lineno + + call cursor(start_lineno, 0) + exe "normal! V".(end_lineno - start_lineno)."jJ" + return 1 +endfunction + +function! sj#elixir#SplitArray() + let [from, to] = sj#LocateBracesAroundCursor('[', ']', [ + \ 'elixirInterpolationDelimiter', + \ 'elixirString', + \ 'elixirStringDelimiter', + \ 'elixirSigilDelimiter', + \ ]) + + if from < 0 + return 0 + endif + + let items = sj#ParseJsonObjectBody(from + 1, to - 1) + + if len(items) == 0 || to - from < 2 + return 1 + endif + + " substitute [1, 2, | tail] + let items[-1] = substitute(items[-1], "\\(|[^>].*\\)", "\n\\1", "") + let body = "[\n" . join(items, ",\n") . "\n]" + + call sj#ReplaceMotion('Va[', body) + return 1 +endfunction + +function! sj#elixir#JoinArray() + normal! $ + + if getline('.')[col('.') - 1] != '[' + return 0 + endif + + let body = sj#Trim(sj#GetMotion('Vi[')) + " remove trailing comma + let body = substitute(body, ',\ze\_s*$', '', '') + + let items = split(body, ",\s*\n") + if len(items) == 0 + return 0 + endif + + " join isolated | tail on the last line + let items[-1] = substitute(items[-1], "[[:space:]]*\\(|[^>].*\\)", " \\1", "") + + let body = join(sj#TrimList(items), ', ') + call sj#ReplaceMotion('Va[', '['.body.']') + return 1 +endfunction + +let s:pipe_pattern = '^\s*|>\s\+' +let s:atom_pattern = ':\k\+' +let s:module_pattern = '\k\%(\k\|\.\)*' +let s:function_pattern = '\k\+[?!]\=' +let s:atom_or_module_pattern = '\%(' . s:atom_pattern . '\.\|' . s:module_pattern . '\.\)\=' + +function! sj#elixir#SplitPipe() + let line = getline('.') + + call sj#PushCursor() + normal! 0f( + let [function_name, function_start, function_end, function_type] = + \ sj#argparser#elixir#LocateFunction() + call sj#PopCursor() + + " We only support function calls that start at the beginning of the line + " (accounting for whitespace) + let prefix = strpart(line, 0, function_start - 1) + let prefix_pattern = '^\s*' . s:atom_or_module_pattern . function_name . '\((\|\s\+\)$' + + if function_start < 0 || prefix !~ prefix_pattern + return 0 + endif + + let comment_pattern = '\s*\(#.*\)\=$' + + if function_end < 0 + let comment_start = match(line, comment_pattern) + + if comment_start < 0 + let rest = 'none' + else + let rest = strpart(line, comment_start) + let function_end = comment_start + endif + else + let rest = strpart(line, function_end + 1) + + if rest !~ '^' . comment_pattern + return 0 + endif + end + + let parser = sj#argparser#elixir#Construct(function_start, function_end, line) + call parser.Process() + + let args = parser.args + + let function_call = sj#Trim(strpart(line, 0, function_start - 2)) + let result = args[0] . "\n|> " . function_call . '(' . join(args[1:], ', ') . ')' . rest + + call sj#ReplaceLines(line('.'), line('.'), result) + + return 1 +endfunction + +function! sj#elixir#JoinPipe() + call sj#PushCursor() + + let line = getline('.') + if line !~ s:pipe_pattern + normal! j + let line = getline('.') + endif + + let line_num = line('.') + let prev_line = sj#Trim(getline(line_num - 1)) + + if line !~ s:pipe_pattern || prev_line =~ s:pipe_pattern + call sj#PopCursor() + return 0 + endif + + let empty_args_pattern = s:pipe_pattern . '\(' . s:atom_or_module_pattern . s:function_pattern . '\)()' + + if line =~ empty_args_pattern + let function_name = substitute(line, empty_args_pattern, '\1', '') + let result = function_name . '(' . prev_line . ')' + call sj#PopCursor() + else + normal! f(l + let [function_name, function_start, function_end, function_type] = + \ sj#argparser#elixir#LocateFunction() + call sj#PopCursor() + + if function_start < 0 + return 0 + endif + + let parser = sj#argparser#elixir#Construct(function_start, function_end, line) + call parser.Process() + + let args = parser.args + let function_call = substitute(strpart(line, 0, function_start - 2), '|>\s\+', '', '') + let result = function_call . '(' . prev_line . ', ' . join(args, ', ') . ')' + endif + + call sj#ReplaceLines(line_num - 1, line_num, result) + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/elm.vim b/bundle/splitjoin.vim/autoload/sj/elm.vim new file mode 100644 index 000000000..8139cfe2f --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/elm.vim @@ -0,0 +1,249 @@ +" vim: foldmethod=marker + +" Split / Join {{{1 +" +" function! sj#elm#JoinBraces() {{{2 +" +" Attempts to join the multiline tuple, record or list closest +" to current cursor position. +function! sj#elm#JoinBraces() + let [from, to] = s:ClosestMultilineBraces() + + if from[0] < 0 + return 0 + endif + + let original = sj#GetByPosition([0, from[0], from[1]], [0, to[0], to[1]]) + let joined = s:JoinOuterBraces(s:JoinLines(original)) + call sj#ReplaceByPosition([0, from[0], from[1]], [0, to[0], to[1]], joined) + + return 1 +endfunction + +" function! sj#elm#SplitBraces() {{{2 +" +" Attempts to split the tuple, record or list furthest to current +" cursor position on the same line. +function! sj#elm#SplitBraces() + try + call sj#PushCursor() + + let [from, to] = s:CurrentLineOutermostBraces(col('.')) + + if from < 0 + return 0 + endif + + let parts = s:SplitParts(from, to) + + if len(parts) <= 2 + return 0 + endif + + let replacement = join(parts, "\n") + + call cursor(line('.'), from) + let [previousLine, previousCol] = searchpos('\S', 'bn') + if previousLine == line('.') && previousCol > 0 && s:CharAt(previousCol) =~ '[=:]' + let replacement = "\n".replacement + let from = previousCol + 1 + end + + call sj#ReplaceCols(from, to, replacement) + + return 1 + finally + call sj#PopCursor() + endtry +endfunction + + +" Helper functions {{{1 + +" function! s:SkipSyntax() {{{2 +" +" Returns Elm's typical skippable syntax (strings and comments) +function! s:SkipSyntax() + return sj#SkipSyntax(['elmString', 'elmTripleString', 'elmComment']) +endfunction + +" function! s:CurrentLineClosestBraces(column) {{{2 +" +" Returns the columns corresponding to the bracing pair closest +" to and surrounding given column as a list ([opening, closing]). +" Returns [-1, -1] when there is none on the same line. +function! s:CurrentLineClosestBraces(column) + try + call sj#PushCursor() + + let skip = s:SkipSyntax() + let currentLine = line('.') + + call cursor(currentLine, a:column) + let from = searchpairpos('[{(\[]', '', '[})\]]', 'bcn', skip, currentLine) + + if from[0] == 0 + return [-1, -1] + end + + call cursor(from[0], from[1]) + normal! % + + if line('.') != currentLine + return [-1, -1] + endif + + let to = col('.') + + return [from[1], to] + finally + call sj#PopCursor() + endtry +endfunction + +" function! s:CurrentLineOutermostBraces(column) {{{2 +" +" Returns the colums corresponding to the bracing pair furthest +" to and surrounding given column as a list ([opening, closing]). +" Returns [-1, -1] when there is none on the same line. +function! s:CurrentLineOutermostBraces(column) + if a:column < 1 + return [-1, -1] + endif + + let currentMatch = s:CurrentLineClosestBraces(a:column) + + if currentMatch[0] < 1 + return [-1, -1] + endif + + let betterMatch = s:CurrentLineOutermostBraces(currentMatch[0] - 1) + + if betterMatch[0] < 1 + return currentMatch + endif + + return betterMatch +endfunction + +" function! s:ClosestMultilineBraces() {{{2 +" +" Returns the position of the closest pair of multiline braces +" around the cursor. +" +" The positions are given as a couple of arrays: +" +" [[startLine, startCol], [endLine, endCol]] +" +" When no position is found, returns: +" +" [[-1, -1], [-1, -1]] +function! s:ClosestMultilineBraces() + try + call sj#PushCursor() + + let skip = s:SkipSyntax() + + let currentLine = line('.') + + normal! $ + + let [toLine, toCol] = searchpairpos('[{(\[]', '', '[})\]]', '', skip, line('$')) + + if toLine < currentLine + return [[-1, -1], [-1, -1]] + endif + + normal! % + + let fromLine = line('.') + let fromCol = col('.') + + if fromLine >= toLine + return [[-1, -1], [-1, -1]] + endif + + return [[fromLine, fromCol], [toLine, toCol]] + finally + call sj#PopCursor() + endtry +endfunction + +" function! s:JoinOuterBraces(text) +" +" Removes spaces separating the first and last char of a string +" with the closest non-space char found. +" +" ex: +" +" [ 1, 2, 3 ] => [1, 2, 3] +function! s:JoinOuterBraces(text) + return substitute(substitute(a:text, '\s*\(.\)$', '\1', ''), '^\(.\)\s*', '\1', '') +endfunction + +" function! s:JoinLines(text) +" +" Joins lines in text, removing complementary spaces around +" newline chars when the last line starts with a ',' and keeping +" one whitespace for other cases. +function! s:JoinLines(text) + return substitute(substitute(a:text, '\s*\n\s*,', ',', 'g'), '\s*\n\s*', ' ', 'g') +endfunction + +" function! s:SplitParts(from, to) +" +" Splits a section of a line according to bracing characters +" rules. +" +" ex: +" "[1,2,3]" +" v +" [ "[ 1", ", 2", ", 3", "]" ] +" +" "{a | foo = bar, baz = buzz}" +" v +" [ "{ a", "| foo = bar", ", baz = buzz", "}" ] +function! s:SplitParts(from, to) + try + call sj#PushCursor() + let skip = s:SkipSyntax() + let currentLine = line('.') + + call cursor(currentLine, a:from) + + let openingCol = a:from + let openingChar = s:CurrentChar() + let parts = [] + + while col('.') < a:to + call searchpair('[{(\[]', ',\|\(\(<\)\@\)\@!\)', '[})\]]', '', skip, currentLine) + let closingCol = col('.') + let closingChar = s:CurrentChar() + let part = openingChar.' '.sj#Trim(sj#GetByPosition([0, currentLine, openingCol + 1, 0], [0, currentLine, closingCol - 1, 0])) + call add(parts, part) + let openingCol = closingCol + let openingChar = closingChar + call cursor(currentLine, openingCol) + endwhile + + call add(parts, s:CharAt(a:to)) + + return parts + finally + call sj#PopCursor() + endtry +endfunction + +" function! s:CharAt(column) +" +" Returns the char sitting at given column of current line. +function! s:CharAt(column) + return getline('.')[a:column - 1] +endfunction + +" function! s:CurrentChar() +" +" Returns the character at current cursor's position +function! s:CurrentChar() + return s:CharAt(col('.')) +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/eruby.vim b/bundle/splitjoin.vim/autoload/sj/eruby.vim new file mode 100644 index 000000000..0891a17df --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/eruby.vim @@ -0,0 +1,55 @@ +function! sj#eruby#SplitIfClause() + let line = getline('.') + let pattern = '\v\<\%(.*\S.*) (if|unless) (.*)\s*\%\>' + + if line =~ pattern + let body = substitute(line, pattern, '<% \2 \3%>\n<%\1 %>\n<% end %>', '') + call sj#ReplaceMotion('V', body) + + return 1 + end + + return 0 +endfunction + +function! sj#eruby#JoinIfClause() + let line = getline('.') + let pattern = '\v^\s*\<\%\s*(if|unless)' + + if line =~ pattern + normal! jj + + if getline('.') =~ 'end' + let body = sj#GetMotion('Vkk') + + let [if_line, body, end_line] = split(body, '\n') + + let if_line = sj#ExtractRx(if_line, '<%\s*\(.\{-}\)\s*%>', '\1') + let body = sj#ExtractRx(body, '\(<%=\?\s*.\{-}\)\s*%>', '\1') + + let replacement = body.' '.if_line.' %>' + + call sj#ReplaceMotion('gv', replacement) + + return 1 + endif + endif + + return 0 +endfunction + +function! sj#eruby#SplitHtmlTags() + if eval(sj#SkipSyntax(['erubyDelimiter', 'erubyExpression'])) + return 0 + endif + + return sj#html#SplitTags() +endfunction + +function! sj#eruby#SplitHtmlAttributes() + if eval(sj#SkipSyntax(['erubyDelimiter', 'erubyExpression'])) + return 0 + endif + + return sj#html#SplitAttributes() +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/go.vim b/bundle/splitjoin.vim/autoload/sj/go.vim new file mode 100644 index 000000000..28a6509aa --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/go.vim @@ -0,0 +1,405 @@ +function! sj#go#SplitImports() + let pattern = '^import\s\+\(\%(\k\+\s\+\)\=\%(".*"\)\)$' + + if getline('.') =~ pattern + call sj#Keeppatterns('s/' . pattern . '/import (\r\1\r)/') + normal! k== + return 1 + else + return 0 + endif +endfunction + +function! sj#go#JoinImports() + if getline('.') =~ '^import\s*($' && + \ getline(line('.') + 1) =~ '^\s*\%(\k\+\s\+\)\=".*"$' && + \ getline(line('.') + 2) =~ '^)$' + call sj#Keeppatterns('s/^import (\_s\+\(\%(\k\+\s\+\)\=\(".*"\)\)\_s\+)$/import \1/') + return 1 + else + return 0 + endif +endfunction + +function! sj#go#SplitVars() abort + let pattern = '^\s*\(var\|type\|const\)\s\+[[:keyword:], ]\+=\=' + if sj#SearchUnderCursor(pattern) <= 0 + return 0 + endif + + call search(pattern, 'Wce', line('.')) + let line = getline('.') + + if line[col('.') - 1] == '=' + " before and after = + let lhs = sj#Trim(strpart(line, 0, col('.') - 1)) + let rhs = sj#Ltrim(strpart(line, col('.'))) + + let values_parser = sj#argparser#go_vars#Construct(rhs) + call values_parser.Process() + + let values = values_parser.args + let comment = values_parser.comment + else + let [comment, comment_start, _] = matchstrpos(line, '\s*\%(\/\/.*\)\=$') + if comment == "" + let lhs = sj#Trim(line) + else + let lhs = sj#Trim(strpart(line, 0, comment_start)) + endif + + let values = [] + endif + + if len(values) > 0 && values[-1] =~ '[{([]\s*$' + " the value is incomplete, so let's not attempt to split it + return 0 + endif + + let [prefix, _, prefix_end] = matchstrpos(lhs, '^\s*\(var\|type\|const\)\s\+') + let lhs = strpart(lhs, prefix_end) + + let variables = [] + let last_type = '' + for variable in reverse(split(lhs, ',\s*')) + let variable = sj#Trim(variable) + let type = matchstr(variable, '^\k\+\s\+\zs\S.*$') + + if empty(type) && !empty(last_type) + " No type, take the last one that we saw going backwards + call add(variables, variable . ' ' . last_type) + else + let last_type = type + call add(variables, variable) + endif + endfor + call reverse(variables) + + let declarations = [] + for i in range(0, len(variables) - 1) + if i < len(values) + call add(declarations, variables[i] . ' = ' . values[i]) + else + call add(declarations, variables[i]) + endif + endfor + + let replacement = prefix . "(\n" + let replacement .= join(declarations, "\n") + let replacement .= "\n)" + if comment != '' + let replacement .= ' ' . sj#Ltrim(comment) + endif + + call sj#ReplaceMotion('_vg_', replacement) + return 0 +endfunction + +function! sj#go#JoinVars() abort + let pattern = '^\s*\(var\|type\|const\)\s\+(' + if sj#SearchUnderCursor(pattern) <= 0 + return 0 + endif + + call search(pattern, 'Wce', line('.')) + + let declarations = sj#TrimList(split(sj#GetMotion('vi('), "\n")) + if len(declarations) == 1 + " Only one line, so just join it as-is + call sj#ReplaceMotion('va(', declarations[0]) + return 1 + endif + + let variables = [] + let values = [] + let types = [] + + for line in declarations + let [lhs, _, match_end] = matchstrpos(line, '.\{-}\s*=\s*') + + if match_end > -1 + let variable_description = matchstr(lhs, '.\{-}\ze\s*=\s*') + let line = substitute(line, ',$', '', '') + call add(values, strpart(line, match_end)) + else + let line = substitute(line, ',$', '', '') + let variable_description = line + endif + + let [variable, _, match_end] = matchstrpos(variable_description, '^\k\+\s*') + let type = strpart(variable_description, match_end) + call add(variables, { 'variable': sj#Rtrim(variable), 'type': type }) + call add(types, type) + endfor + + if len(variables) == 0 + return 0 + endif + + if len(uniq(types)) > 1 + " We have assignment to values, but we also have different types, so it + " can't be on one line + return 0 + endif + + " Handle var one, two string -> one should also get "string" + let declarations = [] + let index = 0 + for entry in variables + if empty(entry.type) || (len(variables) > index + 1 && entry.type ==# variables[index + 1].type) + " This variable's type is the same as the next one's, so skip it + call add(declarations, entry.variable) + elseif empty(entry.type) + call add(declarations, entry.variable) + else + call add(declarations, entry.variable . ' ' . entry.type) + endif + + let index += 1 + endfor + + let combined_declaration = join(declarations, ', ') + if len(values) > 0 + let combined_declaration .= ' = ' . join(values, ', ') + endif + + call sj#ReplaceMotion('va(', combined_declaration) + return 1 +endfunction + +function! sj#go#SplitStruct() + let [start, end] = sj#LocateBracesAroundCursor('{', '}', ['goString', 'goComment']) + if start < 0 && end < 0 + return 0 + endif + + let args = sj#ParseJsonObjectBody(start + 1, end - 1) + if len(args) == 0 + return 0 + endif + + for arg in args + if arg !~ '^\k\+\s*:' + " this is not really a struct instantiation + return 0 + end + endfor + + call sj#ReplaceCols(start + 1, end - 1, "\n".join(args, ",\n").",\n") + return 1 +endfunction + +function! sj#go#JoinStructDeclaration() + let start_lineno = line('.') + let pattern = '^\s*type\s\+.*\s*\zsstruct\s*{$' + + if search(pattern, 'Wc', line('.')) <= 0 && + \ search(pattern, 'Wcb', line('.')) <= 0 + return 0 + endif + + call sj#PushCursor() + normal! f{% + let end_lineno = line('.') + + if start_lineno == end_lineno + " we haven't moved, brackets not found + call sj#PopCursor() + return 0 + endif + + let arguments = [] + for line in getbufline('%', start_lineno + 1, end_lineno - 1) + let argument = substitute(line, ',$', '', '') + let argument = sj#Trim(argument) + + if argument != '' + call add(arguments, argument) + endif + endfor + + if len(arguments) == 0 + let replacement = 'struct{}' + else + let replacement = 'struct{ ' . join(arguments, ', ') . ' }' + endif + + call sj#PopCursor() + call sj#ReplaceMotion('vf{%', replacement) + return 1 +endfunction + +function! sj#go#JoinStruct() + let start_lineno = line('.') + let pattern = '\k\+\s*{$' + + if search(pattern, 'Wc', line('.')) <= 0 && + \ search(pattern, 'Wcb', line('.')) <= 0 + return 0 + endif + + call search(pattern, 'Wce', line('.')) + + normal! % + let end_lineno = line('.') + + if start_lineno == end_lineno + " we haven't moved, brackets not found + return 0 + endif + + let arguments = [] + for line in getbufline('%', start_lineno + 1, end_lineno - 1) + let argument = substitute(line, ',$', '', '') + let argument = sj#Trim(argument) + + if argument !~ '^\k\+\s*:' + " this is not really a struct instantiation + return 0 + end + + if sj#settings#Read('normalize_whitespace') + let argument = substitute(argument, '^\k\+\zs:\s\+', ': ', 'g') + endif + + call add(arguments, argument) + endfor + + let replacement = '{' . join(arguments, ', ') . '}' + call sj#ReplaceMotion('va{', replacement) + return 1 +endfunction + +function! sj#go#SplitSingleLineCurlyBracketBlock() + let [start, end] = sj#LocateBracesAroundCursor('{', '}', ['goString', 'goComment']) + if start < 0 && end < 0 + return 0 + endif + + let body = sj#GetMotion('vi{') + + if getline('.')[0:start - 1] =~# '\ 2 + " more than one line between them, can't join + return 0 + endif + + normal! va{J + return 1 +endfunction + +function! sj#go#SplitFunc() + let pattern = '^func\%(\s*(.\{-})\s*\)\=\s\+\k\+\zs(' + if search(pattern, 'Wcn', line('.')) <= 0 && + \ search(pattern, 'Wbcn', line('.')) <= 0 + return 0 + endif + + let split_type = '' + + let [start, end] = sj#LocateBracesAroundCursor('(', ')', ['goString', 'goComment']) + if start > 0 + let split_type = 'definition_list' + else + let [start, end] = sj#LocateBracesAroundCursor('{', '}', ['goString', 'goComment']) + + if start > 0 + let split_type = 'function_body' + endif + endif + + if split_type == 'function_body' + let contents = sj#Trim(sj#GetCols(start + 1, end - 1)) + call sj#ReplaceCols(start + 1, end - 1, "\n".contents."\n") + return 1 + elseif split_type == 'definition_list' + let parsed = sj#ParseJsonObjectBody(start + 1, end - 1) + + " Keep `a, b int` variable groups on the same line + let arg_groups = [] + let typed_arg_group = '' + for elem in parsed + if match(elem, '\s\+') != -1 + let typed_arg_group .= elem + call add(arg_groups, typed_arg_group) + let typed_arg_group = '' + else + " not typed here, group it with later vars + let typed_arg_group .= elem . ', ' + endif + endfor + + call sj#ReplaceCols(start + 1, end - 1, "\n".join(arg_groups, ",\n").",\n") + return 1 + else + return 0 + endif +endfunction + +function! sj#go#SplitFuncCall() + let [start, end] = sj#LocateBracesAroundCursor('(', ')', ['goString', 'goComment']) + if start < 0 && end < 0 + return 0 + endif + + let args = sj#ParseJsonObjectBody(start + 1, end - 1) + call sj#ReplaceCols(start + 1, end - 1, "\n".join(args, ",\n").",\n") + return 1 +endfunction + +function! sj#go#JoinFuncCallOrDefinition() + let start_lineno = line('.') + + if search('($', 'Wc', line('.')) <= 0 + return 0 + endif + + if strpart(getline('.'), 0, col('.')) =~ '\(var\|type\|const\|import\)\s\+($' + " This isn't a function call, it's a multiline var/const/type declaration + return 0 + endif + + normal! % + let end_lineno = line('.') + + if start_lineno == end_lineno + " we haven't moved, brackets not found + return 0 + endif + + let arguments = [] + for line in getbufline('%', start_lineno + 1, end_lineno - 1) + let argument = substitute(line, ',$', '', '') + let argument = sj#Trim(argument) + call add(arguments, argument) + endfor + + let replacement = '(' . join(arguments, ', ') . ')' + call sj#ReplaceMotion('va(', replacement) + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/haml.vim b/bundle/splitjoin.vim/autoload/sj/haml.vim new file mode 100644 index 000000000..92cf2724c --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/haml.vim @@ -0,0 +1,78 @@ +" TODO (2013-05-09) Only works for very simple things, needs work +function! sj#haml#SplitInterpolation() + let lineno = line('.') + let line = getline('.') + let indent = indent('.') + let pattern = '^\s*%.\{-}\zs=' + + if line !~ pattern + return 0 + endif + + call sj#Keeppatterns('s/'.pattern.'/\r=/') + call s:SetIndent(lineno + 1, lineno + 1, indent + &sw) + + return 1 +endfunction + +function! sj#haml#JoinInterpolation() + if line('.') == line('$') + return 0 + endif + + let line = getline('.') + let next_line = getline(line('.') + 1) + + if !(line =~ '^\s*%\k\+\s*$' && next_line =~ '^\s*=') + return 0 + end + + call sj#Keeppatterns('s/\n\s*//') + return 1 +endfunction + +" Sets the absolute indent of the given range of lines to the given indent +function! s:SetIndent(from, to, indent) + let new_whitespace = repeat(' ', a:indent) + exe a:from.','.a:to.'s/^\s*/'.new_whitespace +endfunction + +function! sj#haml#SplitInlineInterpolation() + let lineno = line('.') + let indent = indent(lineno) + let line = getline(lineno) + let pattern = '^\(.*\)\s*#{\(.\{-}\)}\s*$' + + if line !~ pattern + return 0 + endif + + let new_line = sj#ExtractRx(line, pattern, '\1') + let code = sj#ExtractRx(line, pattern, '\2') + + call sj#ReplaceMotion('V', new_line."\n= ".code) + call sj#SetIndent(lineno + 1, lineno + 1, indent) + return 1 +endfunction + +function! sj#haml#JoinToInlineInterpolation() + if line('.') == line('$') + return 0 + endif + + let lineno = line('.') + let line = getline(lineno) + let indent = indent(lineno) + let next_indent = indent(lineno + 1) + let next_line = getline(lineno + 1) + + if indent == next_indent && next_line =~ '^\s*=' + let code = substitute(sj#Trim(next_line), '^=\s*', '', '') + let line = sj#Rtrim(line).' #{'.code.'}' + call sj#ReplaceLines(lineno, lineno, line) + + return 1 + else + return 0 + endif +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/handlebars.vim b/bundle/splitjoin.vim/autoload/sj/handlebars.vim new file mode 100644 index 000000000..77be8e6c1 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/handlebars.vim @@ -0,0 +1,105 @@ +function! sj#handlebars#SplitComponent() + if !sj#SearchUnderCursor('{{#\=\%(\k\|-\|\/\)\+ .\{-}}}') + return 0 + endif + + let body = sj#GetMotion('vi{') + let body = substitute(body, '\s\+\(\k\+=\)', '\n\1', 'g') + if !sj#settings#Read('handlebars_closing_bracket_on_same_line') + let body = substitute(body, '}$', "\n}", '') + endif + + if sj#settings#Read('handlebars_hanging_arguments') + " substitute just the first newline with a space + let body = substitute(body, '\n', ' ', '') + endif + + call sj#ReplaceMotion('vi{', body) + return 1 +endfunction + +function! sj#handlebars#JoinComponent() + if !(sj#SearchUnderCursor('{{#\=\%(\k\|-\|\/\)\+.*$') && getline('.') !~ '}}') + return 0 + endif + + normal! vi{J + call sj#Keeppatterns('s/\s\+}}/}}/ge') + return 1 +endfunction + +function! sj#handlebars#SplitBlockComponent() + let saved_iskeyword = &iskeyword + + try + set iskeyword+=-,/ + let start_pattern = '{{#\k\+\%( .\{-}\)\=}}' + + if !search(start_pattern, 'bWc', line('.')) + return 0 + endif + + call search('\k', 'W', line('.')) + let component_name = expand('') + call search(start_pattern, 'eW', line('.')) + let start_col = col('.') + 1 + + if !search('{{/'.component_name.'}}', 'W', line('.')) + return 0 + endif + let end_col = col('.') - 1 + + if end_col - start_col > 0 + let body = sj#GetCols(start_col, end_col) + call sj#ReplaceCols(start_col, end_col, "\n".sj#Trim(body)."\n") + else + " empty block component + call sj#ReplaceCols(start_col, start_col, "\n{") + endif + + return 1 + finally + let &iskeyword = saved_iskeyword + endtry +endfunction + +function! sj#handlebars#JoinBlockComponent() + let saved_iskeyword = &iskeyword + + try + set iskeyword+=-,/ + let start_pattern = '{{#\k\+\%( .\{-}\)\=}}\s*$' + + if !search(start_pattern, 'bWc', line('.')) + return 0 + endif + + call search('\k', 'W', line('.')) + let component_name = expand('') + let start_line = line('.') + + if !search('{{/'.component_name.'}}', 'W') + return 0 + endif + let end_line = line('.') + + if end_line - start_line <= 0 + " not right, there needs to be at least one line of a difference + return 0 + endif + + if end_line - start_line > 1 + let body = join(sj#TrimList(getbufline('%', start_line + 1, end_line -1)), ' ') + else + let body = '' + endif + + let start_tag = sj#Rtrim(getline(start_line)) + let end_tag = sj#Ltrim(getline(end_line)) + + call sj#ReplaceLines(start_line, end_line, start_tag.body.end_tag) + return 1 + finally + let &iskeyword = saved_iskeyword + endtry +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/html.vim b/bundle/splitjoin.vim/autoload/sj/html.vim new file mode 100644 index 000000000..2750d4da0 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/html.vim @@ -0,0 +1,144 @@ +function! sj#html#SplitTags() + let line = getline('.') + let tag_regex = '\(<.\{-}>\)\(.*\)\(<\/.\{-}>\)' + let tag_with_content_regex = '\(<.\{-}>\)\(.\+\)\(<\/.\{-}>\)' + + if line =~ tag_regex + let body = sj#GetMotion('Vat') + if line =~ tag_with_content_regex + call sj#ReplaceMotion('Vat', substitute(body, tag_regex, '\1\n\2\n\3', '')) + else + call sj#ReplaceMotion('Vat', substitute(body, tag_regex, '\1\n\3', '')) + endif + return 1 + else + return 0 + endif +endfunction + +" Needs to be called with the cursor on a starting or ending tag to work. +function! sj#html#JoinTags() + if s:noTagUnderCursor() + return 0 + endif + + let tag = sj#GetMotion('vat') + let body = sj#GetMotion('vit') + + if len(split(tag, "\n")) == 1 + " then it's just one line, ignore + return 0 + endif + + let body = sj#Trim(body) + let body = join(sj#TrimList(split(body, "\n")), ' ') + + call sj#ReplaceMotion('vit', body) + + return 1 +endfunction + +function! sj#html#SplitAttributes() + let lineno = line('.') + let line = getline('.') + let skip = sj#SkipSyntax(['htmlString']) + + " Check if we are really on a single tag line + if sj#SearchSkip('<', skip, 'bcWe', line('.')) <= 0 + return 0 + endif + let start = col('.') + + " Avoid matching => + if sj#SearchSkip('\%(^\|[^=]\)\zs>', skip, 'W', line('.')) <= 0 + return 0 + endif + let end = col('.') + + let result = [] + let indent = indent('.') + + let argparser = sj#argparser#html_args#Construct(start, end, getline('.')) + call argparser.Process() + let args = argparser.args + + if len(args) <= 1 + " only one argument would only the tag opener, no attributes + return 0 + endif + + " The first item contains the tag and needs slightly different handling + let args[0] = s:withIndentation(args[0], indent) + + if sj#settings#Read('html_attributes_bracket_on_new_line') + let args[-1] = substitute(args[-1], '\s*/\=>$', "\n\\0", '') + endif + + if sj#settings#Read('html_attributes_hanging') + if len(args) <= 2 + " in the hanging case, nothing to split if there's at least one + " non-opening attribute + return 0 + endif + let body = args[0].' '.join(args[1:-1], "\n") + else + let body = join(args, "\n") + endif + + call sj#ReplaceCols(start, end, sj#Trim(body)) + + if sj#settings#Read('html_attributes_hanging') + " For some strange reason, built-in HTML indenting fails here. + let attr_indent = indent + len(args[0]) + 1 + let start_line = lineno + 1 + let end_line = lineno + len(args[1:-1]) -1 + + for l in range(start_line, end_line) + call setline(l, repeat(' ', attr_indent).sj#Trim(getline(l))) + endfor + endif + + return 1 +endfunction + +function! sj#html#JoinAttributes() + let line = getline('.') + let indent = repeat(' ', indent('.')) + + if s:noTagUnderCursor() + return 0 + endif + + let skip = sj#SkipSyntax(['htmlString']) + + if sj#SearchSkip('<', skip, 'bcW') <= 0 + return 0 + endif + let start_pos = getpos('.') + + if sj#SearchSkip('\%(^\|[^=]\)\zs>', skip, 'Wc') <= 0 + return 0 + endif + let end_pos = getpos('.') + + if start_pos[1] == end_pos[1] + " tag is single-line, nothing to join + return 0 + endif + + let lines = split(sj#GetByPosition(start_pos, end_pos), "\n") + let joined = join(sj#TrimList(lines), ' ') + let joined = substitute(joined, '\s*>$', '>', '') + + call sj#ReplaceByPosition(start_pos, end_pos, joined) + return 1 +endfunction + +function! s:noTagUnderCursor() + return searchpair('<', '', '>', 'cb', '', line('.')) <= 0 + \ && searchpair('<', '', '>', 'c', '', line('.')) <= 0 +endfunction + +function! s:withIndentation(str, indent) + return repeat(' ', a:indent) . a:str +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/java.vim b/bundle/splitjoin.vim/autoload/sj/java.vim new file mode 100644 index 000000000..77f207345 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/java.vim @@ -0,0 +1,183 @@ +let s:skip = sj#SkipSyntax(['javaComment', 'javaString']) + +function! sj#java#SplitIfClauseBody() + if sj#SearchSkip('^\s*if (.\+) .\+;\?', s:skip, 'bc', line('.')) <= 0 + return 0 + endif + + " skip nested () brackets + normal! ^w%l + let body = sj#Trim(sj#GetMotion('vg_')) + + if body == '{' + " nothing to split + return 0 + endi + + if body[0] == '{' + let with_curly_brackets = 1 + normal! f{ + let body = sj#Trim(sj#GetMotion('vi{')) + else + let with_curly_brackets = 0 + endif + + if body[0] == ')' + " normal! l didn't work, body must be on another line, nothing to do here + return 0 + " elseif body =~ '//\|/*' + elseif body =~ '\n' + " it's more than one line, nevermind + return 0 + endif + + if with_curly_brackets + call sj#ReplaceMotion('va{', "{\n".body."\n}") + else + call sj#ReplaceMotion('vg_', "\n".body) + endif + + return 1 +endfunction + +function! sj#java#JoinIfClauseBody() + if sj#SearchSkip('^\s*if\s*(.\+)\s*{$', s:skip, 'e', line('.')) > 0 + normal! va{J + return 1 + elseif sj#SearchSkip('^\s*if\s*(.\+)\s*$', s:skip, 'bc', line('.')) > 0 && + \ indent(nextnonblank(line('.') + 1)) > indent(line('.')) + normal! J + return 1 + else + return 0 + endif +endfunction + +function! sj#java#SplitIfClauseCondition() + normal! ^ + if sj#SearchSkip('^\s*if\s\+(', s:skip, 'ce', line('.')) <= 0 + return 0 + endif + + let start_pos = getpos('.') + normal! % + let end_pos = getpos('.') + + if start_pos[1] != end_pos[1] + " closing ) was on a different line, don't split + return 0 + endif + + if start_pos[2] == end_pos[2] + " same column, we didn't move + return 0 + endif + + let items = sj#TrimList(split(sj#GetByPosition(start_pos, end_pos), '\ze\(&&\|||\)')) + let body = join(items, "\n") + + call sj#ReplaceByPosition(start_pos, end_pos, body) + return 1 +endfunction + +function! sj#java#JoinIfClauseCondition() + normal! ^ + if sj#SearchSkip('^\s*if\s*(', s:skip, 'ce', line('.')) <= 0 + return 0 + endif + + let start_line = line('.') + normal! % + let end_line = line('.') + + if start_line == end_line + " closing ) was on the same line, nothing to do + return 0 + endif + + normal! va)J + return 1 +endfunction + +function! sj#java#SplitFuncall() + if sj#SearchUnderCursor('(.\{-})', '', s:skip) <= 0 + return 0 + endif + + call sj#PushCursor() + + normal! l + let start = col('.') + normal! h%h + let end = col('.') + + let items = sj#ParseJsonObjectBody(start, end) + + if sj#settings#Read('java_argument_split_first_newline') + let body = "(\n" + else + let body = "(" + endif + + let body .= join(items, ",\n") + + if sj#settings#Read('java_argument_split_last_newline') + let body .= "\n)" + else + let body .= ")" + endif + + call sj#PopCursor() + + call sj#ReplaceMotion('va(', body) + return 1 +endfunction + +function! sj#java#JoinFuncall() + if sj#SearchUnderCursor('([^)]*\s*$', '', s:skip) <= 0 + return 0 + endif + + let lines = sj#TrimList(split(sj#GetMotion('vi('), "\n")) + call sj#ReplaceMotion('va(', '('.join(lines, ' ').')') + + return 1 +endfunction + +function! sj#java#SplitLambda() + if !sj#SearchUnderCursor('\%((.\{})\|\k\+\)\s*->\s*.*$') + return 0 + endif + + call search('\%((.\{})\|\k\+\)\s*->\s*\zs.*$', 'W', line('.')) + + if strpart(getline('.'), col('.') - 1) =~ '^\s*{' + " then we have a curly bracket group, easy split: + let body = sj#GetMotion('vi{') + call sj#ReplaceMotion('vi{', "\nreturn ".sj#Trim(body).";\n") + return 1 + endif + + let start_col = col('.') + let end_col = sj#JumpBracketsTill('[\])};,]', {'opening': '([{"''', 'closing': ')]}"'''}) + + let body = sj#GetCols(start_col, end_col) + let replacement = "{\nreturn ".body.";\n}" + + call sj#ReplaceCols(start_col, end_col, replacement) + return 1 +endfunction + +function! sj#java#JoinLambda() + if !sj#SearchUnderCursor('\%((.\{})\|\k\+\)\s*->\s*{\s*$') + return 0 + endif + + normal! $ + + let body = sj#Trim(sj#GetMotion('vi{')) + let body = substitute(body, '^return\s*', '', '') + let body = substitute(body, ';$', '', '') + call sj#ReplaceMotion('va{', body) + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/js.vim b/bundle/splitjoin.vim/autoload/sj/js.vim new file mode 100644 index 000000000..89f119f17 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/js.vim @@ -0,0 +1,267 @@ +function! sj#js#SplitObjectLiteral() + let [from, to] = sj#LocateBracesAroundCursor('{', '}') + + if from < 0 && to < 0 + return 0 + endif + + if synIDattr(synID(line('.'), from, 1), "name") == 'jsxBraces' + " from jsx-pretty + return 0 + endif + + let pairs = sj#ParseJsonObjectBody(from + 1, to - 1) + let body = join(pairs, ",\n") + if sj#settings#Read('trailing_comma') + let body .= ',' + endif + let body = "{\n".body."\n}" + call sj#ReplaceMotion('Va{', body) + + if sj#settings#Read('align') + let body_start = line('.') + 1 + let body_end = body_start + len(pairs) - 1 + call sj#Align(body_start, body_end, 'json_object') + endif + + return 1 +endfunction + +function! sj#js#SplitFunction() + if !sj#SearchUnderCursor('\.*(.*)\s*{.*}') + return 0 + endif + + normal! f{ + let [from, to] = sj#LocateBracesAroundCursor('{', '}') + + if from < 0 && to < 0 + return 0 + endif + + let body = sj#Trim(sj#GetMotion('vi{')) + call sj#ReplaceMotion('va{', "{\n".body."\n}") + return 1 +endfunction + +function! sj#js#JoinObjectLiteral() + let line = getline('.') + + if line =~ '{\s*$' + call search('{', 'c', line('.')) + let body = sj#GetMotion('Vi{') + + let lines = split(body, "\n") + let lines = sj#TrimList(lines) + if sj#settings#Read('normalize_whitespace') + let lines = map(lines, 'substitute(v:val, ":\\s\\+", ": ", "")') + endif + + let body = join(lines, ' ') + let body = substitute(body, ',$', '', '') + + if sj#settings#Read('curly_brace_padding') + let body = '{ '.body.' }' + else + let body = '{'.body.'}' + endif + + call sj#ReplaceMotion('Va{', body) + + return 1 + else + return 0 + endif +endfunction + +function! sj#js#JoinFunction() + let line = getline('.') + + if line =~ 'function\%(\s\+\k\+\)\=(.*) {\s*$' + call search('{', 'c', line('.')) + let body = sj#GetMotion('Vi{') + + let lines = split(body, ';\=\s*\n') + let lines = sj#TrimList(lines) + let body = join(lines, '; ').';' + let body = '{ '.body.' }' + + call sj#ReplaceMotion('Va{', body) + + return 1 + else + return 0 + endif +endfunction + +function! sj#js#SplitArray() + return s:SplitList(['[', ']'], 'cursor_inside') +endfunction + +function! sj#js#SplitArgs() + return s:SplitList(['(', ')'], 'cursor_on_line') +endfunction + +function! sj#js#JoinArray() + return s:JoinList(['[', ']']) +endfunction + +function! sj#js#JoinArgs() + return s:JoinList(['(', ')']) +endfunction + +function! sj#js#SplitOneLineIf() + let line = getline('.') + if line =~ '^\s*if (.\+) .\+;\?' + let lines = [] + " use regular vim movements to know where we have to split + normal! ^w% + let end_if = getpos('.')[2] + call add(lines, line[0:end_if] . '{') + call add(lines, sj#Trim(line[end_if :])) + call add(lines, '}') + + call sj#ReplaceMotion('V', join(lines, "\n")) + + return 1 + else + return 0 + endif +endfunction + +function! sj#js#JoinOneLineIf() + let if_line_no = line('.') + let if_line = getline('.') + let end_line_no = if_line_no + 2 + let end_line = getline(end_line_no) + + if if_line !~ '^\s*if (.\+) {' || end_line !~ '^\s*}\s*$' + return 0 + endif + + let body = sj#Trim(getline(if_line_no + 1)) + let new = if_line[:-2] . body + + call sj#ReplaceLines(if_line_no, end_line_no, new) + return 1 +endfunction + +function! s:SplitList(delimiter, cursor_position) + let start = a:delimiter[0] + let end = a:delimiter[1] + + let lineno = line('.') + let indent = indent('.') + + if a:cursor_position == 'cursor_inside' + let [from, to] = sj#LocateBracesAroundCursor(start, end) + elseif a:cursor_position == 'cursor_on_line' + let [from, to] = sj#LocateBracesOnLine(start, end) + else + echoerr "Invalid value for a:cursor_position: ".a:cursor_position + return + endif + + if from < 0 && to < 0 + return 0 + endif + + let items = sj#ParseJsonObjectBody(from + 1, to - 1) + if empty(items) + return 0 + endif + + if sj#settings#Read('trailing_comma') + let body = start."\n".join(items, ",\n").",\n".end + else + let body = start."\n".join(items, ",\n")."\n".end + endif + + call sj#ReplaceMotion('Va'.start, body) + + " built-in js indenting doesn't indent this properly + for l in range(lineno + 1, lineno + len(items)) + call sj#SetIndent(l, indent + &sw) + endfor + " closing bracket + let end_line = lineno + len(items) + 1 + call sj#SetIndent(end_line, indent) + + return 1 +endfunction + +function! s:JoinList(delimiter) + let start = a:delimiter[0] + let end = a:delimiter[1] + + let line = getline('.') + + if line !~ start . '\s*$' + return 0 + endif + + call search(start, 'c', line('.')) + let body = sj#GetMotion('Vi'.start) + + let lines = split(body, "\n") + let lines = sj#TrimList(lines) + let body = sj#Trim(join(lines, ' ')) + let body = substitute(body, ',\s*$', '', '') + + call sj#ReplaceMotion('Va'.start, start.body.end) + + return 1 +endfunction + +function! sj#js#SplitFatArrowFunction() + if !sj#SearchUnderCursor('\%((.\{})\|\k\+\)\s*=>\s*.*$') + return 0 + endif + + call search('\%((.\{})\|\k\+\)\s*=>\s*\zs.*$', 'W', line('.')) + + if strpart(getline('.'), col('.') - 1) =~ '^\s*{' + " then we have a curly bracket group, easy split: + let body = sj#GetMotion('vi{') + call sj#ReplaceMotion('vi{', "\n".sj#Trim(body)."\n") + return 1 + endif + + let start_col = col('.') + let end_col = sj#JumpBracketsTill('[\])};,]', {'opening': '([{"''', 'closing': ')]}"'''}) + + let body = sj#Trim(sj#GetCols(start_col, end_col)) + if body =~ '^({.*})$' + " then we have ({ }) to avoid ambiguity, not needed anymore: + let body = substitute(body, '^(\({.*}\))$', '\1', '') + endif + + if getline('.') =~ ';\s*\%(//.*\)\=$' + let replacement = "{\nreturn ".body.";\n}" + else + let replacement = "{\nreturn ".body."\n}" + endif + + call sj#ReplaceCols(start_col, end_col, replacement) + return 1 +endfunction + +function! sj#js#JoinFatArrowFunction() + if !sj#SearchUnderCursor('\%((.\{})\|\k\+\)\s*=>\s*{\s*$') + return 0 + endif + + normal! $ + + let body = sj#Trim(sj#GetMotion('vi{')) + let body = substitute(body, '^return\s*', '', '') + let body = substitute(body, '\s*;$', '', '') + + if body =~ '^{.*}$' + " ({ }), because otherwise it's ambiguous + let body = '('.body.')' + endif + + call sj#ReplaceMotion('va{', body) + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/jsx.vim b/bundle/splitjoin.vim/autoload/sj/jsx.vim new file mode 100644 index 000000000..afd1e508f --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/jsx.vim @@ -0,0 +1,133 @@ +function! sj#jsx#SplitJsxExpression() + " Examples: + " + " let x = + " () => + " return + " + let pattern = '\%(\%(let\|const\|var\)\s\+\k\+\s*=\s*\|)\s*=>\|return\s\+\)\s*' . + \ '\zs<\k[^>/[:space:]]*' + + if sj#SearchUnderCursor(pattern) <= 0 + return 0 + endif + + " is it a fully-closed jsx tag? + let body = sj#GetMotion('vat') + if body =~ '^<\(\k\+\).*$' + if body =~ "\n" + " multiple lines, not splitting + return 0 + endif + + call sj#ReplaceMotion('vat', "(\n".sj#Trim(body)."\n)") + return 1 + endif + + " is it a self-closing tag? + let body = sj#GetMotion('va>') + if body =~ '^<\k\+.*/>$' + if body =~ "\n" + " multiple lines, not splitting + return 0 + endif + + call sj#ReplaceMotion('va>', "(\n".sj#Trim(body)."\n)") + return 1 + endif + + return 0 +endfunction + +function! sj#jsx#JoinJsxExpression() + " Examples: + " + " let x = ( + " () => ( + " return ( + " + let pattern = '\%(\%(let\|const\|var\)\s\+\k\+\s*=\s*\|)\s*=>\|return\s\+\)\s*($' + if sj#SearchUnderCursor(pattern) <= 0 + return 0 + endif + + normal! $ + let body = sj#Trim(sj#GetMotion('vi(')) + if body =~ "\n" + " multiline tag, no point in handling + return 0 + endif + + if body !~ '^<\k\+.*/>$' && body !~ '^<\(\k\+\).*$' + " doesn't look like a tag + return 0 + endif + + call sj#ReplaceMotion('va(', body) + return 1 +endfunction + +function! sj#jsx#SplitSelfClosingTag() + if s:noTagUnderCursor() + return 0 + endif + + let tag = sj#GetMotion('va<') + if tag == '' || tag !~ '^<\k' + return 0 + endif + + " is it self-closing? + if tag !~ '/>$' + return 0 + endif + + let tag_name = matchstr(tag, '^<\zs\k[^>/[:space:]]*') + let replacement = substitute(tag, '\s*/>$', '>\n', '') + call sj#ReplaceMotion('va<', replacement) + return 1 +endfunction + +" Needs to be called with the cursor on a starting or ending tag to work. +function! sj#jsx#JoinHtmlTag() + if s:noTagUnderCursor() + return 0 + endif + + let tag = sj#GetMotion('vat') + if tag =~ '^\s*$' + return 0 + endif + + let tag_name = matchstr(tag, '^<\zs\k[^>/[:space:]]*') + let empty_tag_pattern = '>\_s*$' + + if tag =~ empty_tag_pattern + " then there's no contents, let's turn it into a self-closing tag + let self_closing_tag = substitute(tag, empty_tag_pattern, ' />', '') + if self_closing_tag == tag + " then the substitution failed for some reason + return 0 + endif + + call sj#ReplaceMotion('vat', self_closing_tag) + else + " There's contents in the tag, let's try to single-line it + if len(split(tag, "\n")) == 1 + " already single-line, nothing to do + return 0 + endif + + let body = sj#GetMotion('vit') + let body = join(sj#TrimList(split(body, "\n")), ' ') + + call sj#ReplaceMotion('vit', body) + end + + return 1 +endfunction + +function! s:noTagUnderCursor() + return searchpair('<', '', '>', 'cb', '', line('.')) <= 0 + \ && searchpair('<', '', '>', 'c', '', line('.')) <= 0 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/lua.vim b/bundle/splitjoin.vim/autoload/sj/lua.vim new file mode 100644 index 000000000..70746d748 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/lua.vim @@ -0,0 +1,131 @@ +let s:function_pattern = '\(\.\{-}(.\{-})\)\(.*\)\' + +function! sj#lua#SplitFunctionString(str) + let head = sj#ExtractRx(a:str, s:function_pattern, '\1') + let body = sj#Trim(sj#ExtractRx(a:str, s:function_pattern, '\2')) + + if sj#BlankString(body) + let body = '' + else + let body = substitute(body, "; ", "\n", "").'\n' + endif + + let replacement = head."\n".body."end" + let new_line = substitute(a:str, s:function_pattern, replacement, '') + + return new_line +endfunction + +function! sj#lua#SplitFunction() + let line = getline('.') + + if line !~ s:function_pattern + return 0 + else + call sj#ReplaceMotion('V', sj#lua#SplitFunctionString(line)) + + return 1 + endif +endfunction + +function! sj#lua#JoinFunction() + normal! 0 + if search('\', 'cW', line('.')) < 0 + return 0 + endif + + let function_lineno = line('.') + if searchpair('\', '', '^\s*\', 'W') <= 0 + return 0 + endif + let end_lineno = line('.') + + let function_line = getline(function_lineno) + let end_line = sj#Trim(getline(end_lineno)) + + if end_lineno - function_lineno > 1 + let body_lines = sj#GetLines(function_lineno + 1, end_lineno - 1) + let body_lines = sj#TrimList(body_lines) + let body = join(body_lines, '; ') + let body = ' '.body.' ' + else + let body = ' ' + endif + + let replacement = function_line.body.end_line + call sj#ReplaceLines(function_lineno, end_lineno, replacement) + + return 1 +endfunction + +function! sj#lua#SplitTable() + let [from, to] = sj#LocateBracesOnLine('{', '}') + + if from < 0 && to < 0 + return 0 + else + let parser = sj#argparser#js#Construct(from + 1, to -1, getline('.')) + call parser.Process() + let pairs = filter(parser.args, 'v:val !~ "^\s*$"') + + let idx = 0 + while idx < len(pairs) + let item = pairs[idx] + if item =~ s:function_pattern + let pairs[idx] = sj#lua#SplitFunctionString(item) + endif + let idx = idx + 1 + endwhile + + let body = "{\n".join(pairs, ",\n").",\n}" + call sj#ReplaceMotion('Va{', body) + + if sj#settings#Read('align') + let body_start = line('.') + 1 + let body_end = body_start + len(pairs) - 1 + call sj#Align(body_start, body_end, 'lua_table') + endif + + return 1 + endif +endf + +function! sj#lua#JoinTable() + let line = getline('.') + + if line =~ '{\s*$' + call search('{', 'c', line('.')) + let body = sj#GetMotion('Vi{') + + let parser = sj#argparser#js#Construct(0, strlen(body), body) + call parser.Process() + + let lines = sj#TrimList(parser.args) + + let idx = 0 + while idx < len(lines) + let item = lines[idx] + if item =~ s:function_pattern + let head = sj#Trim(sj#ExtractRx(item, s:function_pattern, '\1')) + let body = sj#Trim(sj#ExtractRx(item, s:function_pattern, '\2')) + let body_lines = sj#TrimList(split(body, "\n")) + let replacement = head . ' ' . join(body_lines, '; '). ' end' + + let lines[idx] = substitute(item, s:function_pattern, replacement, '') + endif + let idx = idx + 1 + endwhile + + if sj#settings#Read('normalize_whitespace') + let lines = map(lines, "substitute(v:val, '\\s\\+=\\s\\+', ' = ', '')") + endif + + let body = substitute(join(lines, ', '), ',\s*$', '', '') + + call sj#ReplaceMotion('Va{', '{ '.body.' }') + + return 1 + else + return 0 + end +endf diff --git a/bundle/splitjoin.vim/autoload/sj/perl.vim b/bundle/splitjoin.vim/autoload/sj/perl.vim new file mode 100644 index 000000000..bb34dc883 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/perl.vim @@ -0,0 +1,241 @@ +function! sj#perl#SplitSuffixIfClause() + let pattern = '\(.*\) \(if\|unless\|while\|until\) \(.*\);\s*$' + + if sj#settings#Read('perl_brace_on_same_line') + let replacement = "\\2 (\\3) {\n\\1;\n}" + else + let replacement = "\\2 (\\3) \n{\n\\1;\n}" + endif + + return s:Split(pattern, replacement) +endfunction + +function! sj#perl#SplitPrefixIfClause() + let pattern = '\\\s\\+", "=> ", "")') + let lines = map(lines, 'substitute(v:val, "\\s\\+=>", " =>", "")') + endif + + let body = join(lines, ', ') + + call sj#ReplaceMotion('Va{', '{'.body.'}') + return 1 +endfunction + +function! sj#perl#SplitSquareBracketedList() + let [from, to] = sj#LocateBracesOnLine('[', ']') + if from < 0 && to < 0 + return 0 + endif + + let items = sj#ParseJsonObjectBody(from + 1, to - 1) + let body = "[\n".join(items, ",\n") + if sj#settings#Read('trailing_comma') + let body .= "," + endif + let body .= "\n]" + call sj#ReplaceMotion('Va[', body) + + return 1 +endfunction + +function! sj#perl#SplitRoundBracketedList() + let [from, to] = sj#LocateBracesOnLine('(', ')') + if from < 0 && to < 0 + return 0 + endif + + let items = sj#ParseJsonObjectBody(from + 1, to - 1) + let body = "(\n".join(items, ",\n") + if sj#settings#Read('trailing_comma') + let body .= "," + endif + let body .= "\n)" + call sj#ReplaceMotion('Va(', body) + + return 1 +endfunction + +function! sj#perl#SplitWordList() + let [from, to] = sj#LocateBracesOnLine('qw(', ')') + if from < 0 && to < 0 + return 0 + endif + + call search('qw\zs(', 'b', line('.')) + let remainder_of_line = getline('.')[col('.') - 1 : -1] + + if remainder_of_line !~ '\%(\w\|\s\)\+)' + return 0 + endif + + let items = split(matchstr(remainder_of_line, '\%(\k\|\s\)\+'), '\s\+') + let body = "(\n".join(items, "\n")."\n)" + call sj#ReplaceMotion('Va(', body) + + return 1 +endfunction + +function! sj#perl#JoinSquareBracketedList() + let line = getline('.') + + if search('[\s*$', 'c', line('.')) <= 0 + return 0 + endif + + let body = sj#GetMotion('Vi[') + + let lines = split(body, ",\n") + let lines = sj#TrimList(lines) + let body = join(lines, ', ') + + call sj#ReplaceMotion('Va[', '['.body.']') + return 1 +endfunction + +function! sj#perl#JoinRoundBracketedList() + let line = getline('.') + + if search('(\s*$', 'c', line('.')) <= 0 + return 0 + endif + + let body = sj#GetMotion('Vi(') + + let lines = split(body, ",\n") + let lines = sj#TrimList(lines) + let body = join(lines, ', ') + + call sj#ReplaceMotion('Va(', '('.body.')') + return 1 +endfunction + +function! sj#perl#JoinWordList() + let line = getline('.') + + if search('qw\zs(\s*$', 'c', line('.')) <= 0 + return 0 + endif + + let body = sj#GetMotion('Vi(') + + let lines = split(body, "\n") + let lines = sj#TrimList(lines) + let body = join(lines, ' ') + + call sj#ReplaceMotion('Va(', '('.body.')') + return 1 +endfunction + +function! s:Split(pattern, replacement_pattern) + let line = getline('.') + + if line !~ a:pattern + return 0 + endif + + call sj#ReplaceMotion('V', substitute(line, a:pattern, a:replacement_pattern, '')) + + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/php.vim b/bundle/splitjoin.vim/autoload/sj/php.vim new file mode 100644 index 000000000..5a2fe9142 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/php.vim @@ -0,0 +1,254 @@ +function! sj#php#SplitBraces() + return s:SplitList('(', ')') +endfunction + +function! sj#php#SplitArray() + return s:SplitList('[', ']') +endfunction + +function! sj#php#JoinBraces() + return s:JoinList('(', ')') +endfunction + +function! sj#php#JoinArray() + return s:JoinList('[', ']') +endfunction + +function! sj#php#JoinHtmlTags() + if synIDattr(synID(line("."), col("."), 1), "name") =~ '^php' + " then we're in php, don't try to join tags + return 0 + else + return sj#html#JoinTags() + endif +endfunction + +function! sj#php#SplitIfClause() + let pattern = '\') <= 0 + return 0 + endif + + let start_col = col('.') + let skip = sj#SkipSyntax(['phpStringSingle', 'phpStringDouble', 'phpComment']) + if sj#SearchSkip('?>', skip, 'We', line('.')) <= 0 + return 0 + endif + let end_col = col('.') + + let body = sj#GetCols(start_col, end_col) + let body = substitute(body, '^$', "\n?>", '') + + call sj#ReplaceCols(start_col, end_col, body) + return 1 +endfunction + +function! sj#php#JoinPhpMarker() + if sj#SearchUnderCursor('', skip, 'We') <= 0 + return 0 + endif + let end_lineno = line('.') + + let saved_joinspaces = &joinspaces + set nojoinspaces + exe start_lineno.','.end_lineno.'join' + let &joinspaces = saved_joinspaces + + return 1 +endfunction + +function! s:SplitList(start_char, end_char) + let [from, to] = sj#LocateBracesOnLine(a:start_char, a:end_char) + + if from < 0 && to < 0 + return 0 + endif + + let pairs = sj#ParseJsonObjectBody(from + 1, to - 1) + + if len(pairs) < 1 + return 0 + endif + + let body = a:start_char."\n".join(pairs, ",\n") + if sj#settings#Read('trailing_comma') + let body = body.',' + endif + let body = body."\n".a:end_char + + call sj#ReplaceMotion('Va'.a:start_char, body) + + let body_start = line('.') + 1 + let body_end = body_start + len(pairs) + + call sj#PushCursor() + exe "normal! jV".(body_end - body_start)."j2=" + call sj#PopCursor() + + if sj#settings#Read('align') + call sj#Align(body_start, body_end, 'hashrocket') + endif + + return 1 +endfunction + +function! s:JoinList(start_char, end_char) + let line = getline('.') + + if line !~ a:start_char.'\s*$' + return 0 + endif + + call search(a:start_char.'\s*$', 'ce', line('.')) + + let body = sj#Trim(sj#GetMotion('Vi'.a:start_char)) + let body = substitute(body, ',$', '', '') + + if sj#settings#Read('normalize_whitespace') + let body = substitute(body, '\s*=>\s*', ' => ', 'g') + endif + let body = join(sj#TrimList(split(body, "\n")), ' ') + call sj#ReplaceMotion('Va'.a:start_char, a:start_char.body.a:end_char) + + return 1 +endfunction + +function! s:SplitNextArrow() + let l:arrow_or_paren = search('\v(\(|\S\zs-\>\ze)', '', line('.')) + + if ! arrow_or_paren + return + endif + + if matchstr(getline('.'), '\%' . col('.') . 'c.') == '(' + normal! % + else + exe "normal! i\" + endif + + call s:SplitNextArrow() +endfunction + +function! sj#php#SplitMethodChain() + let pattern = '->[^-);]*' + + if sj#SearchUnderCursor('\S'.pattern) <= 0 + return 0 + endif + + call search(pattern, 'W', line('.')) + let start_col = col('.') + call search(pattern, 'We', line('.')) + let end_col = col('.') + + " try to find a (...) after the keyword + let current_line = line('.') + normal! l + if getline('.')[col('.') - 1] == '(' + normal! % + if line('.') == current_line + " the closing ) is on the same line, grab that one as well + let end_col = col('.') + endif + endif + + let body = sj#GetCols(start_col, end_col) + call sj#ReplaceCols(start_col, end_col, "\n".body) + + if sj#settings#Read('php_method_chain_full') + normal! j + call s:SplitNextArrow() + endif + + return 1 +endfunction + +function! sj#php#JoinMethodChain() + let next_line = nextnonblank(line('.') + 1) + + if getline(next_line) !~ '->' + return 0 + endif + + call sj#Keeppatterns('s/\n\_s*//g') + + if sj#settings#Read('php_method_chain_full') + call sj#php#JoinMethodChain() + endif + + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/python.vim b/bundle/splitjoin.vim/autoload/sj/python.vim new file mode 100644 index 000000000..b440f0dad --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/python.vim @@ -0,0 +1,422 @@ +let s:skip = sj#SkipSyntax(['pythonString', 'pythonComment', 'pythonStrInterpRegion']) + +function! sj#python#SplitStatement() + if sj#SearchSkip('^[^:]*\zs:\s*\S', s:skip, 'c', line('.')) + call sj#Keeppatterns('s/\%#:\s*/:\r/') + normal! == + return 1 + else + return 0 + endif +endfunction + +function! sj#python#JoinStatement() + if sj#SearchSkip(':\s*$', s:skip, 'c', line('.')) > 0 + join + return 1 + else + return 0 + endif +endfunction + +function! sj#python#SplitDict() + let [from, to] = sj#LocateBracesAroundCursor('{', '}', ['pythonString']) + + if from < 0 && to < 0 + return 0 + else + let pairs = sj#ParseJsonObjectBody(from + 1, to - 1) + let body = "{\n".join(pairs, ",\n")."\n}" + if sj#settings#Read('trailing_comma') + let body = substitute(body, ',\?\n}', ',\n}', '') + endif + call sj#ReplaceMotion('Va{', body) + + let body_start = line('.') + 1 + let body_end = body_start + len(pairs) + + let base_indent = indent('.') + for line in range(body_start, body_end) + if base_indent == indent(line) + " then indentation didn't work quite right, let's just indent it + " ourselves + exe line.'normal! >>>>' + endif + endfor + + exe body_start.','.body_end.'normal! ==' + + return 1 + endif +endfunction + +function! sj#python#JoinDict() + let line = getline('.') + + if line =~ '{\s*$' + call search('{', 'c', line('.')) + let body = sj#GetMotion('Vi{') + + let lines = sj#TrimList(split(body, "\n")) + if sj#settings#Read('normalize_whitespace') + let lines = map(lines, 'substitute(v:val, ":\\s\\+", ": ", "")') + endif + + let body = join(lines, ' ') + if sj#settings#Read('trailing_comma') + let body = substitute(body, ',\?$', '', '') + endif + + call sj#ReplaceMotion('Va{', '{'.body.'}') + + return 1 + else + return 0 + endif +endfunction + +function! sj#python#SplitArray() + return s:SplitList('\[.*]', '[', ']') +endfunction + +function! sj#python#JoinArray() + return s:JoinList('\[[^]]*\s*$', '[', ']') +endfunction + +function! sj#python#SplitTuple() + return s:SplitList('(.\{-})', '(', ')') +endfunction + +function! sj#python#JoinTuple() + return s:JoinList('([^)]*\s*$', '(', ')') +endfunction + +function! sj#python#SplitImport() + let import_pattern = '^from \%(.*\) import \zs.*$' + + normal! 0 + if search(import_pattern, 'Wc', line('.')) <= 0 + return 0 + endif + + let import_list = sj#GetMotion('vg_') + + if stridx(import_list, ',') < 0 + return 0 + endif + + let imports = split(import_list, ',\s*') + + call sj#ReplaceMotion('vg_', join(imports, ",\\\n")) + return 1 +endfunction + +function! sj#python#JoinImport() + let import_pattern = '^from \%(.*\) import .*\\\s*$' + + if getline('.') !~ import_pattern + return 0 + endif + + let start_lineno = line('.') + let current_lineno = nextnonblank(start_lineno + 1) + + while getline(current_lineno) =~ '\\\s*$' && current_lineno < line('$') + let current_lineno = nextnonblank(current_lineno + 1) + endwhile + + let end_lineno = current_lineno + + exe start_lineno.','.end_lineno.'s/,\\\n\s*/, /e' + return 1 +endfunction + +function! sj#python#SplitAssignment() + if sj#SearchUnderCursor('^\s*\%(\%(\k\|\.\)\+,\s*\)\+\%(\k\|\.\)\+\s*=\s*\S') <= 0 + return 0 + endif + + let variables = split(sj#Trim(sj#GetMotion('vt=')), ',\s*') + normal! f= + call search('\S', 'W', line('.')) + let values = sj#ParseJsonObjectBody(col('.'), col('$')) + let indent = substitute(getline('.'), '^\(\s*\).*', '\1', '') + + let lines = [] + + if len(variables) == len(values) + let index = 0 + for variable in variables + call add(lines, indent.variable.' = '.values[index]) + let index += 1 + endfor + elseif len(values) == 1 + " consider it an array, and index it + let index = 0 + let array = values[0] + for variable in variables + call add(lines, indent.variable.' = '.array.'['.index.']') + let index += 1 + endfor + else + " the sides don't match, let's give up + return 0 + endif + + call sj#ReplaceMotion('V', join(lines, "\n")) + if sj#settings#Read('align') + call sj#Align(line('.'), line('.') + len(lines) - 1, 'equals') + endif +endfunction + +function! sj#python#JoinAssignment() + let assignment_pattern = '^\s*\%(\k\|\.\)\+\zs\s*=\s*\ze\S' + + if search(assignment_pattern, 'W', line('.')) <= 0 + return 0 + endif + + let start_line = line('.') + let [first_variable, first_value] = split(getline('.'), assignment_pattern) + let variables = [ first_variable ] + let values = [ first_value ] + + let end_line = start_line + let next_line = line('.') + 1 + while next_line > 0 && next_line <= line('$') + exe next_line + + if search(assignment_pattern, 'W', line('.')) <= 0 + break + else + let [variable, value] = split(getline(next_line), assignment_pattern) + call add(variables, sj#Trim(variable)) + call add(values, sj#Trim(value)) + let end_line = next_line + let next_line += 1 + endif + + if v:count > 0 && v:count == (end_line - start_line + 1) + " stop at the user-provided count + break + endif + endwhile + + if len(variables) <= 1 + return 0 + endif + + if len(values) > 1 && values[0] =~ '\[0\]$' + " it might be an array, so we could simplify it + let is_array = 1 + let index = 1 + let array_name = substitute(values[0], '\[0\]$', '', '') + for value in values[1:] + if value !~ '^'.array_name.'\s*\['.index.'\]' + let is_array = 0 + break + endif + let index += 1 + endfor + + if is_array + " the entire right-hand side can be just one item + let values = [ array_name ] + endif + endif + + let body = join(variables, ', ').' = '.join(values, ', ') + call sj#ReplaceLines(start_line, end_line, body) + return 1 +endfunction + +function! sj#python#SplitTernaryAssignment() + if getline('.') !~ '^\s*\%(\k\|\.\)\+\s*=\s*\S' + return 0 + endif + + normal! 0 + let include_syntax = sj#IncludeSyntax(['pythonConditional']) + + if sj#SearchSkip('\', include_syntax, 'W', line('.')) <= 0 + return 0 + endif + let if_col = col('.') + + if sj#SearchSkip('\', include_syntax, 'W', line('.')) <= 0 + return 0 + endif + + let else_col = col('.') + let line = getline('.') + + let assignment_if_true = trim(strpart(line, 0, if_col - 1)) + let if_clause = trim(strpart(line, if_col - 1, else_col - if_col)) + let body_if_false = trim(strpart(line, else_col + len('else'))) + + let assignment_prefix = matchstr(assignment_if_true, '\%(\k\|\.\)\+\s*=') + let assignment_if_false = assignment_prefix . ' ' . body_if_false + + let indent = repeat(' ', shiftwidth()) + let base_indent = repeat(' ', indent(line('.'))) + + let body = join([ + \ base_indent . if_clause . ':', + \ base_indent . indent . assignment_if_true, + \ base_indent . 'else:', + \ base_indent . indent . assignment_if_false, + \ ], "\n") + call sj#ReplaceMotion('V', body) + + return 1 +endfunction + +function! sj#python#JoinTernaryAssignment() + let include_syntax = sj#IncludeSyntax(['pythonConditional']) + let start_lineno = line('.') + normal! 0 + + if sj#SearchSkip('^\s*\zsif\>', include_syntax, 'Wc', line('.')) <= 0 + return 0 + endif + let if_line = trim(getline('.')) + if if_line !~ ':$' + return 0 + endif + let if_clause = strpart(if_line, 0, len(if_line) - 1) + + if search('^\s*\zs\%(\k\|\.\)\+\s*=\s*\S', 'Wc', line('.') + 1) <= 0 + return 0 + endif + let assignment_if_true = trim(getline('.')) + let lhs_if_true = matchstr(assignment_if_true, '^\s*\zs\%(\k\|\.\)\+\s*=') + let body_if_true = trim(strpart(assignment_if_true, len(lhs_if_true))) + + if sj#SearchSkip('^\s*\zselse:', include_syntax, 'Wc', line('.') + 2) <= 0 + return 0 + endif + let else_line = trim(getline('.')) + if else_line !~ ':$' + return 0 + endif + + if search('^\s*\zs\%(\k\|\.\)\+\s*=\s*\S', 'Wc', line('.') + 3) <= 0 + return 0 + endif + let assignment_if_false = trim(getline('.')) + let lhs_if_false = matchstr(assignment_if_false, '^\s*\zs\%(\k\|\.\)\+\s*=') + let body_if_false = trim(strpart(assignment_if_false, len(lhs_if_false))) + + if lhs_if_true != lhs_if_false + return 0 + endif + + let body = lhs_if_true . ' ' . body_if_true . ' ' . if_clause . ' else ' . body_if_false + call sj#ReplaceLines(start_lineno, start_lineno + 3, body) + + return 1 +endfunction + +function! s:SplitList(regex, opening_char, closing_char) + let [from, to] = sj#LocateBracesAroundCursor(a:opening_char, a:closing_char, ['pythonString']) + if from < 0 && to < 0 + return 0 + endif + + call sj#PushCursor() + + let items = sj#ParseJsonObjectBody(from + 1, to - 1) + if len(items) <= 1 + call sj#PopCursor() + return 0 + endif + + if sj#settings#Read('python_brackets_on_separate_lines') + if sj#settings#Read('trailing_comma') + let body = a:opening_char."\n".join(items, ",\n").",\n".a:closing_char + else + let body = a:opening_char."\n".join(items, ",\n")."\n".a:closing_char + endif + else + let body = a:opening_char.join(items, ",\n").a:closing_char + endif + + call sj#PopCursor() + call sj#ReplaceMotion('va'.a:opening_char, body) + return 1 +endfunction + +function! s:JoinList(regex, opening_char, closing_char) + if sj#SearchUnderCursor(a:regex) <= 0 + return 0 + endif + + let body = sj#GetMotion('va'.a:opening_char) + let body = substitute(body, '\_s\+', ' ', 'g') + let body = substitute(body, '^'.a:opening_char.'\s\+', a:opening_char, '') + if sj#settings#Read('trailing_comma') + let body = substitute(body, ',\?\s\+'.a:closing_char.'$', a:closing_char, '') + else + let body = substitute(body, '\s\+'.a:closing_char.'$', a:closing_char, '') + endif + + call sj#ReplaceMotion('va'.a:opening_char, body) + + return 1 +endfunction + +function! sj#python#SplitListComprehension() + for [opening_char, closing_char] in [['(', ')'], ['[', ']'], ['{', '}']] + let [from, to] = sj#LocateBracesAroundCursor(opening_char, closing_char, ['pythonString']) + if from > 0 && to > 0 + break + endif + endfor + + if from < 0 && to < 0 + return 0 + endif + + if to - from < 2 + " empty list + return 0 + endif + + " Start after the opening bracket + let pos = getpos('.') + let pos[2] = from + 1 + call setpos('.', pos) + + let break_columns = [] + let include_syntax = sj#IncludeSyntax(['pythonRepeat', 'pythonConditional']) + + while sj#SearchSkip('\<\%(for\|if\)\>', include_syntax, 'W', line('.')) > 0 + call add(break_columns, col('.') - from) + endwhile + + if len(break_columns) <= 0 + return 0 + endif + + let body = sj#GetMotion('vi' .. opening_char) + let parts = [] + let last_break = 0 + + for break_column in break_columns + let part = strpart(body, last_break, break_column - last_break - 1) + call add(parts, sj#Trim(part)) + let last_break = break_column - 1 + endfor + + let part = strpart(body, last_break, to - last_break - 1) + call add(parts, sj#Trim(part)) + + if sj#settings#Read('python_brackets_on_separate_lines') + let body = opening_char .. "\n" .. join(parts, "\n") .. "\n" .. closing_char + else + let body = opening_char .. join(parts, "\n") .. closing_char + endif + + call sj#ReplaceMotion('va' .. opening_char, body) + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/r.vim b/bundle/splitjoin.vim/autoload/sj/r.vim new file mode 100644 index 000000000..434c45fda --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/r.vim @@ -0,0 +1,196 @@ +" Only real syntax that's interesting is cParen and cConditional +let s:skip = sj#SkipSyntax(['rComment']) + +" function! sj#r#SplitFuncall() +" +" Split the R function call if the cursor lies within the arguments of a +" function call +" +function! sj#r#SplitFuncall() + if !s:IsValidSelection("va(") + return 0 + endif + + call sj#PushCursor() + let items = s:ParseJsonFromMotion("va(\vi(") + let items = map(items, {k, v -> v . (k+1 < len(items) ? "," : "")}) + + let r_indent_align_args = get(g:, 'r_indent_align_args', 1) + if r_indent_align_args && len(items) + let items[0] = "(" . items[0] + let items[-1] = items[-1] . ")" + let lines = items + else + let lines = ["("] + items + [")"] + endif + + call sj#PopCursor() + call s:ReplaceMotionPreserveCursor('va(', lines) + + return 1 +endfunction + +" function! sj#r#JoinFuncall() +" +" Join an R function call if the cursor lies within the arguments of a +" function call +" +function! sj#r#JoinFuncall() + if !s:IsValidSelection("va(") + return 0 + endif + + call sj#PushCursor() + + let existing_text = sj#GetMotion("va(\vi(") + let items = s:ParseJsonObject(existing_text) + let text = join(items, ", ") + + " if replacement wouldn't have any effect, fail to attempt a latter callback + if text == existing_text + return 0 + endif + + call sj#PopCursor() + call s:ReplaceMotionPreserveCursor("va(", ["(" . text . ")"]) + + return 1 +endfunction + +" function! sj#r#JoinSmart() +" +" Reexecute :SplitjoinJoin at the end of the line, where it is more likely +" to find a code block relevant to being joined. +" +function! sj#r#JoinSmart() + try + call sj#PushCursor() + + let cur_pos = getpos(".") + silent normal! $ + let end_pos = getpos(".") + + if cur_pos[1:2] != end_pos[1:2] + execute ":SplitjoinJoin" + return 1 + else + return 0 + endif + finally + call sj#PopCursor() + endtr +endfunction + +" function! s:DoMotion(motion) +" +" Perform a normal-mode motion command +" +function s:DoMotion(motion) + call sj#PushCursor() + execute "silent normal! " . a:motion . "\" + execute "silent normal! \" + call sj#PopCursor() +endfunction + +" function! s:MoveCursor(lines, cols) +" +" Reposition cursor given relative lines offset and columns from the start of +" the line +" +function! s:MoveCursor(lines, cols) + let y = a:lines > 0 ? a:lines . 'j^' : a:lines < 0 ? a:lines . 'k^' : '' + let x = a:cols > 0 ? a:cols . 'l' : a:cols < 0 ? a:cols . 'h' : '' + let motion = y . x + if len(motion) + execute 'silent normal! ' . motion + endif +endfunction + +" function! s:ParseJsonObject(text) +" +" Wrapper around sj#argparser#js#Construct to simply parse a given string +" +function! s:ParseJsonObject(text) + let parser = sj#argparser#js#Construct(0, len(a:text), a:text) + call parser.Process() + return parser.args +endfunction + +" function! s:ParseJsonFromMotion(motion) +" +" Parse a json object from the visual selection of a given normal-mode motion +" string +" +function! s:ParseJsonFromMotion(motion) + let text = sj#GetMotion(a:motion) + return s:ParseJsonObject(text) +endfunction + +" function! s:IsValidSelection(motion) +" +" Test whether a visual selection contains more than a single character after +" performing the given normal-mode motion string +" +function! s:IsValidSelection(motion) + call s:DoMotion(a:motion) + return getpos("'<") != getpos("'>") +endfunction + +" function! s:ReplaceMotionPreserveCursor(motion, rep) {{{2 +" +" Replace the normal mode "motion" selection with a list of replacement lines, +" "rep", separated by line breaks, Assuming the non-whitespace content of +" "motion" is identical to the non-whitespace content of the joined lines of +" "rep", the cursor will be repositioned to the resulting location of the +" current character under the cursor. +" +function! s:ReplaceMotionPreserveCursor(motion, rep) + " default to interpretting all lines of text as originally from text to replace + let rep = a:rep + + " do motion and get bounds & text + call s:DoMotion(a:motion) + let ini = split(sj#GetByPosition(getpos("'<"), getpos(".")), "\n") + let ini = map(ini, {k, v -> sj#Ltrim(v)}) + + " do replacement + let body = join(a:rep, "\n") + call sj#ReplaceMotion(a:motion, body) + + " go back to start of selection + silent normal! `< + + " try to reconcile initial selection against replacement lines + let [cursory, cursorx, leading_ws] = [0, 0, 0] + while len(ini) && len(rep) + let i = stridx(ini[0], rep[0]) + let j = stridx(rep[0], ini[0]) + if i >= 0 + " if an entire line of the replacement text found in initial then we'll + " need our cursor to move to the next line if more lines are insered + let ini[0] = sj#Ltrim(ini[0][i+len(rep[0]):]) + let cursorx += i + len(rep[0]) + let ini = len(ini[0]) ? ini : ini[1:] + let rep = rep[1:] + if len(ini) + let cursory += 1 + let cursorx = 0 + endif + elseif j >= 0 + " if an entire line of the initial is found in the replacement then + " we'll need our cursor to move rightward through length of the initial + let rep[0] = rep[0][j+len(ini[0]):] + let leading_ws = len(rep[0]) + let rep[0] = sj#Ltrim(rep[0]) + let leading_ws = leading_ws - len(rep[0]) + let cursorx += j + len(ini[0]) + let ini = ini[1:] + let cursorx += (len(ini) && len(ini[0]) ? leading_ws : 0) + else + let ini = [] + endif + endwhile + + call s:MoveCursor(cursory, max([cursorx-1, 0])) + call sj#PushCursor() +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/ruby.vim b/bundle/splitjoin.vim/autoload/sj/ruby.vim new file mode 100644 index 000000000..4afc004ae --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/ruby.vim @@ -0,0 +1,1314 @@ +let s:invalid_function_names = [ + \ 'and', 'case', 'class', 'def', 'else', + \ 'elseif', 'for', 'if', 'in', 'module', + \ 'not', 'or', 'rescue', 'return', 'then', + \ 'unless', 'until', 'when', 'while', 'yield' + \ ] + +function! sj#ruby#SplitIfClause() + let pattern = '\v(.*\S.*) \zs(if|unless|while|until) (.*)' + let skip = sj#SkipSyntax(['rubyString', 'rubyComment']) + + normal! 0 + + if sj#SearchSkip(pattern, skip, 'W', line('.')) <= 0 + return 0 + endif + + let line = getline('.') + let body = trim(strpart(line, 0, col('.') - 1)) + let if_clause = trim(strpart(line, col('.') - 1)) + + let replacement = if_clause . "\n" . body . "\nend" + call sj#ReplaceMotion('V', replacement) + + return 1 +endfunction + +function! sj#ruby#JoinIfClause() + let line = getline('.') + let pattern = '\v^\s*(if|unless|while|until)' + + if line !~ pattern + return 0 + endif + + let if_line_no = line('.') + let else_line_pattern = '^'.repeat(' ', indent(if_line_no)).'else\s*\%(#.*\)\=$' + let end_line_pattern = '^'.repeat(' ', indent(if_line_no)).'end\s*\%(#.*\)\=$' + + let else_line_no = search(else_line_pattern, 'W') + call cursor(if_line_no, 1) + let end_line_no = search(end_line_pattern, 'W') + + if end_line_no <= 0 + return 0 + endif + + if end_line_no - if_line_no != 2 + return 0 + endif + + if else_line_no && else_line_no < end_line_no + return 0 + endif + + let [result, offset] = s:HandleComments(if_line_no, end_line_no) + if !result + return 1 + endif + let if_line_no += offset + let end_line_no += offset + + let lines = sj#GetLines(if_line_no, end_line_no) + + let if_line = lines[0] + let end_line = lines[-1] + let body = join(lines[1:-2], "\n") + + let if_line = sj#Trim(if_line) + let body = sj#Trim(body) + + let replacement = body.' '.if_line + + call sj#ReplaceLines(if_line_no, end_line_no, replacement) + return 1 +endfunction + +function! sj#ruby#SplitTernaryClause() + let line = getline('.') + let ternary_pattern = '\v(\@{0,2}\w.*) \? (.*) : (.*)' + let assignment_pattern = '\v^\s*\w* \= ' + + if line =~ ternary_pattern + let assignment = matchstr(line, assignment_pattern) + + if assignment != '' + let line = substitute(line, assignment_pattern, '', '') + let line = substitute(line, '(\(.*\))', '\1', '') + + call sj#ReplaceMotion('V', substitute(line, ternary_pattern, + \ assignment.'if \1\n\2\nelse\n\3\nend', '')) + else + call sj#ReplaceMotion('V', substitute(line, ternary_pattern, + \'if \1\n\2\nelse\n\3\nend', '')) + endif + + return 1 + else + return 0 + endif +endfunction + +function! sj#ruby#JoinTernaryClause() + let line = getline('.') + let pattern = '\v(if|unless) ' + + if line =~ pattern + let if_line_no = line('.') + + let else_line_no = if_line_no + 2 + let end_line_no = if_line_no + 4 + + let else_line = getline(else_line_no) + let end_line = getline(end_line_no) + + let clause_is_valid = 0 + + " Three formats are allowed, all ifs can be replaced with unless + " + " if condition + " true + " else + " false + " end + " + " x = if condition " x = if condition + " true " true + " else " else + " false " false + " end " end + " + if else_line =~ '^\s*else\s*$' && end_line =~ '^\s*end\s*$' + let if_column = match(line, pattern) + let else_column = match(else_line, 'else') + let end_column = match(end_line, 'end') + let if_line_indent = indent(if_line_no) + + if else_column == end_column + if (else_column == if_column) || (else_column == if_line_indent) + let clause_is_valid = 1 + endif + endif + end + + if clause_is_valid + let [result, offset] = s:HandleComments(if_line_no, end_line_no) + if !result + return 1 + endif + let if_line_no += offset + let else_line_no += offset + let end_line_no += offset + + let upper_body = getline(if_line_no + 1) + let lower_body = getline(else_line_no + 1) + let upper_body = sj#Trim(upper_body) + let lower_body = sj#Trim(lower_body) + + let assignment = matchstr(upper_body, '\v^.{-} \= ') + + if assignment != '' && lower_body =~ '^'.assignment + let upper_body = substitute(upper_body, '^'.assignment, '', '') + let lower_body = substitute(lower_body, '^'.assignment, '', '') + else + " clean the assignment var if it's invalid, so we don't have + " to care about it later on + let assignment = '' + endif + + if line =~ 'if' + let body = [upper_body, lower_body] + else + let body = [lower_body, upper_body] + endif + + let body_str = join(body, " : ") + let condition = substitute(line, pattern, '', '') + let condition = substitute(condition, '\v^(\s*)', '\1'.assignment, '') + + let replacement = condition.' ? '.body_str + + if line =~ '\v\= (if|unless)' || assignment != '' + let replacement = substitute(replacement, '\v(\= )(.*)', '\1(\2)', '') + endif + + call sj#ReplaceLines(if_line_no, end_line_no, replacement) + + return 1 + endif + endif + + return 0 +endfunction + +function! sj#ruby#JoinCase() + let line_no = line('.') + let line = getline('.') + if line =~ '.*case' + let end_line_pattern = '^'.repeat(' ', indent(line_no)).'end\s*$' + let end_line_no = search(end_line_pattern, 'W') + let lines = sj#GetLines(line_no + 1, end_line_no - 1) + let counter = 1 + for body_line in lines + call cursor(line_no + counter, 1) + if ! call('sj#ruby#JoinWhenThen', []) + let counter = counter + 1 + endif + endfor + + " try to join else for extremely well formed cases and use + " an alignment tool (optional) + call cursor(line_no, 1) + let new_end_line_no = search(end_line_pattern, 'W') + let else_line_no = new_end_line_no - 2 + let else_line = getline(else_line_no) + if else_line =~ '^'.repeat(' ', indent(line_no)).'else\s*$' + let lines = sj#GetLines(line_no + 1, else_line_no - 1) + if s:AllLinesStartWithWhen(lines) + let next_line = getline(else_line_no + 1) + let next_line = sj#Trim(next_line) + let replacement = else_line.' '.next_line + call sj#ReplaceLines(else_line_no, else_line_no + 1, replacement) + if sj#settings#Read('align') + call sj#Align(line_no + 1, else_line_no - 1, 'when_then') + endif + endif + else + " no else line + if sj#settings#Read('align') + call sj#Align(line_no + 1, new_end_line_no - 1, 'when_then') + endif + endif + + " and check the new endline again for changes + call cursor(line_no, 1) + let new_end_line_no = search(end_line_pattern, 'W') + + if end_line_no > new_end_line_no + return 1 + endif + endif + + return 0 +endfunction + +function! s:AllLinesStartWithWhen(lines) + for line in a:lines + if line !~ '\s*when' + return 0 + end + endfor + return 1 +endfunction + +function! sj#ruby#SplitCase() + let line_no = line('.') + let line = getline('.') + if line =~ '.*case' + let end_line_pattern = '^'.repeat(' ', indent(line_no)).'end\s*$' + let end_line_no = search(end_line_pattern, 'W') + let lines = sj#GetLines(line_no + 1, end_line_no - 1) + let counter = 1 + for body_line in lines + call cursor(line_no + counter, 1) + if call('sj#ruby#SplitWhenThen', []) + let counter = counter + 2 + else + let counter = counter + 1 + endif + endfor + + call cursor(line_no, 1) + let new_end_line_no = search(end_line_pattern, 'W') + let else_line_no = new_end_line_no - 1 + let else_line = getline(else_line_no) + if else_line =~ '^'.repeat(' ', indent(line_no)).'else.*' + call cursor(else_line_no, 1) + call sj#ReplaceMotion('V', substitute(else_line, '\v^(\s*else) (.*)', '\1\n\2', '')) + call cursor(else_line_no, 1) + let new_end_line_no = search(end_line_pattern, 'W') + endif + + if end_line_no > new_end_line_no + return 1 + endif + endif + + return 0 +endfunction + +function! sj#ruby#SplitWhenThen() + let line = getline('.') + let pattern = '\v(s*when.*) then (.*)' + + if line =~ pattern + call sj#ReplaceMotion('V', substitute(line, pattern, '\1\n\2', '')) + return 1 + else + return 0 + endif +endfunction + +function! sj#ruby#JoinWhenThen() + let line = getline('.') + + if line =~ '^\s*when' + let line_no = line('.') + let one_down = getline(line_no + 1) + let two_down = getline(line_no + 2) + let pattern = '\v^\s*(when|else|end)>' + + if one_down !~ pattern && two_down =~ pattern + let one_down = sj#Trim(one_down) + let replacement = line.' then '.one_down + call sj#ReplaceLines(line_no, line_no + 1, replacement) + return 1 + end + end + + return 0 +endfunction + +function! sj#ruby#SplitProcShorthand() + let pattern = '(&:\k\+[!?]\=)' + + if sj#SearchUnderCursor(pattern) <= 0 + return 0 + endif + + if search('(&:\zs\k\+[!?]\=)', '', line('.')) <= 0 + return 0 + endif + + let method_name = matchstr(sj#GetMotion('Vi('), '\k\+[!?]\=') + let body = " do |i|\ni.".method_name."\nend" + + call sj#ReplaceMotion('Va(', body) + return 1 +endfunction + +function! sj#ruby#SplitBlock() + let pattern = '\v\{\s*(\|.{-}\|)?\s*(.*)\s*\}' + + if sj#SearchUnderCursor('\v%(\k|!|\-\>|\?|\))\s*\zs'.pattern) <= 0 + return 0 + endif + + let start = col('.') + normal! % + let end = col('.') + + if start == end + " the cursor hasn't moved, bail out + return 0 + endif + + let body = sj#GetMotion('Va{') + + if sj#settings#Read('ruby_do_block_split') + let multiline_block = 'do \1\n\2\nend' + else + let multiline_block = '{ \1\n\2\n}' + endif + + normal! % + if search('\S\%#', 'Wbn') + let multiline_block = ' '.multiline_block + endif + + let replacement = substitute(body, '^'.pattern.'$', multiline_block, '') + + " remove leftover whitespace + let replacement = substitute(replacement, '\s*\n', '\n', 'g') + + call sj#ReplaceMotion('Va{', replacement) + + normal! j0 + while sj#SearchSkip(';', sj#SkipSyntax(['rubyString']), 'W', line('.')) > 0 + call execute("normal! r\") + endwhile + + return 1 +endfunction + +function! sj#ruby#JoinBlock() + let do_pattern = '\\(\s*|.*|\s*\)\?$' + let end_pattern = '\%(^\|[^.:@$]\)\@<=\' + + let do_line_no = search(do_pattern, 'cW', line('.')) + if do_line_no <= 0 + let do_line_no = search(do_pattern, 'bcW', line('.')) + endif + + if do_line_no <= 0 + return 0 + endif + + let end_line_no = searchpair(do_pattern, '', end_pattern, 'W') + + let [result, offset] = s:HandleComments(do_line_no, end_line_no) + if !result + return 1 + endif + let do_line_no += offset + let end_line_no += offset + + let lines = sj#GetLines(do_line_no, end_line_no) + let lines = sj#TrimList(lines) + let lines = sj#RemoveBlanks(lines) + + let do_line = substitute(lines[0], do_pattern, '{\1', '') + let body = s:JoinBlockBody(lines[1:-2]) + let body = sj#Trim(body) + let end_line = substitute(lines[-1], 'end', '}', '') + + let replacement = do_line.' '.body.' '.end_line + + " shorthand to_proc if possible + let replacement = substitute(replacement, '\s*{ |\(\k\+\)| \1\.\(\k\+[!?]\=\) }$', '(\&:\2)', '') + + call sj#ReplaceLines(do_line_no, end_line_no, replacement) + + return 1 +endfunction + +function! s:JoinBlockBody(lines) + let lines = a:lines + + if len(lines) < 1 + return '' + endif + + let body = lines[0] + " horrible regex taken from vim-ruby + let continuation_regex = + \ '\%(%\@' + let replacement = substitute(line, '||=\s\+\(.*\)$', '||= begin\n\1\nend', '') + call sj#ReplaceMotion('V', replacement) + + return 1 + else + return 0 + endif +endfunction + +function! sj#ruby#JoinCachingConstruct() + let begin_line = getline('.') + let body_line = getline(line('.') + 1) + let end_line = getline(line('.') + 2) + + if begin_line =~ '||=\s\+begin' && end_line =~ '^\s*end' + let lvalue = substitute(begin_line, '\s\+||=\s\+begin.*$', '', '') + let body = sj#Trim(body_line) + let replacement = lvalue.' ||= '.body + + call sj#ReplaceLines(line('.'), line('.') + 2, replacement) + + return 1 + else + return 0 + endif +endfunction + +function! sj#ruby#JoinHash() + let line = getline('.') + + if line =~ '{\s*$' + return s:JoinHashWithCurlyBraces() + elseif line =~ '(\s*$' + return s:JoinHashWithRoundBraces() + elseif line =~ ',\s*$' + " also ends up being called for `(foo, bar,` situations + return s:JoinHashWithoutBraces() + else + return 0 + endif +endfunction + +function! sj#ruby#SplitOptions() + " Variables: + " + " option_type: ['option', 'hash'] + " function_type: ['none', 'with_spaces', 'with_round_braces'] + " + + call sj#PushCursor() + let [function_name, function_from, function_to, function_type] = sj#argparser#ruby#LocateFunction() + call sj#PopCursor() + + if index(s:invalid_function_names, function_name) >= 0 + return 0 + endif + + call sj#PushCursor() + let [hash_from, hash_to] = sj#argparser#ruby#LocateHash() + call sj#PopCursor() + + if hash_from >= 0 && function_from < 0 + let option_type = 'hash' + else + let option_type = 'option' + endif + + if function_from >= 0 + let from = function_from + let to = function_to + else + let from = hash_from + let to = hash_to + endif + + if from < 0 + return 0 + endif + + let start_lineno = line('.') + let [from, to, args, opts, hash_type, cursor_arg] = + \ sj#argparser#ruby#ParseArguments(from, to, getline('.'), { 'expand_options': 1 }) + + if !(from <= col('.') && col('.') <= to) + " then this is not around the cursor, bail out + return 0 + endif + + let no_options = len(opts) < 1 && len(args) > 0 && option_type == 'option' + let both_args_and_opts = sj#settings#Read('ruby_options_as_arguments') && cursor_arg < len(args) + + if no_options || both_args_and_opts + " which case is it? + if no_options + " no options found, but there are arguments, split those + let replacement = join(args, ",\n") + elseif both_args_and_opts + " the cursor is on an argument, split both args and opts + let all_args = [] + call extend(all_args, args) + call extend(all_args, opts) + let replacement = join(all_args, ",\n") + endif + + if !sj#settings#Read('ruby_hanging_args') + " add trailing comma + if sj#settings#Read('ruby_trailing_comma') || sj#settings#Read('trailing_comma') + let replacement .= ',' + endif + + let replacement = "\n".replacement."\n" + elseif len(args) == 1 + " if there's only one argument, there's nothing to do in the "hanging" + " case + return 0 + endif + + if function_type == 'with_spaces' + let replacement = "(".replacement.")" + let from -= 1 " Also replace the space before the argument list + endif + + call sj#ReplaceCols(from, to, replacement) + return 1 + endif + + let replacement = '' + + " first, prepare the already-existing arguments + if len(args) > 0 + let replacement .= join(args, ', ') . ',' + endif + + " add opening brace + if sj#settings#Read('ruby_curly_braces') + + if option_type == 'hash' + " Example: one = {:two => 'three'} + " + let replacement .= "{\n" + elseif function_type == 'with_round_braces' && len(args) > 0 + " Example: create(:inquiry, :state => state) + " + let replacement .= " {\n" + elseif function_type == 'with_round_braces' && len(args) == 0 + " Example: create(one: 'two', three: 'four') + " + let replacement .= "{\n" + else + " add braces in all other cases + let replacement .= " {\n" + endif + + else " !sj#settings#Read('ruby_curly_braces') + + if option_type == 'option' && function_type == 'with_round_braces' && len(args) > 0 + " Example: User.new(:one, :two => 'three') + " + let replacement .= "\n" + elseif option_type == 'option' && function_type == 'with_spaces' && len(args) > 0 + " Example: User.new :one, :two => 'three' + " + let replacement .= "\n" + elseif option_type == 'hash' && function_type == 'none' + " Not a function call, but a hash + " Example: one = {:two => "three"} + " + let replacement .= "{\n" + endif + + endif + + " add options + let replacement .= join(opts, ",\n") + + " add trailing comma + if sj#settings#Read('ruby_trailing_comma') || sj#settings#Read('trailing_comma') + let replacement .= ',' + endif + + " add closing brace + if !sj#settings#Read('ruby_curly_braces') && option_type == 'option' && function_type == 'with_round_braces' + if sj#settings#Read('ruby_hanging_args') + " no need to do anything + else + let replacement = "\n".replacement."\n" + endif + elseif sj#settings#Read('ruby_curly_braces') || option_type == 'hash' || len(args) == 0 + let replacement .= "\n}" + endif + + call sj#ReplaceCols(from, to, replacement) + + if sj#settings#Read('align') && hash_type != 'mixed' + " find index of first option + let first_keyword_index = 0 + for line in split(replacement, "\n", 1) + let line = substitute(sj#Trim(line), ',$', '', '') + if index(opts, line) >= 0 + break + endif + + let first_keyword_index += 1 + endfor + + let alignment_start = start_lineno + first_keyword_index + let alignment_end = alignment_start + len(opts) - 1 + + if hash_type == 'classic' + call sj#Align(alignment_start, alignment_end, 'hashrocket') + elseif hash_type == 'new' + call sj#Align(alignment_start, alignment_end, 'json_object') + endif + endif + + return 1 +endfunction + +function! sj#ruby#SplitArray() + let [from, to] = sj#LocateBracesAroundCursor('[', ']', [ + \ 'rubyInterpolationDelimiter', + \ 'rubyString', + \ 'rubyStringDelimiter', + \ 'rubySymbolDelimiter', + \ 'rubyPercentStringDelimiter', + \ 'rubyPercentSymbolDelimiter', + \ ]) + + if from < 0 + return 0 + endif + + let [from, to, args, opts; _rest] = sj#argparser#ruby#ParseArguments(from + 1, to - 1, getline('.'), { + \ 'expand_options': sj#settings#Read('ruby_expand_options_in_arrays') + \ }) + + if from < 0 + return 0 + endif + let items = extend(args, opts) + + let replacement = join(items, ",\n") + + if sj#settings#Read('ruby_trailing_comma') || sj#settings#Read('trailing_comma') + let replacement .= ',' + endif + + let replacement = "\n".replacement."\n" + call sj#ReplaceCols(from, to, replacement) + return 1 +endfunction + +function! sj#ruby#JoinArray() + normal! $ + + if getline('.')[col('.') - 1] != '[' + return 0 + endif + + let syntax_group = synIDattr(synID(line('.'), col('.'), 1), "name") + if syntax_group =~ 'ruby\%(Percent\)\=\(String\|Symbol\)\%(Delimiter\)\=' + return 0 + endif + + let body = sj#Trim(sj#GetMotion('Vi[')) + " remove trailing comma + let body = substitute(body, ',\ze\_s*$', '', '') + let body = join(sj#TrimList(split(body, ",\s*\n")), ', ') + call sj#ReplaceMotion('Va[', '['.body.']') + + return 1 +endfunction + +function! sj#ruby#JoinContinuedMethodCall() + if getline('.') !~ '\.$' && getline(nextnonblank(line('.') + 1)) !~ '^\s*\.' + return 0 + endif + + let start_lineno = line('.') + silent! normal! zO + normal! j + + while line('.') < line('$') && + \ (getline('.') =~ '\.$' || getline(nextnonblank(line('.') + 1)) =~ '^\s*\.') + normal! j + endwhile + + let end_lineno = line('.') - 1 + + call sj#Keeppatterns(start_lineno.','.end_lineno.'s/\n\_s*//') + return 1 +endfunction + +function! sj#ruby#JoinHeredoc() + let heredoc_pattern = '<<[-~]\?\([^ \t,)]\+\)' + + if sj#SearchUnderCursor(heredoc_pattern) <= 0 + return 0 + endif + + let start_lineno = line('.') + let remainder_of_line = sj#GetCols(col('.'), col('$')) + let delimiter = sj#ExtractRx(remainder_of_line, heredoc_pattern, '\1') + + " we won't be needing the rest of the line + normal! "_D + + if search('^\s*'.delimiter.'\s*$', 'W') <= 0 + return 0 + endif + + let end_lineno = line('.') + + if end_lineno - start_lineno > 1 + let lines = sj#GetLines(start_lineno + 1, end_lineno - 1) + let lines = sj#TrimList(lines) + let body = join(lines, " ") + else + let body = '' + endif + + if body =~ '\%(#{\|''\)' + let quoted_body = '"'.escape(escape(body, '"'), '\').'"' + else + let quoted_body = "'".body."'" + endif + + let replacement = getline(start_lineno).substitute(remainder_of_line, heredoc_pattern, quoted_body, '') + call sj#ReplaceLines(start_lineno, end_lineno, replacement) + undojoin " with the 'normal! D' + + return 1 +endfunction + +function! sj#ruby#SplitString() + let string_pattern = '\(\%(^\|[^\\]\)\zs\([''"]\)\).\{-}[^\\]\+\2' + let empty_string_pattern = '\%(''''\|""\)' + + let [match_start, match_end] = sj#SearchColsUnderCursor(string_pattern) + if match_start <= 0 + let [match_start, match_end] = sj#SearchColsUnderCursor(empty_string_pattern) + if match_start <= 0 + return 0 + endif + endif + + let string = sj#GetCols(match_start, match_end - 1) + let delimiter = string[0] + + if match_end - match_start > 2 + let string_body = sj#GetCols(match_start + 1, match_end - 2)."\n" + else + let string_body = '' + endif + + if delimiter == '"' + let string_body = substitute(string_body, '\\"', '"', 'g') + elseif delimiter == "'" + let string_body = substitute(string_body, "\\''", "'", 'g') + endif + + if sj#settings#Read('ruby_heredoc_type') == '<<-' + call sj#ReplaceCols(match_start, match_end - 1, '<<-EOF') + let replacement = getline('.')."\n".string_body."EOF" + call sj#ReplaceMotion('V', replacement) + elseif sj#settings#Read('ruby_heredoc_type') == '<<~' + call sj#ReplaceCols(match_start, match_end - 1, '<<~EOF') + let replacement = getline('.')."\n".string_body."EOF" + call sj#ReplaceMotion('V', replacement) + if string_body != '' + exe (line('.') + 1).'>' + endif + elseif sj#settings#Read('ruby_heredoc_type') == '<<' + call sj#ReplaceCols(match_start, match_end - 1, '<" + call sj#SetIndent(end_col, indent) + return 1 + endif + + let array_body = sj#GetCols(start_col, end_col - 1) + let array_items = split(array_body, '\s\+') + call sj#ReplaceCols(start_col, end_col - 1, "\n".join(array_items, "\n")."\n") + + call sj#SetIndent(lineno + 1, lineno + len(array_items), indent + &sw) + call sj#SetIndent(lineno + len(array_items) + 1, indent) + + return 1 +endfunction + +function! sj#ruby#JoinArrayLiteral() + let syntax_group = synIDattr(synID(line('.'), col('.'), 1), "name") + if syntax_group !~ 'ruby\%(Percent\)\=\(String\|Symbol\)\%(Delimiter\)\=' + return 0 + endif + + if search('%[wiWI].$', 'Wce', line('.')) <= 0 + return 0 + endif + + let opening_bracket = getline('.')[col('.') - 1] + let closing_bracket = s:ArrayLiteralClosingBracket(opening_bracket) + + let start_lineno = line('.') + let end_lineno = start_lineno + 1 + let end_pattern = '^\s*\V'.closing_bracket.'\m\s*$' + let word_pattern = '^\%(\k\|\s\)*$' + + while end_lineno <= line('$') && getline(end_lineno) !~ end_pattern + if getline(end_lineno) !~ word_pattern + return 0 + endif + let end_lineno += 1 + endwhile + + if getline(end_lineno) !~ end_pattern + return 0 + endif + + if end_lineno - start_lineno < 1 + " nothing to join, bail out + return 0 + endif + + if end_lineno - start_lineno == 1 + call sj#Keeppatterns('s/\n\_s*//') + return 1 + endif + + let words = sj#TrimList(sj#GetLines(start_lineno + 1, end_lineno - 1)) + call sj#ReplaceLines(start_lineno + 1, end_lineno, join(words, ' ').closing_bracket) + exe start_lineno + call sj#Keeppatterns('s/\n\_s*//') + + return 1 +endfunction + +function! sj#ruby#JoinModuleNamespace() + " Initialize matchit, a requirement + if !exists('g:loaded_matchit') + if has(':packadd') + packadd matchit + else + runtime macros/matchit.vim + endif + endif + if !exists('g:loaded_matchit') + " then loading it somehow failed, we can't continue + return 0 + endif + + let namespace_pattern = '^\s*module\s\+\zs[A-Z]\(\k\|::\)*\s*$' + let class_pattern = '^\s*class\s\+\zs[A-Z]\k*\s*\(\k\|::\)\+\s*\%(<\s\+\S\+\)\=$' + let describe_pattern = '^\s*\%(RSpec\.\)\=describe\s\+\zs[A-Z]\(\k\|::\)*\s*do' + + if search(namespace_pattern, 'Wbc', line('.')) <= 0 + return 0 + endif + + " Pin the starting point + let module_start_line = line('.') + let start_indent = indent('.') + let modules = [expand('')] + let keyword = 'module' + normal! j0 + + " Find the end point + let module_end_line = module_start_line + while search(namespace_pattern, 'Wc', line('.')) > 0 + let module_end_line = line('.') + call add(modules, expand('')) + normal! j0 + + " That way, modules get joined piecewise. This might be guarded with an + " option at a later time: + break + endwhile + + " most of these cases don't end in "do" + let do_suffix = '' + + if search(class_pattern, 'Wc', line('.')) > 0 + " then the end is a class line + let module_end_line = line('.') + call add(modules, sj#GetMotion('vg_')) + let keyword = 'class' + elseif search(describe_pattern, 'Wc', line('.')) > 0 + " then the end is an RSpec describe line + let module_end_line = line('.') + let start_col = col('.') + let [_, end_col] = searchpos('\k\s*do$', 'n') + if start_col >= end_col + return 0 + endif + call add(modules, sj#GetCols(start_col, end_col)) + if getline('.') =~ 'RSpec\.describe' + let keyword = 'RSpec.describe' + else + let keyword = 'describe' + endif + let do_suffix = ' do' + else + " go back one line, to the last module + normal! k + endif + + if len(modules) < 2 + " nothing to join + return 0 + endif + + " go to the end of the deepest-nested module/class/do: + call search('^\s*\zs\%(module\|class\|\') + if keyword == 'RSpec' + let keyword = 'RSpec.describe' + endif + let do_suffix = '' + if keyword =~ 'describe$' + let do_suffix = ' do' + endif + + " get the module path + if search('\V'.keyword.'\m\s\+\zs[A-Z]\k*', 'W', line('.')) <= 0 + return 0 + endif + let module_path = expand('') + if search('\s\+<\s\+\S\+$', 'W', line('.')) > 0 + let parent = sj#GetMotion('vg_') + else + let parent = '' + endif + let modules = split(module_path, '::') + + if len(modules) < 2 + " nothing to split + return 0 + endif + + " build up new lines + let lines = [] + for module in modules[:-2] + call add(lines, 'module '.module) + endfor + call add(lines, keyword.' '.modules[-1].parent.do_suffix) + + " shift contents of the class/module + if search('^\s*\zs\%(module\|class\|\%(RSpec\.\)\=describe.*do$\)', 'Wbc', line('.')) <= 0 + return 0 + endif + normal % + let end_line = line('.') - 1 + if end_line - start_line > 0 + let range = start_line.','.end_line + silent exe range.repeat('>', len(modules) - 1) + endif + + " replace the module line + exe start_line + call sj#ReplaceMotion('V', join(lines, "\n")) + + " add the necessary amount of "end"s + exe (end_line + len(lines)) + let ends = split(repeat("end\n", len(modules)), "\n") + call sj#ReplaceMotion('V', join(ends, "\n")) + + return 1 +endfunction + +function! sj#ruby#SplitEndlessDef() + " taken from vim-ruby + let endless_def_pattern = '\' + " then the cursor hasn't moved + return 0 + endif + + if end_line_no - def_line_no != 2 + " then it's not a one-line method + return 0 + endif + + let [result, offset] = s:HandleComments(def_line_no, end_line_no) + if !result + return 1 + endif + let def_line_no += offset + let end_line_no += offset + + let lines = sj#GetLines(def_line_no, end_line_no) + + let def_line = lines[0] + let end_line = lines[-1] + let body = join(lines[1:-2], "\n") + + let def_line = sj#Trim(def_line) + let body = sj#Trim(body) + + let replacement = def_line.' = '.body + + call sj#ReplaceLines(def_line_no, end_line_no, replacement) + return 1 +endfunction + +" Helper functions + +function! s:JoinHashWithCurlyBraces() + normal! $ + + let original_body = sj#GetMotion('Vi{') + let body = original_body + + if sj#settings#Read('normalize_whitespace') + let body = substitute(body, '\s\+=>\s\+', ' => ', 'g') + let body = substitute(body, '\s\+\k\+\zs:\s\+', ': ', 'g') + endif + + " remove trailing comma + let body = substitute(body, ',\ze\_s*$', '', '') + + let body = join(sj#TrimList(split(body, "\n")), ' ') + if sj#settings#Read('curly_brace_padding') + let body = '{ '.body.' }' + else + let body = '{'.body.'}' + endif + + call sj#ReplaceMotion('va{', body) + return 1 +endfunction + +function! s:JoinHashWithRoundBraces() + normal! $ + + let body = sj#GetMotion('Vi(',) + if sj#settings#Read('normalize_whitespace') + let body = substitute(body, '\s*=>\s*', ' => ', 'g') + let body = substitute(body, '\s\+\k\+\zs:\s\+', ': ', 'g') + endif + + " remove trailing comma + let body = substitute(body, ',\ze\_s*$', '', '') + + let body = join(sj#TrimList(split(body, "\n")), ' ') + call sj#ReplaceMotion('Va(', '('.body.')') + + return 1 +endfunction + +function! s:JoinHashWithoutBraces() + let start_lineno = line('.') + let end_lineno = start_lineno + let lineno = nextnonblank(start_lineno + 1) + let line = getline(lineno) + let indent = repeat(' ', indent(lineno)) + + while lineno <= line('$') && + \ ((line =~ '^'.indent && (line =~ '=>' || line =~ '^\s*\k\+:')) || line =~ '^\s*)') + let end_lineno = lineno + let lineno = nextnonblank(lineno + 1) + let line = getline(lineno) + endwhile + + call cursor(start_lineno, 0) + exe "normal! V".(end_lineno - start_lineno)."jJ" + return 1 +endfunction + +function! s:HandleComments(start_line_no, end_line_no) + let start_line_no = a:start_line_no + let end_line_no = a:end_line_no + + let [success, failure] = [1, 0] + let offset = 0 + + let comments = s:FindComments(start_line_no, end_line_no) + + if len(comments) > 1 + echomsg "Splitjoin: Can't join this due to the inline comments. Please remove them first." + return [failure, 0] + endif + + if len(comments) == 1 + let [start_line_no, end_line_no] = s:MigrateComments(comments, a:start_line_no, a:end_line_no) + let offset = start_line_no - a:start_line_no + else + let offset = 0 + endif + + return [success, offset] +endfunction + +function! s:FindComments(start_line_no, end_line_no) + call sj#PushCursor() + + let comments = [] + + for lineno in range(a:start_line_no, a:end_line_no) + exe lineno + normal! 0 + + while search('#', 'Wc', lineno) > 0 + let col = col('.') + + if synIDattr(synID(lineno, col, 1), "name") == 'rubyComment' + let comment = sj#GetCols(col, col('$')) + call add(comments, [lineno, col, comment]) + break + else + normal! l + endif + endwhile + endfor + + call sj#PopCursor() + + return comments +endfunction + +function! s:MigrateComments(comments, start_line_no, end_line_no) + call sj#PushCursor() + + let start_line_no = a:start_line_no + let end_line_no = a:end_line_no + + for [line, col, _c] in a:comments + call cursor(line, col) + normal! "_D + endfor + + for [_l, _c, comment] in a:comments + call append(start_line_no - 1, comment) + + exe start_line_no + normal! == + + let start_line_no = start_line_no + 1 + let end_line_no = end_line_no + 1 + endfor + + call sj#PopCursor() + + return [start_line_no, end_line_no] +endfunction + +function! s:ArrayLiteralClosingBracket(opening_bracket) + let opening_bracket = a:opening_bracket + + if opening_bracket == '{' + let closing_bracket = '}' + elseif opening_bracket == '(' + let closing_bracket = ')' + elseif opening_bracket == '<' + let closing_bracket = '>' + elseif opening_bracket == '[' + let closing_bracket = ']' + else + let closing_bracket = opening_bracket + endif + + return closing_bracket +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/rust.vim b/bundle/splitjoin.vim/autoload/sj/rust.vim new file mode 100644 index 000000000..b8ae6ea68 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/rust.vim @@ -0,0 +1,935 @@ +let s:skip_syntax = sj#SkipSyntax(['String', 'Comment']) +let s:eol_pattern = '\s*\%(//.*\)\=$' + +" TODO (2023-02-25) running substitute() on semicolons won't work well for +" strings. Need a generic solution, sj#DelimiterOffsets( + +function! sj#rust#SplitMatchExpression() + if !sj#SearchUnderCursor('\"'''}) + let [from, to] = sj#LocateBracesAroundCursor('{', '}') + if from < 0 && to < 0 + return 0 + endif + + let parser = sj#argparser#rust_struct#Construct(from + 1, to - 1, getline('.')) + call parser.Process() + let args = parser.args + if len(args) <= 0 + return 0 + endif + + let items = map(args, 'v:val.argument') + let body = join(items, ",\n") + if sj#settings#Read('trailing_comma') + let body .= ',' + endif + let body = "{\n" . body . "\n}" + + call sj#ReplaceCols(from, to, body) + return 1 +endfunction + +function! sj#rust#SplitMatchClause() + if !sj#SearchUnderCursor('^.*\s*=>\s*.*$') + return 0 + endif + + if !search('=>\s*\zs.', 'W', line('.')) + return 0 + endif + + let start_col = col('.') + if !search(',\='.s:eol_pattern, 'W', line('.')) + return 0 + endif + + " handle trailing comma if there is one + if getline('.')[col('.') - 1] == ',' + let content_end_col = col('.') + let body_end_col = content_end_col - 1 + else + let content_end_col = col('.') + let body_end_col = content_end_col + endif + + let body = sj#Trim(sj#GetCols(start_col, body_end_col)) + if body =~ '[({[.,*/%+-]$' + " ends in an opening bracket or operator of some sorts, so it's + " incomplete, don't touch it + return 0 + endif + + let body = substitute(body, '^{\s*\(.\{-}\)\s*}$', '\1', '') + call sj#ReplaceCols(start_col, content_end_col, "{\n".body."\n},") + return 1 +endfunction + +function! sj#rust#JoinMatchClause() + if !sj#SearchUnderCursor('^.*\s*=>\s*{\s*$') + return 0 + endif + + call search('=>\s*\zs{', 'W', line('.')) + + let body = sj#Trim(sj#GetMotion('Vi{')) + if stridx(body, "\n") >= 0 + return 0 + endif + + if len(body) == 0 + call sj#ReplaceMotion('va{', '{}') + return 1 + endif + + " Remove semicolons when joining, they don't work in non-block form + if body[len(body) - 1] == ';' + let body = body[0 : len(body) - 2] + endif + + call sj#ReplaceMotion('Va{', body) + return 1 +endfunction + +function! sj#rust#SplitQuestionMark() + if sj#SearchSkip('.?', s:skip_syntax, 'Wc', line('.')) <= 0 + return 0 + endif + + let current_line = line('.') + let end_col = col('.') + let question_mark_col = col('.') + 1 + let char = getline('.')[end_col - 1] + + let previous_start_col = -2 + let start_col = -1 + + while previous_start_col != start_col + let previous_start_col = start_col + + if char =~ '\k' + call search('\k\+?;', 'bWc', line('.')) + let start_col = col('.') + elseif char == '}' + " go to opening bracket + normal! % + let start_col = col('.') + elseif char == ')' + " go to opening bracket + normal! % + " find first method-call char + call search('\%(\k\|\.\|::\)\+!\?(', 'bWc') + + if line('.') != current_line + " multiline expression, let's just ignore it + return 0 + endif + + let start_col = col('.') + else + break + endif + + if start_col <= 1 + " first character, no previous one + break + endif + + " move backwards one step from the start + let pos = getpos('.') + let pos[2] = start_col - 1 + call setpos('.', pos) + let char = getline('.')[col('.') - 1] + endwhile + + " is it a Result, or an Option? + let expr_type = s:FunctionReturnType() + " default to a Result, if we can't find anything + if expr_type == '' + let expr_type = 'Result' + endif + let expr = sj#GetCols(start_col, end_col) + + if expr_type == 'Result' + let replacement = join([ + \ "match ".expr." {", + \ " Ok(value) => value,", + \ " Err(e) => return Err(e.into()),", + \ "}" + \ ], "\n") + elseif expr_type == 'Option' + let replacement = join([ + \ "match ".expr." {", + \ " None => return None,", + \ " Some(value) => value,", + \ "}" + \ ], "\n") + else + echoerr "Unknown expr_type: ".expr_type + return 0 + endif + + call sj#ReplaceCols(start_col, question_mark_col, replacement) + return 1 +endfunction + +function! sj#rust#JoinMatchStatement() + let match_pattern = '\}"'''}) + if end_col == col('$') + " found end-of-line, one character past the actual end + let end_col -= 1 + endif + + let closure_contents = sj#GetCols(start_col, end_col) + if closure_contents =~ '[({[.,*/%+-]$' + " ends in an opening bracket or operator of some sorts, so it's + " incomplete, don't touch it + return 0 + endif + + call sj#ReplaceCols(start_col, end_col, "{\n".closure_contents."\n}") + return 1 +endfunction + +function! sj#rust#JoinClosure() + if !sj#SearchUnderCursor('|.\{-}| {\s*$') + return 0 + endif + if search('|.\{-}| \zs{\s*$', 'W', line('.')) <= 0 + return 0 + endif + + " check if we've got an empty block: + if sj#GetMotion('va{') =~ '^{\_s*}$' + return 0 + endif + + let closure_contents = sj#Trim(sj#GetMotion('vi{')) + let lines = sj#TrimList(split(closure_contents, "\n")) + + if len(lines) > 1 + let replacement = '{ '.join(lines, ' ').' }' + elseif len(lines) == 1 + let replacement = lines[0] + else + " No contents, leave nothing inside + let replacement = ' ' + endif + + call sj#ReplaceMotion('va{', replacement) + return 1 +endfunction + +function! sj#rust#SplitCurlyBrackets() + " in case we're on a struct name, go to the bracket: + call sj#SearchUnderCursor('\k\+\s*{', 'e') + " in case we're in an if-clause, go to the bracket: + call sj#SearchUnderCursor('\ 0 + call sj#Align(body_start, body_end, 'json_object') + endif + endif + else + " it's just a normal block, ignore the parsed content + let body = substitute(body, ';\ze.', ";\n", 'g') + call sj#ReplaceCols(from, to, "{\n".body."\n}") + endif + + return 1 +endfunction + +function! sj#rust#JoinCurlyBrackets() + let line = getline('.') + if line !~ '{\s*$' + return 0 + endif + call search('{', 'c', line('.')) + + if eval(s:skip_syntax) + return 0 + endif + + " check if we've got an empty block: + if sj#GetMotion('va{') =~ '^{\_s*}$' + call sj#ReplaceMotion('va{', '{}') + return 1 + endif + + let body = sj#GetMotion('Vi{') + let lines = split(body, "\n") + let lines = sj#TrimList(lines) + + let body = join(lines, ' ') + " just in case we're joining a StructName { key: value, }: + let body = substitute(body, ',$', '', '') + + let in_import = 0 + if line =~ '^\s*use\s\+\%(\k\+::\)\+\s*{$' + let in_import = 1 + endif + if !in_import + let pos = getpos('.') + + " we might still be in a nested import, let's see if we can find it + while searchpair('{', '', '}', 'Wb', s:skip_syntax, 0, 100) > 0 + if getline('.') =~ '^\s*use\s\+\%(\k\+::\)\+\s*{$' + let in_import = 1 + break + endif + endwhile + + call setpos('.', pos) + endif + + if in_import + let body = '{'.body.'}' + elseif sj#settings#Read('curly_brace_padding') + let body = '{ '.body.' }' + else + let body = '{'.body.'}' + endif + + if sj#settings#Read('normalize_whitespace') + let body = substitute(body, '\s\+\k\+\zs:\s\+', ': ', 'g') + endif + + call sj#ReplaceMotion('Va{', body) + return 1 +endfunction + +function! sj#rust#SplitUnwrapIntoEmptyMatch() + let unwrap_pattern = '\S\.\%(unwrap\|expect\)(' + if sj#SearchUnderCursor(unwrap_pattern, 'e', s:skip_syntax) <= 0 + return 0 + endif + + normal! % + let unwrap_end_col = col('.') + normal! % + call search(unwrap_pattern, 'Wb', line('.')) + let end_col = col('.') + + let start_col = col('.') + while start_col > 0 + let current_expr = strpart(getline('.'), start_col - 1, end_col) + if current_expr =~ '^)' + normal! % + elseif current_expr =~ '^\%(::\|\.\)' + normal! h + else + if sj#SearchSkip('\%(::\|\.\)\=\k\+\%#', s:skip_syntax, 'Wb', line('.')) <= 0 + break + endif + endif + + if start_col == col('.') + " then nothing has changed this loop, break out + break + else + let start_col = col('.') + endif + endwhile + + let expr = sj#GetCols(start_col, end_col) + if expr == '' + return 0 + endif + + if start_col >= end_col + " the expression is probably split into several lines, let's ignore it + return 0 + endif + + call sj#ReplaceCols(start_col, unwrap_end_col, join([ + \ "match ".expr." {", + \ "", + \ "}", + \ ], "\n")) + return 1 +endfunction + +function! sj#rust#SplitIfLetIntoMatch() + let if_let_pattern = 'if\s\+let\s\+\(.*\)\s\+=\s\+\(.\{-}\)\s*{' + let else_pattern = '}\s\+else\s\+{' + + if search(if_let_pattern, 'We', line('.')) <= 0 + return 0 + endif + + let match_line = substitute(getline('.'), if_let_pattern, "match \\2 {\n\\1 => {", '') + let body = sj#Trim(sj#GetMotion('vi{')) + + " multiple lines or ends with `;` -> wrap it in a block + if len(split(body, "\n")) > 1 || body =~ ';'.s:eol_pattern + let body = "{\n".body."\n}" + endif + + " Is there an else clause? + call sj#PushCursor() + let else_body = '()' + normal! % + if search(else_pattern, 'We', line('.')) > 0 + let else_body = sj#Trim(sj#GetMotion('vi{')) + + " multiple lines or ends with `;` -> wrap it in a block + if len(split(else_body, "\n")) > 1 || else_body =~ ';'.s:eol_pattern + let else_body = "{\n".else_body."\n}" + endif + + " Delete block, delete rest of line: + normal! "_da{T}"_D + endif + + " Back to the if-let line: + call sj#PopCursor() + call sj#ReplaceMotion('V', match_line) + normal! j$ + call sj#ReplaceMotion('Va{', body.",\n_ => ".else_body.",\n}") + + return 1 +endfunction + +function! sj#rust#SplitArgs() + return s:SplitList(['(', ')'], 'cursor_on_line') +endfunction + +function! sj#rust#SplitArray() + return s:SplitList(['[', ']'], 'cursor_inside') +endfunction + +function! sj#rust#JoinEmptyMatchIntoIfLet() + let match_pattern = '\' + " it's a default match, ignore this one for now + let inner_start_lineno = search(pattern_pattern, 'Wb', outer_start_lineno) + if inner_start_lineno <= 0 + return 0 + endif + + if getline(inner_start_lineno) =~ '^\s*_\s*=>' + " more than one _ => clause? + return 0 + endif + endif + + if getline(inner_start_lineno) =~ '{,\=\s*$' + " it's a block, mark its area: + exe inner_start_lineno + normal! 0f{% + let inner_end_lineno = line('.') + else + " not a }, so just one line + let inner_end_lineno = inner_start_lineno + endif + + if prevnonblank(inner_start_lineno - 1) != outer_start_lineno + " the inner start is not immediately after the outer start + return 0 + endif + + let match_value = sj#Trim(matchstr(getline(outer_start_lineno), match_pattern)) + let match_pattern = sj#Trim(matchstr(getline(inner_start_lineno), pattern_pattern)) + + " currently on inner start, so let's take its contents: + if inner_start_lineno == inner_end_lineno + " one-line body, take everything up to the comma + exe inner_start_lineno + let body = substitute(getline('.'), '^\s*.\{-}\s\+=>\s*\(.\{-}\),\=\s*$', '\1', '') + else + " block body, take everything inside + let body = sj#Trim(sj#GetMotion('vi{')) + endif + + " look for an else clause + call sj#PushCursor() + exe outer_start_lineno + let else_body = '' + if search('^\s*_\s*=>\s*\zs\S', 'W', outer_end_lineno) > 0 + let fallback_value = strpart(getline('.'), col('.') - 1) + + if fallback_value =~ '^(\s*)\|^{\s*}' + " ignore it + elseif fallback_value =~ '^{' + " the else-clause is going to be in a block + let else_body = sj#Trim(sj#GetMotion('vi{')) + else + " one-line value, remove its trailing comma and any comments + let else_body = substitute(fallback_value, ','.s:eol_pattern, '', '') + endif + endif + + call sj#PopCursor() + + " jump on outer start + exe outer_start_lineno + call sj#ReplaceCols(match_start_col, col('$'), 'if let '.match_pattern.' = '.match_value." {\n") + normal! $ + call sj#ReplaceMotion('va{', "{\n".body."\n}") + + if else_body != '' + normal! 0f{% + call sj#ReplaceMotion('V', "} else {\n".else_body."\n}") + endif + + return 1 +endfunction + +function! sj#rust#SplitImportList() + if sj#SearchUnderCursor('^\s*use\s\+\%(\k\+::\)\+{', 'e') <= 0 + return 0 + endif + + let prefix = sj#Trim(strpart(getline('.'), 0, col('.') - 1)) + let body = sj#GetMotion('vi{') + let parser = sj#argparser#rust_struct#Construct(1, len(body), body) + + call parser.Process() + + let expanded_imports = [] + for arg in parser.args + let import = arg.argument + + if import == 'self' + let expanded_import = substitute(prefix, '::$', ';', '') + else + let expanded_import = prefix . import . ';' + end + + call add(expanded_imports, expanded_import) + endfor + + if len(expanded_imports) <= 0 + return 0 + endif + + let attributes = s:GetLineAttributes(line('.')) + + if len(attributes) > 0 + let attribute_block = join(attributes, "\n") + let expanded_imports = [expanded_imports[0]] + map(expanded_imports[1:-1], 'attribute_block . "\n" . v:val') + endif + + let replacement = join(expanded_imports, "\n") + if body =~ '\n' + " Select a multiline area + call sj#ReplaceMotion('va{$o0', replacement . "\n") + else + call sj#ReplaceMotion('V', replacement) + endif + + return 1 +endfunction + +function! sj#rust#JoinImportList() + let import_pattern = '^\s*use\s\+\%(\k\+::\)\+' + let attribute_pattern = '^\s*#[' + + if sj#SearchUnderCursor(import_pattern) <= 0 + return 0 + endif + + let first_import = getline('.') + let first_import = substitute(first_import, ';'.s:eol_pattern, '', '') + let imports = [sj#Trim(first_import)] + + let start_line = line('.') + let last_line = line('.') + let attributes = s:GetLineAttributes(start_line) + + " If there's no attributes, get the next line, otherwise skip the attribute + " lines + exe 'normal! ' . (len(attributes) + 1) . 'j' + + while sj#SearchUnderCursor(import_pattern) > 0 + if line('.') == last_line + " we haven't moved, stop here + break + endif + + let local_attributes = s:GetLineAttributes(line('.')) + if local_attributes != attributes + " This import is not compatible, stop here + break + endif + + let last_line = line('.') + + let import_line = getline('.') + let import_line = substitute(import_line, ';'.s:eol_pattern, '', '') + + call add(imports, sj#Trim(import_line)) + exe 'normal! ' . (len(attributes) + 1) . 'j' + endwhile + + if len(imports) <= 1 + return 0 + endif + + " find common prefix based on first two imports + let first_prefix_parts = split(imports[0], '::') + let second_prefix_parts = split(imports[1], '::') + + if first_prefix_parts[0] != second_prefix_parts[0] + " no match at all, nothing we can do + return 0 + endif + + " find only the next ones that match the common prefix + let common_prefix = '' + for i in range(1, min([len(first_prefix_parts), len(second_prefix_parts)]) - 1) + if first_prefix_parts[i] != second_prefix_parts[i] + let common_prefix = join(first_prefix_parts[:(i - 1)], '::') + break + endif + endfor + + if common_prefix == '' + if len(imports[0]) > len(imports[1]) + let longer_import = imports[0] + let shorter_import = imports[1] + else + let longer_import = imports[1] + let shorter_import = imports[0] + endif + + " it hasn't been changed, meaning we completely matched the shorter import + " within the longer. + if longer_import == shorter_import + " they're perfectly identical, just delete the first line and move on + exe start_line . 'delete' + return 1 + elseif stridx(longer_import, shorter_import) == 0 + " the shorter is included, consider it a prefix, and we'll puts `self` + " in there later + let common_prefix = shorter_import + else + " something unexpected went wrong, let's give up + return 0 + endif + endif + + let compatible_imports = imports[:1] + for import in imports[2:] + if stridx(import, common_prefix) == 0 + call add(compatible_imports, import) + else + break + endif + endfor + + " Get the differences between the imports + let differences = [] + for import in compatible_imports + let difference = strpart(import, len(common_prefix)) + let difference = substitute(difference, '^::', '', '') + + if difference =~ '^{.*}$' + " there's a list of imports, merge them together + let parser = sj#argparser#rust_struct#Construct(2, len(difference) - 1, difference) + call parser.Process() + for part in map(parser.args, 'v:val.argument') + call add(differences, part) + endfor + elseif len(difference) == 0 + " this is the parent module + call add(differences, 'self') + else + call add(differences, difference) + endif + endfor + + if exists('*uniq') + " remove successive duplicates + call uniq(differences) + endif + + let replacement = common_prefix . '::{' . join(differences, ', ') . '};' + + let attribute_line_count = (len(compatible_imports) - 1) * len(attributes) + let end_line = start_line + len(compatible_imports) + attribute_line_count - 1 + + call sj#ReplaceLines(start_line, end_line, replacement) + return 1 +endfunction + +function! sj#rust#JoinArgs() + return s:JoinList(['(', ')']) +endfunction + +function! sj#rust#JoinArray() + return s:JoinList(['[', ']']) +endfunction + +function! s:FunctionReturnType() + let found_result = search(')\_s\+->\_s\+\%(\k\|::\)*Result\>', 'Wbn') + let found_option = search(')\_s\+->\_s\+\%(\k\|::\)*Option\>', 'Wbn') + + if found_result <= 0 && found_option <= 0 + return '' + elseif found_result > found_option + return 'Result' + elseif found_option > found_result + return 'Option' + else + return '' + endif +endfunction + +function s:GetLineAttributes(line) + let end_line = prevnonblank(a:line - 1) + + if getline(end_line) !~ '^\s*#[' + return [] + endif + + let start_line = end_line + let tested_line = start_line + + while getline(tested_line) =~ '^\s*#[' + let start_line = tested_line + let tested_line = prevnonblank(tested_line - 1) + endwhile + + return getline(start_line, end_line) +endfunction + +function! s:SplitList(delimiter, cursor_position) + let start = a:delimiter[0] + let end = a:delimiter[1] + + let lineno = line('.') + let indent = indent('.') + + if a:cursor_position == 'cursor_inside' + let [from, to] = sj#LocateBracesAroundCursor(start, end) + elseif a:cursor_position == 'cursor_on_line' + let [from, to] = sj#LocateBracesOnLine(start, end) + else + echoerr "Invalid value for a:cursor_position: ".a:cursor_position + return + endif + + if from < 0 && to < 0 + return 0 + endif + + let line = getline('.') + + if start == '(' && from > 1 && strpart(line, 0, from - 1) =~ '\\)$' + let parser_type = 'fn' + else + let parser_type = 'list' + endif + + let parser = sj#argparser#rust_list#Construct(parser_type, from + 1, to - 1, line) + call parser.Process() + let items = parser.args + if empty(items) + return 0 + endif + + if sj#settings#Read('trailing_comma') + let body = start."\n".join(items, ",\n").",\n".end + else + let body = start."\n".join(items, ",\n")."\n".end + endif + + call sj#ReplaceMotion('Va'.start, body) + + return 1 +endfunction + +function! s:JoinList(delimiter) + let start = a:delimiter[0] + let end = a:delimiter[1] + + let line = getline('.') + + if line !~ start . '\s*$' + return 0 + endif + + call search(start, 'c', line('.')) + let body = sj#GetMotion('Vi'.start) + + let lines = split(body, "\n") + let lines = sj#TrimList(lines) + let body = sj#Trim(join(lines, ' ')) + let body = substitute(body, ',\s*$', '', '') + + call sj#ReplaceMotion('Va'.start, start.body.end) + + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/scss.vim b/bundle/splitjoin.vim/autoload/sj/scss.vim new file mode 100644 index 000000000..e70b259cf --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/scss.vim @@ -0,0 +1,68 @@ +function! sj#scss#SplitNestedDefinition() + if search('{\s*$', 'Wn', line('.')) <= 0 + return 0 + endif + + if search('\s\zs\S\+', 'Wbc', line('.')) <= 0 + return 0 + endif + + let prefix = sj#Trim(strpart(getline('.'), 0, col('.') - 2)) + let suffix = sj#Trim(strpart(getline('.'), col('.') - 2, col('$'))) + let suffix = substitute(suffix, '\s*{$', '', '') + + if prefix == '' || suffix == '' + return 0 + endif + + call sj#ReplaceMotion('V', prefix.' {') + normal! f{ + let body = sj#GetMotion('vi{') + call sj#ReplaceMotion('vi{', suffix." {\n".body."}\n") + + return 1 +endfunction + +function! sj#scss#JoinNestedDefinition() + if search('{\s*$', 'We', line('.')) <= 0 + return 0 + endif + + let outer_start_lineno = line('.') + + " find end point + normal! % + let outer_end_lineno = line('.') + let inner_end_lineno = prevnonblank(outer_end_lineno - 1) + if inner_end_lineno == 0 + " No inner end } found + return 0 + endif + if getline(inner_end_lineno) !~ '^\s*}\s*$' + " not a } character + return 0 + endif + + exe inner_end_lineno + normal! 0f}% + let inner_start_lineno = line('.') + + if prevnonblank(inner_start_lineno - 1) != outer_start_lineno + " the inner start is not immediately after the outer start + return 0 + endif + + let outer_definition = sj#Trim(substitute(getline(outer_start_lineno), '{\s*$', '', '')) + let inner_definition = sj#Trim(substitute(getline(inner_start_lineno), '{\s*$', '', '')) + + " currently on inner start, so let's take its contents: + let body = sj#Trim(sj#GetMotion('vi{')) + + " jump on outer start + exe outer_start_lineno + call sj#ReplaceMotion('V', outer_definition.' '.inner_definition. ' {') + normal! 0f{ + call sj#ReplaceMotion('va{', "{\n".body."\n}") + + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/settings.vim b/bundle/splitjoin.vim/autoload/sj/settings.vim new file mode 100644 index 000000000..67b2f8864 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/settings.vim @@ -0,0 +1,37 @@ +" This function accepts multiple option names and returns the value of the +" first one that is set. It respects buffer-local values before global ones, +" so: +" +" sj#settings#Read('option_foo', 'option_bar') +" +" would read the values of, in sequence: +" +" - b:splitjoin_option_foo +" - g:splitjoin_option_foo +" - b:splitjoin_option_bar +" - g:splitjoin_option_bar +" +" and return the first one that exists. If none exist, it reads the default +" value, if one is set in `g:splitjoin_default_settings` +" +function! sj#settings#Read(...) + let options = a:000 + + for option in options + if exists('b:splitjoin_'.option) + return b:['splitjoin_'.option] + endif + + if exists('g:splitjoin_'.option) + return g:['splitjoin_'.option] + endif + endfor + + for option in options + if has_key(g:splitjoin_default_settings, option) + return g:splitjoin_default_settings[option] + endif + endfor + + return 0 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/sh.vim b/bundle/splitjoin.vim/autoload/sj/sh.vim new file mode 100644 index 000000000..72d9e7520 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/sh.vim @@ -0,0 +1,40 @@ +function! sj#sh#SplitBySemicolon() + let line = getline('.') + let parser = sj#argparser#sh#Construct(0, col('$'), line) + call parser.Process() + + if len(parser.args) <= 1 + return 0 + endif + + let body = join(parser.args, "\n") + call sj#ReplaceMotion('V', body) + return 1 +endfunction + +function! sj#sh#SplitWithBackslash() + if !search('\S', 'Wc', line('.')) + return 0 + endif + + exe "normal! i\\\" + return 1 +endfunction + +function! sj#sh#JoinWithSemicolon() + if !nextnonblank(line('.') + 1) + return 0 + endif + + call sj#Keeppatterns('s/;\=\s*\n\_s*/; /e') + return 1 +endfunction + +function! sj#sh#JoinBackslashedLine() + if getline('.') !~ '\\\s*$' + return 0 + endif + + call sj#Keeppatterns('s/\\\=\s*\n\_s*//e') + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/tex.vim b/bundle/splitjoin.vim/autoload/sj/tex.vim new file mode 100644 index 000000000..704b74e2d --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/tex.vim @@ -0,0 +1,69 @@ +function! sj#tex#SplitBlock() + let arg_pattern = '[a-zA-Z*]' + let opts_pattern = '\%(\%({.\{-}}\)\|\%(\[.\{-}]\)\)*' + + if searchpair('\s*\zs\\begin{'.arg_pattern.'\{-}}'.opts_pattern, '', '\\end{'.arg_pattern.'\{-}}', 'bc', '') != line('.') + return 0 + endif + + let start = getpos('.') + if searchpair('\\begin{'.arg_pattern.'\{-}}', '', '\\end{'.arg_pattern.'\{-}\zs}', '') != line('.') + return 0 + endif + let end = getpos('.') + + let block = sj#GetByPosition(start, end) + + let pattern = '^\(\\begin{'.arg_pattern.'\{-}}'.opts_pattern.'\)\(.\{-}\)\(\\end{'.arg_pattern.'\{-}}\)$' + let match = matchlist(block, pattern) + if empty(match) + return 0 + endif + + let [_match, open, body, close; _rest] = match + let body = sj#Trim(substitute(body, '\\\\', '\\\\'."\n", 'g')) + let body = sj#Trim(substitute(body, '\s*\\item', "\n".'\\item', 'g')) + let replacement = open."\n".body."\n".close + + call sj#ReplaceByPosition(start, end, replacement) + return 1 +endfunction + +function! sj#tex#JoinBlock() + let arg_pattern = '[a-zA-Z*]' + let opts_pattern = '\%(\%({.\{-}}\)\|\%(\[.\{-}]\)\)*' + + if search('\s*\\begin{', 'bcW', line('.')) <= 0 + return 0 + endif + call search('\\begin{', 'cW', line('.')) + + let start = getpos('.') + if searchpair('\\begin{'.arg_pattern.'\{-}}', '', '\\end{'.arg_pattern.'\{-}\zs}') <= 0 + return 0 + endif + let end = getpos('.') + + let block = sj#GetByPosition(start, end) + + let pattern = '^\(\\begin{'.arg_pattern.'\{-}}'.opts_pattern.'\)\_s\+\(.\{-}\)\_s\+\(\\end{'.arg_pattern.'\{-}}\)$' + let match = matchlist(block, pattern) + if empty(match) + return 0 + endif + + let [_match, open, body, close; _rest] = match + + let lines = split(body, '\\\\\_s\+') + let body = join(lines, '\\ ') + + if body =~ '\\item' + let lines = sj#TrimList(split(body, '\\item')) + let body = '\item '.join(lines, ' \item ') + endif + + let replacement = open." ".body." ".close + + call sj#ReplaceByPosition(start, end, replacement) + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/vim.vim b/bundle/splitjoin.vim/autoload/sj/vim.vim new file mode 100644 index 000000000..2aa0837ac --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/vim.vim @@ -0,0 +1,110 @@ +function! sj#vim#Split() + if sj#BlankString(getline('.')) + return 0 + endif + + let new_line = sj#GetMotion('vg_') + + if sj#BlankString(new_line) + return 0 + else + let add_whitespace = 1 + + if col('.') > 1 + let pair = strpart(getline('.'), col('.') - 2, 2) + + if pair =~ '\s' + " we're breaking on whitespace, so make sure to add some: + let add_whitespace = 1 + elseif pair =~ '^\S\S$' + " we're breaking a word, so make sure not to have any: + let add_whitespace = 0 + endif + endif + + if add_whitespace + let new_line = "\n\\ ".sj#Trim(new_line) + else + let new_line = "\n\\".sj#Trim(new_line) + endif + + call sj#ReplaceMotion('vg_', new_line) + call sj#Keeppatterns('s/\s\+$//e') + + return 1 + endif +endfunction + +function! sj#vim#Join() + let continuation_pattern = '^\s*\\' + let current_lineno = line('.') + let next_lineno = current_lineno + 1 + let next_line = getline(next_lineno) + + if next_lineno > line('$') || next_line !~ continuation_pattern + return 0 + endif + + if next_line =~ continuation_pattern.'\s' + " Then there's some whitespace after the \, rely on :join + keeppatterns exe next_lineno.'s/'.continuation_pattern.'//' + exe current_lineno.','.next_lineno.'join' + else + " No whitespace, let's join them directly + keeppatterns exe current_lineno.'s/\n'.continuation_pattern.'//' + endif + + if sj#settings#Read('normalize_whitespace') + call sj#CompressWhitespaceOnLine() + endif + + return 1 +endfunction + +function! sj#vim#SplitIfClause() + let line = getline('.') + let pattern = '\v^\s*if .{-} \| .{-} \|\s*endif' + + if line !~# pattern + return 0 + endif + + let line_no = line('.') + let lines = split(line, '|') + let lines = map(lines, 'sj#Trim(v:val)') + let replacement = join(lines, "\n") + + call sj#ReplaceLines(line_no, line_no, replacement) + + return 1 +endfunction + +function! sj#vim#JoinIfClause() + let line = getline('.') + let pattern = '\v^\s*if' + + if line !~# pattern + return 0 + endif + + let if_line_no = line('.') + let endif_line_pattern = '^'.repeat(' ', indent(if_line_no)).'endif' + + let endif_line_no = search(endif_line_pattern, 'W') + + if endif_line_no <= 0 + return 0 + endif + + if endif_line_no - if_line_no != 2 + return 0 + endif + + let lines = sj#GetLines(if_line_no, endif_line_no) + let lines = map(lines, 'sj#Trim(v:val)') + let replacement = join(lines, ' | ') + + call sj#ReplaceLines(if_line_no, endif_line_no, replacement) + + return 1 +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/vue.vim b/bundle/splitjoin.vim/autoload/sj/vue.vim new file mode 100644 index 000000000..3a9f60461 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/vue.vim @@ -0,0 +1,32 @@ +function! sj#vue#SplitCssDefinition() + if s:GetVueSection() != 'style' + return 0 + endif + return sj#css#SplitDefinition() +endfunction + +function! sj#vue#JoinCssDefinition() + if s:GetVueSection() != 'style' + return 0 + endif + return sj#css#JoinDefinition() +endfunction + +function! sj#vue#SplitCssMultilineSelector() + if s:GetVueSection() != 'style' + return 0 + endif + return sj#css#SplitMultilineSelector() +endfunction + +function! sj#vue#JoinCssMultilineSelector() + if s:GetVueSection() != 'style' + return 0 + endif + return sj#css#JoinMultilineSelector() +endfunction + +function! s:GetVueSection() + let l:startofsection = search('\v^\<(template|script|style)\>', 'bnW') + return substitute(getline(startofsection), '\v[<>]', '', 'g') +endfunction diff --git a/bundle/splitjoin.vim/autoload/sj/yaml.vim b/bundle/splitjoin.vim/autoload/sj/yaml.vim new file mode 100644 index 000000000..1605d9194 --- /dev/null +++ b/bundle/splitjoin.vim/autoload/sj/yaml.vim @@ -0,0 +1,541 @@ +" Array Callbacks: +" ================ + +function! sj#yaml#SplitArray() + let [line, line_no, whitespace] = s:ReadCurrentLine() + + let prefix = '' + let array_part = '' + let indent = 1 + let end_offset = 0 + + let nestedExp = '\v^\s*((-\s+)+)(\[.*\])$' + let line = s:StripComment(line) + + " Split arrays which are map properties + " E.g. + " prop: [1, 2] + if line =~ ':\s*\[.*\]$' + let [key, array_part] = s:SplitKeyValue(line) + let prefix = key . ":\n" + + " Split nested arrays + " E.g. + " - [1, 2] + elseif line =~ nestedExp + let prefix = substitute(line, nestedExp, '\1', '') + let array_part = substitute(line, nestedExp, '\3', '') + let indent = len(substitute(line, '\v[^-]', '', 'g')) + let end_offset = -1 + endif + + if array_part != '' + let body = substitute(array_part, '\v^\s*\[(.*)\]\s*$', '\1', '') + let array_items = s:SplitArrayBody(body) + + call sj#ReplaceMotion('V', prefix . '- ' . join(array_items, "\n- ")) + silent! normal! zO + call s:SetIndentWhitespace(line_no, whitespace) + call s:IncreaseIndentWhitespace(line_no + 1, line_no + len(array_items) + end_offset, whitespace, indent) + + return 1 + endif + + return 0 +endfunction + +function! sj#yaml#JoinArray() + let [line, line_no, whitespace] = s:ReadCurrentLine() + + let lines = [] + let first_line = s:StripComment(line) + + let nestedExp = '\v^(\s*(-\s+)+)(-\s+.*)$' + let join_type = '' + + " Nested arrays + " E.g. + " - - 'one' + " - 'two' + if first_line =~ nestedExp && s:IsValidLineNo(line_no) + let join_type = 'nested' + let [lines, last_line_no] = s:GetChildren(line_no) + let lines = map(lines, 's:StripComment(v:val)') + let lines = [substitute(first_line, nestedExp, '\3', '')] + lines + let first_line = sj#Rtrim(substitute(first_line, nestedExp, '\1', '')) + + " Normal arrays + " E.g. + " list: + " - 'one' + " - 'two' + elseif first_line =~ ':$' && s:IsValidLineNo(line_no + 1) + let join_type = 'normal' + let [lines, last_line_no] = s:GetChildren(line_no) + let lines = map(lines, 's:StripComment(v:val)') + endif + + if !empty(lines) + if join_type == 'nested' + let body_lines = lines[1:len(lines)] + else + let body_lines = lines + endif + + for line in body_lines + if line !~ '^\s*$' && line !~ '^\s*-' + " one non-blank line is not part of the array, it must be a nested + " construct, can't handle that + return 0 + endif + + if line =~ nestedExp + " can't handle nested subexpressions + return 0 + endif + endfor + + let lines = map(lines, 'sj#Trim(substitute(v:val, "^\\s*-", "", ""))') + let lines = filter(lines, '!sj#BlankString(v:val)') + let replacement = first_line . ' [' . s:JoinArrayItems(lines) . ']' + + call sj#ReplaceLines(line_no, last_line_no, replacement) + silent! normal! zO + call s:SetIndentWhitespace(line_no, whitespace) + + return 1 + endif + + " then there's nothing to join + return 0 +endfunction + +" Map Callbacks: +" ================ + +function! sj#yaml#SplitMap() + let [from, to] = sj#LocateBracesOnLine('{', '}') + + if from >= 0 && to >= 0 + let [line, line_no, whitespace] = s:ReadCurrentLine() + let line = s:StripComment(line) + let pairs = sj#ParseJsonObjectBody(from + 1, to - 1) + let body = join(pairs, "\n") + let body_start = line_no + + let indent_level = 0 + let end_offset = -1 + + " Increase indention if the map is inside a nested array. + " E.g. + " - - { one: 1 } + if line =~ '^\s*-\s' + let indent_level = s:NestedArrayLevel(line) + endif + + " Move body into next line if it is a map property. + " E.g. + " prop: { one: 1 } + " - prop: { one: 1 } + if line =~ '^\v\s*(-\s+)*[^{]*:\s+\{.*' + let body = "\n" . body + let indent_level += 1 + let end_offset = 0 + let body_start = line_no + 1 + endif + + call sj#ReplaceMotion('Va{', body) + silent! normal! zO + call s:SetIndentWhitespace(line_no, whitespace) + call s:IncreaseIndentWhitespace(line_no + 1, line_no + len(pairs) + end_offset, whitespace, indent_level) + call sj#Keeppatterns(line_no . 's/\s*$//e') + + if sj#settings#Read('align') + let body_end = body_start + len(pairs) - 1 + call sj#Align(body_start, body_end, 'json_object') + endif + + return 1 + endif + + return 0 +endfunction + +function! sj#yaml#JoinMap() + let [line, line_no, whitespace] = s:ReadCurrentLine() + + if !s:IsValidLineNo(line_no + 1) + return 0 + endif + + let first_line = s:StripComment(line) + let lines = [] + let last_line_no = 0 + let join_type = '' + + let nestedExp = '\v^(\s*(-\s+)+)(.*)$' + let nestedPropExp = '\v^(\s*(-\s+)+.+:)$' + + " Nested in a map inside an array. + " E.g. + " - prop: + " one: 1 + if first_line =~ nestedPropExp + let join_type = 'nested_in_map_in_array' + let [lines, last_line_no] = s:GetChildren(line_no) + let first_line = sj#Rtrim(substitute(first_line, nestedPropExp, '\1', '')) + + " Map inside an array. + " E.g. + " - one: 1 + " two: 2 + elseif first_line =~ nestedExp + let join_type = 'nested_in_array' + let [lines, last_line_no] = s:GetChildren(line_no) + let lines = [substitute(first_line, nestedExp, '\3', '')] + lines + let first_line = sj#Rtrim(substitute(first_line, nestedExp, '\1', '')) + + if len(lines) <= 1 + " only 1 line means nothing to join in this case + return 0 + endif + + " Normal map + " E.g. + " map: + " one: 1 + " two: 2 + elseif first_line =~ '\k\+:\s*$' + let join_type = 'normal' + let [lines, last_line_no] = s:GetChildren(line_no) + endif + + if len(lines) > 0 + if join_type == 'nested_in_array' + let body_lines = lines[1:len(lines)] + else + let body_lines = lines + endif + + if len(body_lines) > 0 + let base_indent = len(matchstr(body_lines[0], '^\s*')) + endif + + for line in body_lines + if line =~ '^\s*-' + " one of the lines is a part of an array, we can't handle nested subexpressions + return 0 + endif + + if len(matchstr(line, '^\s*')) != base_indent + " a nested map, can't handle that + return 0 + endif + endfor + + let lines = sj#TrimList(lines) + let lines = s:NormalizeWhitespace(lines) + let lines = map(lines, 's:StripComment(v:val)') + + if sj#settings#Read('curly_brace_padding') + let replacement = first_line . ' { '. join(lines, ', ') . ' }' + else + let replacement = first_line . ' {'. join(lines, ', ') . '}' + endif + + call sj#ReplaceLines(line_no, last_line_no, replacement) + silent! normal! zO + call s:SetIndentWhitespace(line_no, whitespace) + + return 1 + endif + + return 0 +endfunction + +" Helper Functions: +" ================= + +" Reads line, line number and indention +function! s:ReadCurrentLine() + let line_no = line('.') + let line = getline(line_no) + let whitespace = s:GetIndentWhitespace(line_no) + + return [line, line_no, whitespace] +endfunction + +" Strip comments from string starting with a # +function! s:StripComment(s) + return substitute(a:s, '\v\s+#.*$', '', '') +endfunction + +" Check if current buffer has the line number +function! s:IsValidLineNo(no) + return a:no >= 0 && a:no <= line('$') +endfunction + +" Normalize whitespace, if enabled +function! s:NormalizeWhitespace(lines) + if sj#settings#Read('normalize_whitespace') + return map(a:lines, 'substitute(v:val, ":\\s\\+", ": ", "")') + endif + return a:lines +endfunction + +function! s:GetIndentWhitespace(line_no) + return substitute(getline(a:line_no), '^\(\s*\).*$', '\1', '') +endfunction + +function! s:SetIndentWhitespace(line_no, whitespace) + silent call sj#Keeppatterns(a:line_no . 's/^\s*/' . a:whitespace) +endfunction + +function! s:IncreaseIndentWhitespace(from, to, whitespace, level) + if a:whitespace =~ "\t" + let new_whitespace = a:whitespace . repeat("\t", a:level) + else + let new_whitespace = a:whitespace . repeat(' ', &sw * a:level) + endif + + for line_no in range(a:from, a:to) + call s:SetIndentWhitespace(line_no, new_whitespace) + endfor +endfunction + +" Get following lines with a greater indent than the current line +function! s:GetChildren(line_no) + let line_no = a:line_no + let next_line_no = line_no + 1 + let indent = indent(line_no) + let next_line = getline(next_line_no) + + " Count '- ' as indent, if an object is in an array + " E.g. (GetChildren for prop_a) + " list: + " - prop_a: + " - 1 + " prop_b + " - 2 + let line = getline(line_no) + if line =~ '^\s*\(\-\s\s*\)..*:$' + let prefix = substitute(getline(a:line_no), '^\s*\(\-\s\s*\)..*:$', '\1', '') + let indent += len(prefix) + end + + while s:IsValidLineNo(next_line_no) && + \ (sj#BlankString(next_line) || indent(next_line_no) > indent) + let next_line_no = next_line_no + 1 + let next_line = getline(next_line_no) + endwhile + let next_line_no = next_line_no - 1 + + " Preserve trailing empty lines + while sj#BlankString(getline(next_line_no)) && next_line_no > line_no + let next_line_no = next_line_no - 1 + endwhile + + return [sj#GetLines(line_no + 1, next_line_no), next_line_no] +endfunction + +" Split a string into individual array items. +" E.g. +" 'one, two' => ['one', 'two'] +" '{ one: 1 }, { two: 2 }' => ['{ one: 1 }', '{ two: 2 }'] +function! s:SplitArrayBody(body) + let items = [] + + let partial_item = '' + let rest = sj#Ltrim(a:body) + + while !empty(rest) + let char = rest[0] + + if char == '{' + let [item, rest] = s:ReadMap(rest) + let rest = s:SkipWhitespaceUntilComma(rest) + + call add(items, s:StripCurlyBrackets(item)) + + elseif char == '[' + let [item, rest] = s:ReadArray(rest) + let rest = s:SkipWhitespaceUntilComma(rest) + + call add(items, sj#Trim(item)) + + elseif char == '"' || char == "'" + let [item, rest] = s:ReadString(rest) + let rest = s:SkipWhitespaceUntilComma(rest) + + call add(items, sj#Trim(item)) + + else + let [item, rest] = s:ReadUntil(rest, ',') + call add(items, sj#Trim(item)) + endif + + let rest = sj#Ltrim(rest[1:]) + endwhile + + return items +endfunction + +" Read string until occurence of end_char +function! s:ReadUntil(str, end_char) + let idx = 0 + while idx < len(a:str) + if a:str[idx] == a:end_char + return idx == 0 + \ ? ['', a:str[1:]] + \ : [a:str[:idx-1], a:str[idx+1:]] + endif + + let idx += 1 + endwhile + + return [a:str, ''] +endfunction + +" Read the next string fenced by " or ' +function! s:ReadString(str) + if len(a:str) > 0 + let fence = a:str[0] + if fence == '"' || fence == "'" + let [str, rest] = s:ReadUntil(a:str[1:], fence) + return [fence . str . fence, rest] + endif + endif + + return ['', a:str] +endfunction + +" Read the next array, including nested arrays. +" E.q. +" '[[1, 2]], [1]' => ['[[1, 2]], ', [1]'] +function! s:ReadArray(str) + return s:ReadContainer(a:str, '[', ']') +endfunction + +" Read the next map, including nested maps. +" E.q. +" '{ one: 1, foo: { two: 2 } }, {}' => ['{ one: 1, foo: { two: 2 } }, ', {}'] +function! s:ReadMap(str) + return s:ReadContainer(a:str, '{', '}') +endfunction + +function! s:ReadContainer(str, start_char, end_char) + let content = '' + let rest = a:str + let depth = 0 + + while !empty(rest) + let char = rest[0] + let rest = rest[1:] + + let content .= char + + if char == a:start_char + let depth += 1 + elseif char == a:end_char + let depth -= 1 + + if depth == 0 | break | endif + endif + endwhile + + return [content, rest] +endfunction + +" skip whitespace and next comma +function! s:SkipWhitespaceUntilComma(str) + let [space, rest] = s:ReadUntil(a:str, ',') + + if !sj#BlankString(space) + throw '"' . space . '" is not whitespace!' + end + return rest +endfunction + +function! s:JoinArrayItems(items) + return join(map(a:items, 's:AddCurlyBrackets(v:val)'), ', ') +endfunction + +" Add curly brackets if required for joining +" E.g. +" 'one: 1' => '{ one: 1 }' +" 'one' => 'one' +function! s:AddCurlyBrackets(line) + let line = sj#Trim(a:line) + + if line !~ '^\v\[.*\]$' && line !~ '^\v\{.*\}$' + let [key, value] = s:SplitKeyValue(line) + if key != '' + return '{ ' . a:line . ' }' + endif + endif + + return a:line +endfunction + +" Strip curly brackets if possible +" E.g. +" '{ one: 1 }' => 'one: 1' +" '{ one: 1, two: 2 }' => '{ one: 1, two: 2 }' +function! s:StripCurlyBrackets(item) + let item = sj#Trim(a:item) + + if item =~ '^{.*}$' + let parser = sj#argparser#js#Construct(2, len(item) - 1, item) + call parser.Process() + + if len(parser.args) == 1 + let item = substitute(item, '^{\s*', '', '') + let item = substitute(item, '\s*}$', '', '') + endif + endif + + return item +endfunction + +" Split a string into key and value +" E.g. +" 'one: 1' => ['one', '1'] +" 'one' => ['', 'one'] +" 'one:' => ['one', ''] +" 'a:val' => ['', 'a:val'] +function! s:SplitKeyValue(line) + let line = sj#Trim(a:line) + let parts = [] + + let first_char = line[0] + + let key = '' + let value = '' + + " Read line starts with a fenced string. E.g + " 'one': 1 + " 'one' + if first_char == '"' || first_char == "'" + let [key, rest] = s:ReadString(line) + let [_, value] = s:ReadUntil(rest, ':') + else + let parts = split(line . ' ', ': ') + let [key, value] = [parts[0], join(parts[1:], ': ')] + endif + + if value == '' && a:line !~ '\s*:$' + let [key, value] = ['', key] + endif + + return [sj#Trim(key), sj#Trim(value)] +endfunction + +" Calculate the nesting level of an array item +" E.g. +" - foo => 1 +" - - bar => 2 +function! s:NestedArrayLevel(line) + let prefix = substitute(a:line, '\v^\s*((-\s+)+).*', '\1', '') + let levels = substitute(prefix, '[^-]', '', 'g') + return len(levels) +endfunction diff --git a/bundle/splitjoin.vim/doc/splitjoin.txt b/bundle/splitjoin.vim/doc/splitjoin.txt new file mode 100644 index 000000000..884045302 --- /dev/null +++ b/bundle/splitjoin.vim/doc/splitjoin.txt @@ -0,0 +1,2127 @@ +*splitjoin.txt* Switch between single-line and multiline forms of code + +============================================================================== +CONTENTS *splitjoin* *splitjoin-contents* + + Installation...........................: |splitjoin-installation| + Usage..................................: |splitjoin-usage| + + C......................................: |splitjoin-c| + Clojure................................: |splitjoin-clojure| + Coffeescript...........................: |splitjoin-coffee| + CSS....................................: |splitjoin-css| + CUE....................................: |splitjoin-cue| + Elixir.................................: |splitjoin-elixir| + Elm....................................: |splitjoin-elm| + Eruby..................................: |splitjoin-eruby| + Go.....................................: |splitjoin-go| + HAML...................................: |splitjoin-haml| + Handlebars.............................: |splitjoin-handlebars| + HTML...................................: |splitjoin-html| + Java...................................: |splitjoin-java| + Javascript.............................: |splitjoin-javascript| |splitjoin-json| + JSX/TSX................................: |splitjoin-jsx| |splitjoin-tsx| + Lua....................................: |splitjoin-lua| + Perl...................................: |splitjoin-perl| + PHP....................................: |splitjoin-php| + Python.................................: |splitjoin-python| + R......................................: |splitjoin-r| + Ruby...................................: |splitjoin-ruby| + Rust...................................: |splitjoin-rust| + SCSS/Less..............................: |splitjoin-scss| |splitjoin-less| + Shell..................................: |splitjoin-shell| + TeX....................................: |splitjoin-tex| + Vimscript..............................: |splitjoin-vimscript| + YAML...................................: |splitjoin-yaml| + + Settings...............................: |splitjoin-settings| + Internals..............................: |splitjoin-internals| + Issues.................................: |splitjoin-issues| + + +============================================================================== +INSTALLATION *splitjoin-installation* + +The easiest way to install the plugin is with a plugin manager: + +- vim-plug: https://github.com/junegunn/vim-plug +- Vundle: https://github.com/VundleVim/Vundle.vim + +If you use one, just follow the instructions in its documentation. + +You can install the plugin yourself using Vim's |packages| functionality by +cloning the project (or adding it as a submodule) under +`~/.vim/pack//start/`. For example: +> + git clone https://github.com/AndrewRadev/splitjoin.vim ~/.vim/pack/_/start/splitjoin +< +This should automatically load the plugin for you on Vim start. Alternatively, +you can add it to `~/.vim/pack//opt/` instead and load it in your +.vimrc manually with: +> + packadd splitjoin +< +If you'd rather not use git, you can download the files from the "releases" +tab and unzip them in the relevant directory: +https://github.com/AndrewRadev/splitjoin.vim/releases. + + +============================================================================== +USAGE *splitjoin-usage* + + *:SplitjoinSplit* + *:SplitjoinJoin* + +After the plugin is installed, the mapping `gS` will perform splitting and |gJ| +-- joining of the code under the cursor. These mappings are configurable +with |g:splitjoin_split_mapping| and |g:splitjoin_join_mapping|, respectively. +You could also use the commands |:SplitjoinSplit| and |:SplitjoinJoin|, either +directly, or in your own scripts. + +Note that |gJ| is a built-in mapping that is used for joining lines while +preserving whitespace. However, if no splitting/joining is possible at this +point, the plugin will fall back to the default mapping. If you'd rather have +the splitjoin mappings be no-ops in that case, you could set the mapping +variables to empty strings, which will simply not create them at all. You can +then make your own simple mappings by using the commands: +> + let g:splitjoin_split_mapping = '' + let g:splitjoin_join_mapping = '' + + nmap j :SplitjoinJoin + nmap s :SplitjoinSplit +< +For the record, my personal preference is to avoid mnemonics in this case and +go for an approach that makes more sense to my fingers instead: +> + nmap sj :SplitjoinSplit + nmap sk :SplitjoinJoin +< +Notice that I'm using "sj" for splitting, not joining. To me, "splitting" a +line results in expanding it downwards, so using "j" seems more intuitive, +likewise for "k". The "s" key is ordinarily taken (try :help s), but I don't +use it, so I've mapped it to . Your mileage may vary. + +Splitting ~ + +Splitting a line consists of checking for blocks of text that the plugin knows +how to split and, well, doing that. For example, if we have a ruby hash: +> + { :one => 'two', :three => 'four' } +< +Then, with the cursor within the hash, we can split it to get: +> + { + :one => 'two', + :three => 'four' + } +< +This works for various other things, you can see some examples for each +filetype below. + +If there are several different kinds of splittings that can be executed, there +is a fixed priority. For instance, this: +> + { :one => 'two', :three => 'four' } if foo? +< +will be split into this: +> + if foo? + { :one => 'two', :three => 'four' } + end +< +In this case, the plugin does not take into account where exactly the cursor +is located on the line, it just always gives priority to the if clause. + +For ruby hashes in particular, the cursor position is considered, however. +Let's take this as an example: +> + foo 1, 2, { :bar => :baz }, { :baz => :qux } +< +If the cursor is located on the first hash, the result will be: +> + foo 1, 2, { + :bar => :baz + }, { :baz => :qux } +< +If it's on the second hash, or on any other part of the method call (like on +"foo"), you'll get this: +> + foo 1, 2, { :bar => :baz }, { + :baz => :qux + } +< +In general, if you're trying to split a structure, try to "be inside" when you +do so. This doesn't make sense in cases like the "if" statement, but it does +for hashes. + +Joining ~ + +Joining might impose more constraints. Take this as an example: +> + if foo? + bar + end +< +In order to turn this into: +> + bar if foo? +< +you need to place your cursor on the line with the "if" clause. If your cursor +is on the "bar" line, joining will not work. This might be considered a bug (I +find it simpler cognitively to join blocks when I'm within them), but it +simplifies the implementation and solves some ambiguity. This might be a nice +example: +> + if foo? + { + :one => :two, + :three => :four + } + end +< +Joining when on the line ":one => :two" would currently do nothing. However, +if we wanted to detect the type of joining we could do, we might give priority +to the if clause instead of the hash, which would not make a lot of sense. Of +course, with smart prioritization (or a change in implementation), it might be +possible to get things working sensibly, but this seems to be good enough for +now: To join the hash, be on the "{" line, to join the "if" clause (not a good +idea, mind you, doesn't do anything that makes sense), be on the "if foo?" +line. + +The basic rule of thumb here is that, to join a structure, the cursor should +usually be at its beginning (the opening tag, the opening brace, etc.). + +Settings ~ + +The plugin has many settings that implement different coding styles. It can be +made to align dictionary items, leave or remove trailing commas, and so on. +See |splitjoin-settings| for the full list. + +Note that all of the settings apart from mapping ones can be set as both +global variables, and buffer-local ones. So, for instance, you could set +|g:splitjoin_align| to 0 in order to avoid aligning code in most cases, but +set |b:splitjoin_align| to 1 in your `~/.vim/ftplugin/ruby.vim` file to align +ruby code in particular. The buffer-local variables will take precedence. + +Now for some examples for the filetypes that have splitjoin implementations. + + +============================================================================== +C *splitjoin-c* + +If clauses ~ +> + if (val1 && val2 || val3); + + if (val1 + && val2 + || val3); +< +Function calls ~ +> + myfunction(arg1, arg2, arg3, arg4); + + myfunction(arg1, + arg2, + arg3, + arg4); +< +============================================================================== +CLOJURE *splitjoin-clojure* + +Lists ~ +> + (map (fn [x] (.toUpperCase x)) (.split "one two three" " ")) + (map + (fn [x] (.toUpperCase x)) + (.split "one two three" " ")) + + [::namespace/function one two three] + [::namespace/function + one + two + three] + + #{:a :b :c :d} + #{:a + :b + :c + :d} +< + +============================================================================== +COFFEESCRIPT *splitjoin-coffee* + +Functions ~ +> + (foo, bar) -> console.log foo + + (foo, bar) -> + console.log foo +< +If/unless/while/until clauses ~ + +Since it's possible to join a multiline if into either a postfix or suffix +variant, a variable controls which one it'll be, +|splitjoin_coffee_suffix_if_clause|. By default, it's 1, which joins into the +suffix format. +> + console.log bar if foo? + if foo? then console.log bar + + if foo? + console.log bar +< +Ternary operator ~ + +Splitting takes into account the entire line. If the line starts with +assignment, it tries to squeeze in the assignment part on both lines. + +Joining attempts to do the same process in reverse -- if the same variable is +being assigned to different things in both cases, that variable is moved out +in front of the if-clause. Otherwise, it just joins the if-then-else without +any magic. +> + foo = if bar? then 'baz' else 'qux' + + if bar? + foo = 'baz' + else + foo = 'qux' + + foo = if bar? then 'baz' else 'qux' +< +Object literals ~ +> + one = { one: "two", three: "four" } + + one = + one: "two" + three: "four" +< +Object literals in function calls ~ + +Only splitting works this way for now, the reverse direction falls back to the +normal object literal joining. +> + foo = functionCall(one, two, three: four, five: six) + + foo = functionCall one, two, + three: four + five: six +< +Multiline strings ~ + +Note that strings are split only at the end of the line. This seems to be the +most common case, and the restriction avoids conflicts with other kinds of +splitting. +> + foo = "example with #{interpolation} and \"nested\" quotes" + foo = """ + example with #{interpolation} and "nested" quotes + """ + + bar = 'example with single quotes' + bar = ''' + example with single quotes + ''' +< + +============================================================================== +CSS *splitjoin-css* + +These also work for SCSS and LESS -- see |splitjoin-scss|, |splitjoin-less|. + +Style definitions ~ +> + a { color: #0000FF; text-decoration: underline; } + + a { + color: #0000FF; + text-decoration: underline; + } + +Multiline selectors ~ +> + h1, + h2, + h3 { + font-size: 18px; + font-weight: bold; + } + + h1, h2, h3 { + font-size: 18px; + font-weight: bold; + } +< + +============================================================================== +CUE *splitjoin-cue* + +CUE Structs are JSON objects but with a cleaner syntax. Lists and Function +arguments behave like JSON's. +See |splitjoin-json|. + +Structs ~ + +Structs are first class, so the cursor can precede the first curly brace. +> + a: foo: { x: bar: baz: bool, y: bar: baz: int, z: bar: baz: string } + + a: foo: { + x: bar: baz: bool + y: bar: baz: int + z: bar: baz: string + } +< +Lists ~ + +The same applies to lists. +> + foo: [ 'x:y:z', "\xFFFF0000", a.foo.y ] + + foo: [ + 'x:y:z', + "\xFFFF0000", + a.foo.y + ] +< +Function Arguments ~ + +Function splitting requires the cursor to be positioned inside the +parenthesis, preferably near the closing one. +> + bar: m.Baz(foo[2].bar.baz, 42, true) + + bar: m.Baz( + foo[2].bar.baz, + 42, + true + ) +< + + +============================================================================== +ELIXIR *splitjoin-elixir* + +Do-blocks ~ +> + def function(arguments) when condition, do: body + def function(arguments) when condition do + body + end + + let :one, do: two() |> three(four()) + let :one do + two() |> three(four()) + end + + if(foo, do: bar, else: baz) + if foo do + bar + else + baz + end +< +Comma-separated method calls (join only): ~ +> + for a <- 1..10, + Integer.is_odd(a) do + a + end + + for a <- 1..10, Integer.is_odd(a) do + a + end +< +Pipelines: ~ +> + String.length("splitjoin") + + "splitjoin" + |> String.length() +< +This doesn't currently work properly for multi-line arguments: +> + String.length( + Enum.join([ + "split", + "join" + ]) + ) + + if true do + "splitjoin" + end + |> String.length() +< + +============================================================================== +ELM *splitjoin-elm* + +Lists, tuples, records ~ +> + myUpdatedRecord = + {myPreviousRecord | firstName = "John", lastName = "Doe"} + + myUpdatedRecord = + { myPreviousRecord + | firstName = "John" + , lastName = "Doe" + } +< + +============================================================================== +ERUBY *splitjoin-eruby* + +Tags ~ +> +
bar
+ +
+ bar +
+< +If/unless clauses ~ +> + <%= foo if bar? %> + + <% if bar? %> + <%= foo %> + <% end %> +< +Hashes ~ +> + <% foo = { :bar => 'baz', :one => :two, :five => 'six' } %> + + <% foo = { + :bar => 'baz', + :one => :two, + :five => 'six' + } %> +< +Option hashes ~ +> + <%= link_to 'Google', 'http://google.com', :class => 'google', :id => 'google-link' %> + + <%= link_to 'Google', 'http://google.com', { + :class => 'google', + :id => 'google-link' + } %> +< + +============================================================================== +GO *splitjoin-go* + +Imports ~ +> + import "fmt" + + import ( + "fmt" + ) +< +Var/const ~ +> + var foo string + + var ( + foo string + ) +< +Structs ~ +> + StructType{one: 1, two: "asdf", three: []int{1, 2, 3}} + + StructType{ + one: 1, + two: "asdf", + three: []int{1, 2, 3}, + } +< + +============================================================================== +HAML *splitjoin-haml* + +Evaluated ruby ~ +> + %div= 1 + 2 + + %div + = 1 + 2 +< + +============================================================================== +HANDLEBARS *splitjoin-handlebars* + +Components ~ +> + {{some/component-name foo=bar bar=baz}} + + {{some/component-name + foo=bar + bar=baz + }} +< +Block components ~ +> + {{#component-name foo=bar}}Some content{{/component-name}} + + {{#component-name foo=bar}} + Some contents + {{/component-name}} +< + +============================================================================== +HTML *splitjoin-html* + +This works for other HTML-like languages as well: XML, Eruby, JSX, TSX, Vue +templates, Svelte.js, Django templates. + +Tags ~ +> +
bar
+ +
+ bar +
+< +Attributes ~ +> + + + +< + +============================================================================== +JAVA *splitjoin-java* + +If-clause bodies ~ +> + if (isTrue()) + doSomething(); + + if (isTrue()) doSomething(); + + if (isTrue()) { + doSomething(); + } + + if (isTrue()) { doSomething(); } +< +If-clause conditions ~ +> + if (val1 && val2 || val3) + body(); + + if (val1 + && val2 + || val3) + body(); +< +Function calls ~ +> + myfunction(arg1, arg2, arg3, arg4); + + myfunction(arg1, + arg2, + arg3, + arg4); +< +Lambda functions ~ +> + some_function(foo -> "bar"); + + some_function(foo -> { + return "bar"; + }); +< + +============================================================================== +JAVASCRIPT *splitjoin-javascript* + *splitjoin-json* + +Object and Array splitting also work for JSON, if it's set as a separate +filetype. (If it's just set to "javascript", it'll just apply javascript +logic). If the filetype is "json", trailing commas will automatically be +disabled, too. + +Objects ~ + +Just like in ruby and python, the cursor needs to be inside the object in +order to split it. +> + var one = {one: "two", three: "four"}; + + var one = { + one: "two", + three: "four" + }; +< +Arrays ~ +> + var one = ['two', 'three', 'four']; + + var one = [ + 'two', + 'three', + 'four' + ]; + +Function Arguments ~ +> + var foo = bar('one', 'two', 'three'); + + var foo = bar( + 'one', + 'two', + 'three' + ); +< +Functions ~ + +When the cursor is on the "function" keyword, the script attempts to split the +curly braces of the function. This is a bit more convenient for the common +use-case of one-line to multi-line functions. +> + var callback = function (something, other) { something_else; }; + + var callback = function (something, other) { + something_else; + }; +< +One-line if conditionals ~ +> + if (isTrue()) { + doSomething(); + } + + if (isTrue()) doSomething(); +< +Fat-arrow functions ~ +> + some_function(foo => "bar"); + + some_function(foo => { + return "bar"; + }); +< + +============================================================================== +JSX *splitjoin-jsx* + *splitjoin-tsx* + +Both HTML and Javascript splitters and joiners work for JSX and TSX. There's +also one additional transformation: + +Self-closing tags ~ +> + let button = ; + + +Note that, in Vim, these languages are supported within the `javascrptreact` +and `typescriptreact` filetypes, so if callbacks don't work right, +double-check the detected filetype of the buffer. + +============================================================================== +LUA *splitjoin-lua* + +For Lua, only splitting and joining functions is implemented at this point. +Note that joining a function attempts to connect the lines of the body by +using ";". This doesn't always work -- a few constructs are not syntactically +valid if joined in this way. Still, the idea is to inline small functions, so +it's assumed this is not a big issue. + +Functions ~ +> + function example () + print("foo") + print("bar") + end + + function example () print("foo"); print("bar") end + + local something = other(function (one, two) + print("foo") + end) + + local something = other(function (one, two) print("foo") end) +< + +============================================================================== +PERL *splitjoin-perl* + +If/unless/while/until clauses ~ + +The variable |splitjoin_perl_brace_on_same_line| controls the format of the +curly braces when joining. If it's set to 0, the opening curly brace will be +on its own line. Otherwise, it'll be placed on the same line as the if-clause +(the default behaviour). +> + print "a = $a\n" if $debug; + + if ($debug) { + print "a = $a\n"; + } +< +And/or clauses ~ + +It only makes sense to split these -- joining results in joining an if/unless +clause. The variable |splitjoin_perl_brace_on_same_line| affects the results +as explained above. +> + open PID, ">", $pidfile or die; + + unless (open PID, ">", $pidfile) { + die; + } +< +Hashes ~ +> + my $info = {name => $name, age => $age}; + + my $info = { + name => $name, + age => $age, + }; +< +Lists ~ +> + my $var = ['one', 'two', 'three']; + + my $var = [ + 'one', + 'two', + 'three' + ]; + + my @var = ('one', 'two', 'three'); + + my @var = ( + 'one', + 'two', + 'three' + ); +< +Word lists ~ +> + my @var = qw(one two three); + + my @var = qw( + one + two + three + ); +< + +============================================================================== +PHP *splitjoin-php* + +Arrays ~ +> + foo = array('one' => 'two', 'two' => 'three') + + foo = array( + 'one' => 'two', + 'two' => 'three' + ) +< +Short arrays ~ +> + $one = ['two', 'three', 'four'] + + $one = [ + 'two', + 'three', + 'four' + ] +< +If-clauses ~ +> + if ($foo) { $a = "bar"; } + + if ($foo) { + $a = "bar"; + } +< +PHP markers ~ +> + + + +< +Method calls~ + +Affects all the arrows after the cursor when |splitjoin_php_method_chain_full| +is set to 1. Otherwise, it affects only a single arrow (the default +behaviour). +> + $var = $one->two->three()->four(); + + $var = $one + ->two->three()->four(); + + # OR + + $var = $one + ->two + ->three() + ->four(); + + +============================================================================== +PYTHON *splitjoin-python* + +Just like in ruby, the cursor needs to be inside the dict in order to split it +correctly, otherwise it tries to split it as a statement (which works, due to +the dict having ":" characters in it). + +Dicts ~ +> + knights = {'gallahad': 'the pure', 'robin': 'the brave'} + + knights = { + 'gallahad': 'the pure', + 'robin': 'the brave' + } +< +Lists ~ +> + spam = [1, 2, 3] + + spam = [1, + 2, + 3] +< +Tuples ~ +> + spam = (1, 2, 3) + + spam = (1, + 2, + 3) +< +List comprehensions ~ +> + result = [x * y for x in range(1, 10) for y in range(10, 20) if x != y] + + result = [x * y + for x in range(1, 10) + for y in range(10, 20) + if x != y] +< +Statements ~ +> + if foo: bar() + + if foo: + bar() +< +Imports ~ +> + from foo import bar, baz + + from foo import bar,\ + baz +< +Ternary assignment ~ +> + max_x = x1 if x1 > x2 else x2 + + if x1 > x2: + max_x = x1 + else: + max_x = x2 +< +Multiple assignment ~ +> + a, b = foo("bar"), [one, two, three] + + a = foo("bar") + b = [one, two, three] + + un, pack = something + + un = something[0] + pack = something[1] +< +Note that splitting `a, b = b, a` would not result in an expression that works +the same way, due to the special handling by python of this case to swap two +values. + +============================================================================== +R *splitjoin-r* + +Function calls ~ +> + print(1, 2, 3) + + # with g:r_indent_align_args = 0 + print( + 1, + 2, + 3 + ) + + # with g:r_indent_align_args = 1 + print(1, + 2, + 3) +< + +============================================================================== +RUBY *splitjoin-ruby* + +If/unless/while/until clauses ~ + +Joining works for more-than-one-line "if" clauses as well, but it doesn't look +very pretty. It's generally recommended to use it only when the body is a +single line. +> + return "the answer" if 6 * 9 == 42 + + if 6 * 9 == 42 + return "the answer" + end +< +Hashes ~ + +To split a hash, you need to be within the curly brackets. Otherwise, the +plugin attempts to split it as a block. +> + foo = { :bar => 'baz', :one => 'two' } + + foo = { + :bar => 'baz', + :one => 'two' + } +< +Option hashes ~ + +There's an option, |splitjoin_ruby_curly_braces|, that controls whether the +curly braces are present after splitting or joining. +> + foo 1, 2, :one => 1, :two => 2 + + foo 1, 2, { + :one => 1, + :two => 2 + } + + # note that after joining, the result will be: + + foo 1, 2, { :one => 1, :two => 2 } +< +Method arguments ~ + +These only get split if there is no option hash at the end. + +The variable |splitjoin_ruby_hanging_args| controls whether the arguments +will be left "hanging", aligned near the brackets, or if the brackets will be +put on their own lines. + +Joining for the "hanging" style doesn't really work, since there's no easy, +reliable way to detect the continued arguments. However, a simple vanilla-vim +|J| should do the trick. +> + params.permit(:title, :action, :subject_type, :subject_id, :own) + + params.permit(:title, + :action, + :subject_type, + :subject_id, + :own) + + # with splitjoin_ruby_hanging_args == 0 + + params.permit( + :title, + :action, + :subject_type, + :subject_id, + :own + ) +< +Caching constructs ~ +> + @two ||= 1 + 1 + + @two ||= begin + 1 + 1 + end +< +Blocks ~ +> + Bar.new { |b| puts b.to_s } + + Bar.new do |b| + puts b.to_s + end +< +Block &-shorthand ~ +> + [1, 2, 3].map(&:to_s) + + [1, 2, 3].map do |i| + i.to_s + end +< +Heredocs ~ + +You can change whether it splits into `<<`, `<<-`, or `<<~` by setting the +value of the |splitjoin_ruby_heredoc_type| setting (by default, it's `<<~`). +> + string = 'something' + + string = <<~EOF + something + EOF +< +Ternaries ~ +> + if condition + do_foo + else + do_bar + end + + condition ? do_foo : do_bar +< +Cases ~ + +Splits or joins single when clauses, if the cursors sits on the line of +such a when, or the whole case, if you have or cursor in the line of the +case-keyword, as shown in the example. +> + case condition + when :a + do_foo + when :b + do_bar + else + do_baz + end + + case condition + when :a then do_foo + when :b then do_bar + else do_baz + end +> +Arrays ~ +> + list = ['one', 'two', 'three'] + + list = [ + 'one', + 'two', + 'three' + ] +< +Array literals ~ +> + list = %w{one two three} + + list = %w{ + one + two + three + } +< +Module namespacing ~ + +Note that splitting and joining module namespaces relies on the built-in +|matchit| plugin. Splitjoin will attempt to load it if it isn't loaded +already, but if that fails for some reason, this logic will silently not work. +> + module Foo + class Bar < Baz + def qux + end + end + end + + class Foo::Bar < Baz + def qux + end + end +< +Module namespacing in RSpec tests ~ + +Same as above: uses the |matchit| plugin. +> + module Foo + RSpec.describe Bar do + it "does stuff" do + end + end + end + + RSpec.describe Foo::Bar do + it "does stuff" do + end + end +< +Method continuations ~ + +For the moment, this only works in the direction of joining, since splitting +is too ambiguous (how to decide whether to split the method dot or the +function's arguments?). +> + one. + two.three(foo, bar) + one + .two.three(foo, bar) + + one.two.three(foo, bar) + +Ruby 3.0 endless def ~ +> + def foo(one, two) + bar + end + + def foo(one, two) = bar +< + +============================================================================== +RUST *splitjoin-rust* + +Structs ~ +> + Scanner { source: String::new(), line: 1 } + + Scanner { + source: String::new(), + line: 1 + } +< +Function definitions, calls, and arrays ~ +> + fn function_call(values: Vec, s: &'static str) -> (); + fn function_call( + values: Vec, + s: &'static str, + ) -> (); + + function_call(Vec::::new(), &ref); + function_call( + Vec::::new(), + &ref, + ); + + vec![one, two, three]; + vec![ + one, + two, + three, + ]; +< +Match clauses ~ +> + match one { + Ok(two) => some_expression(three), + } + + match one { + Ok(two) => { + some_expression(three) + }, + } +< +Question mark operator ~ + +The plugin determines how to split a `?` by looking upwards for a `-> Result` +or `-> Option` . If it can't find anything when splitting, it'll default to a +`Result`. If it can't find anything when joining, it'll convert the match into +an `.unwrap()` instead. +> + let file = File::open("foo.txt")?; + + let file = match File::open("foo.txt") { + Ok(value) => value, + Err(e) => return Err(e.into()), + }; + + let thing = Some(3)?; + + let thing = match Some(3) { + None => return None, + Some(value) => value, + }; +< +Closures ~ +> + function_call(|x| x.to_string(), y); + + function_call(|x| { + x.to_string() + }, y); +< +Unwrap/Expect match split ~ + +This one only splits for the moment. The cursor MUST be on `unwrap` in order +to get this effect, in this particular example. + +The plugin attempts to find the end of the expression, and make a match +statement out of it. It also works with `expect` calls. +> + let foo = Some::value(chain).of(things).unwrap(); + + let foo = match Some::value(chain).of(things) { + + } +< +Empty matches and if-let ~ +> + if let Some(value) = iterator.next() { + println!("do something with {}", value); + } + + match iterator.next() { + Some(value) => { + println!("do something with {}", value); + }, + _ => (), + } +< +Import lists ~ + +With the cursor on the pre-curly bracket part of an import: +> + use std::io::{Read, Write, Process}; + + use std::io::Read; + use std::io::Write; + use std::io::Process; +< +Joining works downwards -- attempting to join the current line with as many as +possible below. It will look for the common part between the first two imports +and try to apply it on any later ones that match. + +With the cursor in the curly brackets: +> + use std::io::{Read, Write}; + + use std::io::{ + Read, + Write + }; +< + +============================================================================== +SCSS *splitjoin-scss* +LESS *splitjoin-less* + +Everything that works for CSS should work as well, and there's extra +functionality for the added nesting possibility. + +Nested definitions ~ + +When splitting, the cursor position determines which part gets extracted into +a separate definition. Joining only works if there's only one child definition. +> + ul li { + a { + padding: 10px; + } + } + + ul li a { + padding: 10px; + } +< + +============================================================================== +SHELL *splitjoin-shell* + +Support for shell scripts is quite basic -- splitting and joining by +semicolon. That's why it should be compatible with Bash, ZSH, etc. Activates +for the `sh` and `zsh` filetypes. +> + echo "one"; echo "two" + + echo "one" + echo "two" +< +If the line is not made up of semicolon-separated commands, it gets broken up +with a backslash at the cursor position, for example with the cursor on the +pipe: +> + echo "one" | wc -c + + echo "one" \ + | wc -l +< +If there is a broken line like that, joining should always join it first, +before trying anything else. + +============================================================================== +TEX *splitjoin-tex* + +Begin-end blocks ~ +> + \begin{align*} x = y\\ y = z \end{align*} + + \begin{align*} + x = y\\ + y = z + \end{align*} +< +Enumerations ~ +> + \begin{enumerate} \item item1 \item item2 \end{enumerate} + + \begin{enumerate} + \item item1 + \item item2 + \end{enumerate} +< + +============================================================================== +VIMSCRIPT *splitjoin-vimscript* + +Vimscript can generally be split anywhere by simply placing the remainder of +the line on the next one, prefixed by a backslash. That's why joining is +fairly easy to do for the most general case -- anything that is followed by a +line, starting with a backslash, can be joined with the current one. +> + let example_one = { + \ 'one': 'two', + \ 'three': 'four' + \ } + + " is joined (one line at a time) into: + + let example_one = { 'one': 'two', 'three': 'four' } + + command! Foo if one | + \ 'two' | + \ else | + \ 'three' | + \ endif + + " is joined (one line at a time) into: + + command! Foo if one | 'two' | else | 'three' | endif +< +Splitting is a bit trickier, since anything can be split at any point. While +it's possible to handle some specific cases like dictionaries, arrays, and +commands, for now the plugin takes the simple approach of splitting precisely +where the cursor is right now. In the future, this may be replaced with +specific splits based on the context. + + +============================================================================== +YAML *splitjoin-yaml* + +Arrays ~ +> + root: + one: [1, 2] + two: ['three', 'four'] + + root: + one: + - 1 + - 2 + two: + - 'three' + - 'four' +< +Maps ~ +> + root: + one: { foo: bar } + two: { three: ['four', 'five'], six: seven } + + root: + one: + foo: bar + two: + three: ['four', 'five'] + six: seven +< + +============================================================================== +SETTINGS *splitjoin-settings* + +These are the variables that control the behaviour of the plugin. + +Check the tags on the right side. The ones starting with `g:` are global +settings, while the ones starting with `b:` are buffer-local. The settings +that don't start with either of these two exist in both forms -- you can have +one global value for the setting and different buffer-local ones. + + + *b:splitjoin_split_callbacks* + *b:splitjoin_join_callbacks* +> + let b:splitjoin_split_callbacks = [...] + let b:splitjoin_join_callbacks = [...] +< +Default value: depends on the filetype + +These two variables contain lists of functions that are called to execute the +splitting or joining functionality. If they are set to an empty array in a +particular file, this will effectively disable the plugin for it. You can look +through the source code of the plugin to see the functions that are currently +being executed for your filetype. + +Example: +Putting the following in ftplugin/ruby.vim will disable the join functionality +for ruby files: +> + let b:splitjoin_join_callbacks = [] +< + + + *g:splitjoin_split_mapping* + *g:splitjoin_join_mapping* +> + let g:splitjoin_split_mapping = 'cS' + let g:splitjoin_join_mapping = 'cJ' +< + +Default values: 'gS' and 'gJ' + +Changing these values changes the default mappings of the plugin. Note that, +if no splitting or joining can be performed, these default mappings will fall +back to performing the key sequence's built-in functionality. + +Set to a blank string to disable default mappings completely. You can still +create your own mapping the old-fashioned way using the |:SplitjoinSplit| and +|:SplitjoinJoin| commands, though in the case with no possible +splitting/joining, nothing will happen. + + + *splitjoin_quiet* +> + let g:splitjoin_quiet = 1 +< +Default value: 0 + +The plugin will output a message, "Splitjoin: Working...", when splitting or +joining, which will be cleared when it's done. Ideally, this will only show up +for a fraction of a second, but with very large code blocks, there might be +work to do. A regrettable example is splitting a large ruby hash -- the plugin +itself doesn't do a lot of work, but indentation ends up rather slow in this +particular scenario. + +In order to silence these messages, if you find them annoying, set this +variable to 1. + + *splitjoin_mapping_fallback* +> + let g:splitjoin_mapping_fallback = 0 +< +Default value: 1 + +If the plugin doesn't split or join something, it'll execute the sequence of +keys normally. So, if joining is mapped to its default of `gJ` and there's +nothing valid to join, it'll execute the built-in |gJ|. + +Setting this value to 0 will disable this behaviour. + + + *splitjoin_disabled_split_callbacks* +> + let g:splitjoin_disabled_split_callbacks = ['sj#html#SplitAttributes'] +< +Default value: [] + +This setting allows you to disable a particular split type by adding its +function callback to the list. To find the specific name to disable, you'd +need to dig into the source code of the plugin in +`ftplugin//splitjoin.vim`. If you're not sure which callback +performs which split, try disabling them one by one until you've got the +behaviour you're looking for. + + *splitjoin_disabled_join_callbacks* +> + let g:splitjoin_disabled_join_callbacks = ['sj#html#JoinAttributes'] +< +Default value: [] + +This setting allows you to disable a particular join type by adding its +function callback to the list. To find the specific name to disable, you'd +need to dig into the source code of the plugin in +`ftplugin//splitjoin.vim`. If you're not sure which callback +performs which join, try disabling them one by one until you've got the +behaviour you're looking for. + + *splitjoin_normalize_whitespace* +> + let g:splitjoin_normalize_whitespace = 0 +< +Default value: 1 + +This variable controls whether duplicate whitespace should be reduced within a +joined structure, which makes a lot of sense in most situations, particularly +when the items are aligned. Set it to 0 to disable this behaviour. + +Example: +When this setting is enabled, the extra whitespace around "=>" symbols in ruby +hashes is removed: +> + one = { + :one => 'two', + :three => 'four', + :a => 'b' + } + + one = { :one => 'two', :three => 'four', :a => 'b' } +< + + *splitjoin_align* +> + let g:splitjoin_align = 1 +< +Default value: 0 + +This is a flag that controls whether a few constructs should be aligned by a +certain character. As a specific example, when you split ruby hashes, this can +align them by the "=>" signs. In a way, |splitjoin_normalize_whitespace| is +a complement to this setting, since you'd probably want to reduce the extra +whitespace when joining. + +Set the flag to 1 to attempt alignment. In order for it to work, it requires +that you have either Tabular (https://github.com/godlygeek/tabular) or Align +(http://www.vim.org/scripts/script.php?script_id=294) installed. If that's not +the case, the value of this setting will be ignored. + +Example: +> + one = { :one => 'two', :three => 'four', :a => 'b' } + + one = { + :one => 'two', + :three => 'four', + :a => 'b' + } +< + + *splitjoin_curly_brace_padding* +> + let g:splitjoin_curly_brace_padding = 0 +< +Default value: 1 + +Controls whether joining things with curly braces will add a space between the +brackets and the joined body. So, setting it to 0 or 1 results in, respectively: +> + import {one, two, three} from 'foo'; + import { one, two, three } from 'foo'; +< + + + *splitjoin_trailing_comma* +> + let g:splitjoin_trailing_comma = 1 +< +Default value: 0 + +This adds a trailing comma when splitting lists of things. There is a +ruby-specific setting called |splitjoin_ruby_trailing_comma|, but it's +preferred to use this one. You can easily set it per-filetype by using the +buffer-local variable with the same name. + +Example: +> + one = { :one => 'two', :a => 'b' } + + one = { + :one => 'two', + :a => 'b', + } +< + + *splitjoin_ruby_curly_braces* +> + let g:splitjoin_ruby_curly_braces = 0 +< +Default value: 1 + +This flag controls the formatting of ruby option hashes when splitting. +When it's 1, curly braces will be present in option blocks. Example: +> + User.new :one, :first_name => "Andrew", :last_name => "Radev" + + User.new :one, { + :first_name => "Andrew", + :last_name => "Radev" + } +< +When the flag is 0, the result will be: +> + User.new :one, + :first_name => "Andrew", + :last_name => "Radev" +< +This won't always have effect. In some cases, it's not syntactically valid to +omit the curly braces, which is part of the reason I prefer having them +around. However, when there's a non-optional argument or the option hashes is +wrapped in round braces, it should work just fine. + +Regardless of the value of this option, the second example will be joined back +to: +> + User.new :one, :first_name => "Andrew", :last_name => "Radev" +< +That's because it's easy to infer that it's an option block. Unfortunately, +it's more difficult to decide whether we have an option block or a plain hash +if there are braces, so the first example will always be joined to: +> + User.new :one, { :first_name => "Andrew", :last_name => "Radev" } +< + + *splitjoin_ruby_trailing_comma* +> + let g:splitjoin_ruby_trailing_comma = 1 +< +Default value: 0 + +This controls whether to put a trailing comma on a split hash. With this set +to 1, a hash will split like so: +> + User.new :one, :first_name => "Andrew", :last_name => "Radev" + + User.new :one, { + :first_name => "Andrew", + :last_name => "Radev", + } +< +Note the trailing comma for the last element. + +It's preferred to use the global option |splitjoin_trailing_comma|, and set the +buffer-local one for ruby to something different, if you'd like to. + + + *splitjoin_ruby_hanging_args* +> + let g:splitjoin_ruby_hanging_args = 0 +< +Default value: 1 + +This controls whether to split function arguments in the "hanging" style: +> + params.permit(:title, + :action, + :subject) +< +If it is set to 0, the result will be: +> + params.permit( + :title, + :action, + :subject + ) +< + + *splitjoin_ruby_do_block_split* +> + g:splitjoin_ruby_do_block_split +< +Default value: 1 + +This controls whether to convert split blocks to their do-form. It's set to +"1" by default, so block split like so: +> + [1, 2, 3].map { |n| n ** 2 } + + [1, 2, 3].map do |n| + n ** 2 + end +< +If it is set to 0, the result will be: +> + [1, 2, 3].map { |n| n ** 2 } + + [1, 2, 3].map { |n| + n ** 2 + } +< + + *splitjoin_ruby_options_as_arguments* +> + let g:splitjoin_ruby_options_as_arguments = 1 +< +Default value: 0 + +If set to 1, this will split options along with arguments, except if the +cursor is on one of the options. + +Ordinarily, splitting function arguments will always split options, if there +are any, and only split positional arguments, if there are no options. Like in +this example: +> + User.new(:one, :two, first_name: "Andrew", last_name: "Radev") + + User.new(:one, :two, + first_name: "Andrew", + last_name: "Radev") +< +With this option set to 1, the plugin will split BOTH arguments and options, +if the cursor is on the arguments, and it will split ONLY options if the +cursor is on the options. + +So, with the cursor on "two": +> + User.new(:one, + :two, + first_name: "Andrew", + last_name: "Radev") +< +But with the cursor on "first_name", you'll still get: +> + User.new(:one, :two, + first_name: "Andrew", + last_name: "Radev") +< +The output varies according to other settings as well, like curly brackets. + + + *splitjoin_ruby_expand_options_in_arrays* +> + let g:splitjoin_ruby_expand_options_in_arrays = 1 +< +Default value: 0 + +If set to 1, then the last hash of an array will be expanded to "options". For +example: +> + array = [one, two, { three: four, five: six }] + array = [ + one, + two, + three: four, + five: six, + ] +< + + *splitjoin_coffee_suffix_if_clause* +> + let g:splitjoin_coffee_suffix_if_clause = 0 +< + +Default value: 1 + +This flag controls the kind of if-clause to use when joining multiline +if-clauses in coffeescript. Given the following example: +> + if foo? + console.log bar +< +Joining this construct with |splitjoin_coffee_suffix_if_clause| set to 1 (the +default) would produce: +> + console.log bar if foo? +< +Doing that with |splitjoin_coffee_suffix_if_clause| set to 0 would result in: +> + if foo? then console.log bar +< + + *splitjoin_perl_brace_on_same_line* +> + let g:splitjoin_perl_brace_on_same_line = 0 +< +Default value: 1 + +This flag controls the placement of curly braces when joining if-clauses. When +it's 1 (the default), the opening brace will be placed on the same line: +> + if ($debug) { + print "a = $a\n"; + } +< +If it's set to 0, the brace will get its own line: +> + if ($debug) + { + print "a = $a\n"; + } +< + + *splitjoin_ruby_heredoc_type* +> + let g:splitjoin_ruby_heredoc_type = '<<-' +< +Default value: "<<~" + +This setting can be one of "<<-", "<<~", and "<<" and controls how strings +will be split into heredocs. If it's "<<-", the following form is used +> + do + foo = <<-EOF + something + EOF + end +< +If it's set to "<<", the result is this: +> + do + foo = < + do + foo = <<~EOF + something + EOF + end +< + + *splitjoin_python_brackets_on_separate_lines* +> + let g:splitjoin_python_brackets_on_separate_lines = 1 +< +Default value: 0 + +If set to 1, then python will split lists and tuples so that the opening and +closing bracket are placed on separate lines. If it's 0, the first argument +will remain where it is, and the rest will be split on separate lines. + +Example: +> + # let g:splitjoin_python_brackets_on_separate_lines = 1 + some_method( + one, + two + ) + + # let g:splitjoin_python_brackets_on_separate_lines = 0 + some_method(one, + two) +< +The first example might look a bit odd, but if you have the python-pep8-indent +plugin (https://github.com/hynek/vim-python-pep8-indent), it should look quite +reasonable. + + + *splitjoin_handlebars_closing_bracket_on_same_line* +> + let g:splitjoin_handlebars_closing_bracket_on_same_line = 1 +< +Default value: 0 + +If set to 1, then handlebars will keep the closing "}}" on the same line as +the last line of the component. At the time of writing, this isn't indented +very well, but it might be improved in the future. + +If it's 0, the closing "}}" will be placed on its own line. + + + *splitjoin_handlebars_hanging_arguments* +> + let g:splitjoin_handlebars_hanging_arguments = 1 +< +Default value: 0 + +If set to 1, then handlebars will keep one argument on the first line when +splitting, so the component will look "hanging". With the closing bracket on +the same line, as above, and the right indentation plugin, it might look like +this: +> + {{foo-bar one="two" + three="four"}} +< +If it's 0, the default, all parameters will be on their own line: +> + {{foo-bar + one="two" + three="four"}} +< + + *splitjoin_html_attributes_bracket_on_new_line* +> + let g:splitjoin_html_attributes_bracket_on_new_line = 1 +< +Default value: 0 + +If set to 1, then splitting HTML attributes will put the closing angle bracket +on a new line on its own, like this: +> +
+ text +
+< +When set to 0, as is the default, it will look like this: +> +
+ text +
+< + *splitjoin_html_attributes_hanging* +> + let g:splitjoin_html_attributes_hanging = 1 +< +Default value: 0 + +If set to 1, then splitting HTML attributes will keep the first attribute on +the same line, and split the rest. Combined with indentation support, it +should look like this: +> + +< +When set to 0, as is the default, it will look like this: +> + +< + + *splitjoin_php_method_chain_full* +> + let g:splitjoin_php_method_chain_full = 1 +< +Default value: 0 + +If set to 1, then splitting a method chain will split all the arrows after the +cursor. +> + $var = $foo->one()->two()->three(); + +Splitting on "->two" if set to 0: +> + $var = $foo->one() + ->two()->three(); + +If set to 1: +> + $var = $foo->one() + ->two() + ->three(); + +Joining a chain will also join all the methods calls. + + *splitjoin_java_argument_split_first_newline* + *splitjoin_java_argument_split_last_newline* +> + let g:splitjoin_java_argument_split_first_newline = 1 + let g:splitjoin_java_argument_split_last_newline = 1 +< +Default value: 0 + +These variables control whether a newline will be placed between the bracket +and the first or last item of a java argument list when splitting. With the +default, both set to 0, a split might look like this: +> + functionCall(one, + two, + three) +< +With both values set to 1, a space will be left before the first item and +after the last one: +> + functionCall( + one, + two, + three + ) +< +This is a setting that might be generalized for other languages and constructs +at a later time. + + *splitjoin_c_argument_split_first_newline* + *splitjoin_c_argument_split_last_newline* +> + let g:splitjoin_c_argument_split_first_newline = 1 + let g:splitjoin_c_argument_split_last_newline = 1 +< +Default value: 0 + +Same like the arguments for java. + + *splitjoin_vim_split_whitespace_after_backslash* +> + let g:splitjoin_vim_split_whitespace_after_backslash = 0 +< +Default value: 1 + +Whether to leave a single space after the `\` of a line break. In many cases, +when breaking a line, one space is left between the backslash and the line +part. However, if it's broken at a `.`, it could cause parsing problems. Not +leaving a space might be safer, set to 0 to do that. + + +============================================================================== +INTERNALS *splitjoin-internals* + +The only interface of the plugin is in 'plugin/splitjoin.vim'. It's a fairly +short file containing two commands, |:SplitjoinSplit| and |:SplitjoinJoin|. All +of the actual splitting and joining logic is in autoloaded files. The only +things that these two commands do are: + +- Check the |b:splitjoin_join_callbacks| and |b:splitjoin_split_callbacks| + respectively for a list of function names. +- Invoke the functions, in order. If any of the functions returns a number + different than 0, stop. + +The actual functions may do whatever they want, but it makes sense for them to +return 0 only if they haven't made any modifications to the buffer. + +The function names could be buffer-local, global, autoloaded, anything the +|function()| call can use. + +Obviously, extending the plugin is straightforward -- it's enough to define a +function for splitting and one for joining and add those to the buffer +variable. Of course, that doesn't imply it's easy -- the functions would need +to actually perform all the necessary manipulations and simply inform the +plugin if they've been successful by returning a number other than 0 as a +result. + +The file 'autoload/sj.vim' contains helpers that might be useful for said +manipulations. There are functions for replacing bodies of text defined by +normal mode motions or by line ranges, for saving and restoring the cursor +position and possibly other interesting functions that might assist. They +should be commented reasonably well. + +The other files in 'autoload/sj' might be useful as well, although they're +mostly filetype-specific. + +The files in 'autoload/sj/argparser' contain small parsers for parts of a few +of the languages that are supported. They're necessary for splitting +dictionary objects, since those can have a lot of structure and usually can't +be analyzed properly with just regular expressions. + +============================================================================== +ISSUES *splitjoin-issues* + +- If |g:splitjoin_align| is truthy and the Align plugin is being used, the + "undo" action undoes only the alignment first, then the splitting. +- Joining ruby option hashes could result in a pair of unnecessary curly + braces. + +Any other issues and suggestions are very welcome on the github bugtracker: +https://github.com/AndrewRadev/splitjoin.vim/issues + + +vim:tw=78:sw=4:ft=help:norl: diff --git a/bundle/splitjoin.vim/ftplugin/c/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/c/splitjoin.vim new file mode 100644 index 000000000..ab015bfc4 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/c/splitjoin.vim @@ -0,0 +1,13 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#c#SplitIfClause', + \ 'sj#c#SplitFuncall', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#c#JoinFuncall', + \ 'sj#c#JoinIfClause', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/clojure/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/clojure/splitjoin.vim new file mode 100644 index 000000000..a9d142a79 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/clojure/splitjoin.vim @@ -0,0 +1,11 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#clojure#SplitList', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#clojure#JoinList', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/coffee/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/coffee/splitjoin.vim new file mode 100644 index 000000000..a956ff89a --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/coffee/splitjoin.vim @@ -0,0 +1,20 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#coffee#SplitTernaryClause', + \ 'sj#coffee#SplitTripleString', + \ 'sj#coffee#SplitString', + \ 'sj#coffee#SplitFunction', + \ 'sj#coffee#SplitIfClause', + \ 'sj#coffee#SplitObjectLiteral', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#coffee#JoinString', + \ 'sj#coffee#JoinFunction', + \ 'sj#coffee#JoinIfElseClause', + \ 'sj#coffee#JoinIfClause', + \ 'sj#coffee#JoinObjectLiteral', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/cs/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/cs/splitjoin.vim new file mode 100644 index 000000000..a96550150 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/cs/splitjoin.vim @@ -0,0 +1,15 @@ +" Use C syntax for C# for now + +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#c#SplitIfClause', + \ 'sj#c#SplitFuncall', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#c#JoinFuncall', + \ 'sj#c#JoinIfClause', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/css/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/css/splitjoin.vim new file mode 100644 index 000000000..58f870c9d --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/css/splitjoin.vim @@ -0,0 +1,13 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#css#SplitDefinition', + \ 'sj#css#SplitMultilineSelector', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#css#JoinDefinition', + \ 'sj#css#JoinMultilineSelector', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/cue/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/cue/splitjoin.vim new file mode 100644 index 000000000..4084bef2d --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/cue/splitjoin.vim @@ -0,0 +1,16 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#cue#SplitStructLiteral', + \ 'sj#cue#SplitArray', + \ 'sj#cue#SplitArgs', + \ 'sj#cue#SplitImports', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#cue#JoinStructLiteral', + \ 'sj#cue#JoinArray', + \ 'sj#cue#JoinArgs', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/elixir/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/elixir/splitjoin.vim new file mode 100644 index 000000000..563b74958 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/elixir/splitjoin.vim @@ -0,0 +1,12 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#elixir#SplitDoBlock', + \ 'sj#elixir#SplitArray', + \ 'sj#elixir#SplitPipe', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#elixir#JoinDoBlock', + \ 'sj#elixir#JoinArray', + \ 'sj#elixir#JoinCommaDelimitedItems', + \ 'sj#elixir#JoinPipe', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/elm/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/elm/splitjoin.vim new file mode 100644 index 000000000..2eef958bf --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/elm/splitjoin.vim @@ -0,0 +1,11 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#elm#SplitBraces', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#elm#JoinBraces', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/eruby/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/eruby/splitjoin.vim new file mode 100644 index 000000000..5179e920a --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/eruby/splitjoin.vim @@ -0,0 +1,13 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#eruby#SplitHtmlTags', + \ 'sj#eruby#SplitIfClause', + \ 'sj#ruby#SplitOptions', + \ 'sj#eruby#SplitHtmlAttributes', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#eruby#JoinIfClause', + \ 'sj#ruby#JoinHash', + \ 'sj#html#JoinAttributes', + \ 'sj#html#JoinTags', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/go/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/go/splitjoin.vim new file mode 100644 index 000000000..d9411a5b7 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/go/splitjoin.vim @@ -0,0 +1,17 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#go#SplitImports', + \ 'sj#go#SplitVars', + \ 'sj#go#SplitFunc', + \ 'sj#go#SplitFuncCall', + \ 'sj#go#SplitStruct', + \ 'sj#go#SplitSingleLineCurlyBracketBlock', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#go#JoinImports', + \ 'sj#go#JoinVars', + \ 'sj#go#JoinStructDeclaration', + \ 'sj#go#JoinStruct', + \ 'sj#go#JoinFuncCallOrDefinition', + \ 'sj#go#JoinSingleLineFunctionBody', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/haml/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/haml/splitjoin.vim new file mode 100644 index 000000000..f811a82f6 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/haml/splitjoin.vim @@ -0,0 +1,9 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#haml#SplitInterpolation', + \ 'sj#haml#SplitInlineInterpolation', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#haml#JoinInterpolation', + \ 'sj#haml#JoinToInlineInterpolation', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/handlebars/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/handlebars/splitjoin.vim new file mode 100644 index 000000000..421c455af --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/handlebars/splitjoin.vim @@ -0,0 +1,13 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#html#SplitTags', + \ 'sj#html#SplitAttributes', + \ 'sj#handlebars#SplitBlockComponent', + \ 'sj#handlebars#SplitComponent', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#html#JoinAttributes', + \ 'sj#html#JoinTags', + \ 'sj#handlebars#JoinBlockComponent', + \ 'sj#handlebars#JoinComponent', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/html/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/html/splitjoin.vim new file mode 100644 index 000000000..921f387fa --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/html/splitjoin.vim @@ -0,0 +1,13 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#html#SplitTags', + \ 'sj#html#SplitAttributes' + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#html#JoinAttributes', + \ 'sj#html#JoinTags' + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/htmldjango/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/htmldjango/splitjoin.vim new file mode 100644 index 000000000..0a96b1180 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/htmldjango/splitjoin.vim @@ -0,0 +1,13 @@ +" Use html callbacks + +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#html#SplitTags' + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#html#JoinTags' + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/java/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/java/splitjoin.vim new file mode 100644 index 000000000..1784e43a4 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/java/splitjoin.vim @@ -0,0 +1,17 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#java#SplitIfClauseBody', + \ 'sj#java#SplitIfClauseCondition', + \ 'sj#java#SplitLambda', + \ 'sj#java#SplitFuncall', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#java#JoinLambda', + \ 'sj#java#JoinIfClauseCondition', + \ 'sj#java#JoinFuncall', + \ 'sj#java#JoinIfClauseBody', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/javascript/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/javascript/splitjoin.vim new file mode 100644 index 000000000..3dc9fbe98 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/javascript/splitjoin.vim @@ -0,0 +1,21 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#js#SplitFunction', + \ 'sj#js#SplitObjectLiteral', + \ 'sj#js#SplitFatArrowFunction', + \ 'sj#js#SplitArray', + \ 'sj#js#SplitOneLineIf', + \ 'sj#js#SplitArgs', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#js#JoinFatArrowFunction', + \ 'sj#js#JoinArray', + \ 'sj#js#JoinArgs', + \ 'sj#js#JoinFunction', + \ 'sj#js#JoinOneLineIf', + \ 'sj#js#JoinObjectLiteral', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/javascriptreact/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/javascriptreact/splitjoin.vim new file mode 100644 index 000000000..8ccac6207 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/javascriptreact/splitjoin.vim @@ -0,0 +1 @@ +runtime ftplugin/jsx/splitjoin.vim diff --git a/bundle/splitjoin.vim/ftplugin/json/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/json/splitjoin.vim new file mode 100644 index 000000000..85bc76cc7 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/json/splitjoin.vim @@ -0,0 +1,12 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#js#SplitObjectLiteral', + \ 'sj#js#SplitArray', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#js#JoinArray', + \ 'sj#js#JoinObjectLiteral', + \ ] + +" JSON does not support trailing commas +let b:splitjoin_trailing_comma = 0 diff --git a/bundle/splitjoin.vim/ftplugin/jsx/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/jsx/splitjoin.vim new file mode 100644 index 000000000..ad733a240 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/jsx/splitjoin.vim @@ -0,0 +1,24 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#jsx#SplitJsxExpression', + \ 'sj#html#SplitTags', + \ 'sj#html#SplitAttributes', + \ 'sj#js#SplitObjectLiteral', + \ 'sj#js#SplitFatArrowFunction', + \ 'sj#js#SplitArray', + \ 'sj#js#SplitFunction', + \ 'sj#js#SplitOneLineIf', + \ 'sj#js#SplitArgs', + \ 'sj#jsx#SplitSelfClosingTag' + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#jsx#JoinJsxExpression', + \ 'sj#html#JoinAttributes', + \ 'sj#jsx#JoinHtmlTag', + \ 'sj#js#JoinFatArrowFunction', + \ 'sj#js#JoinArray', + \ 'sj#js#JoinArgs', + \ 'sj#js#JoinFunction', + \ 'sj#js#JoinOneLineIf', + \ 'sj#js#JoinObjectLiteral', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/less/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/less/splitjoin.vim new file mode 100644 index 000000000..7a944548e --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/less/splitjoin.vim @@ -0,0 +1,15 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#css#SplitMultilineSelector', + \ 'sj#scss#SplitNestedDefinition', + \ 'sj#css#SplitDefinition', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#scss#JoinNestedDefinition', + \ 'sj#css#JoinDefinition', + \ 'sj#css#JoinMultilineSelector', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/lua/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/lua/splitjoin.vim new file mode 100644 index 000000000..b593f398e --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/lua/splitjoin.vim @@ -0,0 +1,13 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#lua#SplitTable', + \ 'sj#lua#SplitFunction', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#lua#JoinTable', + \ 'sj#lua#JoinFunction', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/perl/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/perl/splitjoin.vim new file mode 100644 index 000000000..43b189853 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/perl/splitjoin.vim @@ -0,0 +1,22 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#perl#SplitSuffixIfClause', + \ 'sj#perl#SplitPrefixIfClause', + \ 'sj#perl#SplitAndClause', + \ 'sj#perl#SplitOrClause', + \ 'sj#perl#SplitHash', + \ 'sj#perl#SplitWordList', + \ 'sj#perl#SplitSquareBracketedList', + \ 'sj#perl#SplitRoundBracketedList', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#perl#JoinIfClause', + \ 'sj#perl#JoinHash', + \ 'sj#perl#JoinWordList', + \ 'sj#perl#JoinSquareBracketedList', + \ 'sj#perl#JoinRoundBracketedList', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/php/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/php/splitjoin.vim new file mode 100644 index 000000000..f34700d2a --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/php/splitjoin.vim @@ -0,0 +1,19 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#php#SplitMethodChain', + \ 'sj#php#SplitArray', + \ 'sj#php#SplitIfClause', + \ 'sj#php#SplitElseClause', + \ 'sj#php#SplitBraces', + \ 'sj#html#SplitTags', + \ 'sj#php#SplitPhpMarker', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#php#JoinPhpMarker', + \ 'sj#php#JoinMethodChain', + \ 'sj#php#JoinArray', + \ 'sj#php#JoinBraces', + \ 'sj#php#JoinIfClause', + \ 'sj#php#JoinElseClause', + \ 'sj#php#JoinHtmlTags', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/python/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/python/splitjoin.vim new file mode 100644 index 000000000..d95840a85 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/python/splitjoin.vim @@ -0,0 +1,24 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#python#SplitListComprehension', + \ 'sj#python#SplitTuple', + \ 'sj#python#SplitAssignment', + \ 'sj#python#SplitTernaryAssignment', + \ 'sj#python#SplitDict', + \ 'sj#python#SplitArray', + \ 'sj#python#SplitStatement', + \ 'sj#python#SplitImport', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#python#JoinTuple', + \ 'sj#python#JoinDict', + \ 'sj#python#JoinArray', + \ 'sj#python#JoinTernaryAssignment', + \ 'sj#python#JoinStatement', + \ 'sj#python#JoinImport', + \ 'sj#python#JoinAssignment', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/r/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/r/splitjoin.vim new file mode 100644 index 000000000..b19f17087 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/r/splitjoin.vim @@ -0,0 +1,12 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#r#SplitFuncall' + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#r#JoinFuncall', + \ 'sj#r#JoinSmart' + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/ruby/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/ruby/splitjoin.vim new file mode 100644 index 000000000..c86b10309 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/ruby/splitjoin.vim @@ -0,0 +1,35 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#ruby#SplitModuleNamespace', + \ 'sj#ruby#SplitArray', + \ 'sj#ruby#SplitArrayLiteral', + \ 'sj#ruby#SplitProcShorthand', + \ 'sj#ruby#SplitBlock', + \ 'sj#ruby#SplitIfClause', + \ 'sj#ruby#SplitCachingConstruct', + \ 'sj#ruby#SplitWhenThen', + \ 'sj#ruby#SplitCase', + \ 'sj#ruby#SplitTernaryClause', + \ 'sj#ruby#SplitOptions', + \ 'sj#ruby#SplitString', + \ 'sj#ruby#SplitEndlessDef', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#ruby#JoinModuleNamespace', + \ 'sj#ruby#JoinArray', + \ 'sj#ruby#JoinArrayLiteral', + \ 'sj#ruby#JoinBlock', + \ 'sj#ruby#JoinHash', + \ 'sj#ruby#JoinIfClause', + \ 'sj#ruby#JoinTernaryClause', + \ 'sj#ruby#JoinCachingConstruct', + \ 'sj#ruby#JoinContinuedMethodCall', + \ 'sj#ruby#JoinHeredoc', + \ 'sj#ruby#JoinWhenThen', + \ 'sj#ruby#JoinCase', + \ 'sj#ruby#JoinOnelineDef', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/rust/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/rust/splitjoin.vim new file mode 100644 index 000000000..663064288 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/rust/splitjoin.vim @@ -0,0 +1,24 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#rust#SplitBlockClosure', + \ 'sj#rust#SplitExprClosure', + \ 'sj#rust#SplitMatchExpression', + \ 'sj#rust#SplitMatchClause', + \ 'sj#rust#SplitQuestionMark', + \ 'sj#rust#SplitCurlyBrackets', + \ 'sj#rust#SplitImportList', + \ 'sj#rust#SplitUnwrapIntoEmptyMatch', + \ 'sj#rust#SplitIfLetIntoMatch', + \ 'sj#rust#SplitArray', + \ 'sj#rust#SplitArgs', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#rust#JoinEmptyMatchIntoIfLet', + \ 'sj#rust#JoinMatchClause', + \ 'sj#rust#JoinMatchStatement', + \ 'sj#rust#JoinClosure', + \ 'sj#rust#JoinCurlyBrackets', + \ 'sj#rust#JoinImportList', + \ 'sj#rust#JoinArray', + \ 'sj#rust#JoinArgs', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/scss/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/scss/splitjoin.vim new file mode 100644 index 000000000..7a944548e --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/scss/splitjoin.vim @@ -0,0 +1,15 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#css#SplitMultilineSelector', + \ 'sj#scss#SplitNestedDefinition', + \ 'sj#css#SplitDefinition', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#scss#JoinNestedDefinition', + \ 'sj#css#JoinDefinition', + \ 'sj#css#JoinMultilineSelector', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/sh/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/sh/splitjoin.vim new file mode 100644 index 000000000..1a1394daa --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/sh/splitjoin.vim @@ -0,0 +1,9 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#sh#SplitBySemicolon', + \ 'sj#sh#SplitWithBackslash', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#sh#JoinBackslashedLine', + \ 'sj#sh#JoinWithSemicolon', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/snakemake/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/snakemake/splitjoin.vim new file mode 100644 index 000000000..0dbb85c63 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/snakemake/splitjoin.vim @@ -0,0 +1,9 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#coffee#SplitString', + \ 'sj#python#SplitStatement', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#python#JoinStatement', + \ 'sj#coffee#JoinString', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/svelte/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/svelte/splitjoin.vim new file mode 100644 index 000000000..266425cb4 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/svelte/splitjoin.vim @@ -0,0 +1 @@ +runtime ftplugin/vue/splitjoin.vim diff --git a/bundle/splitjoin.vim/ftplugin/tex/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/tex/splitjoin.vim new file mode 100644 index 000000000..936a8dd16 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/tex/splitjoin.vim @@ -0,0 +1,7 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#tex#SplitBlock', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#tex#JoinBlock', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/tsx/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/tsx/splitjoin.vim new file mode 100644 index 000000000..ad733a240 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/tsx/splitjoin.vim @@ -0,0 +1,24 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#jsx#SplitJsxExpression', + \ 'sj#html#SplitTags', + \ 'sj#html#SplitAttributes', + \ 'sj#js#SplitObjectLiteral', + \ 'sj#js#SplitFatArrowFunction', + \ 'sj#js#SplitArray', + \ 'sj#js#SplitFunction', + \ 'sj#js#SplitOneLineIf', + \ 'sj#js#SplitArgs', + \ 'sj#jsx#SplitSelfClosingTag' + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#jsx#JoinJsxExpression', + \ 'sj#html#JoinAttributes', + \ 'sj#jsx#JoinHtmlTag', + \ 'sj#js#JoinFatArrowFunction', + \ 'sj#js#JoinArray', + \ 'sj#js#JoinArgs', + \ 'sj#js#JoinFunction', + \ 'sj#js#JoinOneLineIf', + \ 'sj#js#JoinObjectLiteral', + \ ] diff --git a/bundle/splitjoin.vim/ftplugin/typescript/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/typescript/splitjoin.vim new file mode 100644 index 000000000..0134f6126 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/typescript/splitjoin.vim @@ -0,0 +1 @@ +runtime ftplugin/javascript/splitjoin.vim diff --git a/bundle/splitjoin.vim/ftplugin/typescriptreact/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/typescriptreact/splitjoin.vim new file mode 100644 index 000000000..0f42fafe7 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/typescriptreact/splitjoin.vim @@ -0,0 +1 @@ +runtime ftplugin/tsx/splitjoin.vim diff --git a/bundle/splitjoin.vim/ftplugin/vim/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/vim/splitjoin.vim new file mode 100644 index 000000000..dfce04a4e --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/vim/splitjoin.vim @@ -0,0 +1,13 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#vim#SplitIfClause', + \ 'sj#vim#Split', + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#vim#JoinIfClause', + \ 'sj#vim#Join', + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/vue/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/vue/splitjoin.vim new file mode 100644 index 000000000..15c8614f1 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/vue/splitjoin.vim @@ -0,0 +1,25 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#html#SplitTags', + \ 'sj#html#SplitAttributes', + \ 'sj#vue#SplitCssDefinition', + \ 'sj#vue#SplitCssMultilineSelector', + \ 'sj#js#SplitObjectLiteral', + \ 'sj#js#SplitFatArrowFunction', + \ 'sj#js#SplitArray', + \ 'sj#js#SplitFunction', + \ 'sj#js#SplitOneLineIf', + \ 'sj#js#SplitArgs', +\ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#html#JoinAttributes', + \ 'sj#html#JoinTags', + \ 'sj#vue#JoinCssDefinition', + \ 'sj#vue#JoinCssMultilineSelector', + \ 'sj#js#JoinFatArrowFunction', + \ 'sj#js#JoinArray', + \ 'sj#js#JoinArgs', + \ 'sj#js#JoinFunction', + \ 'sj#js#JoinOneLineIf', + \ 'sj#js#JoinObjectLiteral', +\ ] diff --git a/bundle/splitjoin.vim/ftplugin/xml/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/xml/splitjoin.vim new file mode 100644 index 000000000..782f8c7f6 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/xml/splitjoin.vim @@ -0,0 +1,13 @@ +" Wrap them in conditions to avoid messing up erb + +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#html#SplitTags' + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#html#JoinTags' + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/yaml/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/yaml/splitjoin.vim new file mode 100644 index 000000000..ffb1002f0 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/yaml/splitjoin.vim @@ -0,0 +1,13 @@ +if !exists('b:splitjoin_split_callbacks') + let b:splitjoin_split_callbacks = [ + \ 'sj#yaml#SplitArray', + \ 'sj#yaml#SplitMap' + \ ] +endif + +if !exists('b:splitjoin_join_callbacks') + let b:splitjoin_join_callbacks = [ + \ 'sj#yaml#JoinArray', + \ 'sj#yaml#JoinMap' + \ ] +endif diff --git a/bundle/splitjoin.vim/ftplugin/zsh/splitjoin.vim b/bundle/splitjoin.vim/ftplugin/zsh/splitjoin.vim new file mode 100644 index 000000000..053ed1a70 --- /dev/null +++ b/bundle/splitjoin.vim/ftplugin/zsh/splitjoin.vim @@ -0,0 +1,7 @@ +let b:splitjoin_split_callbacks = [ + \ 'sj#sh#SplitBySemicolon', + \ ] + +let b:splitjoin_join_callbacks = [ + \ 'sj#sh#JoinWithSemicolon', + \ ] diff --git a/bundle/splitjoin.vim/plugin/splitjoin.vim b/bundle/splitjoin.vim/plugin/splitjoin.vim new file mode 100644 index 000000000..5b8cf5856 --- /dev/null +++ b/bundle/splitjoin.vim/plugin/splitjoin.vim @@ -0,0 +1,91 @@ +if exists("g:loaded_splitjoin") || &cp + finish +endif + +let g:loaded_splitjoin = '1.2.0' " version number +let s:keepcpo = &cpo +set cpo&vim + +" Defaults: +" ========= + +let g:splitjoin_default_settings = { + \ 'quiet': 0, + \ 'disabled_split_callbacks': [], + \ 'disabled_join_callbacks': [], + \ 'mapping_fallback': 1, + \ + \ 'normalize_whitespace': 1, + \ 'trailing_comma': 0, + \ 'align': 0, + \ 'curly_brace_padding': 1, + \ 'ruby_curly_braces': 1, + \ 'ruby_heredoc_type': '<<~', + \ 'ruby_trailing_comma': 0, + \ 'ruby_hanging_args': 1, + \ 'ruby_do_block_split': 1, + \ 'ruby_options_as_arguments': 0, + \ 'ruby_expand_options_in_arrays': 0, + \ 'c_argument_split_first_newline': 0, + \ 'c_argument_split_last_newline': 0, + \ 'coffee_suffix_if_clause': 1, + \ 'perl_brace_on_same_line': 1, + \ 'php_method_chain_full': 0, + \ 'python_brackets_on_separate_lines': 0, + \ 'handlebars_closing_bracket_on_same_line': 0, + \ 'handlebars_hanging_arguments': 0, + \ 'html_attribute_bracket_on_new_line': 0, + \ 'java_argument_split_first_newline': 0, + \ 'java_argument_split_last_newline': 0, + \ 'vim_split_whitespace_after_backslash': 1, + \ } + +if !exists('g:splitjoin_join_mapping') + let g:splitjoin_join_mapping = 'gJ' +endif + +if !exists('g:splitjoin_split_mapping') + let g:splitjoin_split_mapping = 'gS' +endif + +" Public Interface: +" ================= + +command! SplitjoinSplit call sj#Split() +command! SplitjoinJoin call sj#Join() + +nnoremap SplitjoinSplit :call sj#Split() +nnoremap SplitjoinJoin :call sj#Join() + +if g:splitjoin_join_mapping != '' + exe 'nnoremap '.g:splitjoin_join_mapping.' :call Mapping(g:splitjoin_join_mapping, "sj#Join")' +endif + +if g:splitjoin_split_mapping != '' + exe 'nnoremap '.g:splitjoin_split_mapping.' :call Mapping(g:splitjoin_split_mapping, "sj#Split")' +endif + +" Internal Functions: +" =================== + +" Used to create a mapping for the given a:function that falls back to the +" built-in key sequence (a:mapping) if the function returns 0, meaning it +" didn't do anything. +" +function! s:Mapping(mapping, function) + if !sj#settings#Read('mapping_fallback') + call call(a:function, []) + return + endif + + if !v:count + if !call(a:function, []) + execute 'normal! '.a:mapping + endif + else + execute 'normal! '.v:count.a:mapping + endif +endfunction + +let &cpo = s:keepcpo +unlet s:keepcpo diff --git a/bundle/splitjoin.vim/spec/plugin/c_spec.rb b/bundle/splitjoin.vim/spec/plugin/c_spec.rb new file mode 100644 index 000000000..3133ef69a --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/c_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe "c" do + let(:filename) { 'test.c' } + + before :each do + vim.set(:expandtab) + vim.set(:shiftwidth, 2) + end + + specify "if_clause" do + set_file_contents "if (val1 && val2 || val3);" + + vim.search 'if' + split + + assert_file_contents <<~EOF + if (val1 + && val2 + || val3); + EOF + + join + + assert_file_contents "if (val1 && val2 || val3);" + end + + specify "function_call" do + set_file_contents "myfunction(lots, of, different, parameters)" + + vim.search '(' + split + + assert_file_contents <<~EOF + myfunction(lots, + of, + different, + parameters) + EOF + + join + + assert_file_contents "myfunction(lots, of, different, parameters)" + end + + specify "ignores strings" do + set_file_contents "\"myfunction(several, parameters)\"" + + vim.search '(' + split + + assert_file_contents "\"myfunction(several, parameters)\"" + end + + specify "ignores comments" do + set_file_contents "/* myfunction(several, parameters) */" + + vim.search '(' + split + + assert_file_contents "/* myfunction(several, parameters) */" + end +end diff --git a/bundle/splitjoin.vim/spec/plugin/clojure_spec.rb b/bundle/splitjoin.vim/spec/plugin/clojure_spec.rb new file mode 100644 index 000000000..378058d04 --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/clojure_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +describe "clojure" do + let(:filename) { 'test.clj' } + + specify "Lists with ([ brackets" do + set_file_contents "(dispatch [::events/insert-staff-entry day-ord shift unit @why @who])" + + vim.search 'dispatch' + split + + assert_file_contents <<~EOF + (dispatch + [::events/insert-staff-entry day-ord shift unit @why @who]) + EOF + + join + + assert_file_contents <<~EOF + (dispatch [::events/insert-staff-entry day-ord shift unit @why @who]) + EOF + + vim.search 'day-ord' + split + + assert_file_contents <<~EOF + (dispatch [::events/insert-staff-entry + day-ord + shift + unit + @why + @who]) + EOF + + join + + assert_file_contents <<~EOF + (dispatch [::events/insert-staff-entry day-ord shift unit @why @who]) + EOF + end + + specify "Dictionaries" do + set_file_contents '#{:a :b :c :d}' + + vim.search ':b' + split + + assert_file_contents <<~'EOF' + #{:a + :b + :c + :d} + EOF + + join + + assert_file_contents '#{:a :b :c :d}' + end + + specify "Doesn't get confused with brackets in strings" do + set_file_contents '(map (fn [x] (.toUpperCase x)) (.split "Dasher Dancer) Prancer" " "))' + + vim.search 'Dancer' + split + + assert_file_contents <<~EOF + (map (fn [x] (.toUpperCase x)) (.split + "Dasher Dancer) Prancer" + " ")) + EOF + + join + + assert_file_contents '(map (fn [x] (.toUpperCase x)) (.split "Dasher Dancer) Prancer" " "))' + end +end diff --git a/bundle/splitjoin.vim/spec/plugin/coffee_spec.rb b/bundle/splitjoin.vim/spec/plugin/coffee_spec.rb new file mode 100644 index 000000000..b4be5a559 --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/coffee_spec.rb @@ -0,0 +1,187 @@ +require 'spec_helper' + +# Note: Coffeescript is not a built-in filetype, so these specs work with no +# automatic indentation. It's still possible that bugs would creep in due when +# real indentation is factored in, though I've attempted to minimize the +# effects of that. +# +describe "coffee" do + let(:filename) { 'test.coffee' } + + # Coffee is not built-in, so let's set it up manually + def setup_coffee_filetype + vim.set(:filetype, 'coffee') + vim.set(:expandtab) + vim.set(:shiftwidth, 2) + end + + specify "functions" do + set_file_contents "(foo, bar) -> console.log foo" + setup_coffee_filetype + + split + + assert_file_contents <<~EOF + (foo, bar) -> + console.log foo + EOF + + join + + assert_file_contents "(foo, bar) -> console.log foo" + end + + specify "postfix if-clauses" do + set_file_contents "console.log bar if foo?" + setup_coffee_filetype + + split + + assert_file_contents <<~EOF + if foo? + console.log bar + EOF + + join + + assert_file_contents "console.log bar if foo?" + end + + specify "suffix if-clauses" do + set_file_contents "if foo? then console.log bar" + setup_coffee_filetype + + split + + assert_file_contents <<~EOF + if foo? + console.log bar + EOF + + join + + assert_file_contents "console.log bar if foo?" + end + + specify "ternary operator" do + set_file_contents <<~EOF + do -> + foo = if bar? then 'baz' else 'qux' + EOF + setup_coffee_filetype + + vim.search 'foo' + split + + assert_file_contents <<~EOF + do -> + if bar? + foo = 'baz' + else + foo = 'qux' + EOF + + vim.search 'bar' + join + + set_file_contents <<~EOF + do -> + foo = if bar? then 'baz' else 'qux' + EOF + end + + specify "joining ternary operator without any assignment magic" do + set_file_contents <<~EOF + if bar? + foo = "baz" + else + baz = "qux" + EOF + setup_coffee_filetype + + vim.search 'bar' + join + + assert_file_contents <<~EOF + if bar? then foo = "baz" else baz = "qux" + EOF + end + + specify "object literals" do + set_file_contents 'one = { one: "two", three: "four" }' + setup_coffee_filetype + + split + + assert_file_contents <<~EOF + one = + one: "two" + three: "four" + EOF + + join + + assert_file_contents 'one = { one: "two", three: "four" }' + end + + specify "function calls with object literals" do + set_file_contents 'foo = functionCall(one, two, three: four, five: six)' + setup_coffee_filetype + + split + + assert_file_contents <<~EOF + foo = functionCall one, two, + three: four + five: six + EOF + + join + + assert_file_contents 'foo = functionCall one, two, { three: four, five: six }' + end + + specify "strings" do + set_file_contents <<~EOF + foo = "example with \#{interpolation} and \\"nested\\" quotes" + EOF + setup_coffee_filetype + + vim.search 'example' + split + + assert_file_contents <<~EOF + foo = """ + example with \#{interpolation} and "nested" quotes + """ + EOF + + join + + assert_file_contents <<~EOF + foo = "example with \#{interpolation} and \\"nested\\" quotes" + EOF + end + + specify "triple strings" do + set_file_contents <<~EOF + foo = """example with \#{interpolation} and "nested" quotes""" + EOF + setup_coffee_filetype + + vim.search 'example' + split + + assert_file_contents <<~EOF + foo = """ + example with \#{interpolation} and "nested" quotes + """ + EOF + + join + + assert_file_contents <<~EOF + foo = "example with \#{interpolation} and \\"nested\\" quotes" + EOF + end +end diff --git a/bundle/splitjoin.vim/spec/plugin/css_spec.rb b/bundle/splitjoin.vim/spec/plugin/css_spec.rb new file mode 100644 index 000000000..19ab0d1d2 --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/css_spec.rb @@ -0,0 +1,86 @@ +require 'spec_helper' + +describe "css" do + let(:filename) { 'test.css' } + + before :each do + vim.set 'expandtab' + vim.set 'shiftwidth', 2 + end + + specify "single-line style definitions" do + set_file_contents <<~EOF + a { color: #0000FF; text-decoration: underline; } + EOF + + split + + assert_file_contents <<~EOF + a { + color: #0000FF; + text-decoration: underline; + } + EOF + + join + + assert_file_contents <<~EOF + a { color: #0000FF; text-decoration: underline; } + EOF + end + + specify "empty single-line style definitions (regression)" do + set_file_contents <<~EOF + body { + + } + EOF + + join + assert_file_contents 'body {}' + + split + assert_file_contents <<~EOF + body { + + } + EOF + + set_file_contents <<~EOF + body { + } + EOF + + join + assert_file_contents 'body {}' + end + + specify "multiline selectors" do + set_file_contents <<~EOF + h1, h2, h3 { + font-size: 18px; + font-weight: bold; + } + EOF + + split + + assert_file_contents <<~EOF + h1, + h2, + h3 { + font-size: 18px; + font-weight: bold; + } + EOF + + join + + assert_file_contents <<~EOF + h1, h2, h3 { + font-size: 18px; + font-weight: bold; + } + EOF + end +end diff --git a/bundle/splitjoin.vim/spec/plugin/elixir_spec.rb b/bundle/splitjoin.vim/spec/plugin/elixir_spec.rb new file mode 100644 index 000000000..930f8f322 --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/elixir_spec.rb @@ -0,0 +1,720 @@ +require 'spec_helper' + +describe "elixir" do + let(:filename) { "test.ex" } + + before :each do + vim.set(:expandtab) + vim.set(:shiftwidth, 2) + end + + describe "function definitions" do + specify "0 arity" do + set_file_contents <<~EOF + defmodule Foo do + def bar() do + :bar + end + end + EOF + + vim.search 'def bar' + join + + assert_file_contents <<~EOF + defmodule Foo do + def bar(), do: :bar + end + EOF + + vim.search 'def bar' + split + + assert_file_contents <<~EOF + defmodule Foo do + def bar() do + :bar + end + end + EOF + end + + specify "0 arity no parens" do + set_file_contents <<~EOF + defmodule Foo do + def bar do + :bar + end + end + EOF + + vim.search 'def bar' + join + + assert_file_contents <<~EOF + defmodule Foo do + def bar, do: :bar + end + EOF + + vim.search 'def bar' + split + + assert_file_contents <<~EOF + defmodule Foo do + def bar do + :bar + end + end + EOF + end + + specify "1 arity" do + set_file_contents <<~EOF + defmodule Foo do + def bar(foo) do + :bar + end + end + EOF + + vim.search 'def bar' + join + + assert_file_contents <<~EOF + defmodule Foo do + def bar(foo), do: :bar + end + EOF + + vim.search 'def bar' + split + + assert_file_contents <<~EOF + defmodule Foo do + def bar(foo) do + :bar + end + end + EOF + end + end + + describe "do-blocks" do + specify "with round brackets" do + set_file_contents <<~EOF + let(:one, do: two() |> three(four())) + EOF + + vim.search ':one' + split + + assert_file_contents <<~EOF + let(:one) do + two() |> three(four()) + end + EOF + + join + + assert_file_contents <<~EOF + let(:one, do: two() |> three(four())) + EOF + end + + specify "with no brackets" do + set_file_contents <<~EOF + let :one, do: two() |> three(four()) + EOF + + vim.search ':one' + split + + assert_file_contents <<~EOF + let :one do + two() |> three(four()) + end + EOF + + join + + assert_file_contents <<~EOF + let :one, do: two() |> three(four()) + EOF + end + end + + describe "if-blocks" do + specify "with no brackets" do + set_file_contents <<~EOF + if 2 > 1, do: print("OK"), else: print("Not OK") + EOF + + vim.search '2 > 1' + split + + assert_file_contents <<~EOF + if 2 > 1 do + print("OK") + else + print("Not OK") + end + EOF + + join + + assert_file_contents <<~EOF + if 2 > 1, do: print("OK"), else: print("Not OK") + EOF + end + + specify "with round brackets" do + set_file_contents <<~EOF + if(2 > 1, do: print("OK"), else: print("Not OK")) + EOF + + vim.search '2 > 1' + split + + assert_file_contents <<~EOF + if 2 > 1 do + print("OK") + else + print("Not OK") + end + EOF + + join + + assert_file_contents <<~EOF + if 2 > 1, do: print("OK"), else: print("Not OK") + EOF + end + end + + describe "joining comma-separated arguments" do + specify "with a level of indent" do + set_file_contents <<~EOF + for a <- 1..10, + Integer.is_odd(a) do + a + end + EOF + + vim.search 'for' + join + + assert_file_contents <<~EOF + for a <- 1..10, Integer.is_odd(a) do + a + end + EOF + end + + specify "with no indent" do + set_file_contents <<~EOF + for a <- 1..10, + Integer.is_odd(a) do + a + end + EOF + + vim.search 'for' + join + + assert_file_contents <<~EOF + for a <- 1..10, Integer.is_odd(a) do + a + end + EOF + end + + specify "multiple lines" do + set_file_contents <<~EOF + if Enum.member?(one, two), + do: query |> where(three, four), + else: five + EOF + + vim.search 'Enum' + join + + assert_file_contents <<~EOF + if Enum.member?(one, two), do: query |> where(three, four), else: five + EOF + end + end + + specify "arrays" do + set_file_contents <<~EOF + [a, b, c] + EOF + + split + + assert_file_contents <<~EOF + [ + a, + b, + c + ] + EOF + + vim.search('[') + join + + assert_file_contents <<~EOF + [a, b, c] + EOF + + set_file_contents <<~EOF + [a: 1, b: 2, c: %{a: 1, b: 2}] + EOF + + split + + assert_file_contents <<~EOF + [ + a: 1, + b: 2, + c: %{a: 1, b: 2} + ] + EOF + + vim.search('[') + join + + assert_file_contents <<~EOF + [a: 1, b: 2, c: %{a: 1, b: 2}] + EOF + + set_file_contents <<~EOF + [] + EOF + + vim.search('[') + split + + assert_file_contents <<~EOF + [] + EOF + + vim.search('[') + join + + assert_file_contents <<~EOF + [] + EOF + end + + describe "pipes" do + specify "1 arity" do + set_file_contents <<~EOF + foo(bar) + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + bar + |> foo() + EOF + + vim.search "|>" + join + + assert_file_contents <<~EOF + foo(bar) + EOF + end + + specify "1 arity, no parens" do + set_file_contents <<~EOF + foo bar + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + bar + |> foo() + EOF + end + + specify "1 arity, qualified call" do + set_file_contents <<~EOF + My.Module.foo(bar) + EOF + + vim.search 'My' + split + + assert_file_contents <<~EOF + bar + |> My.Module.foo() + EOF + + vim.search "|>" + join + + assert_file_contents <<~EOF + My.Module.foo(bar) + EOF + end + + specify "1 arity, atom call" do + set_file_contents <<~EOF + :module.foo(bar) + EOF + + vim.search ':module' + split + + assert_file_contents <<~EOF + bar + |> :module.foo() + EOF + + vim.search "|>" + join + + assert_file_contents <<~EOF + :module.foo(bar) + EOF + end + + specify "1 arity, indented" do + set_file_contents <<~EOF + if bla do + foo(bar) + end + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + if bla do + bar + |> foo() + end + EOF + + vim.search "|>" + join + + assert_file_contents <<~EOF + if bla do + foo(bar) + end + EOF + end + + specify "2 arity" do + set_file_contents <<~EOF + foo(bar, baz) + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + bar + |> foo(baz) + EOF + + vim.search '|>' + join + + assert_file_contents <<~EOF + foo(bar, baz) + EOF + end + + specify "2 arity, no parens" do + set_file_contents <<~EOF + foo bar, baz + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + bar + |> foo(baz) + EOF + end + + specify "2 arity, qualified call" do + set_file_contents <<~EOF + My.Module.foo(bar, baz) + EOF + + vim.search 'My' + split + + assert_file_contents <<~EOF + bar + |> My.Module.foo(baz) + EOF + + vim.search "|>" + join + + assert_file_contents <<~EOF + My.Module.foo(bar, baz) + EOF + end + + specify "2 arity, atom call" do + set_file_contents <<~EOF + :module.foo(bar, baz) + EOF + + vim.search ':module' + split + + assert_file_contents <<~EOF + bar + |> :module.foo(baz) + EOF + + vim.search "|>" + join + + assert_file_contents <<~EOF + :module.foo(bar, baz) + EOF + end + + specify "2 arity, indented" do + set_file_contents <<~EOF + if bla do + foo(bar, baz) + end + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + if bla do + bar + |> foo(baz) + end + EOF + + vim.search '|>' + join + + assert_file_contents <<~EOF + if bla do + foo(bar, baz) + end + EOF + end + + specify "join from line preceding pipe operator" do + set_file_contents <<~EOF + bar + |> foo() + EOF + + vim.search 'bar' + join + + assert_file_contents <<~EOF + foo(bar) + EOF + end + + specify "join from middle of pipeline does nothing" do + set_file_contents <<~EOF + bar + |> foo() + |> baz() + |> bla() + EOF + + vim.search 'baz' + # Call command instead of mapping to avoid default mapping + vim.command 'SplitjoinJoin' + + assert_file_contents <<~EOF + bar + |> foo() + |> baz() + |> bla() + EOF + end + + specify "join from start of pipeline" do + set_file_contents <<~EOF + bar + |> foo() + |> baz() + |> bla() + EOF + + vim.search 'foo' + join + + assert_file_contents <<~EOF + foo(bar) + |> baz() + |> bla() + EOF + end + + specify "complex args" do + set_file_contents <<~EOF + foo((1 + 2) * 3, bar(baz, bla)) + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + (1 + 2) * 3 + |> foo(bar(baz, bla)) + EOF + + vim.search '|>' + join + + assert_file_contents <<~EOF + foo((1 + 2) * 3, bar(baz, bla)) + EOF + end + + specify "split from a pipeline does nothing" do + set_file_contents <<~EOF + bar + |> foo(baz) + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + bar + |> foo(baz) + EOF + end + + specify "splitting multiple functions on one line does nothing" do + set_file_contents <<~EOF + foo("one") + bar("two") + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + foo("one") + bar("two") + EOF + + vim.search 'bar' + split + + assert_file_contents <<~EOF + foo("one") + bar("two") + EOF + end + + specify "splitting multiple no-parens functions on one line does nothing" do + set_file_contents <<~EOF + IO.puts 3 + String.length "foo" + EOF + + vim.search 'IO' + split + + assert_file_contents <<~EOF + IO.puts 3 + String.length "foo" + EOF + + vim.search 'String.length' + split + + assert_file_contents <<~EOF + IO.puts 3 + String.length "foo" + EOF + end + + specify "splitting with whitespace and a comment at the end works" do + set_file_contents <<~EOF + foo("one", "two") # bar + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + "one" + |> foo("two") # bar + EOF + + set_file_contents <<~EOF + foo "one", "two" # bar + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + "one" + |> foo("two") # bar + EOF + end + + specify "ignores function calls that do not start at the beginning of the line" do + set_file_contents <<~EOF + if foo, + do: "one" |> bla(a) + else: foo + EOF + + vim.search 'bla' + # Call command instead of mapping to avoid default mapping + vim.command 'SplitjoinSplit' + + assert_file_contents <<~EOF + if foo, + do: "one" |> bla(a) + else: foo + EOF + + # Call command instead of mapping to avoid default mapping + vim.command 'SplitjoinJoin' + + assert_file_contents <<~EOF + if foo, + do: "one" |> bla(a) + else: foo + EOF + end + + specify "with a pipe within the args" do + set_file_contents <<~EOF + IO.inspect("foo" |> String.length()) + EOF + + vim.search 'foo' + split + + assert_file_contents <<~EOF + "foo" |> String.length() + |> IO.inspect() + EOF + + join + + assert_file_contents <<~EOF + IO.inspect("foo" |> String.length()) + EOF + end + end +end diff --git a/bundle/splitjoin.vim/spec/plugin/elm_spec.rb b/bundle/splitjoin.vim/spec/plugin/elm_spec.rb new file mode 100644 index 000000000..24ea57101 --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/elm_spec.rb @@ -0,0 +1,533 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'elm' do + let(:filename) { 'Test.elm' } + + before :each do + vim.set(:expandtab) + vim.set(:shiftwidth, 4) + end + + describe 'splitting/joining a list' do + describe 'splitting a list' do + specify 'with a simple list' do + set_file_contents <<~EOF + list = + [1, 22, 333, 4444] + EOF + + vim.search '[' + split + + assert_file_contents <<~EOF + list = + [ 1 + , 22 + , 333 + , 4444 + ] + EOF + end + + specify 'with a space and tab-riddled list' do + set_file_contents <<~EOF + list = + [ \t1\t , \t22,\t 333 , \t4444\t ] + EOF + + vim.search '[' + split + + assert_file_contents <<~EOF + list = + [ 1 + , 22 + , 333 + , 4444 + ] + EOF + end + + specify 'with a list holding function call results' do + set_file_contents <<~EOF + list = + [1 + 2, modBy 3 4, remBy 5 (num + 6), maybeNum |> Maybe.withDefault 7] + EOF + + vim.search '[' + split + + assert_file_contents <<~EOF + list = + [ 1 + 2 + , modBy 3 4 + , remBy 5 (num + 6) + , maybeNum |> Maybe.withDefault 7 + ] + EOF + end + + specify 'with a list of lists' do + set_file_contents <<~EOF + list = + [[123, 456], [78, 89, 90]] + EOF + + vim.search '[' + split + + assert_file_contents <<~EOF + list = + [ [123, 456] + , [78, 89, 90] + ] + EOF + end + + specify 'with a sub-list in a list of list' do + set_file_contents <<~EOF + list = + [ [123, 456] + , [78, 89, 90] + ] + EOF + + vim.search '123' + split + + assert_file_contents <<~EOF + list = + [ [ 123 + , 456 + ] + , [78, 89, 90] + ] + EOF + # there is kind of a bug in the syntax here + # it should indent the sub-list instead, like: + # + # list = + # [ [ 123 + # , 456 + # ] + # , [78, 89, 90] + # ] + end + + specify 'with a list of tuples' do + set_file_contents <<~EOF + list = + [(123, 456), (78, 89)] + EOF + + vim.search '123' + split + + assert_file_contents <<~EOF + list = + [ (123, 456) + , (78, 89) + ] + EOF + end + + specify 'with a list of messy strings' do + set_file_contents <<~EOF + list = + ["One, two", "\\"One, two\\", \\"three, four\\""] + EOF + + vim.search '[' + split + + assert_file_contents <<~EOF + list = + [ "One, two" + , "\\"One, two\\", \\"three, four\\"" + ] + EOF + end + + specify 'with a list of only one element' do + set_file_contents <<~EOF + list = + [(1, "\\"two, three, four")] + EOF + + vim.search '[' + split + + assert_file_contents <<~EOF + list = + [(1, "\\"two, three, four")] + EOF + end + end + + specify 'joining a list' do + set_file_contents <<~EOF + list = + [ 1 + , 2 + , 3 + , 4 + ] + EOF + + vim.search '[' + join + + assert_file_contents <<~EOF + list = + [1, 2, 3, 4] + EOF + end + + specify 'joining a list whithin a list containing lists' do + set_file_contents <<~EOF + list = + [ [ [1, 2, 3, 4], + [3, 4, 6], + [2, 3, 4, 5, 6] + ], + [ [2, 3, 4, 5, 6], + [0, 4, 2], + [6, 2, 3, 4] + ] + ] + EOF + + vim.search '1' + join + + assert_file_contents <<~EOF + list = + [ [[1, 2, 3, 4], [3, 4, 6], [2, 3, 4, 5, 6]], + [ [2, 3, 4, 5, 6], + [0, 4, 2], + [6, 2, 3, 4] + ] + ] + EOF + end + end + + describe 'splitting/joining a tuple' do + describe 'splitting a tuple' do + specify 'with a simple tuple' do + set_file_contents <<~EOF + tuple = + (123, "blah", pi) + EOF + + vim.search '(' + split + + assert_file_contents <<~EOF + tuple = + ( 123 + , "blah" + , pi + ) + EOF + end + + specify 'with a tuple holding tricky content' do + set_file_contents <<~EOF + tuple = + (("\\" (booh, gotcha!)"), [(pi / 6, rotate <| square)], (12, 43)) + EOF + + vim.search '(' + split + + assert_file_contents <<~EOF + tuple = + ( ("\\" (booh, gotcha!)") + , [(pi / 6, rotate <| square)] + , (12, 43) + ) + EOF + end + + specify 'with a tuple holding a list' do + set_file_contents <<~EOF + tuple = + ([123, 456, 789], 12121) + EOF + + vim.search '[' + split + + assert_file_contents <<~EOF + tuple = + ( [123, 456, 789] + , 12121 + ) + EOF + end + + specify 'with something that is not a tuple' do + set_file_contents <<~EOF + notATuple = + ([1, 2, 3, 4] |> List.map ((*) 2)) + EOF + + vim.search '(' + split + + assert_file_contents <<~EOF + notATuple = + ([1, 2, 3, 4] |> List.map ((*) 2)) + EOF + end + end + + describe 'joining a tuple' do + specify 'a simple tuple' do + set_file_contents <<~EOF + aTuple = + ( 123 + , "456" + , [7, 8, 9] + ) + EOF + + vim.search '7' + join + + assert_file_contents <<~EOF + aTuple = + (123, "456", [7, 8, 9]) + EOF + end + end + end + + describe 'splitting/joining a record' do + describe 'splitting a record' do + specify 'a record type definition' do + set_file_contents <<~EOF + type alias MyAwesomeType = + {firstName : String, lastName : String} + EOF + + vim.search '{' + split + + assert_file_contents <<~EOF + type alias MyAwesomeType = + { firstName : String + , lastName : String + } + EOF + end + + specify 'a record instanciation' do + set_file_contents <<~EOF + myLittleRecord = + {firstName = "John", lastName = "Doe"} + EOF + + vim.search '{' + split + + assert_file_contents <<~EOF + myLittleRecord = + { firstName = "John" + , lastName = "Doe" + } + EOF + end + + specify 'a record update' do + set_file_contents <<~EOF + myUpdatedRecord = + {myPreviousRecord | firstName = "John", lastName = "Doe"} + EOF + + vim.search '{' + split + + assert_file_contents <<~EOF + myUpdatedRecord = + { myPreviousRecord + | firstName = "John" + , lastName = "Doe" + } + EOF + end + + specify 'a tricky record definition' do + set_file_contents <<~EOF + type alias MyAwsomeType = + { id: {short: String, long: String}, count: Int } + EOF + + vim.search 'long' + split + + assert_file_contents <<~EOF + type alias MyAwsomeType = + { id: {short: String, long: String} + , count: Int + } + EOF + + vim.search 'long' + split + + assert_file_contents <<~EOF + type alias MyAwsomeType = + { id: + { short: String + , long: String + } + , count: Int + } + EOF + # there is kind of a bug in the syntax here + # it should indent the sub-record instead, like: + # + # type alias MyAwsomeType = + # { id: + # { short: String + # , long: String + # } + # , count: Int + # } + end + + specify 'a tricky record update' do + set_file_contents <<~EOF + myUpdatedRecord = + {myPreviousRecord | firstName = "John" |> String.toUpper, lastName = "Doe", address = { city = "Paris, 12e", zipCode = "75012", street: "123 rue de Picpus"}} + EOF + + vim.search 'city' + split + + assert_file_contents <<~EOF + myUpdatedRecord = + { myPreviousRecord + | firstName = "John" |> String.toUpper + , lastName = "Doe" + , address = { city = "Paris, 12e", zipCode = "75012", street: "123 rue de Picpus"} + } + EOF + + vim.search 'city' + split + + assert_file_contents <<~EOF + myUpdatedRecord = + { myPreviousRecord + | firstName = "John" |> String.toUpper + , lastName = "Doe" + , address = + { city = "Paris, 12e" + , zipCode = "75012" + , street: "123 rue de Picpus" + } + } + EOF + end + end + + describe 'joining a record' do + specify 'joining a record type alias' do + set_file_contents <<~EOF + type alias MyAwesomeType = + { firstName : String + , lastName : String + } + EOF + + vim.search '{' + join + + assert_file_contents <<~EOF + type alias MyAwesomeType = + {firstName : String, lastName : String} + EOF + end + + specify 'joining a new record' do + set_file_contents <<~EOF + myBrokenRecord = + { firstName = "Foo" + , lastName = "Bar, Baz" + } + EOF + + vim.search '{' + join + + assert_file_contents <<~EOF + myBrokenRecord = + {firstName = "Foo", lastName = "Bar, Baz"} + EOF + end + + specify 'joining a record update' do + set_file_contents <<~EOF + myUpdatedRecord = + { myPreviousRecord + | firstName = "John" + , lastName = "Doe" + } + EOF + + vim.search '{' + join + + assert_file_contents <<~EOF + myUpdatedRecord = + {myPreviousRecord | firstName = "John", lastName = "Doe"} + EOF + end + + specify 'a tricky record update' do + set_file_contents <<~EOF + myUpdatedRecord = + { myPreviousRecord + | firstName = + "John" + |> String.toUpper + , lastName = "Doe" + , address = + { city = "Paris, 12e" + , zipCode = "75012" + , street: "123 rue de Picpus" + } + } + EOF + + vim.search 'city' + join + + assert_file_contents <<~EOF + myUpdatedRecord = + { myPreviousRecord + | firstName = + "John" + |> String.toUpper + , lastName = "Doe" + , address = + {city = "Paris, 12e", zipCode = "75012", street: "123 rue de Picpus"} + } + EOF + + vim.search 'city' + join + + assert_file_contents <<~EOF + myUpdatedRecord = + {myPreviousRecord | firstName = "John" |> String.toUpper, lastName = "Doe", address = {city = "Paris, 12e", zipCode = "75012", street: "123 rue de Picpus"}} + EOF + end + end + end +end diff --git a/bundle/splitjoin.vim/spec/plugin/eruby_spec.rb b/bundle/splitjoin.vim/spec/plugin/eruby_spec.rb new file mode 100644 index 000000000..fe5cb0577 --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/eruby_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe "eruby" do + let(:filename) { 'test.erb' } + + before :each do + vim.set(:expandtab) + vim.set(:shiftwidth, 2) + end + + after :each do + vim.command('silent! unlet g:splitjoin_ruby_trailing_comma') + end + + specify "method options with a trailing comma" do + vim.command('let g:splitjoin_ruby_trailing_comma = 1') + + set_file_contents <<~EOF + <%= link_to "Home", "/", remote: true, confirm: "Y?" %> + EOF + + vim.search 'Home' + split + + assert_file_contents <<~EOF + <%= link_to "Home", "/", { + remote: true, + confirm: "Y?", + } %> + EOF + + vim.search 'Home' + join + + assert_file_contents <<~EOF + <%= link_to "Home", "/", { remote: true, confirm: "Y?" } %> + EOF + end +end diff --git a/bundle/splitjoin.vim/spec/plugin/go_spec.rb b/bundle/splitjoin.vim/spec/plugin/go_spec.rb new file mode 100644 index 000000000..c2f5f4c79 --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/go_spec.rb @@ -0,0 +1,462 @@ +require 'spec_helper' + +describe "go" do + let(:filename) { 'test.go' } + + before :each do + vim.set(:expandtab) + vim.set(:shiftwidth, 2) + end + + specify "imports" do + set_file_contents <<~EOF + import "fmt" + EOF + + vim.search('import') + split + + assert_file_contents <<~EOF + import ( + "fmt" + ) + EOF + + vim.search('import') + join + + assert_file_contents <<~EOF + import "fmt" + EOF + end + + specify "imports with names" do + set_file_contents <<~EOF + import _ "fmt" + EOF + + vim.search('import') + split + + assert_file_contents <<~EOF + import ( + _ "fmt" + ) + EOF + + vim.search('import') + join + + assert_file_contents <<~EOF + import _ "fmt" + EOF + end + + describe "structs" do + specify "instantiation" do + set_file_contents <<~EOF + StructType{one: 1, two: "asdf", three: []int{1, 2, 3}} + EOF + + vim.search 'one:' + split + + assert_file_contents <<~EOF + StructType{ + one: 1, + two: "asdf", + three: []int{1, 2, 3}, + } + EOF + + join + + assert_file_contents <<~EOF + StructType{one: 1, two: "asdf", three: []int{1, 2, 3}} + EOF + end + + specify "instantiation without padding" do + set_file_contents <<~EOF + StructType{one: 1, two: "asdf", three: []int{1, 2, 3}} + EOF + vim.command('let b:splitjoin_curly_brace_padding = 0') + + vim.search 'one:' + split + + assert_file_contents <<~EOF + StructType{ + one: 1, + two: "asdf", + three: []int{1, 2, 3}, + } + EOF + + join + + assert_file_contents <<~EOF + StructType{one: 1, two: "asdf", three: []int{1, 2, 3}} + EOF + end + + specify "definition" do + set_file_contents <<~EOF + type str struct{ A, B int } + EOF + + vim.search 'A' + split + + assert_file_contents <<~EOF + type str struct { + A, B int + } + EOF + + vim.search 'struct' + join + + assert_file_contents <<~EOF + type str struct{ A, B int } + EOF + end + + specify "empty definition" do + set_file_contents <<~EOF + type empty struct{} + EOF + + vim.search '{' + split + + assert_file_contents <<~EOF + type empty struct { + + } + EOF + + vim.search 'struct' + join + + assert_file_contents <<~EOF + type empty struct{} + EOF + + vim.search 'type' + split + + assert_file_contents <<~EOF + type ( + empty struct{} + ) + EOF + end + end + + describe "funcs" do + def assert_split_join(initial, split_expected, join_expected) + set_file_contents initial + vim.search 'Func(\zs\k' + + split + + assert_file_contents split_expected + + join + + assert_file_contents join_expected + end + + it "handles function definitions" do + initial = <<~EOF + func Func(a, b int, c time.Time, d func(int) error, e func(int, int) (int, error), f ...time.Time) { + } + EOF + split = <<~EOF + func Func( + a, b int, + c time.Time, + d func(int) error, + e func(int, int) (int, error), + f ...time.Time, + ) { + } + EOF + joined = <<~EOF + func Func(a, b int, c time.Time, d func(int) error, e func(int, int) (int, error), f ...time.Time) { + } + EOF + assert_split_join(initial, split, joined) + end + + it "handles function definitions with return types" do + initial = <<~EOF + func Func(a, b int, c time.Time, d func(int) error, e func(int, int) (int, error), f ...time.Time) (r string, err error) { + } + EOF + split = <<~EOF + func Func( + a, b int, + c time.Time, + d func(int) error, + e func(int, int) (int, error), + f ...time.Time, + ) (r string, err error) { + } + EOF + joined = <<~EOF + func Func(a, b int, c time.Time, d func(int) error, e func(int, int) (int, error), f ...time.Time) (r string, err error) { + } + EOF + assert_split_join(initial, split, joined) + end + + it "handles method definitions" do + initial = <<~EOF + func (r Receiver) Func(a, b int, c time.Time, d func(int) error, e func(int, int) (int, error), f ...time.Time) { + } + EOF + split = <<~EOF + func (r Receiver) Func( + a, b int, + c time.Time, + d func(int) error, + e func(int, int) (int, error), + f ...time.Time, + ) { + } + EOF + joined = <<~EOF + func (r Receiver) Func(a, b int, c time.Time, d func(int) error, e func(int, int) (int, error), f ...time.Time) { + } + EOF + assert_split_join(initial, split, joined) + end + + it "handles method definitions with return types" do + initial = <<~EOF + func (r Receiver) Func(a, b int, c time.Time, d func(int) error, e func(int, int) (int, error), f ...time.Time) (r string, err error) { + } + EOF + split = <<~EOF + func (r Receiver) Func( + a, b int, + c time.Time, + d func(int) error, + e func(int, int) (int, error), + f ...time.Time, + ) (r string, err error) { + } + EOF + joined = <<~EOF + func (r Receiver) Func(a, b int, c time.Time, d func(int) error, e func(int, int) (int, error), f ...time.Time) (r string, err error) { + } + EOF + assert_split_join(initial, split, joined) + end + end + + specify "func calls" do + set_file_contents <<~EOF + err := Func(a, b, c, d) + EOF + + vim.search 'a,' + split + + assert_file_contents <<~EOF + err := Func( + a, + b, + c, + d, + ) + EOF + + join + + assert_file_contents <<~EOF + err := Func(a, b, c, d) + EOF + end + + specify "func definition bodies" do + set_file_contents <<~EOF + func foo(x, y int) bool { return x+y == 5 } + EOF + + vim.search 'return' + split + + assert_file_contents <<~EOF + func foo(x, y int) bool { + return x+y == 5 + } + EOF + + join + + assert_file_contents <<~EOF + func foo(x, y int) bool { return x+y == 5 } + EOF + end + + describe "variable declarations" do + specify "one per line" do + set_file_contents <<~EOF + type ChanDir int + + func Func() { + var foo string + const bar string + } + EOF + + vim.search('var') + split + vim.search('const') + split + vim.search('type') + split + + assert_file_contents <<~EOF + type ( + ChanDir int + ) + + func Func() { + var ( + foo string + ) + const ( + bar string + ) + } + EOF + + vim.search('var') + join + vim.search('const') + join + vim.search('type') + join + + assert_file_contents <<~EOF + type ChanDir int + + func Func() { + var foo string + const bar string + } + EOF + end + + specify "comma-separated without type" do + set_file_contents <<~EOF + const ( + const4 = "4" + const5 = "5", + ) + EOF + + join + + assert_file_contents <<~EOF + const const4, const5 = "4", "5" + EOF + + split + + assert_file_contents <<~EOF + const ( + const4 = "4" + const5 = "5" + ) + EOF + end + + specify "comma-separated with type, without values" do + set_file_contents <<~EOF + const ( + const4 string + const5 string + ) + EOF + + join + + assert_file_contents <<~EOF + const const4, const5 string + EOF + + split + + assert_file_contents <<~EOF + const ( + const4 string + const5 string + ) + EOF + end + + specify "different types don't get joined" do + set_file_contents <<~EOF + const ( + const4 string + const5 int + ) + EOF + + join + + # Triggers the built-in gJ + assert_file_contents <<~EOF + const ( const4 string + const5 int + ) + EOF + end + + specify "join single line as a special case" do + set_file_contents <<~EOF + const ( + const4, const5 = "4", "5" + ) + EOF + + join + + assert_file_contents <<~EOF + const const4, const5 = "4", "5" + EOF + end + + specify "doesn't split multiline declarations" do + set_file_contents <<~EOF + var first = map[string]any{ + "k": "v", + } + EOF + + split + + assert_file_contents <<~EOF + var first = map[string]any{ + "k": "v", + } + EOF + + vim.normal 'f{' + join + vim.search('var first') + split + + assert_file_contents <<~EOF + var ( + first = map[string]any{ "k": "v", } + ) + EOF + end + end +end diff --git a/bundle/splitjoin.vim/spec/plugin/haml_spec.rb b/bundle/splitjoin.vim/spec/plugin/haml_spec.rb new file mode 100644 index 000000000..a37871d33 --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/haml_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe "haml" do + let(:filename) { 'test.haml' } + + # Haml is not built-in, so let's set it up manually + def setup_haml_filetype + vim.set(:filetype, 'haml') + vim.set(:expandtab) + vim.set(:shiftwidth, 2) + end + + specify "interpolation" do + set_file_contents '%div= 1 + 2' + setup_haml_filetype + + split + + assert_file_contents <<~EOF + %div + = 1 + 2 + EOF + + join + + assert_file_contents '%div= 1 + 2' + end +end diff --git a/bundle/splitjoin.vim/spec/plugin/handlebars_spec.rb b/bundle/splitjoin.vim/spec/plugin/handlebars_spec.rb new file mode 100644 index 000000000..76dc04b49 --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/handlebars_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' + +# Note: Handlebars is not a built-in filetype, so these specs work with no +# automatic indentation. It's still possible that bugs would creep in due when +# real indentation is factored in, though I've attempted to minimize the +# effects of that. +# +describe "handlebars" do + let(:filename) { 'test.hbs' } + + # Coffee is not built-in, so let's set it up manually + def setup_handlebars_filetype + vim.set(:filetype, 'html.handlebars') + vim.set(:expandtab) + vim.set(:shiftwidth, 2) + end + + specify "components" do + set_file_contents "{{some/component-name foo=bar bar=baz}}" + setup_handlebars_filetype + + split + + assert_file_contents <<~EOF + {{some/component-name + foo=bar + bar=baz + }} + EOF + + join + + assert_file_contents "{{some/component-name foo=bar bar=baz}}" + end + + specify "components (bracket on the same line)" do + set_file_contents "{{some/component-name foo=bar bar=baz}}" + setup_handlebars_filetype + vim.command('let b:splitjoin_handlebars_closing_bracket_on_same_line = 1') + + split + + assert_file_contents <<~EOF + {{some/component-name + foo=bar + bar=baz}} + EOF + + join + + assert_file_contents "{{some/component-name foo=bar bar=baz}}" + end + + specify "block components" do + set_file_contents "{{#some-component foo=bar}}Some content{{/some-component}}" + setup_handlebars_filetype + + split + + assert_file_contents <<~EOF + {{#some-component foo=bar}} + Some content + {{/some-component}} + EOF + + join + + assert_file_contents "{{#some-component foo=bar}}Some content{{/some-component}}" + end +end diff --git a/bundle/splitjoin.vim/spec/plugin/html_spec.rb b/bundle/splitjoin.vim/spec/plugin/html_spec.rb new file mode 100644 index 000000000..d619d98e5 --- /dev/null +++ b/bundle/splitjoin.vim/spec/plugin/html_spec.rb @@ -0,0 +1,140 @@ +require 'spec_helper' + +describe "html" do + let(:filename) { 'test.html' } + + after :each do + vim.command('silent! unlet g:splitjoin_html_attributes_bracket_on_new_line') + vim.command('silent! unlet g:splitjoin_html_attributes_hanging') + end + + def simple_test(joined_html, split_html) + set_file_contents joined_html + split + remove_indentation + + assert_file_contents split_html + + vim.normal 'gg' + join + remove_indentation + + assert_file_contents joined_html + end + + specify "tags in other content" do + set_file_contents 'One
Two
Three' + vim.search 'div' + split + remove_indentation + + assert_file_contents <<~EOF + One
+ Two +
Three + EOF + + vim.search 'div' + join + remove_indentation + + assert_file_contents 'One
Two
Three' + end + + specify "tags" do + joined_html = '
bar
' + + split_html = <<~EOF +
+ bar +
+ EOF + + simple_test(joined_html, split_html) + end + + specify "attributes" do + joined_html = '
' + split_html = <<~EOF +
+ EOF + + simple_test(joined_html, split_html) + end + + specify "attributes in self-closing tags" do + joined_html = '
' + split_html = <<~EOF +
+ EOF + + simple_test(joined_html, split_html) + end + + specify "attributes in self-closing tags with bracket on new line" do + vim.command('let g:splitjoin_html_attributes_bracket_on_new_line = 1') + + joined_html = '
' + split_html = <<~EOF +
+ EOF + + simple_test(joined_html, split_html) + end + + specify "attributes with bracket on new line" do + vim.command('let g:splitjoin_html_attributes_bracket_on_new_line = 1') + + joined_html = <<~EOF +
+
+ EOF + split_html = <<~EOF +
+
+ EOF + + simple_test(joined_html, split_html) + end + + specify "hanging attributes" do + vim.command('let g:splitjoin_html_attributes_hanging = 1') + + joined_html = <<~EOF + + EOF + split_html = <<~EOF + + EOF + + simple_test(joined_html, split_html) + end + + specify "brackets within strings" do + joined_html = <<~EOF +