{"id":13412911,"url":"https://github.com/Wansmer/treesj","last_synced_at":"2025-03-14T18:32:38.386Z","repository":{"id":63694328,"uuid":"564749620","full_name":"Wansmer/treesj","owner":"Wansmer","description":"Neovim plugin for splitting/joining blocks of code","archived":false,"fork":false,"pushed_at":"2024-08-05T10:51:15.000Z","size":290,"stargazers_count":965,"open_issues_count":3,"forks_count":29,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-08-05T12:18:33.171Z","etag":null,"topics":["neovim","neovim-plugin","nvim-lua","refactoring","splitjoin","treesitter"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Wansmer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-11T12:09:43.000Z","updated_at":"2024-08-05T10:51:17.000Z","dependencies_parsed_at":"2024-01-03T03:32:48.495Z","dependency_job_id":"0faba99d-255d-4097-bef7-020ba40313d6","html_url":"https://github.com/Wansmer/treesj","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wansmer%2Ftreesj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wansmer%2Ftreesj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wansmer%2Ftreesj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Wansmer%2Ftreesj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Wansmer","download_url":"https://codeload.github.com/Wansmer/treesj/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243625199,"owners_count":20321253,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["neovim","neovim-plugin","nvim-lua","refactoring","splitjoin","treesitter"],"created_at":"2024-07-30T20:01:30.983Z","updated_at":"2025-03-14T18:32:38.374Z","avatar_url":"https://github.com/Wansmer.png","language":"Lua","funding_links":[],"categories":["Editing Support","Lua"],"sub_categories":["Scrollbar"],"readme":"# TreeSJ\n\nNeovim plugin for splitting/joining blocks of code like arrays, hashes,\nstatements, objects, dictionaries, etc.\n\nWritten in Lua, using [Tree-Sitter](https://tree-sitter.github.io/tree-sitter/).\n\nInspired by and partly repeats the functionality of\n[splitjoin.vim](https://github.com/AndrewRadev/splitjoin.vim).\n\n\u003c!-- panvimdoc-ignore-start --\u003e\n\nhttps://github.com/Wansmer/treesj/assets/46977173/4277455b-81fd-4e99-9af7-43c77dbf542b\n\n\u003c!--toc:start--\u003e\n\n- [Features](#features)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Settings](#settings)\n- [Commands](#commands)\n- [How plugin works](#how-plugin-works)\n- [Configuration](#configuration)\n  - [Languages](#languages)\n  - [Basic node](#basic-node)\n  - [Advanced node](#advanced-node)\n  - [TreeSJ instance](#treesj-instance)\n\n\u003c!--toc:end--\u003e\n\n\u003c!-- panvimdoc-ignore-end --\u003e\n\n## Features\n\n- **Can be called from anywhere in the block**: No need to move cursor to\n  specified place to split/join block of code;\n- **Make cursor sticky**: The cursor follows the text on which it was called;\n- **Autodetect mode**: Toggle-mode present. Split or join blocks by same key\n  mapping;\n- **Do it recursively**: Expand or collapse all nested nodes? Yes, you can;\n- **Recognize nested languages**: Filetype doesn't matter, detect language with\n  treesitter;\n- **Repeat formatting with `dot`**: `.` support for each action.\n- **Smart**: Different behavior depending on the context.\n\n## Requirements\n\n- [Neovim 0.9+](https://github.com/neovim/neovim/releases)\n- [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) (optional: if you install parsers with `nvim-treesitter`)\n\n## Installation\n\nWith [lazy.nvim](https://github.com/folke/lazy.nvim):\n\n```lua\nreturn {\n  'Wansmer/treesj',\n  keys = { '\u003cspace\u003em', '\u003cspace\u003ej', '\u003cspace\u003es' },\n  dependencies = { 'nvim-treesitter/nvim-treesitter' }, -- if you install parsers with `nvim-treesitter`\n  config = function()\n    require('treesj').setup({--[[ your config ]]})\n  end,\n}\n```\n\nWith [packer.nvim](https://github.com/wbthomason/packer.nvim):\n\n```lua\nuse({\n  'Wansmer/treesj',\n  requires = { 'nvim-treesitter/nvim-treesitter' }, -- if you install parsers with `nvim-treesitter`\n  config = function()\n    require('treesj').setup({--[[ your config ]]})\n  end,\n})\n```\n\n## Settings\n\nDefault configuration:\n\n```lua\nlocal tsj = require('treesj')\n\nlocal langs = {--[[ configuration for languages ]]}\n\ntsj.setup({\n  ---@type boolean Use default keymaps (\u003cspace\u003em - toggle, \u003cspace\u003ej - join, \u003cspace\u003es - split)\n  use_default_keymaps = true,\n  ---@type boolean Node with syntax error will not be formatted\n  check_syntax_error = true,\n  ---If line after join will be longer than max value,\n  ---@type number If line after join will be longer than max value, node will not be formatted\n  max_join_length = 120,\n  ---Cursor behavior:\n  ---hold - cursor follows the node/place on which it was called\n  ---start - cursor jumps to the first symbol of the node being formatted\n  ---end - cursor jumps to the last symbol of the node being formatted\n  ---@type 'hold'|'start'|'end'\n  cursor_behavior = 'hold',\n  ---@type boolean Notify about possible problems or not\n  notify = true,\n  ---@type boolean Use `dot` for repeat action\n  dot_repeat = true,\n  ---@type nil|function Callback for treesj error handler. func (err_text, level, ...other_text)\n  on_error = nil,\n  ---@type table Presets for languages\n  -- langs = {}, -- See the default presets in lua/treesj/langs\n})\n```\n\n## Commands\n\nTreeSJ provide user commands:\n\n- `:TSJToggle` - toggle node under cursor (split if one-line and join if\n  multiline);\n- `:TSJSplit` - split node under cursor;\n- `:TSJJoin` - join node under cursor;\n\nSimilar with lua:\n\n```bash\n:lua require('treesj').toggle()\n:lua require('treesj').split()\n:lua require('treesj').join()\n```\n\nIn the lua version, you can optionally pass a preset that will overwrite the\ndefault preset values. It should contain `split` or `join` keys. Key `both`\nwill be ignored.\n\nE.g.:\n\n```lua\n-- For default preset\nvim.keymap.set('n', '\u003cleader\u003em', require('treesj').toggle)\n-- For extending default preset with `recursive = true`\nvim.keymap.set('n', '\u003cleader\u003eM', function()\n    require('treesj').toggle({ split = { recursive = true } })\nend)\n```\n\n## How plugin works\n\nWhen you run the plugin, TreeSJ detects the node under the cursor, recognizes\nthe language, and looks for it in the presets. If the current node is not\nconfigured, TreeSJ checks the parent node, and so on, until a configured node is\nfound.\n\nPresets for node can be two types:\n\n- With preset for self - if this type is found, the node will be formatted;\n- With referens for nested nodes or fields - in this case, search will be\n  continued among this node descendants;\n\n**Example**:\n\n\u003e \"|\" - meaning cursor\n\n```txt\n// with preset for self\nconst arr = [ 1, |2, 3 ];\n                 |\n    first node is 'number' - not configured,\n    parent node is 'array' - configured and will be split\n\n// with referens\ncons|t arr = [ 1, 2, 3 ];\n    |\n  first node is 'variable_declarator' - not configured,\n  parent node is 'lexical_declaration' - configured and has reference\n  { target_nodes = { 'array', 'object' } },\n  first configured nested node is 'array' and array will be splitted\n```\n\n# Configuration\n\n## Languages\n\nBy default, TreeSJ has presets for these languages:\n\n- **Javascript**;\n- **Typescript**;\n- **Tsx**;\n- **Jsx**;\n- **Lua**;\n- **CSS**;\n- **SCSS**;\n- **HTML**;\n- **Pug**;\n- **Vue**;\n- **Svelte**;\n- **JSON**;\n- **JSONC**;\n- **JSON5**;\n- **Toml**;\n- **Yaml**;\n- **Perl**;\n- **PHP** (both `php` and `php_only`);\n- **Ruby**;\n- **Python**;\n- **Starlark**;\n- **Go**;\n- **Java**;\n- **Rust**;\n- **R**;\n- **C/C++**;\n- **Nix**;\n- **Kotlin**;\n- **Bash**;\n- **SQL**;\n- **Dart**;\n- **Elixir**;\n- **Haskell**;\n- **Zig**;\n- **Julia**;\n\nFor adding your favorite language, add it to `langs` sections in your\nconfiguration. Also, see how [to implement\nfallback](https://github.com/Wansmer/treesj/discussions/19) to splitjoin.vim.\n\nIt is also possible to configure fallback for any node (see [Advanced\nnode](#advanced-node)).\n\nTo find out what nodes are called in your language, analyze your code with\n[nvim-treesitter/playground](https://github.com/nvim-treesitter/playground) or\nlook in the [source code of the\nparsers](https://tree-sitter.github.io/tree-sitter/).\n\n**Example:**\n\n```lua\nlocal langs = {\n  javascript = {\n    array = {--[[ preset ]]},\n    object = {--[[ preset ]]}\n    ['function'] = { target_nodes = {--[[ targets ]]}}\n  },\n}\n```\n\nIf you have completely configured your language, and it works as well as you\nexpected, feel free to open PR and share it.\n(Please, read [manual](/tests/README.md) before PR)\n\n## Basic node\n\nDefault preset for node:\n\n```lua\nlocal node_type = {\n  -- `both` will be merged with both presets from `split` and `join` modes tables.\n  -- If you need different values for different modes, they can be overridden\n  -- in mode tables unless otherwise noted.\n  both = {\n    ---If a node contains descendants with a type from the list, it will not be formatted\n    ---@type string[]\n    no_format_with = { 'comment' },\n    ---Separator for arrays, objects, hash e.c.t. (usually ',')\n    ---@type string\n    separator = '',\n    ---Set last separator or not\n    ---@type boolean\n    last_separator = false,\n    ---If true, empty brackets, empty tags, or node which only contains nodes from 'omit' no will handling\n    ---@type boolean\n    format_empty_node = true,\n    ---All nested configured nodes will process according to their presets\n    ---@type boolean\n    recursive = true,\n    ---Type of configured node that must be ignored\n    ---@type string[]\n    recursive_ignore = {},\n\n    --[[ Working with the options below is explained in detail in `advanced node configuration` section. ]]\n    ---Set `false` if node should't be splitted or joined.\n    ---@type boolean|function For function: function(tsnode: TSNode): boolean\n    enable = true,\n    ---@type function|nil function(tsj: TreeSJ): void\n    format_tree = nil,\n    ---@type function|nil function(lines: string[], tsn?: TSNode): string[]\n    format_resulted_lines = nil,\n    ---Passes control to an external script and terminates treesj execution.\n    ---@type function|nil function(node: TSNode): void\n    fallback = nil,\n\n    --[[ The options below should be the same for both modes. ]]\n    ---The text of the node will be merged with the previous one, without wrapping to a new line\n    ---@type table List-like table with types 'string' (type of node) or 'function' (function(child: TreeSJ): boolean).\n    omit = {},\n    ---Non-bracket nodes (e.g., with 'then|()' ... 'end' instead of { ... }|\u003c ... \u003e|[ ... ])\n    ---If value is table, should be contains follow keys: { left = 'text', right = 'text' }. Empty string uses by default\n    ---@type boolean|table\n    non_bracket_node = false,\n    ---If you need to process only nodes in the range from / to.\n    ---If `shrink_node` is present, `non_bracket_node` will be ignored\n    ---Learn more in advanced node configuration\n    ---@type table|nil\n    shrink_node = nil,\n    -- shrink_node = { from = string, to = string },\n  },\n  -- Use only for join. If contains field from 'both',\n  -- field here have higher priority\n  join = {\n    ---Adding space in framing brackets or last/end element\n    ---@type boolean\n    space_in_brackets = false,\n    ---Insert space between nodes or not\n    ---@type boolean\n    space_separator = true,\n    ---Adds instruction separator like ';' in statement block.\n    ---It's not the same as `separator`: `separator` is a separate node, `force_insert` is a last symbol of code instruction.\n    ---@type string\n    force_insert = '',\n    ---The `force_insert` symbol will be omitted if the type of node contains in this list\n    -- (e.g. function_declaration inside statement_block in JS no require instruction separator (';'))\n    ---@type table List-like table with types 'string' (type of node) or 'function' (function(child: TreeSJ): boolean).\n    no_insert_if = {},\n  },\n  -- Use only for split. If contains field from 'both',\n  -- field here have higher priority\n  split = {\n    ---All nested configured nodes will process according to their presets\n    ---@type boolean\n    recursive = false,\n    ---Which indent must be on the last line of the formatted node.\n    --- 'normal' – indent equals of the indent from first line;\n    --- 'inner' – indent, like all inner nodes (indent of start line of node + vim.fn.shiftwidth()).\n    ---@type 'normal'|'inner'\n    last_indent = 'normal',\n    ---Which indent must be on the last line of the formatted node.\n    --- 'normal' – indent equals of the indent from first line;\n    --- 'inner' – indent, like all inner nodes (indent of start line of node + vim.fn.shiftwidth()).\n    ---@type 'normal'|'inner'\n    inner_indent = 'inner',\n  },\n  ---If 'true', node will be completely removed from langs preset\n  ---@type boolean\n  disable = false,\n  ---TreeSJ will search child from list into this node and redirect to found child\n  ---If list not empty, another fields (split, join) will be ignored\n  ---@type string[]|table See `advanced node configuration`\n  target_nodes = {},\n}\n```\n\nAll nodes in every language have similar characteristics.\nTreeSJ provide default presets for common nodes:\n\n`set_default_preset(override)` - default.\n\n`set_preset_for_list(override)` - list-like nodes.\n\n`set_preset_for_dict(override)` - dict-like nodes.\n\n`set_preset_for_statement(override)` - statement-like nodes.\n\n`set_preset_for_args(override)` - arguments-like nodes.\n\n`set_preset_for_non_bracket(override)` - non-bracket nodes;\n\nTakes a table with the settings to be overwritten as an argument.\n\n**Usage example**:\n\n```lua\nlocal lang_utils = require('treesj.langs.utils')\n\nlocal langs = {\n  javascript = {\n    object = lang_utils.set_preset_for_dict(),\n    array = lang_utils.set_preset_for_list(),\n    formal_parameters = lang_utils.set_preset_for_args(),\n    arguments = lang_utils.set_preset_for_args(),\n    statement_block = lang_utils.set_preset_for_statement({\n      join = {\n        no_insert_if = {\n          'function_declaration',\n          'try_statement',\n          'if_statement',\n        },\n      },\n    }),\n  },\n  lua = {\n    table_constructor = lang_utils.set_preset_for_dict(),\n    arguments = lang_utils.set_preset_for_args(),\n    parameters = lang_utils.set_preset_for_args(),\n  },\n}\n```\n\nAlso, you can use whole preset for language if your language has the same types\nof nodes:\n\n\u003e For example, `css` and `scss` have the same structure, and you can use already\n\u003e configured preset\n\n```lua\nlocal tsj_utils = require('treesj.langs.utils')\nlocal css = require('treesj.langs.css')\n\nlocal langs = {\n  scss = tsj_utils.merge_preset(css, {--[[\n    Here you can override existing nodes\n    or add language-specific nodes\n]]})\n}\n```\n\n## Advanced node\n\nAlthough most nodes have similar parameters and can be configured declarative,\nsometimes you need to change the values, text, or order of children on the fly.\n\nTo do this, some options accept functions as a value, which are passed instances\nof [TSNode](https://neovim.io/doc/user/treesitter.html#treesitter-node),\n[TreeSJ](#treesj-instance), or an array of rows to insert.\n\n#### Option `enable`\n\nThe `enable` option can be a boolean value or a function. The function takes the\nfound [TSNode](https://neovim.io/doc/user/treesitter.html#treesitter-node) node\nas arguments and should return a boolean value.\n\n\u003cdetails\u003e\n\u003csummary\u003eExample of usage\u003c/summary\u003e\n\nThe problem:\n\n```go\n// from 'import_spec_list'\nimport (\n    \"123\"\n)\n\n// to 'import_spec' and back\nimport \"123\"\n\n// but disable 'import_spec_list' when two or more 'import_spec' inside and\n// disable 'import_spec' when it already inside 'import_spec_list'\nimport (\n    \"123\"\n    \"321\"\n)\n```\n\nThis can be implemented with:\n\n```lua\nlocal go = {\n  import_spec = lang_utils.set_preset_for_args({\n    both = {\n      -- If the parent is a 'import_spec_list', then skip this node and\n      -- look for a suitable one for formatting further\n      enable = function(tsn)\n        return tsn:parent():type() ~= 'import_spec_list'\n      end,\n    },\n    split = {\n      -- If the parent was something else, then wrap the import in parentheses,\n      -- which actually converts the 'import_spec' into the 'import_spec_list'\n      format_tree = function(tsj)\n        tsj:wrap({ left = '(', right = ')' })\n      end,\n    },\n  }),\n  import_spec_list = lang_utils.set_preset_for_args({\n    join = {\n      enable = function(tsn)\n        -- If the node contains more than one named child node, then disable\n        return tsn:named_child_count() \u003c 2\n      end,\n      format_tree = function(tsj)\n        -- If there was only one named element, then remove the brackets\n        tsj:remove_child({ '(', ')' })\n      end,\n    },\n  }),\n}\n```\n\n\u003c/details\u003e\n\n#### Option `fallback`\n\nThe `fallback` option passes control to a third-party script that can be called\nwithin the function. When this option is used, `treesj` only searches for the\nrequired node, but does not process it.\n\nA found TSNode instance is passed to the function, which can be handled\nindependently. For example, you can get the region of its location, check its\nsiblings, etc.\n\n\u003cdetails\u003e\n\u003csummary\u003eExample of usage\u003c/summary\u003e\n\nThe problem:\n\nThis action is difficult to implement with `treesj`, so you can pass control to\n`splitjoin.vim` if the found node is a `class` or `module`.\n\n\u003e Note that in the above example, `splitjoin.vim' requires the cursor to be on the\n\u003e name of a class or module. In some cases, you may need to keep track of the\n\u003e position of the cursor from which the handler is called.\n\n```ruby\n# RESULT OF JOIN\nclass Foo::Bar::Baz \u003c Quux\n  def initialize\n    # foo\n  end\nend\n\n# RESULT OF SPLIT\nmodule Foo\n  module Bar\n    class Baz \u003c Quux\n      def initialize\n        # foo\n      end\n    end\n  end\nend\n```\n\nThis can be implemented with:\n\n```lua\nruby = {\n  module = {\n    both = {\n      no_format_with = {}, -- Need to avoid 'no format with comment'\n      fallback = function(_)\n        vim.cmd('SplitjoinJoin')\n      end,\n    },\n  },\n  class = {\n    both = {\n      no_format_with = {},\n      fallback = function(_)\n        vim.cmd('SplitjoinSplit')\n      end,\n    },\n  },\n},\n```\n\n\u003c/details\u003e\n\n#### Option `no_insert_if`\n\nThe `preset[join].no_insert_if` option takes an array with node types or\nfunctions. The function takes a TreeSJ as a parameter (each child of the root\nnode in turn) and should return a boolean if that child matches the condition.\n\nThe utilities have helper functions that you will need most often:\n\n`lang_utils.helpers.if_penultimate`\n\n`lang_utils.helpers.if_second`\n\n`lang_utils.helpers.by_index(index)`\n\n`lang_utils.helpers.has_parent(parent_type)`\n\n`lang_utils.helpers.match(pattern)`\n\n`lang_utils.helpers.contains(tsn_types)`\n\n\u003cdetails\u003e\n\n\u003csummary\u003eExample of usage\u003c/summary\u003e\n\nThe problem:\n\n```kotlin\n// Kotlin's 'statements' should be separated with ';' after each child\n// when it joins, but for the last named child it is not necessary,\n// and we want to skip it\n\n// from\ncall_expression(\"arg1\") {\n  call1(1, 2)\n  call2(1, 2)\n}\n\n// to                                             | here no ';', great\ncall_expression(\"arg1\") { call1(1, 2); call2(1, 2) }\n```\n\nThis can be implemented with:\n\n```lua\nlocal lang_utils = require('treesj.langs.utils')\n\nlocal kotlin = {\n  statements = lang_utils.set_preset_for_non_bracket({\n    join = {\n      force_insert = ';',\n      no_insert_if = {\n        lang_utils.no_insert.if_penultimate,\n      },\n    },\n  }),\n}\n```\n\n\u003c/details\u003e\n\n#### Option `omit`\n\nThe `preset[both]omit` option accepts a list of node types or functions. If at\nthe time the tree is built, the type of the child matches one of the listed\nones, then this child remains unchanged. (does not wrap to a new line in case of\na split, or its position and spaces do not change in the case of a join).\n\nWhen some value of list is `function` the principle of operation is the same as\nthat of `no_insert_if`.\n\n#### Option `non_bracket_node`\n\nSome languages have nodes that can be split and joined, but they don't have\nbrackets or other framing elements.\n\nIn this case, it is necessary not only to simulate the framing nodes, but also\nto calculate a new range for inserting rows.\n\n`preset[both].non_bracket_node` can be a boolean value (in which case wrapping\nnode imitators are created with an empty value) or take a table that\nspecifies what text should wrap the actual base node.\n\nE.g., table value: `{ left = 'text', right = 'text' }`\n\n\u003cdetails\u003e\n\n\u003csummary\u003eExample of usage\u003c/summary\u003e\n\nThe problem:\n\n```lua\n-- `block` in Lua does not have parentheses, but when it joins,\n-- it must jump up a line and pull an `end` node that is not part of it.\n-- To do this, you need to create imitators of framing nodes one line above\n-- and one line below\n               | -- imitator left-side bracket\nfunction test()|\n  | -- real start of `block`\n  |print(123)\n  return 123| -- real end of `block`\n| -- imitator right-side bracket\n|end\n\n-- from\nfunction test()\n  print(123)\n  return 123\nend\n\n-- to and back\nfunction test() print(123) return 123 end\n```\n\nThis can be implemented with:\n\n```lua\nlocal lang_utils = require('treesj.langs.utils')\n\nlocal lua = {\n  block = {\n    both = {\n      non_bracket_node = true,\n      -- non_bracket_node = { left = '', right = '' }, - it is similar\n    },\n    join = {\n      space_in_brackets = true,\n    },\n  },\n}\n```\n\n\u003c/details\u003e\n\n#### Option `shrink_node`\n\nThe `shrink_node` option does not process or modify text outside the specified\nchild types in from/to. This is needed when the node does not have a container,\nbut can be split or join. For better understanding, see example of usage.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eExample of usage\u003c/summary\u003e\n\nThe problem:\n\nIn the Kotlin language, the `function_declaration` node has parameters in\nbrackets, but they are not packaged in a separate container and go at the same\nlevel as the other children of the `function_declaration`.\n\nThe difficulty is that the last element in `function_declaration` is\n`function_body`, which must remain unchanged and can be multi-line.\n\n```kotlin\n| - start of `function_declaration`\n|        | - from             | - to\n|        |                    | | - `function_body` must remain unchanged\n|fun test(a: String, b: String) {\n         | - start insert range\n                              | - end insert range\n         Everything outside the insertion range will remain unchanged,\n         even though it is part of the node\n  val var1 = 1\n  val var2 = 2\n}|\n | - end of `function_declaration`\n\n// from\nfun test(a: String, b: String) {\n  val var1 = 1\n  val var2 = 2\n}\n// to\nfun test(\n  a: String,\n  b: String\n) {\n  val var1 = 1\n  val var2 = 2\n}\n```\n\nThis can be implemented with:\n\n```lua\n{\n  function_declaration = lang_utils.set_preset_for_args({\n    both = {\n      non_bracket_node = true,\n      shrink_node = { from = '(', to = ')' },\n    },\n  }),\n}\n```\n\n\u003c/details\u003e\n\n#### Option `target_nodes`\n\nIn most cases, `target_nodes` is a list of node types to redirect the search for\nthe configured node deeper. But in reality it is treated like a dictionary:\n\n```lua\n-- these values are equivalent\ntarget_nodes = { 'block', 'statement' }\ntarget_nodes = { ['block'] = 'block', ['statement'] = 'statement' }\n```\n\nThe key must be a node type or a field name. The value is the name of any\nconfigured node whose preset is to be used.\n\nIf the field name is specified in the keys, then it has the highest priority\nover node types.\n\nThis also means that you can redirect found fields or nodes for processing with\nother (including custom) presets.\n\n```lua\n{\n  block = lang_utils.set_preset_for_statement(),\n  my_custom_preset_for_block_inside_fun_dec = {--[[ another preset ]]}\n  function_declaration = {\n      target_nodes = { ['block'] = 'my_custom_preset_for_block_inside_fun_dec' }\n  }\n}\n```\n\n\u003cdetails\u003e\n\n\u003csummary\u003eExample of usage\u003c/summary\u003e\n\nThe problem:\n\n```rust\nmatch x {\n    //   | - it is a field `value` and now it `integer_literal`\n    _ =\u003e 12\n    //   `integer_literal` is not configured and can't be configured\n    //   but you can transform this field into a `block` node\n}\n\nmatch x {\n    _ =\u003e {\n        12\n    }\n}\n\n```\n\nThis can be implemented with:\n\n```lua\nlocal rust = {\n  match_arm = {\n    target_nodes = { 'value' },\n  },\n  value = lang_utils.set_preset_for_statement({\n    split = {\n      format_tree = function(tsj)\n        if tsj:type() ~= 'block' then\n          tsj:wrap({ left = '{', right = '}' })\n        end\n      end,\n    },\n    join = {\n      no_insert_if = { lang_utils.helpers.if_penultimate },\n      format_tree = function(tsj)\n        local node = tsj:tsnode()\n        local parents = { 'match_arm', 'closure_expression' }\n        local has_parent = vim.tbl_contains(parents, node:parent():type())\n        if has_parent and node:named_child_count() == 1 then\n          tsj:remove_child({ '{', '}' })\n        end\n      end,\n    },\n  }),\n}\n```\n\n\u003c/details\u003e\n\n#### Option `format_tree`\n\n`format_tree` is a function that takes a TreeSJ root node. In this function, you\ncan work with the context and add or remove elements.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eExample of usage\u003c/summary\u003e\n\nThe problem:\n\nPython's `import_from_statement` does not have a container for a list of imported modules.\n\nHere you need to add parentheses to the middle and end of TreeSJ when splitting and remove these parentheses when joining.\n\n```python\n# from\nfrom re import search, match,sub\n\n# to this and back\nfrom re import (\n    search,\n    match,\n    sub,\n)\n```\n\nThis can be implemented with:\n\n```lua\nlocal python = {\n  import_from_statement = lang_utils.set_preset_for_args({\n    both = {\n      -- There is no need to wrap the second element, the 'import' node,\n      -- and the first parenthesis, which does not already exist.\n      omit = { lang_utils.omit.if_second, 'import', ' (' },\n    },\n    split = {\n      last_separator = true,\n      format_tree = function(tsj)\n        -- If there are no brackets, then create them\n        if not tsj:has_children({ '(', ')' }) then\n          tsj:create_child({ text = ' (' }, 4)\n          tsj:create_child({ text = ')' }, #tsj:children() + 1)\n          -- Since the elements have moved, you need to add the penultimate\n          -- separator manually\n          local penult = tsj:child(-2)\n          penult:update_text(penult:text() .. ',')\n        end\n      end,\n    },\n    join = {\n      format_tree = function(tsj)\n        -- Remove brackets\n        tsj:remove_child({ '(', ')' })\n      end,\n    },\n  }),\n}\n```\n\n\u003c/details\u003e\n\n#### Option `format_resulted_lines`\n\nThe `format_resulted_lines` function takes as an argument an array of strings\nthat will replace the content of the base node. The second optional argument is\nthe node that was processed. Useful if you want to format terms based on some\nvalues, such as a range.\n\nThe function should return an array of strings that may have been modified.\n\nIf the mode is \"join\", then after executing this function, these strings will be\nconcatenated.\n\nE.g.:\n\n```lua\n-- base node\nlocal dict = { one = 'one', two = 'two' }\n\n-- array of string for replacement is { \"{\", \"    one = 'one',\", \"    two = 'two',\", \"}\" }\n\n-- base node after format\nlocal dict = {\n    one = 'one',\n    two = 'two',\n}\n\n```\n\n\u003cdetails\u003e\n\n\u003csummary\u003eExample of usage\u003c/summary\u003e\n\nThe problem:\n\nPython's `import_from_statement` does not have a container for a list of\nimported modules.\n\nHere you need to add parentheses to the middle and end of TreeSJ when splitting\nand remove these parentheses when joining.\n\n```ruby\n# from\nif cond\n    do_that('cond')\nelse\n    do_this('not nond')\nend\n\n# to this and back\ncond ? do_that('cond') : do_this('not nond')\n```\n\nThis can be implemented with:\n\n```lua\nlocal ruby = {\n  conditional = lang_utils.set_default_preset({\n    join = { enable = false },\n    split = {\n      omit = { lang_utils.omit.if_second },\n      format_tree = function(tsj)\n        local children = tsj:children()\n        table.insert(children, tsj:create_child({ text = 'end', type = 'end' }))\n        tsj:child('?'):update_text('if ')\n        tsj:child(':'):update_text('else')\n        local first, second = tsj:child(1), tsj:child(2)\n        children[1] = second\n        children[2] = first\n        tsj:update_children(children)\n      end,\n      format_resulted_lines = function(lines)\n        return vim.tbl_map(function(line)\n          -- Need to remove one indent on `else` element\n          if line:match('%s.else$') then\n            local rgx = '^' .. (' '):rep(vim.fn.shiftwidth())\n            return line:gsub(rgx, '')\n          else\n            return line\n          end\n        end, lines)\n      end,\n    },\n  }),\n}\n```\n\n\u003c/details\u003e\n\n## TreeSJ instance\n\nAfter the node for formatting is found, an instance of the TreeSJ class is\ncreated. Each of its children and descendants is also an instance of this class.\nThe methods of this class can be used to change formatting behavior on the fly\n(see advanced node configuration).\n\n### TreeSJ Lifecycle\n\n- **Checking the found Node**: There is a check that there is no syntax error in\n  the node, there are no descendants with types from `preset[mode].no_format_with`\n  and `preset[mode].enable` is true.\n- **Creating root TreeSJ**: A node for formatting has been found and checked,\n  and an instance of TreeSJ is created based on it.\n- **Build tree**: It iterates all the children of the base node and creates\n  the TreeSJ children. If the `preset[mode].recursive` is true, then a check is made\n  to see if the child or its children are configured, if so, a tree is built\n  for it and its children.\n- **Remove or insert last separator**: if a separator is specified in the\n  preset.\n- **Run `preset[mode].format_tree`**: custom function from preset.\n- **Mode-based preparation**: spacing or indenting, adding a\n  `preset.join.force_insert`, forming a list of strings to replace the base node\n  content.\n- **Run `preset[mode].format_resulted_lines`**: custom function from preset.\n\n### Methods\n\n\u003cdetails\u003e\n\n\u003csummary\u003eShow/Hide methods...\u003c/summary\u003e\n\n#### has_children\n\nChecks if the specified types of children exist among the list of children.\nIf types are omitted, checks that there is at least one child.\n\n```lua\n---@param types? string[]\n---@return boolean\nfunction TreeSJ:has_children(types)\n```\n\n#### iter_children\n\nIterate all TreeSJ children.\nUse: `... for child, index in tsj:iter_children() do ...`\n\n```lua\n---@return function, table\nfunction TreeSJ:iter_children()\n```\n\n#### children\n\nGet the children list of the current TreeSJ. Returns all children if `types` are\nomitted, otherwise returns all children of the listed types.\n\n```lua\n---@param types? string[] List-like table with child's types for filtering\n---@return TreeSJ[]\nfunction TreeSJ:children(types)\n```\n\n#### root\n\nGet root TreeSJ node of current TreeSJ\n\n```lua\n---@return TreeSJ TreeSJ instance\nfunction TreeSJ:root()\n```\n\n#### parent\n\nGet parent TreeSJ\n\n```lua\n---@return TreeSJ|nil\nfunction TreeSJ:parent()\n```\n\n#### child\n\nGet the child of the current node, using its `type` (`tsj:type()`) or `index`.\n\n- The `index` can be a negative value, which means to search from the end of the\n  list.\n- If a `type` is passed, the first element found will be returned. To get an\n  array of similar elements, use `TreeSJ:children(types)`.\n\n```lua\n---@param type_or_index number|string Type of TreeSJ child or it index in children list\n---@return TreeSJ|nil\nfunction TreeSJ:child(type_or_index)\n```\n\n#### create_child\n\nCreating a new TreeSJ instance as a child of current TreeSJ.\n\n- data: {text=string, type=string|nil, copy_from=TreeSJ|nil}\n  The \"copy_from\" field is used if a node needs to be duplicated and expects TreeSJ.\n  If a TreeSJ instance is passed to it, then the \"text\" and \"type\" fields will be ignored.\n- index: If index present, puts it in children list and returned this child,\n  if not – returned child, but not puts it in children list. Index can be a negative value,\n  meaning insert from the end. If an index is specified that is outside the list of children,\n  then `nil` will be returned.\n\n```lua\n---@param data table { text = string, type? = string }. If `type` not present, uses value of `text`\n---@param index? integer Index where the child should be inserted.\n---@return TreeSJ|nil\nfunction TreeSJ:create_child(data, index)\n```\n\n#### update_children\n\nUpdating children list of current TreeSJ\n\n```lua\n---@param children TreeSJ[]\nfunction TreeSJ:update_children(children)\n```\n\nThis function must be called every time you update the list of children from\noutside, for example:\n\n```lua\n-- When a function should be called\nlocal children = tsj:children()\nlocal child = tsj:create_child({ text = 'end' })\ntable.insert(children, child)\ntsj:update_children(children) -- important\n\n-- Here it is not necessary\ntsj:create_child({ text = 'end' }, #tsj:children() + 1)\n```\n\n#### remove_child\n\nRemoves children by the passed types or index.\n\n```lua\n---@param types_or_index string|string[]|integer Type, types, or index of child to remove\nfunction TreeSJ:remove_child(types_or_index)\n```\n\n#### wrap\n\nCreates the first and last elements in the list of children of the current TreeSJ.\n\n- If the `wrap` mode is passed (the default), then a new list of children is\n  created with one element as itself, and the wrapping elements are added to it.\n- If the `inline` mode is passed, then the real list of children of the current\n  TreeSJ is used to add framing elements.\n\n```lua\n---@param data table { left = string, right = string }\n---@param mode? 'wrap'|'inline' 'wrap' by default\nfunction TreeSJ:wrap(data, mode)\n```\n\n#### swap_children\n\nHelps to swap elements by their indexes\n\n```lua\n---@param index1 integer\n---@param index2 integer\nfunction TreeSJ:swap_children(index1, index2)\n```\n\n#### tsnode\n\nGet [TSNode](https://neovim.io/doc/user/treesitter.html#treesitter-node) or\nTSNode imitator of current TreeSJ. If you plan to use tsnode methods in the\nfuture, you first need to check that the returned value is not an imitator.\n\n```lua\n---@return TSNode|table TSNode or TSNode imitator\nfunction TreeSJ:tsnode()\n```\n\n#### prev\n\nGet left side TreeSJ\n\n```lua\n---@return TreeSJ|nil\nfunction TreeSJ:prev()\n```\n\n#### next\n\nGet right side TreeSJ\n\n```lua\n---@return TreeSJ|nil\nfunction TreeSJ:next()\n```\n\n#### type\n\nGet node type of current TreeSJ.\n\n```lua\n---@return string\nfunction TreeSJ:type()\n```\n\n#### text\n\nGet text of current TreeSJ.\nAt the time of the execution of the `format_tree` function, the text will always\nbe returned, not the table.\n\n```lua\n---@return string|table\nfunction TreeSJ:text()\n```\n\n#### update_text\n\nUpdating text of current TreeSJ. If the mode \"split\" and \"recursively\" is\nactive, then it can be an array. In such a case, you need to update the text in\nits children directly.\n\n```lua\n---@param new_text string|string[]\nfunction TreeSJ:update_text(new_text)\n```\n\nWhen working in recursive mode, you need to check that the nodes in which you\nwant to change the text do not have children for recursive processing. In this\ncase, the text will be glued from the children of the node, and you need to\nchange the text in them.\n\nE.g.:\n\n```lua\n{\n  format_tree = function(tsj)\n    if tsj:type() ~= 'statement_block' then\n      -- ...\n      local body = tsj:child(2)\n      if body:will_be_formatted() then\n        local set_return\n        if body:has_preset('split') then\n          set_return = body:child(1)\n        else\n          set_return = body:child(1):child(1)\n        end\n        set_return:update_text('return ' .. set_return:text())\n      else\n        body:update_text('return ' .. body:text())\n      end\n      -- ...\n    end\n  end,\n}\n```\n\n#### will_be_formatted\n\nReturns true if the current TreeSJ will be formatted.\nThe conditions are met: recursion is active, the current element has a preset, or among its descendants there are nodes that will be processed.\n\n```lua\n---@return boolean\nfunction TreeSJ:will_be_formatted()\n```\n\n#### is_ignore\n\nChecks if the current TreeSJ child must be ignored while recursive formatting.\n\n```lua\n---@return boolean\nfunction TreeSJ:is_ignore()\n```\n\n#### has_to_format\n\nChecks if the TreeSJ contains children that need to be formatted\n\n```lua\n---@return boolean\nfunction TreeSJ:has_to_format()\n```\n\n#### is_first\n\nChecks if the current node is first among sibling\n\n```lua\n---@return boolean\nfunction TreeSJ:is_first()\n```\n\n#### is_last\n\nChecks if the current node is last among sibling\n\n```lua\n---@return boolean\nfunction TreeSJ:is_last()\n```\n\n#### is_framing\n\nChecks if the current node is first or is last among sibling\n\n```lua\n---@return boolean\nfunction TreeSJ:is_framing()\n```\n\n#### is_omit\n\nChecks if the text of current TreeSJ's type contains at `preset[mode].omit`\n\n```lua\n---@return boolean\nfunction TreeSJ:is_omit()\n```\n\n#### is_imitator\n\nChecks if the current TreeSJ is node-imitator\n\n```lua\n---@return boolean\nfunction TreeSJ:is_imitator()\n```\n\n#### preset\n\nGet preset for current TreeSJ\n\n```lua\n---@param mode? 'split'|'join' Current mode (split|join)\n---@return table|nil\nfunction TreeSJ:preset(mode)\n```\n\n#### parent_preset\n\nGet the preset of the TreeSJ parent\n\n```lua\n---@param mode? 'split'|'join' Current mode (split|join)\n---@return table|nil\nfunction TreeSJ:parent_preset(mode)\n```\n\n#### update_preset\n\nUpdates the presets for the current TreeSJ.\n\n```lua\n---@param new_preset table\n---@param mode? 'split'|'join'\nfunction TreeSJ:update_preset(new_preset, mode)\n```\n\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWansmer%2Ftreesj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FWansmer%2Ftreesj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FWansmer%2Ftreesj/lists"}