Custom bracket auto-closing in Sublime Text
Asked Answered
N

3

6

I'm trying to auto-close the asterisk (*) character in Markdown files.
I've been looking through all the language setting files, and am turning up nothing to use as an example. I've also tried writing a snippet, but found it inefficient (it doesn't wrap around the selection).

I searched around and found BracketHighlighter (which claims to allow custom auto-close pairings) but with no luck (installed through Package Control, also restarted).

Any ideas on where I should start or what I'm doing wrong?


Solution (thanks to @skuroda)

skuroda's answer will do fine - however, I've made a few tweaks that I would like to append to their answer:

{ "keys": ["*"], "command": "insert_snippet", "args": {"contents": "$0**"}, "context":
    [
        { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
        { "key": "preceding_text", "operator": "regex_contains", "operand": "\\*\\*", "match_all": true },
        { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }

    ]
}

Which adds two ** if the asterisk key is pressed next to two preceding asterisks (e.g. **| then ***| becomes **|** where | is the cursor. This helps a lot with emboldening text.

Nervy answered 15/4, 2013 at 22:14 Comment(0)
F
3

You may need to tweak the context some, but this should be a start. This is based on the auto pair key bindings for the built in brackets.

{ "keys": ["*"], "command": "insert_snippet", "args": {"contents": "*$0*"}, "context":
    [
        { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
        { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|;|\\}|$)", "match_all": true },
        { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }

    ]
},
{ "keys": ["*"], "command": "insert_snippet", "args": {"contents": "*${0:$SELECTION}*"}, "context":
    [
        { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true },
        { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }
    ]
},
{ "keys": ["*"], "command": "move", "args": {"by": "characters", "forward": true}, "context":
    [
        { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
        { "key": "following_text", "operator": "regex_contains", "operand": "^\\*", "match_all": true },
        { "key": "selector", "operator": "equal", "operand": "text.html.markdown", "match_all": true }
    ]
}
Fairman answered 16/4, 2013 at 0:18 Comment(3)
Helpful, but where is this file located, or where would I save this file?Nervy
These are based on the default key bindings (Preferences -> Key Bindings - Default). You would place these entries in your user key bindings file (Preferences -> Key Bindings - User). Oh and as a side note, I thought bracket highlighter just let you define custom brackets for highlighting not pairing, though I could be wrong.Fairman
Thanks, that worked well. I've tweaked some of the code so that embolden is easily accessible as well.Nervy
F
2

Use this

{ "keys": ["*"], "command": "insert_snippet", "args": {"name": "Packages/User/my-snippet.sublime-snippet" }}

now go to Preference>Browse Packages and then User folder Create a file

my-snippet.sublime-snippet

and use following code inside

<snippet><content><![CDATA[
*${0:$SELECTION}*
]]></content></snippet>

Good Luck

Fervency answered 16/5, 2015 at 4:28 Comment(0)
G
1

Here's my version. It's also based on the built-in auto pair key bindings, but with some tweaks.

[
    { "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 },
            { "key": "following_text", "operator": "regex_contains", "operand": "^(?:\\s|\\.|,|:|;|!|\\?|'|\"|‐|-|—|\\)|]|\\}|⟩|>|›|»|$)", "match_all": true },
            { "key": "preceding_text", "operator": "not_regex_contains", "operand": "\\S$", "match_all": true },
            { "key": "selector", "operator": "equal", "operand": "text.plain, text.html.markdown", "match_all": true },
        ]
    },
    { "keys": ["*"], "command": "insert_snippet", "args": {"contents": "*${0:$SELECTION}*"}, "context":
        [
            { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
            { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true },
            { "key": "selector", "operator": "equal", "operand": "text.plain, text.html.markdown", "match_all": true },
        ]
    },
    { "keys": ["*"], "command": "move", "args": {"by": "characters", "forward": true}, "context":
        [
            { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
            { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
            { "key": "following_text", "operator": "regex_contains", "operand": "^\\*", "match_all": true },
            { "key": "selector", "operator": "equal", "operand": "text.plain, text.html.markdown", "match_all": true },
        ]
    },
    { "keys": ["backspace"], "command": "run_macro_file", "args": {"file": "res://Packages/Default/Delete Left Right.sublime-macro"}, "context":
        [
            { "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
            { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
            { "key": "preceding_text", "operator": "regex_contains", "operand": "\\*$", "match_all": true },
            { "key": "following_text", "operator": "regex_contains", "operand": "^\\*", "match_all": true },
            { "key": "selector", "operator": "equal", "operand": "text.plain, text.html.markdown", "match_all": true },
        ]
    },
]

Unlike the default, this version will:

  • make character pairs only when the caret is preceded by a whitespace character or beginning of a line (e.g. the default doesn't allow auto pairing after o, but does after ö or $ - this version excludes everything)
  • make pairs when the caret is followed by various characters (., :, !, " and more - the default only allows pairs to be made before a space, tab and some brackets)
  • only work in plain text Markdown files (.txt and .md)

I use this version for quotation marks too, replacing the default (of course allowing quotation marks globally).

Double characters

My version of double character functionality (similar to OP's/based on the default, but with a tweak):

[
    { "keys": ["*"], "command": "insert_snippet", "args": {"contents": "$0**"}, "context":
        [
            { "key": "preceding_text", "operator": "regex_contains", "operand": "\\*\\*", "match_all": true },
            { "key": "following_text", "operator": "not_regex_contains", "operand": "\\*", "match_all": true },
            { "key": "selector", "operator": "equal", "operand": "text.plain, text.html.markdown", "match_all": true },
        ]
    },
]

This will make double characters, in this case asterisks (*), if there are already two consecutive asterisks anywhere in the line preceding the caret.

The tweak allows skipping asterisks (one at a time) when the caret is followed by any asterisk.

OP's version always adds two new asterisks if there are already two consecutive asterisks in the line preceding the caret, even if the caret is followed by an asterisk, which is undesired behaviour (for example just after adding a second double asterisk, pressing the asterisk key would add two more instead of skipping the new asterisks).

Explanations

From Sublime Text's default key bindings:

One rule states that in order to make a pair the caret must be preceded by lowercase letters, uppercase letters, numbers, the same symbol or an underscore:

{ "key": "preceding_text", "operator": ^"not_regex_contains", "operand": "[\"a-zA-Z0-9_]$"^, "match_all": true },

However, it still allow a pair if the caret is preceded by other letters or symbols (ö, ž, đ, ç, , Ψ, ?, ~, [, , $, etc.) It shouldn't do that so I tweaked it. The following requires the caret to be preceded by a whitespace character or beginning of a line (it excludes all other symbols, not just basic letters and numbers):

"not_regex_contains", "operand": "\\S$"

It's a double negative to include beginnings of lines. The following would only work after whitespace characters (space, tab, other) but NOT at the beginnings of lines:

"regex_contains", "operand": "\\s$"

Another rule states that in order to make a pair the caret must also be followed by certain characters: tab, space, ), ], }, >, end of line:

{ "key": "following_text", "operator": "regex_contains", "operand": "^(?:\t| |\\)|]|\\}|>|$)", "match_all": true },

I thought that character pairing should also be allowed if other characters follow the caret, namely punctuation marks and other whitespace characters not included in the default.

The following includes more punctuation marks (., ,, :, ;, !, ?, ', ", , -, , , , ») and all whitespace characters:

"^(?:\\s|\\.|,|:|;|!|\\?|'|\"|‐|-|—|\\)|]|\\}|⟩|>|›|»|$)"

Note that a whitespace character preceding the caret is required, meaning that a pair would not be made when not wanted.

This would help in a situation where one wants to start writing formatted text at the end of a sentence but before the punctuation at the end of that sentence, for example (| represents the caret):

"This is a sentence|"

"This is a sentence |" // user types a space

"This is a sentence *|*" // User types a character and it gets a pair instead of staying single

"This is a sentence *with more words|*"

This question was asked several years ago, but hopefully my answer will be useful to everyone in the future who is interested.

Guardrail answered 19/8, 2018 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.