Sublime Forum

Smarter, Context-Sensitive Auto-Bracket Insertion

#1

I write a lot of JavaScript. Mostly in MooTools, but even jQuery and “vanilla” writers would benefit from the following, I think:

If I type out the following (the | is my cursor):

window.addEvent ('domready', function () {|

Sublime will somewhat dumbly do the following when I press return:

window.addEvent ('domready', function () { | }

Now, I’m grateful that it’s trying, but in cases like this, it’s actually more of a nuisance. What I would love for it to do, instead:

window.addEvent ('domready', function () { | }); // <-- Ooooh

Yeah. Getting shivers? How awesome would that be? It would add the semi-colon and the closing round bracket. It could even be super smart and put an invisible insertion marker between the } and ) in the event this function has more arguments, and I could just tab past it to the right of the semi-colon if it didn’t.

There is another instance in which the current auto-bracket closing is frustrating:

[code]var App = new Class ({
‘initialize’: function (options) {|

'method': function () {
	// some stuff here
}

});[/code]

Pressing return with my insertion point where it is above would yield:

[code]var App = new Class ({
‘initialize’: function (options) {
|
} // <-- SYNTAX ERROR, MISSING COMMA

'method': function () {
	// some stuff here
}

});[/code]

See the syntax error? It would have to place a comma after the automatically-inserted bracket. Now, that’s probably asking a lot, but even if it added the comma EVERY time in this context, it’d be a lot easier for developers to remove the superfluous comma than to continually go in and add one every. single. time.

I think that about covers my most frequently-encountered quirks with the bracket-closing magic of Sublime. I am 100% oblivious to the complexity of implementing such improvements, but I do feel like Sublime, which is scopes and such, is probably in the best position of all editors to make it happen.

Thank you for reading!

0 Likes

#2

ST has two js function snippets, triggered by ‘f’ and ‘fun’. These could be copied or edited to achieve what your after (almost…).

The first version is slightly trickier because the closing brace ) is already present when you might trigger the snippet. In which case, you might create a snippet specifically for ‘addEvent’, to complete all the relevant parts. Alternatively, you might create a more generic snippet that follows this pattern:

window.yourMethod('yourEvent', function () { | });
You could then tab through to type yourMethod and yourEvent, and land on the second line?

BTW I assume you’ve already adopted my *sublime *JS completions :wink:

0 Likes

#3

<snippet> <content><![CDATA[${1:yourMethod}('${2:yourEvent}', function () { ${3:// fn body} }${4});$0]]></content> <tabTrigger>meth</tabTrigger> <scope>source.js -string -comment -constant</scope> <description>My Method</description> </snippet>

Save this as something like ‘MyMethod.sublime-snippet’ in your Packages\User folder. It’s triggered by typing ‘meth’ (after window.).

0 Likes

#4

Ah, I did briefly consider making snippets, but figured it’d be cooler if it just kind of worked. I’ve been using Sublime for all of three days now, so am not quite familiar enough with how scopes work or how to target them to make anything robust enough.

The last case I gave (with the methods inside the class) is probably one of the most annoying, but I could try to train myself to drop a snippet there instead. It’s definitely a viable solution while I wait for a more elegant solution.

I haven’t been using the JS snippets of yours, but thanks for sharing! I don’t use many of the native methods these days, as MooTools usually has ways around needing them for most things. But it’s a good template for me to tweak and make a MooTools version :smile:

Thanks again!

0 Likes

#5

I can help.

Add the following snippet to your User Keybindings for the first one: [code] // Auto-pair curly brackets
{ “keys”: “{”], “command”: “insert_snippet”, “args”: {“contents”: “{$0});”}, “context”:

  { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
  { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
  {
    "operand": "source.js",
    "operator": "equal", 
    "match_all": false, 
    "key": "selector"
  },
  { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|$)", "match_all": true }
]

},[/code]

You’ll have to play around with the scope and the previous_text so that it only triggers when you want it. aaronmw can probably come up with the regex.

0 Likes

#6

C0D312,

That snippet seems to behave like this:

element.addEvent (|)

... add the arguments...

element.addEvent ('click', function () {|});)

I think the problem that needs to be solved is that the auto-bracket-adding just needs to know where that bracket sits in terms of scope, and whether or not it’s the last thing on the line. If it is, put a semicolon after it. If it’s in a JSON object, put a comma after it. Etc. Typing element.addEvent ( should result in element.addEvent (|); and not just element.addEvent (|). Unless, of course, there is already text on the right side of the cursor.

... This already exists, let's say...
var result = mySearch ();

... But then we tweak...

var result = mySearch (function () {
    |
}); // <-- No auto-added semi-colon

Then, if there were some magic to detect how many un-matched brackets were on the left side of the cursor, you could just automatically close them with the same logic.

window.addEvent ('blah', function () {| // <-- There are two un-matched brackets here. ( and {.

… Since there’s no text on the right side of the cursor (or rather, there’s no match for those brackets?), add the semi-colon:

window.addEvent ('blah', function () {
    |
}); // <-- Both un-matched brackets closed, with semi-colon

And again, if this code already exists and we’re just typing inside it:

window.addEvent ('blah'); // <-- Looks like this before we go and add that second argument

... add the argument ...

window.addEvent ('blah', function () {|); // <-- Only ONE un-matched bracket on the left of the cursor; the {

... press return ...

window.addEvent ('blah', function () {
    |
}); // <-- Only closed one, and didn't add a semi-colon

Is this something that can be done with a snippet? Or how do users currently deal with the lack of semi-colons, I wonder? Just add them in afterwards?

0 Likes

#7

Okay, C0D312’s snippet actually makes more sense to me now that I’ve had more experience writing these. I’ll keep working with that one.

Thanks!

0 Likes

#8

So far these seem to be working. But I’ll likely find instances where they do stupid things. I’ll update them as that happens :stuck_out_tongue:

	// object.property.method (| --> object.property.method (|);
	{ "keys": "("], "command": "insert_snippet", "args": {"contents": "($0);"}, "context":
		
			{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
			{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
			{
				"operand": "source.js",
				"operator": "equal",
				"match_all": false,
				"key": "selector"
			},
			{ "key": "following_text", "operator": "not_regex_contains", "operand": ";", "match_all": true },
			{ "key": "preceding_text", "operator": "regex_contains", "operand": "[a-zA-Z0-9_$]+(\\.[a-zA-Z0-9_$]+)+ ]?", "match_all": true }
		]
	},

	// 'obj_method': function () {| --> 'obj_method': function () {|},
	{ "keys": "{"], "command": "insert_snippet", "args": {"contents": "{$1},$0"}, "context":
		
			{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
			{ "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
			{
				"operand": "source.js",
				"operator": "equal",
				"match_all": false,
				"key": "selector"
			},
			{ "key": "preceding_text", "operator": "regex_contains", "operand": "'[a-zA-Z0-9_$]+': ]?function ]?\\(.*\\) ]?$", "match_all": true }
		]
	}
0 Likes