sublime_text icon indicating copy to clipboard operation
sublime_text copied to clipboard

close_tag with "/" doesn't work in jsx in ST4

Open jeremytm opened this issue 2 years ago • 13 comments

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

  1. Start ST in safe mode
  2. Update key bindings as suggested in top answer here.
  3. Create new file, and set source type to Javascript > JSX
  4. 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> running close_tag will successfully close the tag.
  • With <div></ running close_tag will successfully close the tag.
  • With <div>< running close_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

jeremytm avatar Mar 19 '22 20:03 jeremytm

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)

deathaxe avatar Mar 20 '22 12:03 deathaxe

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

Penguin98kStudio avatar Mar 23 '22 14:03 Penguin98kStudio

Are u sure? Running ST4129 on Win11 in SAFE MODE using your bindings, results in ...

Animation

deathaxe avatar Mar 23 '22 16:03 deathaxe

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

Penguin98kStudio avatar Mar 23 '22 16:03 Penguin98kStudio

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>

Penguin98kStudio avatar Apr 30 '22 05:04 Penguin98kStudio

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>

deathaxe avatar Jul 31 '22 13:07 deathaxe

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.

deathaxe avatar Jul 31 '22 18:07 deathaxe

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.

phyllisstein avatar Aug 01 '22 13:08 phyllisstein

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.

deathaxe avatar Aug 01 '22 16:08 deathaxe

FWIW, JS Custom provides a jsx_close_tag command, and by default replaces the built-in close_tag command in JSX scopes.

Thom1729 avatar Aug 01 '22 16:08 Thom1729

Nevertheless it should work out of the box.

deathaxe avatar Aug 01 '22 17:08 deathaxe

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.

Thom1729 avatar Aug 01 '22 18:08 Thom1729

Will there be any updates soon related to this issue? Until then, JSCustom can be used to get JSX tag close working as intended.

ipmanlk avatar Aug 19 '22 20:08 ipmanlk