-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
52 changed files
with
4,034 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.DS_Store | ||
/plugin | ||
todo.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
test: | ||
nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/ {minimal_init = 'tests/minimal.vim'}" | ||
|
||
test-langs: | ||
nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/langs/ {minimal_init = 'tests/minimal.vim'}" | ||
|
||
test-chold: | ||
nvim --headless --noplugin -u tests/minimal.vim -c "PlenaryBustedDirectory tests/chold/ {minimal_init = 'tests/minimal.vim'}" | ||
|
||
lint-fix: | ||
stylua ./lua/treesj/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
# TreeSJ: split or join blocks of code | ||
|
||
Neovim plugin for splitting/joining blocks of code like arrays, hashes, statements, objects, dictionaries, etc. Written in Lua, using [Tree-Sitter](https://tree-sitter.github.io/tree-sitter/). | ||
|
||
Inspired by and partly repeats the functionality of [splitjoin.vim](https://github.com/AndrewRadev/splitjoin.vim). | ||
|
||
> *⚡Disclaimer: The plugin is under active development. Documentation will be added when all planned features are implemented. Feel free to open an issue or PR 💪* | ||
|
||
https://user-images.githubusercontent.com/46977173/201088511-b336cec5-cec4-437f-95b3-0208c83377fd.mov | ||
|
||
<sup align="center">Theme: [Catppuccin](https://github.com/catppuccin/nvim), Font: JetBrains Mono</sup> | ||
|
||
## Features | ||
|
||
- **Can be called from anywhere in the block**: No need to move cursor to specified place to split/join block of code; | ||
- **Make cursor sticky**: The cursor follows the text on which it was called; | ||
- **Autodetect mode**: Toggle-mode present. Split or join blocks by same key mapping; | ||
- **Do it recursively**: Expand or collapse all nested nodes? Yes, you can; | ||
- **Recognize nested languages**: Filetype doesn't matter, detect language with treesitter. | ||
|
||
## Requirements | ||
|
||
1. [Neovim 0.8+](https://github.com/neovim/neovim/releases) | ||
2. [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) | ||
|
||
## Installation | ||
|
||
With [packer.nvim](https://github.com/wbthomason/packer.nvim): | ||
|
||
```lua | ||
use({ | ||
'https://github.com/Wansmer/treesj', | ||
requires = { 'nvim-treesitter' }, | ||
config = function() | ||
require('treesj').setup({--[[ your config ]]}) | ||
end, | ||
}) | ||
``` | ||
|
||
## Configuration | ||
|
||
### Plugin configuration | ||
|
||
Default configuration: | ||
|
||
```lua | ||
local tsj = require('treesj') | ||
|
||
local langs = {--[[ configuration for languages ]]} | ||
|
||
tsj.setup({ | ||
-- Use default keymaps | ||
-- (<space>m - toggle, <space>j - join, <space>s - split) | ||
use_default_keymaps = true, | ||
|
||
-- Node with syntax error will not be formatted | ||
check_syntax_error = true, | ||
|
||
-- If line after join will be longer than max value, | ||
-- node will not be formatted | ||
max_join_length = 120, | ||
|
||
-- hold|start|end: | ||
-- hold - cursor follows the node/place on which it was called | ||
-- start - cursor jumps to the first symbol of the node being formatted | ||
-- end - cursor jumps to the last symbol of the node being formatted | ||
cursor_behavior = 'hold', | ||
|
||
-- Notify about possible problems or not | ||
notify = true, | ||
langs = langs, | ||
}) | ||
``` | ||
Also, TreeSJ provide user commands: | ||
|
||
1. `:TSJToggle` - toggle node under cursor (split if one-line and join if multiline); | ||
2. `:TSJSplit` - split node under cursor; | ||
3. `:TSJJoin` - split node under cursor; | ||
|
||
### Languages configuration | ||
|
||
By default, TreeSJ has presets for these languages: | ||
|
||
- **Javascript**; | ||
- **Typescript**; | ||
- **Lua**; | ||
- **CSS**; | ||
- **SCSS**; | ||
- **HTML**; | ||
- **JSON**; | ||
|
||
For adding your favorite language, add it to `langs` sections in your configuration. | ||
|
||
To find out what nodes are called in your language, analyze your code with [nvim-treesitter/playground](https://github.com/nvim-treesitter/playground) or look in the [source code of the parsers](https://tree-sitter.github.io/tree-sitter/). | ||
|
||
**Example:** | ||
|
||
```lua | ||
local langs = { | ||
javascript = { | ||
array = {--[[ preset ]]}, | ||
object = {--[[ preset ]]} | ||
['function'] = { target_nodes = {--[[ targets ]]}} | ||
}, | ||
} | ||
``` | ||
|
||
If you have completely configured your language, and it works as well as you expected, feel free to open PR and share it. | ||
|
||
### Nodes configuration | ||
|
||
Default preset for node: | ||
|
||
```lua | ||
local somenode = { | ||
-- Use for split and join. Will merge to both resulting presets | ||
-- If you need various values for different modes, | ||
-- it can be overridden in modes sections | ||
both = { | ||
-- string[]: TreeSJ will stop if node contains node from list | ||
no_format_with = { 'comment' }, | ||
|
||
-- string: Separator for arrays, objects, hash e.c.t. | ||
-- Will auto add to option 'omit' for 'both' | ||
separator = '', | ||
|
||
-- boolean: Set last separator or not | ||
last_separator = false, | ||
|
||
-- string[]: Nodes in list will be joined for previous node | ||
-- (e.g. tag_name in HTML start_tag or separator (',') in JS object) | ||
-- NOTE: Must be same for both modes | ||
omit = {}, | ||
}, | ||
|
||
-- Use only for join. If contains field from 'both', | ||
-- field here have higher priority | ||
join = { | ||
|
||
-- Adding space in framing brackets or last/end element | ||
space_in_brackets = false, | ||
|
||
-- Count of spaces between nodes | ||
space_separator = 1, | ||
|
||
-- string: Add instruction separator like ';' in statement block | ||
-- Will auto add to option 'omit' for 'both' | ||
force_insert = '', | ||
|
||
-- string[]: The insert symbol will be omitted if node contains in this list | ||
-- (e.g. function_declaration inside statement_block in JS no require instruction separator (';')) | ||
no_insert_if = {}, | ||
|
||
-- boolean: All nested configured nodes will process according to their presets | ||
recursive = true, | ||
|
||
-- [string]: Type of configured node that must be ignored | ||
recursive_ignore = {}, | ||
}, | ||
|
||
-- Use only for split. If contains field from 'both', | ||
-- field here have higher priority | ||
split = { | ||
-- boolean: All nested configured nodes will process according to their presets | ||
recursive = false, | ||
|
||
-- [string]: Type of configured node that must be ignored | ||
-- E.g., you probably don't want the parameters of each nested function to be expanded. | ||
recursive_ignore = {}, | ||
}, | ||
-- If 'true', node will be completely removed from langs preset | ||
disable = false, | ||
|
||
-- TreeSJ will search child from list into this node and redirect to found child | ||
-- If list not empty, another fields (split, join) will be ignored | ||
target_nodes = {}, | ||
} | ||
``` | ||
|
||
All nodes in every language have similar characteristics. | ||
TreeSJ provide default presets for common nodes: | ||
|
||
`set_default_preset(override)` - default. | ||
|
||
`set_preset_for_list(override)` - list-like nodes. | ||
|
||
`set_preset_for_dict(override)` - dict-like nodes. | ||
|
||
`set_preset_for_statement(override)` - statement-like nodes. | ||
|
||
`set_preset_for_args(override)` - arguments-like nodes. | ||
|
||
Takes a table with the settings to be overwritten as an argument. | ||
|
||
**Usage example**: | ||
|
||
```lua | ||
local tsj_utils = require('treesj.langs.utils') | ||
|
||
local langs = { | ||
javascript = { | ||
object = tsj_utils.set_preset_for_dict(), | ||
array = tsj_utils.set_preset_for_list(), | ||
formal_parameters = tsj_utils.set_preset_for_args(), | ||
arguments = tsj_utils.set_preset_for_args(), | ||
statement_block = tsj_utils.set_preset_for_statement({ | ||
join = { | ||
no_insert_if = { | ||
'function_declaration', | ||
'try_statement', | ||
'if_statement', | ||
}, | ||
}, | ||
}), | ||
}, | ||
lua = { | ||
table_constructor = tsj_utils.set_preset_for_dict(), | ||
arguments = tsj_utils.set_preset_for_args(), | ||
parameters = tsj_utils.set_preset_for_args(), | ||
}, | ||
} | ||
``` | ||
|
||
Also, you can use whole preset for language if your language has the same types of nodes: | ||
|
||
> For example, `css` and `scss` have the same structure, and you can use already configured preset | ||
```lua | ||
local tsj_utils = require('treesj.langs.utils') | ||
local css = require('treesj.langs.css') | ||
|
||
local langs = { | ||
scss = u.merge_preset(css, {--[[ | ||
Here you can override existing nodes | ||
or add language-specific nodes | ||
]]}) | ||
} | ||
``` | ||
|
||
## How it works | ||
|
||
When you run the plugin, TreeSJ detects the node under the cursor, recognizes the language, and looks for it in the presets. | ||
If the current node is not configured, TreeSJ checks the parent node, and so on, until a configured node is found. | ||
|
||
Presets for node can be two types: | ||
|
||
1. With preset for self - if this type is found, the node will be formatted; | ||
2. With referens for nested nodes - in this case, search will be continued among this node descendants; | ||
|
||
**Example**: | ||
|
||
> "|" - meaning cursor | ||
```javascript | ||
const arr = [ 1, 2, 3 ] | ||
``` | ||
|
||
```txt | ||
// with preset for self | ||
const arr = [ 1, |2, 3 ]; | ||
| | ||
first node is 'number' - not configured, | ||
parent node is 'array' - configured and will be split | ||
// with referens | ||
cons|t arr = [ 1, 2, 3 ]; | ||
| | ||
first node is 'variable_declarator' - not configured, | ||
parent node is 'lexical_declaration' - configured and has reference | ||
{ target_nodes = { 'array', 'object' } }, | ||
first configured nested node is 'array' and array will be splitted | ||
``` |
Oops, something went wrong.