AndrewRadev/splitjoin.vim

JSX/TSX split self-closing tags

williamboman opened this issue · 6 comments

Hi!

I'd love to be able to "split" a self-closing tag into a start and end tag:

<div />

gS

<div>
  
</div>

<div>
  foobar
</div>

gJ

<div />

Do you feel like this would be in scope of this plugin?

Yes, that definitely seems like a good fit. I've pushed a branch called self-closing-tags that you could try out, see if it works for you. There's a few gotchas.

First off, for the second example, joining to a self-closing tag only works with an empty tag, so the particular example wouldn't work, but this should:

<div>
</div>

gJ

<div />

Second, I've only implemented this for JSX and TSX. In plain HTML, as far as I know, not every tag can be self-closing, and the ones that are can't be expanded.

This might, however, be a bit of a problem in terms of filetypes. I don't use a lot of react, but I experimented a bit with some tutorials and it seems the recommended file extension they use is just .js. But this ends up setting the filetype in my Vim to javascript, and I need to set javascriptreact to get this functionality. I'm not sure what the solution is here, apart from using a .jsx file. You could put the following in ~/.vim/ftplugin/javascript.vim:

runtime ftplugin/jsx/splitjoin.vim

This will set the applied callbacks to the jsx ones, and not the javascript ones. I wonder if it makes sense to provide a setting to just use the jsx callbacks for all javascript files. It shouldn't be a practical issue, but the imprecision annoys me.

The other thing to note is that, for a self-closing tag, there's already a different kind of split activated -- splitting attributes. So, right now, this is the flow of splitting:

let button = <Button foo="bar" bar="baz" />;
// splits to:
let button = <Button
  foo="bar"
  bar="baz" />;
// splits further to:
let button = <Button
  foo="bar"
  bar="baz">
</Button>;

// joins to:
let button = <Button foo="bar" bar="baz">
</Button>;
// joins further to:
let button = <Button foo="bar" bar="baz" />;

You can get whatever form you need with partial splitting and joining, so I think this should be okay. It's a question of priority, and I don't feel it's clear which should expand first -- the attributes on new lines or the closing tag -- depends on whether you want to add more attributes or add children to the node.

What do you think? Could you try that branch out and see how it feels, look for any issues?

Oh wow that was fast 👏!

In plain HTML, as far as I know, not every tag can be self-closing, and the ones that are can't be expanded.

Correct! There's only a handful of void elements

But this ends up setting the filetype in my Vim to javascript, and I need to set javascriptreact to get this functionality. I'm not sure what the solution is here, apart from using a .jsx file.

I believe it's standard to just target the javascriptreact and typescriptreact filetypes for JSX-specific plugins.

The other thing to note is that, for a self-closing tag, there's already a different kind of split activated -- splitting attributes. So, right now, this is the flow of splitting:

Makes sense! However it is a bit annoying to me because in every JS/TS project we use Prettier to automatically format the code so this plugin sometimes does unnecessary things. Would be nice to disable attr splitting - can I control this via autocmd + overriding b:splitjoin_{split,join}_callbacks perhaps?

What do you think? Could you try that branch out and see how it feels, look for any issues?

It's working pretty good! I'm having some issues joining tags where the end tag is on the same line: https://asciinema.org/a/6NYPHn4U681s8mKJ3KVX4Eqib.

diff --git a/ftplugin/tsx/splitjoin.vim b/ftplugin/tsx/splitjoin.vim
index 4ae2957..6b2bbbb 100644
--- a/ftplugin/tsx/splitjoin.vim
+++ b/ftplugin/tsx/splitjoin.vim
@@ -6,12 +6,13 @@ let b:splitjoin_split_callbacks = [
       \ 'sj#js#SplitArray',
       \ 'sj#js#SplitFunction',
       \ 'sj#js#SplitOneLineIf',
-      \ 'sj#js#SplitArgs'
+      \ 'sj#js#SplitArgs',
+      \ 'sj#jsx#SplitSelfClosingTag'
       \ ]
 
 let b:splitjoin_join_callbacks = [
       \ 'sj#html#JoinAttributes',
-      \ 'sj#html#JoinTags',
+      \ 'sj#jsx#JoinHtmlTag',
       \ 'sj#js#JoinFatArrowFunction',
       \ 'sj#js#JoinArray',
       \ 'sj#js#JoinArgs',

I believe it's standard to just target the javascriptreact and typescriptreact filetypes for JSX-specific plugins.

Yes, I happily agree :)

It's working pretty good! I'm having some issues joining tags where the end tag is on the same line: https://asciinema.org/a/6NYPHn4U681s8mKJ3KVX4Eqib.

Usually, joining only works for multiline code, but I can see how your case makes sense. I've applied a fix, so this should work now.

Would be nice to disable attr splitting - can I control this via autocmd + overriding b:splitjoin_{split,join}_callbacks perhaps?

Yes, and I realize this is a bit inconvenient. What you could always do is define your own list of callbacks in your own ftplugin files. For me, it seems to work in the "after" directory because the load path just ends up that way, so you could create a file called ~/.vim/after/ftplugin/javascriptreact.vim and put this in there:

let b:splitjoin_split_callbacks = [
      \ 'sj#html#SplitTags',
      \ '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#JoinHtmlTag',
      \ 'sj#js#JoinFatArrowFunction',
      \ 'sj#js#JoinArray',
      \ 'sj#js#JoinArgs',
      \ 'sj#js#JoinFunction',
      \ 'sj#js#JoinOneLineIf',
      \ 'sj#js#JoinObjectLiteral',
      \ ]

This would be the callbacks from splitjoin, excluding sj#html#SplitAttributes and sj#html#JoinAttributes. But I realize this is inconvenient, especially given you'd need to do it for both JSX and TSX. It also means if I make changes to the callbacks or reorder them, your copy could break (has happened before internally).

So, I've created the disabled_split_callbacks and disabled_join_callbacks settings -- you can find explanations in the help files in the branch. Let me know what you think.

So, I've created the disabled_split_callbacks and disabled_join_callbacks settings -- you can find explanations in the help files in the branch. Let me know what you think.

It's.. perfect!

Excellent :). I've merged the branch to master, so I'll consider this issue closed for now. If you encounter any problems with it, feel free to reopen or create a new issue.