Sublime Forum

CloseTagCommand — closes closest unclosed tag

#1

I made a little plugin to close the last open XML or HTML node/tag.

import sublime, sublimeplugin, re

class CloseTagCommand(sublimeplugin.TextCommand):
  def run(self, view, args):
    leftOfCursor = view.substr(sublime.Region(0, view.sel()[0].begin()))
    regex = re.compile('<(?!/)(\w+?)>(?!\s\S]+?</\\1>)')
    tags = regex.findall(leftOfCursor)

    if len(tags) > 0:
      tag = '</' + tags[len(tags) - 1] + '>'
      view.insert(view.sel()[0].begin(), tag)

I have it bound to CTRL+. (option+command+. in TextMate)

<binding key="ctrl+period" command="closeTag"/>
0 Likes

#2

The above version doesn’t work correctly in a couple of common scenarios, such as the following (| is the cursor):

<html><a href="#">|
<html><div><div>hello</div>|

Here is a version that does:

[code]import sublime, sublimeplugin, re

class CloseTagCommand(sublimeplugin.TextCommand):
def run(self, view, args):
leftOfCursor = view.substr(sublime.Region(0, view.sel()[0].begin()))
regex = re.compile(’<(/?\w+)^>]*>’)
tags = regex.findall(leftOfCursor)
opentags = ]
for tag in tags:
if tag[0] == ‘/’:
if opentags-1] == tag[1:]: opentags.pop()
else: opentags.append(tag)

if len(opentags) > 0:
  tag = '</' + opentags-1] + '>'
  view.insert(view.sel()[0].begin(), tag)[/code]
0 Likes

#3

Here’s an updated version that works with the Sublime Text 2 alpha:

import sublime, sublime_plugin, re

class CloseTagCommand(sublime_plugin.TextCommand):
    def run(self, edit ):
        leftOfCursor = self.view.substr(sublime.Region(0, self.view.sel()[0].begin()))
        regex = re.compile('<(/?\w+)^>]*>')
        tags = regex.findall(leftOfCursor)
        opentags = ]
        for tag in tags:
            if tag[0] == '/':
                if opentags-1] == tag[1:]: opentags.pop()
            else: opentags.append(tag)

        if len(opentags) > 0:
            tag = '</' + opentags-1] + '>'
            self.view.insert(edit, self.view.sel()[0].begin(), tag)
0 Likes

#4

Awesome. Thanks for the update. I’ll have to grab that later.

0 Likes

#5

This is a really dumb question but I just spent 10 minutes on the forums looking for someone else who asked it…

I saved the contents into Data\Packages\closeTag.py, and I added the following keymap (using ST2 dev portable):

{ “keys”: “ctrl+period”], “command”: “closeTag” }

When I run ctrl.period I get this output in the console:
unknown command closeTag

What exactly am I missing? I do see this: Reloading plugin C:\Users\Matt\Desktop\st2\Data\Packages\closeTag.py so I can tell the plugin is getting loaded…

0 Likes

#6

The above plugin is for Sublime Text 1, and will require changes to work with Sublime Text 2

0 Likes

#7

OK figured it out (@jps the 3rd post is actually for ST2).

I didn’t know how command names are mapped from their classes to the actual command name, but it turns out that CloseTagCommand is avaiable as “close_tag”.

After that it works great!

EDIT

Also, to prevent it from closing tags such as or use this RE:
regex = re.compile(’<(\w+) ^/]?>’)

0 Likes

#8

Sorry for my ignorance. I just started trying SublimeText so far, and like it a lot. But I really want tag closing.

This is what I did:

  1. Went to Tools > New Plugin, and pasted in this:
import sublime, sublime_plugin, re

class CloseTagCommand(sublime_plugin.TextCommand):
    def run(self, edit ):
        leftOfCursor = self.view.substr(sublime.Region(0, self.view.sel()[0].begin()))
        regex = re.compile('<(/?\w+)^>]*>')
        tags = regex.findall(leftOfCursor)
        opentags = ]
        for tag in tags:
            if tag[0] == '/':
                if opentags-1] == tag[1:]: opentags.pop()
            else: opentags.append(tag)

        if len(opentags) > 0:
            tag = '</' + opentags-1] + '>'
            self.view.insert(edit, self.view.sel()[0].begin(), tag)

I then saved it to the default plugin directory: C:\Users~\AppData\Roaming\Sublime Text\Packages

  1. I then went to Preference > User Key Bindings, and pasted in the command I found here like this:
<!--
Place your key bindings in here, this will ensure they don't get overwritten
when installing new versions of Sublime Text
-->
<bindings>
	<binding key="ctrl+period" command="closeTag"/>
</bindings>

Hitting Ctrl + . does not do anything though. And nothing shows up in the console when I do.

How can I make this work? Thanks

0 Likes

#9

It looks like you’re using a Sublime Text 2 plugin but a Sublime Text 1 keybinding. Which version are you running?
A Sublime Text 2 keybinding looks like this:

{ "keys": "ctrl+period"], "command": "close_tag" },

0 Likes

#10

You’re right, I was using 1.4

Switched to 2 and it works great! Thanks

0 Likes

#11

I’ve just followed through the instructions above on a Mac. But when I try to run it using CTRL-period, I get the following output in the console:

no command for selector: noop:

I’ve copied the code in ~/Library/Application Support/Sublime Text 2/Packages/User in a file named: close_tag.py

And added the following line to user key bindings:

{ "keys": "ctrl+period"], "command": "close_tag" }

Am I missing something?

0 Likes

#12

period isn’t bindable in OS X, however the next version is getting an input handling refresh, and it’ll be bindable as “ctrl+.” then. Then dev build will be available soon.

0 Likes

#13

You should take care when using regular expressions for parsing HTML, it is generally not a good idea. There are other far better tools for that purpose. Parsing HTML with regular expressions will only add to your list of problems. You might find this post interesting on Stack Overflow: http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454

0 Likes

#14

Which one?

For the purpose of this plugin, it will work the majority of the time. It’s meant to be called manually so I think it’s more than good enough.

But if you know of a good parser than can be easily integrated, it could lead to for interesting plugins. The parsers I’ve used are meant to extract information: they return a tree that doesn’t have the character position of the nodes in the document.

0 Likes

#15

[quote=“indiver”]I’ve just followed through the instructions above on a Mac. But when I try to run it using CTRL-period, I get the following output in the console:

no command for selector: noop:

I’ve copied the code in ~/Library/Application Support/Sublime Text 2/Packages/User in a file named: close_tag.py

And added the following line to user key bindings:

{ "keys": "ctrl+period"], "command": "close_tag" }

Am I missing something?[/quote]

Having the same issues, any info on how we can get this working? Such a handy Textmate feature.

:smile:

0 Likes

#16

{ "keys": "super+."], "command": "close_tag" } ]

Use the above.

0 Likes

#17

thanks for this, really helped me a lot moving from textmate.

0 Likes

#18

If anybody is interested I have created a plugin that auto closes tags when entering “</” (if mapped to the “/” key).
Please have a look at github.com/kihlstrom/CloseTagOnSlash

0 Likes

#19

@wastek THANKS! I love this plugin and for me is in top 5 sublime plugins :smile:

0 Likes

#20

@wastek Good stuff, thanks for sharing. I made the following change which removes an extra closing ‘>’.

if tag is not None and not tag.endswith('>'): self.view.insert(edit, self.view.sel()[0].begin(), tag + '>') elif tag is not None: self.view.insert(edit, self.view.sel()[0].begin(), tag)

0 Likes