1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-23 07:00:04 +08:00

chore(bundle): use bundle splitjoin

This commit is contained in:
Eric Wong 2024-06-27 18:10:36 +08:00
parent 332214f846
commit de0760cef7
126 changed files with 20478 additions and 1 deletions

View File

@ -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' : '<Plug>(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']}])

View File

@ -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]

View File

@ -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'

4
bundle/splitjoin.vim/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
_project.vim
tags
doc/tags
tmp/

18
bundle/splitjoin.vim/.gitmodules vendored Normal file
View File

@ -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

View File

@ -0,0 +1,2 @@
--color
--exclude-pattern="./spec/support/**/*"

View File

@ -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/

View File

@ -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.

View File

@ -0,0 +1,9 @@
source 'http://rubygems.org'
gem 'rake'
gem 'rspec'
gem 'vimrunner'
gem 'pry'
gem 'guard'
gem 'guard-rspec'

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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
<div id="foo">bar</div>
```
Into this:
``` html
<div id="foo">
bar
</div>
```
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/<any-name>/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/<any-name>/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).

View File

@ -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

View File

@ -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("\<plug>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("\<plug>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! \<esc>"
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('\\\@<!\V'.closing_bracket, 'W', line('.'))
else
" different closing, use searchpair
call searchpair('\V'.opening_bracket, '', '\V'.closing_bracket, 'W', '', line('.'))
endif
endif
normal! l
let remainder_of_line = s:RemainderOfLine()
if remainder_of_line =~ '^$'
" we have no more content, the current column is the end of the expression
return col('.')
endif
endwhile
" we're past the final column of the expression, so return the previous
" one:
return col('.') - 1
finally
let &whichwrap = saved_whichwrap
let &virtualedit = saved_virtualedit
endtry
endfunction
function! s:RemainderOfLine()
return strpart(getline('.'), col('.') - 1)
endfunction
function! s:BracketMatch(text, brackets)
let index = 0
let offset = match(a:text, '^\s*\zs')
let text = strpart(a:text, offset)
for char in split(a:brackets, '\zs')
if text[0] ==# char
return [index, offset]
else
let index += 1
endif
endfor
return [-1, 0]
endfunction

View File

@ -0,0 +1,27 @@
function! sj#argparser#clojure#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#clojure#Process'),
\ })
return parser
endfunction
function! sj#argparser#clojure#Process() dict
while !self.Finished()
if self.body[0] =~ '\s'
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

View File

@ -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('<cword>')
" 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('<cword>')
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 <generics> 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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('<cword>')
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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] =~# '\<struct\s*{$'
" struct { is enforced by gofmt
let padding = ' '
else
let padding = ''
endif
call sj#ReplaceMotion('va{', padding."{\n".sj#Trim(body)."\n}")
return 1
endfunction
function! sj#go#JoinSingleLineFunctionBody()
let start_lineno = line('.')
if search('{$', 'Wc', line('.')) <= 0
return 0
endif
normal! %
let end_lineno = line('.')
if start_lineno == end_lineno
" we haven't moved, brackets not found
return 0
endif
if end_lineno - start_lineno > 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

View File

@ -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

View File

@ -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('<cword>')
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('<cword>')
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

View File

@ -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

View File

@ -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

View File

@ -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('\<function\>.*(.*)\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 ({ <object> }) 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 =~ '^{.*}$'
" ({ <object> }), because otherwise it's ambiguous
let body = '('.body.')'
endif
call sj#ReplaceMotion('va{', body)
return 1
endfunction

View File

@ -0,0 +1,133 @@
function! sj#jsx#SplitJsxExpression()
" Examples:
"
" let x = <tag>
" () => <tag>
" return <tag>
"
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\+\).*</\1>$'
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\+\).*</\1>$'
" 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</'.tag_name.'>', '')
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*</\s*'.tag_name.'\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

View File

@ -0,0 +1,131 @@
let s:function_pattern = '\(\<function\>.\{-}(.\{-})\)\(.*\)\<end\>'
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('\<function\>', 'cW', line('.')) < 0
return 0
endif
let function_lineno = line('.')
if searchpair('\<function\>', '', '^\s*\<end\>', '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

View File

@ -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 = '\<if\s*(.\{-})\s*{.*}'
if search(pattern, 'Wbc') <= 0
return 0
endif
normal! f(
normal %
normal! f{
let body = sj#GetMotion('Va{')
let body = substitute(body, '^{\s*\(.\{-}\)\s*}$', "{\n\\1\n}", '')
call sj#ReplaceMotion('Va{', body)
return 1
endfunction
function! sj#perl#JoinIfClause()
let current_line = getline('.')
let if_clause_pattern = '^\s*\(if\|unless\|while\|until\)\s*(\(.*\))\s*{\=\s*$'
if current_line !~ if_clause_pattern
return 0
endif
let condition = substitute(current_line, if_clause_pattern, '\2', '')
let operation = substitute(current_line, if_clause_pattern, '\1', '')
let start_line = line('.')
call search('{', 'W')
if searchpair('{', '', '}', 'W') <= 0
return 0
endif
let end_line = line('.')
let body = sj#GetMotion('Vi{')
let body = join(split(body, ";\\s*\n"), '; ')
let body = substitute(body, ';\s\+', '; ', 'g')
let body = sj#Trim(body)
let replacement = body.' '.operation.' '.condition.';'
call sj#ReplaceLines(start_line, end_line, replacement)
return 1
endfunction
function! sj#perl#SplitAndClause()
let pattern = '\(.*\) and \(.*\);\s*$'
if sj#settings#Read('perl_brace_on_same_line')
let replacement = "if (\\1) {\n\\2;\n}"
else
let replacement = "if (\\1) \n{\n\\2;\n}"
endif
return s:Split(pattern, replacement)
endfunction
function! sj#perl#SplitOrClause()
let pattern = '\(.*\) or \(.*\);\s*$'
if sj#settings#Read('perl_brace_on_same_line')
let replacement = "unless (\\1) {\n\\2;\n}"
else
let replacement = "unless (\\1) \n{\n\\2;\n}"
endif
return s:Split(pattern, replacement)
endfunction
function! sj#perl#SplitHash()
let [from, to] = sj#LocateBracesOnLine('{', '}')
if from < 0 && to < 0
return 0
endif
let pairs = sj#ParseJsonObjectBody(from + 1, to - 1)
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, 'hashrocket')
endif
return 1
endfunction
function! sj#perl#JoinHash()
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)
if sj#settings#Read('normalize_whitespace')
let lines = map(lines, 'substitute(v:val, "=>\\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

View File

@ -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 = '\<if\s*(.\{-})\s*{.*}'
if search(pattern, 'Wbc', line('.')) <= 0
return 0
endif
normal! f(
normal %
normal! f{
let body = sj#GetMotion('Va{')
let body = substitute(body, '^{\s*\(.\{-}\)\s*}$', "{\n\\1\n}", '')
call sj#ReplaceMotion('Va{', body)
return 1
endfunction
function! sj#php#SplitElseClause()
let pattern = '\<else\s*{.*}'
if search(pattern, 'Wbc', line('.')) <= 0
return 0
endif
normal! f{
let body = sj#GetMotion('Va{')
let body = substitute(body, '^{\s*\(.\{-}\)\s*}$', "{\n\\1\n}", '')
call sj#ReplaceMotion('Va{', body)
return 1
endfunction
function! sj#php#JoinIfClause()
let pattern = '\<if\s*(.\{-})\s*{\s*$'
if search(pattern, 'Wbc', line('.')) <= 0
return 0
endif
normal! f(
normal %
normal! f{
let body = sj#GetMotion('Va{')
let body = substitute(body, "\\s*\n\\s*", ' ', 'g')
call sj#ReplaceMotion('Va{', body)
return 1
endfunction
function! sj#php#JoinElseClause()
let pattern = '\<else\s*{\s*$'
if search(pattern, 'Wbc', line('.')) <= 0
return 0
endif
normal! f{
let body = sj#GetMotion('Va{')
let body = substitute(body, "\\s*\n\\s*", ' ', 'g')
call sj#ReplaceMotion('Va{', body)
return 1
endfunction
function! sj#php#SplitPhpMarker()
if sj#SearchUnderCursor('<?=\=\%(php\)\=.\{-}?>') <= 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, '^<?\(=\=\%(php\)\=\)\s*', "<?\\1\n", '')
let body = substitute(body, '\s*?>$', "\n?>", '')
call sj#ReplaceCols(start_col, end_col, body)
return 1
endfunction
function! sj#php#JoinPhpMarker()
if sj#SearchUnderCursor('<?=\=\%(php\)\=\s*$') <= 0
return 0
endif
let start_lineno = line('.')
let skip = sj#SkipSyntax(['phpStringSingle', 'phpStringDouble', 'phpComment'])
if sj#SearchSkip('?>', 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\<cr>"
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

View File

@ -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('\<if\>', include_syntax, 'W', line('.')) <= 0
return 0
endif
let if_col = col('.')
if sj#SearchSkip('\<else\>', 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

View File

@ -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(\<esc>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(\<esc>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 . "\<esc>"
execute "silent normal! \<esc>"
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

File diff suppressed because it is too large Load Diff

View File

@ -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('\<match .* {')
return 0
endif
call sj#JumpBracketsTill('{', {'opening': '([<"''', 'closing': ')]>"'''})
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 = '\<match .* {$'
if sj#SearchSkip(match_pattern, s:skip_syntax, 'Wc', line('.')) <= 0
\ && sj#SearchSkip(match_pattern, s:skip_syntax, 'Wbc', line('.')) <= 0
return 0
endif
" is it a Result, or an Option?
let return_type = s:FunctionReturnType()
let match_position = getpos('.')
let match_line = match_position[1]
let match_col = match_position[2]
let remainder_of_line = strpart(getline('.'), match_col - 1)
let expr = substitute(remainder_of_line, '^match \(.*\) {$', '\1', '')
let first_line = match_line + 1
let second_line = match_line + 2
let closing_line = match_line + 3
let ok_pattern = '^\s*Ok(\(\k\+\))\s*=>\s*\1'
let err_pattern = '^\s*Err(\k\+)\s*=>\s*return\s\+Err('
let some_pattern = '^\s*Some(\(\k\+\))\s*=>\s*\1'
let none_pattern = '^\s*None\s*=>\s*return\s\+None\>'
if getline(first_line) =~# ok_pattern || getline(second_line) =~# ok_pattern
let expr_type = 'Result'
elseif getline(first_line) =~# none_pattern || getline(second_line) =~# none_pattern
let expr_type = 'Option'
else
return 0
endif
if getline(second_line) =~# err_pattern || getline(first_line) =~# err_pattern
let expr_type = 'Result'
elseif getline(second_line) =~# some_pattern || getline(first_line) =~# some_pattern
let expr_type = 'Option'
else
return 0
endif
if search('^\s*}\ze', 'We', closing_line) <= 0
return 0
endif
let end_position = getpos('.')
if expr_type == return_type
call sj#ReplaceByPosition(match_position, end_position, expr.'?')
else
call sj#ReplaceByPosition(match_position, end_position, expr.'.unwrap()')
endif
return 1
endfunction
function! sj#rust#SplitBlockClosure()
if sj#SearchUnderCursor('|.\{-}|\s*\zs{', 'Wc', s:skip_syntax, line('.')) <= 0
return 0
endif
let closure_contents = sj#GetMotion('vi{')
let closure_contents = substitute(closure_contents, ';\ze.', ";\n", 'g')
call sj#ReplaceMotion('va{', "{\n".sj#Trim(closure_contents)."\n}")
return 1
endfunction
function! sj#rust#SplitExprClosure()
if !sj#SearchUnderCursor('|.\{-}| [^{]')
return 0
endif
if search('|.\{-}| \zs.', 'W', line('.')) <= 0
return 0
endif
let start_col = col('.')
let end_col = sj#JumpBracketsTill('\%([,;]\|$\)', {'opening': '([<{"''', 'closing': ')]>}"'''})
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('\<if .\{-}{', 'e')
let [from, to] = sj#LocateBracesAroundCursor('{', '}')
if from < 0 && to < 0
return 0
endif
if (to - from) < 2
call sj#ReplaceMotion('va{', "{\n\n}")
return 1
endif
let body = sj#Trim(sj#GetCols(from + 1, to - 1))
if len(body) == 0
call sj#ReplaceMotion('va{', "{\n\n}")
return 1
endif
let prefix = sj#GetCols(0, from - 1)
let indent = indent(line('.')) + (exists('*shiftwidth') ? shiftwidth() : &sw)
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
if prefix =~ '^\s*use\s\+\%(\k\+::\)\+\s*$'
" then it's a module import:
" use my_mod::{Alpha, Beta as _, Gamma};
let imports = map(args, 'v:val.argument')
let body = join(imports, ",\n")
if sj#settings#Read('trailing_comma')
let body .= ','
endif
call sj#ReplaceCols(from, to, "{\n".body."\n}")
elseif parser.IsValidStruct()
let is_only_pairs = parser.IsOnlyStructPairs()
let items = []
let last_arg = ''
for arg in args
let last_arg = arg.argument
" attributes are not indented, so let's give them appropriate whitespace
let whitespace = repeat(' ', indent)
let components = map(copy(arg.attributes), 'whitespace.v:val')
call add(components, arg.argument)
call add(items, join(components, "\n"))
endfor
let body = join(items, ",\n")
if sj#settings#Read('trailing_comma')
if last_arg =~ '^\.\.'
" interpolated struct, a trailing comma would be invalid
else
let body .= ','
endif
endif
call sj#ReplaceCols(from, to, "{\n".body."\n}")
if is_only_pairs && sj#settings#Read('align')
let body_start = line('.') + 1
let body_end = body_start + len(items) - 1
if items[-1] =~ '^\.\.'
" interpolated struct, don't align that one
let body_end -= 1
endif
if body_end - body_start > 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 = '\<match\s\+\zs.\{-}\ze\s\+{$'
let pattern_pattern = '^\s*\zs.\{-}\ze\s\+=>'
if search(match_pattern, 'Wc', line('.')) <= 0
return 0
endif
let outer_start_lineno = line('.')
let [_, match_start_col] = searchpos('\<match\s\+', 'nbW', line('.'))
" find end point
normal! f{%
let outer_end_lineno = line('.')
let inner_start_lineno = search(pattern_pattern, 'Wb', outer_start_lineno)
if inner_start_lineno <= 0
return 0
endif
let inner_start_lineno = line('.')
if getline(inner_start_lineno) =~ '^\s*_\s*=>'
" 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) =~ '\<fn \k\+\%(<.*>\)$'
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

View File

@ -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

View File

@ -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

View File

@ -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\\\<cr>"
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',
\ ]

View File

@ -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

View File

@ -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',
\ ]

View File

@ -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',
\ ]

View File

@ -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',
\ ]

View File

@ -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',
\ ]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
runtime ftplugin/jsx/splitjoin.vim

View File

@ -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

View File

@ -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',
\ ]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',
\ ]

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',
\ ]

View File

@ -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

View File

@ -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',
\ ]

View File

@ -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',
\ ]

View File

@ -0,0 +1 @@
runtime ftplugin/vue/splitjoin.vim

View File

@ -0,0 +1,7 @@
let b:splitjoin_split_callbacks = [
\ 'sj#tex#SplitBlock',
\ ]
let b:splitjoin_join_callbacks = [
\ 'sj#tex#JoinBlock',
\ ]

View File

@ -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',
\ ]

View File

@ -0,0 +1 @@
runtime ftplugin/javascript/splitjoin.vim

View File

@ -0,0 +1 @@
runtime ftplugin/tsx/splitjoin.vim

View File

@ -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

View File

@ -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',
\ ]

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
let b:splitjoin_split_callbacks = [
\ 'sj#sh#SplitBySemicolon',
\ ]
let b:splitjoin_join_callbacks = [
\ 'sj#sh#JoinWithSemicolon',
\ ]

View File

@ -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 <silent> <plug>SplitjoinSplit :<c-u>call sj#Split()<cr>
nnoremap <silent> <plug>SplitjoinJoin :<c-u>call sj#Join()<cr>
if g:splitjoin_join_mapping != ''
exe 'nnoremap <silent> '.g:splitjoin_join_mapping.' :<c-u>call <SID>Mapping(g:splitjoin_join_mapping, "sj#Join")<cr>'
endif
if g:splitjoin_split_mapping != ''
exe 'nnoremap <silent> '.g:splitjoin_split_mapping.' :<c-u>call <SID>Mapping(g:splitjoin_split_mapping, "sj#Split")<cr>'
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

View File

@ -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

View File

@ -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

View File

@ -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

Some files were not shown because too many files have changed in this diff Show More