wansmer / treesj Goto Github PK
View Code? Open in Web Editor NEWNeovim plugin for splitting/joining blocks of code
License: MIT License
Neovim plugin for splitting/joining blocks of code
License: MIT License
Fix tests that fail
Certain tests for sql
and dart
fail, but in these cases the nodes are formatted correctly when the plugin is run manually.
TODO: find and fix the problem
Problematic tests:
treesj/tests/langs/dart/join_spec.lua
Line 43 in 3203aa5
treesj/tests/langs/dart/split_spec.lua
Line 43 in 3203aa5
treesj/tests/langs/sql/split_spec.lua
Line 25 in 3203aa5
I have migrated from splitjoin.vim to this plugin. This plugin works great, but my favorite feature of splitjoin.vim was how it could split expression in match arms into a block. This feature is a little broken in splitjoin.vim, and I always planned to remake it using tree sitter before I encounter this plugin.
I was planning to implement this myself and submit a PR, but from what I looked, this plugin only has support for splitting nodes with clear delimiters. So I became unsure to how to implement it.
// It would allow spliting a "match_arm.value"
match x {
_ => 12
}
// Into this
match x {
_ => {
12
}
}
This could also be extended to apply to Rust closures (|| 1
into || { 1 }
) and JavaScript arrow function (() => 1
into () => { return 1; }
), for example.
If you could provide any directions, I can try implementing it.
By default, TreeSJ drops the trailing comma when joining tuples, so:
(
1,
)
becomes (1)
instead of (1, )
. This is a problem in Python if the tuple has only a single member, as the two forms mean different things: a parenthesized number vs. a tuple.
This can be easy to miss if the join happens as part of a nested collapse. Would be great if TreeSJ could automatically preserve the comma in this particular case when dealing with Python.
Given a Python file such as
bar(zzzz, {"thing": "blah"})
Call :TSJToggle
twice and it becomes
bar(
zzzz,
{ "thing": "blash"},
)
and then
bar(zzzz, { "thing": "blash", },)
Note the extra
around {
and }
which weren't there before, plus the extra two ,
s within , },)
Splitting the following line results in invalid syntax.
from re import search
from re import (,
search,
)
I am aware there is little use in splitting the line but maybe it is easy to fix.
The expected result would be
from re import (
search,
)
For Go language a trailing comma is required when items place each on the own line, for example
func a(b int, c int, d int)
func a(
b int,
c int,
d int, // <-comma here is required
)
The same applied for arrays and dictionaries.
Can't find an option for this, can you point on it if I miss?
Ruby uses different delimiters depending on whether a block is inline or multiline. For example,
# inline block (joined)
some_list.each { |x| puts x }
# multiline block (split)
some_list.each do |x|
puts x
end
It does not look like the ruby treesj preset supports the block
(or do_block
) node type as of right now. I would love to see this added as a feature and would be happy to create a PR.
From what I can tell - and correct me if I'm wrong - there are two limitations that are blocking this kind feature:
non_bracketed_node
where the "split" and "join" node representations must use the same syntax. In this case, support for individual bracketed nodes would need to be supported
-- boolean: Non-bracket nodes (e.g., with then end instead of {}|<>|[])
-- NOTE: Must be same for both modes
non_bracket_node = false,
do
replaces {
and end
replaces }
This plugin has some great improvements over splitjoin.vim, hoping to see it continuously improve π
Happy to hear your thoughts!
Hey, thanks for making this, it's really useful.
Do you think it would be possible to add support for Rust?
E.G. Struct definitions/initialization:
struct SomeStruct {x: i32, y: i32 }
// ---
struct SomeStruct {
x: i32,
y: i32,
}
let x = SomeStruct { x: 123, y: 234 };
// ---
let x = SomeStruct {
x: 123,
y: 234,
};
Or function definitions/calls:
fn f(x: i32, y: i32) -> i32 {}
// ---
fn f(
x: i32,
y: i32,
) -> i32 {}
f(123, 234);
// ---
f(
123,
234,
);
I think the Go language can be a good candidate for adding to preset languages.
Hello,
When splitting the following code
local dict = { 'a', 'b', 'c' }
the result is:
local dict = {
'a',
'b',
'c',
}
My question: Why are the dict elements not properly indented like it's done in e.g. .js
documents? Is this intended behaviour? How can I change this behaviour? I would expect the code to be like:
local dict = {
'a',
'b',
'c',
}
Great work! Just wanted to create this ticket so I can track the feature. Currently I'm using splitjoin.vim but I'd love to switch to a lua based plugin that supports treesitter
π, thanks for building this plugin!
I found an issue when splitting or joining arrow functions in Typescript. The plugin seems to have trouble handing blocks without a return
statement in the function body.
I have the following function
export async function pretendDelay() {
const delay = Math.floor(Math.random() * 1000); // Between 0 and 1 seconds
return new Promise((resolve) => {
setTimeout(resolve, delay);
});
}
Note that the setTimeout
doesn't have a return
.
Promise
, the plugin tries to return the value of the block (changing the semantics), and places an extra semicolon in the end (adding a syntax error).return
statement, and keeps the invalid semicolon.It would make sense to fall back onto mini.splitjoin
if TreeSitter isn't configured for a buffer or split operation fails.
For example, trying to split a long string in Bash returns:
[TreeSJ]: Node "string_content" for lang "bash" is not configured
...while the same line can be perfectly split by mini.splitjoin.
Example:
For construction
it { is_expected.to validate_presence_of(:email) }
While calling require('treesj').join()
inside block it will not convert do
/end
to {
and }
. Everything works ok if the cursor outside, or directly on do
or end
Setup:
struct Test;
impl Test {
fn test() { }
fn test2() { }
}
What it actually does:
impl Test { fn test() { }; fn test2() { }; }
What is expected(and works):
impl Test { fn test() { } fn test2() { } }
The semicolons should not be added
I can see a config called use_default_keymaps
but there is no information about what the default keymaps are.
Ran into an issue when using the Lua config example from the README. Got this error:
attempt to index global 'lu' (a nil value)
which points to this line:
langs = lu._prepare_presets(langs.presets),
Ruby has some syntactic sugar with namespaces. In particular modules (and classes) can be nested within modules, like so:
module MyModule
class MyClass
end
end
# is the same as
class MyModule::MyClass
end
Some examples taken from splitjoin.vim would be:
# RESULT OF JOIN
class Foo::Bar::Baz < Quux
def initialize
# foo
end
end
# RESULT OF SPLIT
module Foo
module Bar
class Baz < Quux
def initialize
# foo
end
end
end
end
# RESULT OF JOIN
class Foo::Bar::Baz < Quux
end
# RESULT OF SPLIT
module Foo
module Bar
class Baz < Quux
end
end
end
# RESULT OF JOIN
module A::B
end
# RESULT OF SPLIT
module A
module B
end
end
# EXAMPLE WITH MIXED
module Foo::Bar
class Baz::Qux
end
end
# RESULT OF JOIN
class Foo::Bar::Baz::Qux
end
This one may be tricky to implement given that they may join into a class
or a module
(depending on whether the nested object is one or the other)
Hey, how do you do guys?
I'm trying to use the package in the following way:
use({
'Wansmer/treesj',
requires = { 'nvim-treesitter' },
commit = '7be9ebd',
config = function()
require('treesj').setup({
notify = true,
dot_repeat = true,
use_default_keymaps = true,
check_syntax_error = true,
max_join_length = 120,
cursor_behavior = 'hold',
})
end,
})
But every time I start Neovim, I encounter this error:
packer.nvim: Error running setup for treesj: [string "..."]:0: module 'treesj' not found:
^Ino field package.preload['treesj']
^Ino file './treesj.lua'
^Ino file '/__w/neovim/neovim/.deps/usr/share/luajit-2.1.0-beta3/treesj.lua'
^Ino file '/usr/local/share/lua/5.1/treesj.lua'
^Ino file '/usr/local/share/lua/5.1/treesj/init.lua'
^Ino file '/__w/neovim/neovim/.deps/usr/share/lua/5.1/treesj.lua'
^Ino file '/__w/neovim/neovim/.deps/usr/share/lua/5.1/treesj/init.lua'
^Ino file '/home/adrianmiranda/.cache/nvim/packer_hererocks/2.1.0-beta3/share/lua/5.1/treesj.lua'
^Ino file '/home/adrianmiranda/.cache/nvim/packer_hererocks/2.1.0-beta3/share/lua/5.1/treesj/init.lua'
^Ino file '/home/adrianmiranda/.cache/nvim/packer_hererocks/2.1.0-beta3/lib/luarocks/rocks-5.1/treesj.lua'
^Ino file '/home/adrianmiranda/.cache/nvim/packer_hererocks/2.1.0-beta3/lib/luarocks/rocks-5.1/treesj/init.lua'
^Ino file './treesj.so'
^Ino file '/usr/local/lib/lua/5.1/treesj.so'
^Ino file '/__w/neovim/neovim/.deps/usr/lib/lua/5.1/treesj.so'
^Ino file '/usr/local/lib/lua/5.1/loadall.so'
^Ino file '/home/adrianmiranda/.cache/nvim/packer_hererocks/2.1.0-beta3/lib/lua/5.1/treesj.so'
Is there anything wrong with my configuration?
if I want use treesj in javascript need TSInstall javascript ?
Hi, I'm trying to add a forced join keymap to my config, but apparently passing a preset like this to the toggle
and join
methods is ignored: { max_join_length = 999 }
.
I would love if I could disable the max join length feature on demand. Ideally this would be done via a separate boolean setting or setting max_join_length = 0
.
to reproduce
-- before
if 1 == 1 then print("beep") end
run this three commands (listchars only changed for visibiliy)
:set noexpandtab
:lua vim.opt.listchars = "tab:> ,space:."
:TSJSplit
-- after
if 1 == 1 then
print("beep") -- has four spaces, not a tab character
end
Please help. I am not sure what is wrong in my configuration.
My config is
require("lazy").setup({
{
'Wansmer/treesj',
keys = { '<space>m', '<space>j', '<space>s' },
dependencies = { 'nvim-treesitter/nvim-treesitter' },
config = function()
require('treesj').setup({ --[[ your config ]] })
end,
},
})
The above gets lazy loaded till I press one of `m', 'j' or 's'.
After that when I press :TSJToggle
, it is giving me the below error
[TreeSJ]: Cannot join "table_constructor" containing node from one of this: { "commend" }
fn main() {
assert!(true);
^
}
When the cursor is at ^
, performing require("treesj").split()
will cause the following error:
"parenthesized_expression" for lang "rust" is not configured
Can we have built-in support for this?
{
'Wansmer/treesj',
-- keys = { '<space>m', '<space>j', '<space>s' },
dependencies = { 'nvim-treesitter/nvim-treesitter' },
config = function()
require('treesj').setup({--[[ your config ]]})
end,
},
-- Load TreesJ modules
local tsj = require('treesj')
-- local langs = {--[[ configuration for languages ]]}
tsj.setup({
---@type boolean Use default keymaps (<space>m - toggle, <space>j - join, <space>s - split)
use_default_keymaps = false,
---@type boolean Node with syntax error will not be formatted
check_syntax_error = true,
---If line after join will be longer than max value,
---@type number If line after join will be longer than max value, node will not be formatted
max_join_length = 120,
---Cursor behavior:
---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
---@type 'hold'|'start'|'end'
cursor_behavior = 'hold',
---@type boolean Notify about possible problems or not
notify = true,
---@type table Presets for languages
-- langs = lu._prepare_presets(langs.presets),
---@type boolean Use `dot` for repeat action
dot_repeat = true,
---@type nil|function Callback for treesj error handler. func (err_text, level, ...other_text)
on_error = nil,
})
-- Load which-key for keybinding docs
local wk = require("which-key")
-- Register TreesJ keybindings
wk.register({
["<leader>tst"] = { function() require('treesj').toggle() end, "Toggle node under cursor" },
["<leader>tss"] = { function() require('treesj').split() end, "Split node under cursor" },
["<leader>tsj"] = { function() require('treesj').join() end, "Join node under cursor" },
})
Thank you for the great plugin! :)
I have been able to configure language support for some kotlin language constructs but I have trouble with function declarations:
Its treesitter ast does not group function parameters in a common parent node like other languages do.
For example the function
fun test(a: String, b: String){ }
has the tree
function_declaration [0, 0] - [1, 1] simple_identifier [0, 4] - [0, 8] parameter [0, 9] - [0, 18] simple_identifier [0, 9] - [0, 10] user_type [0, 12] - [0, 18] type_identifier [0, 12] - [0, 18] parameter [0, 20] - [0, 29] simple_identifier [0, 20] - [0, 21] user_type [0, 23] - [0, 29] type_identifier [0, 23] - [0, 29] function_body [0, 30] - [1, 1]
Is it still possible to configure treesj for this case?
I use the space key to enter the command mode with nmap <space> :
in my vim config
So I've disabled treesj's default mappings with use_default_keymaps = false,
in favor of my custom mappings but there is still a noticeable delay of half a second to a second while using the spacebar.
given this array (with |
as cursor position)
no-empty:
- wa|rn
- allowEmptyCatch: true
joining will result in this:
no-empty: [ warn, - { allowEmptyCatch: true } ]
which is invalid yaml, as it should be this:
no-empty: [ warn, { allowEmptyCatch: true } ]
I moved from Intellij (IdeaVim) to NeoVim, but for some reason, I wasn't able to find a plugin that behaves like smartjoin in IdeaVim, which added features to the original join(J) like making single line methods in Kotlin(fun foo() = //...
), combining multi-line concat strings("ABC" + <newline> "Next Line"
-> "ABCNext Line"
), folding multiple ifs, and folding lambdas into a single line({ doStuff() }
). I see that this project seems to already have the groundwork down for something similar, so I thought it wouldn't be that hard to make this. Since the project is about joining and splitting, the reverse of the actions above doesn't seem too hard either. Though I do see that it's a bit different cause you are looking into neighboring nodes instead of only up the tree...
For reference: IdeaJoin(smartjoin) examples
here's my config for lazy.nvim:
local opts = { noremap = true, silent = true, expr = false }
return {
"Wansmer/treesj",
dependencies = { "nvim-treesitter/nvim-treesitter" },
opts = { use_default_keymaps = false, max_join_length = 150, dot_repeat = true },
cmd = { "TSJToggle", "TSJJoin", "TSJSplit" },
keys = {
{ "gM", "<cmd>TSJToggle<cr>", opts },
{ "gJ", "<cmd>TSJJoin<cr>", opts },
{ "gS", "<cmd>TSJSplit<cr>", opts },
},
}
I've looked through the readme, and it looks like right now it's only possible to add a fallback that will be used for buffers where treesitter is not present. However, I'd like to also have a fallback in cases where I do have treesitter, but no node is found.
The main use case for this would be literal strings that can't be parsed by tresitter, but still contain code that can be split/joined.
I'd be up for implementing this as well, what do you think about this feature?
hey! amazing work on this plugin, works very well for me in some of the languages I have tested it on!
I wanted to request support for C/C++ as well, as it would be very useful and the fact that there are very few options (if any) for these languages for this level of functionality. I am aware that it might be a complicated endeavor (as suggested by discussions in splitjoin.vim
plugin), but its good to start a new discussion.
I think it would be a great addition to this plugin, and will attract many more devs and appreciation!
First of all, thank you for this awesome plugin!
PHP supports creating arrays with both array()
and `[]. For example, this is a common array structure in PHP:
array( 'one', 'two' )
When splitting, this is what I would expect to happen:
array(
'one',
'two'
)
However, for some reason the array notation gets split up and this happens instead:
array
(
'one',
'two'
)
Any idea how I might be able to fix this in my config? Thanks!
Split code like:
from re import search, match, sub...
Thanks a lot for the plugin.
Thanks for this plugin, I really like the idea of auto-detection :)
My feature suggestions would be to extend the plugin functionality to also work for functions and if-statements, as I often find myself splitting/joining them in these situations.
Below are two examples in lua.
function foo () return "bar" end
function foo ()
return "bar"
end
if foo then return end
if foo then
return
end
Thanks for this awesome plugin.
I think it would be great if the repeat last action using dot(.
) can be implemented.
When I use <space>j
in Lua to join something like this:
local myvar = {
a = 'something',
b = {}
}
It becomes:
local myvar = { a = 'something', b = { } }
Notice the extra space in the empty dict. I would like if the space was not there, like this:
local myvar = { a = 'something', b = {} }
Would it be possible split/join a chain of method calls?
# from:
all_names = some_object.associations.map{ |o| o.name }.join(',').downcase
# to:
all_names = some_object
.associations
.map{ |o| o.name }
.join(',')
.downcase
Note: I just installed this plugin and it works amazing, and so fast! Thanks for all the work <3
local function test()
print('hello')
end
test()
^^ call TSJSplit with cursor on/within parens
--- becomes
test(
)
Using default config, in Lua file.
can a check be added so that splits dont happen when no elements are present?
Hi, thank you for this awesome tool.
I'm struggling to configure it for pug.
I've looked into the html
implementation but did not find anything useful. I always get the message:
Cannot "join" node longer than 120 symbols. Check your settings to change it.
Here is my current pug config:
local langs = require('treesj.langs')
local u = require('treesj.langs.utils')
langs["pug"] = {
attributes = u.set_default_preset({
both = {
space_separator = 1,
separator = ' '
},
}),
}
Why does it search first, is there something wrong with my configuration?
branch main
commit 950d06e
ο Error 20:41:07 msg_show.lua_error Error executing Lua callback: /Users/cj/.local/share/nvim/lazy/treesj/lua/treesj/init.lua:25: Vim:E486: Pattern not found: rstnersitnrsetnei
stack traceback:
[C]: in function 'normal'
/Users/cj/.local/share/nvim/lazy/treesj/lua/treesj/init.lua:25: in function 'set_opfunc_and_format'
/Users/cj/.local/share/nvim/lazy/treesj/lua/treesj/init.lua:30: in function 'format'
/Users/cj/.local/share/nvim/lazy/treesj/lua/treesj/init.lua:37: in function </Users/cj/.local/share/nvim/lazy/treesj/lua/treesj/init.lua:36>
basically title says it all
I'm trying to split attributes in TSX with:
local tsj = require('treesj')
local u = require('treesj.langs.utils')
local typescript = require('treesj.langs.typescript')
tsj.setup({
use_default_keymaps = false,
max_join_length = 9999,
langs = {
tsx = u.merge_preset(typescript, {
jsx_opening_element = u.set_default_preset({
both = {
omit = { 'identifier' },
},
}),
jsx_self_closing_element = u.set_default_preset({
both = {
omit = { 'identifier' },
no_format_with = {},
},
}),
})
},
})
This is the results I've got:
<PageCanvas
class="px-2 py-1"
canvasClass={clsx("page-thumbnail-canvas mx-auto shadow", props.active && 'ring-4')}
page={props.page}
zoom={() => 0.28}
onUpdate={props.onUpdate}
/
>
Notice the character /
and >
at the end also got split? How to fix this?
Thank you.
Does it make sense to have this feature? Emmet has it, it's called https://docs.emmet.io/actions/split-join-tag/
|| Error executing Lua callback: ...e/nvim/site/pack/packer/opt/treesj/lua/treesj/format.lua:67: Cursor position outside buffer
|| stack traceback:
|| [C]: in function 'nvim_win_set_cursor'
|| ...e/nvim/site/pack/packer/opt/treesj/lua/treesj/format.lua:67: in function '_format'
|| ...are/nvim/site/pack/packer/opt/treesj/lua/treesj/init.lua:20: in function <...are/nvim/site/pack/packer/opt/treesj/lua/treesj/init.lua:19>
I am woking on adding support for C++, and encountered the above trace with the config:
treesj.setup({
langs = {
cpp = {
compound_statement = treesj_utils.set_preset_for_list({
both = {
separator = ';',
last_separator = true,
no_format_with = {}
}
})
},
},
})
in the code segment:
if (true) {
^^^^^ cursor anywhere before `{`
int a = 10;
int b = 15;
}
using the TSJSplit
. The entire document is messed up after this crash as well.
TSJJoin
in the same case, gives the error: Cannot join node longer ....
.
otherwise, it works as expected:
if (true) {
^ cursor at or after `{`
int a = 10;
int b = 15;
}
---
if (true) { int a = 10; int b = 15; }
For reference, the entire block { .... }
is the TS node compound_statement
.
NOTE:
I know this use case does not make sense because why would you put different statements on the same line, but its just something I encountered while playing around. This is a bug though, and might be encountered elsewhere, because this config is 'correct'.
Hi! How configure plugin for never using backslash in Python(imports, etc)?
For example:
from airflow.providers.apache.spark.operators.spark_submit import \
SparkSubmitOperator
Prefer:
from airflow.providers.apache.spark.operators.spark_submit import (
SparkSubmitOperator
)
Minor idea:
Right now, just gives out a warning and does nothing when on a comment. How about having treesj simply split the comment by textwidth
instead? Essentially use gqq
as behavior when trying to splitjoin a comment, since it's a straightforward and common split-join operation one does on comments.
Could add this feature as opt-in, since not everyone would like it, I guess
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.