Search code examples
sublimetext3sublime-text-plugin

How to make Sublime Text 3 indent newlines when caret is inside brackets (or other symbols)


To customize Sublime Text's behaviour on when to indent newlines, depending on the current line, one can change the whatever.tmPreferences file appropriately setting the increaseIndentPattern and decreaseIndentPattern options, like shown for example in this other answer.

However, I can't seem to work out how to generate the following behaviour: given a line like

[<cursor here>]

with the cursor between the square brakets, pressing enter I want the following result:

[
    <cursor here>
]

This is for example what happens when modifying an xml file one presses enter between two brackets, like in <sometag><cursor here></sometag>.

I tried to look into the tmPreferences files for the xml but to no avail.

A similar question has been asked here, but the present one is different for several reasons:

  1. I want this behaviour to be implemented only for specific file extensions, and to be shipped with a package. So I'm also asking where exactly I should put the instructions for this custom keybinding.
  2. In the linked question the matter is simpler: to just correctly add and indent newlines between some kind of braces. It is not straightforward (to me) how to generalize this behaviour as in the example cited above in which we want a newline between XML-like tags, as in this case we will have to somehow deal with regexes and verify that the left and right patterns match.

How can this behaviour be implemented?


Solution

  • To make a keybinding that will be shipped with a package, create a Default.sublime-keymap file in your package.

    Normally Sublime Text looks at the syntax being used to highlight the document, as opposed to the file extensions used, to determine whether keybindings/plugins should be active etc. This is mainly so that it will work on files that haven't been saved yet. If you want to follow this guideline, you can use the selector keybinding context. In the case of XML files, you would probably want to use source.xml. Otherwise, you will need to create an EventListener that defines a on_query_context method to check the view.file_name(). You could use the os.path.splitext method to retrieve the file extension.

    If you are truly dealing with XML, then you could use the default auto_indent_tag keybinding as inspiration:

    { "keys": ["enter"], "command": "auto_indent_tag", "context":
        [
            { "key": "setting.auto_indent", "operator": "equal", "operand": true },
            { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
            { "key": "selector", "operator": "equal", "operand": "punctuation.definition.tag.begin", "match_all": true },
            { "key": "preceding_text", "operator": "regex_contains", "operand": ">$", "match_all": true },
            { "key": "following_text", "operator": "regex_contains", "operand": "^</", "match_all": true },
        ]
    },
    

    to build something like:

    { "keys": ["enter"], "command": "insert_snippet", "args": { "contents": "\n\t$1\n" }, "context":
        [
            { "key": "setting.auto_indent", "operator": "equal", "operand": true },
            { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true },
            { "key": "selector", "operator": "equal", "operand": "text.xml punctuation.definition.tag.begin", "match_all": true },
            { "key": "preceding_text", "operator": "regex_contains", "operand": ">$", "match_all": true },
            { "key": "following_text", "operator": "regex_contains", "operand": "^</", "match_all": true },
        ]
    },
    

    The regular expressions used here are very simple, just checking the the text immediately before the caret is > and the text immediately after the caret is </. This is possible because the selector checks that a) we are in an XML syntax, and b) the text immediately after the caret is scoped as punctuation.definition.tag.begin. (You can manually check the scope immediately to the right of the caret from the Tools menu -> Developer -> Show Scope Name.) If you are using a custom syntax, you will need to ensure you adjust these accordingly.

    In this case, because we are using a keybinding on the Enter key, the indentation rules specified in tmPreferences files are ignored.