sublime_text
sublime_text copied to clipboard
close_tag with "/" doesn't work in jsx in ST4
Description of the bug
After making the key-bindings change provided here, which works fine in ST3, it no longer works in ST4.
Issue is verified in safe mode.
Steps to reproduce
- Start ST in safe mode
- Update key bindings as suggested in top answer here.
- Create new file, and set source type to
Javascript > JSX
- Type
<div></
Expected behavior
The <div>
tag should have been closed and completed.
Actual behavior
The <div>
tag is not closed or completed.
Sublime Text build number
4126
Operating system & version
macOS 12.3
(Linux) Desktop environment and/or window manager
No response
Additional information
Note, using the normal close_tag command using super+alt+.
we can determine the issue is very specific:
- With
<div>
runningclose_tag
will successfully close the tag. - With
<div></
runningclose_tag
will successfully close the tag. - With
<div><
runningclose_tag
does nothing.
Issue remains in dev build 4129.
This issue also happens with Babel's Javascript (Babel)
file type.
OpenGL context information
No response
I don't see close_tag
doing anything with <div></
being present in a JSX/TSX file. It's working fine in HTML/XML though.
As both JSX and TSX are affected the default key binding should include both source.jsx
and source.tsx
.
{ "keys": ["/"], "command": "close_tag", "args": { "insert_slash": true }, "context":
[
{ "key": "selector", "operator": "equal", "operand": "(text.html, text.xml, source.jsx, source.tsx) - string - comment", "match_all": true },
{ "key": "preceding_text", "operator": "regex_contains", "operand": "<$", "match_all": true },
{ "key": "setting.auto_close_tags" }
]
},
Info
ST4129 (Win11)
As @jeremytm mentioned, the function close_tag
works if you manually run the command anywhere in a jsx/tsx file, except after a <
. (a bug )
Using this (a modification of the default keybind, with the addition of source.jsx
& tsx
)
{
"keys": ["/"], "command": "close_tag",
"args": { "insert_slash": true },
"context": [
{
"key": "selector",
"operator": "equal",
"operand": "(text.html, text.xml, source.jsx, source.tsx) - string - comment",
"match_all": true
},
{
"key": "preceding_text",
"operator": "regex_match",
"operand": ".*<$",
"match_all": true
}, { "key": "setting.auto_close_tags" }
]
}
as you can see the regex .*<$
ensures the command only runs on keystroke /
after a <
and coincidentally that's the only place in jsx/tsx
where the close_tag
command fails, so effectively nothing happens
Easiest work arounds are:
- modify the regex to
.*</
and just type/
twice and the command will work - or chain commands as in insert /, then close tag
{
"keys": ["/"], "command": "chain",
"args": {
"commands": [
["insert", { "characters": "/" }],
["close_tag", { "insert_slash": true }]
]
},
"context": [
{
"key": "selector",
"operator": "equal",
"operand": "(text.html, text.xml, source.jsx, source.tsx) - string - comment",
"match_all": true
},
{
"key": "preceding_text",
"operator": "regex_match",
"operand": ".*<$",
"match_all": true
}, { "key": "setting.auto_close_tags" }
]
}
and that will work as expected
Proposed Solution
Fix the bug in the original command and add the scopes for jsx/tsx as defaults
Are u sure? Running ST4129 on Win11 in SAFE MODE using your bindings, results in ...
Are u sure? Running ST4129 on Win11 in SAFE MODE using your bindings, results in ...
@deathaxe
sorry I just re-checked (in safe-mode) it seems to work as long as you are already inside a parent tag i.e
<div>
<|
this fails but
<div>
<text>
<|
</div>
this works
test on linux/ ubuntu
Update: another Bug
Apparently If You Have a Self Closing Tag like
<View>
<Button>
<Text/>
<|
</View>
then pressing /
would insert </Text>
the self closed element instead of </Button>
The issue seem related with how syntax definitions push/set/pop contexts onto stack.
If TextMate Preferences.sublime-syntax is used to highlight *.tmPreferences files, close_tag
fails in various locations as well.
Example
<plist version="1.0">
<dict>
<key>name<| <-- close_tag works!
<string><| <-- close_tag fails!
</dict>
</plist>
For close_tag
to work the <
must not be scoped meta.tag
.
<div><
// ^^ - meta.tag
@Thom1729: So this issue may be fixed via syntax definition.
As mentioned in @jeremytm ‘s original report, updating the keymap to include a JSX scope causes Sublime to emit a close_tag
command when you type </
. The command just doesn’t do anything. In the recording below, I’ve set Sublime to log commands, and tried closing tags in HTML and in JSX.
https://user-images.githubusercontent.com/2207980/182155759-60750659-28d0-4ab9-b3c7-186c67491bd4.mp4
It’s an issue with Sublime’s built-in close_tag
—no amount of tuning the keymap will fix it. Someone motivated to do so could write a package to temporarily duplicate the command for JSX, TSX, Babel, etc. Sadly that’s not me today.
You are very correct, but the point is close_tag
doing nothing because JSX scopes </
meta.tag
. If syntax definition is modified to not to so until the first tag name letter is typed (e.g.: </C
) close_tag
will work as expected.
Note: It's probably enought not to scope <
until /
is typed.
I've already tested a syntax change successfully. But as it breaks other things, which I don't want to dive into fixing, I pinged the main maintainer to do so.
FWIW, JS Custom provides a jsx_close_tag
command, and by default replaces the built-in close_tag
command in JSX scopes.
Nevertheless it should work out of the box.
I agree that it should work out of the box. If it's possible to substantially improve the behavior via the syntax, then I'm all for it. I'm not sure if I'll have time to look at it in the next few days, though.
One possible complicating factor is that JSX has a more complex structure than XML. In particular, JSX attributes may contain other JSX elements. The existing close_tag
implementation may not be able to handle that. And the devs are very hesitant to make changes to default commands. One option would be to basically add jsx_close_tag
as a separate command into the core JavaScript package, though of course this would also be a bigger deal than simply changing the syntax.
Will there be any updates soon related to this issue? Until then, JSCustom can be used to get JSX tag close working as intended.