When I edit a JS file in ST3, I can select a string, press the backtick and ST3 will surround the selected string with backticks, the same as it works with quotes and backets. But when the source file type is Vue Component
, then this does not work and instead the single backtick replaces the selected string.
Is there any way to change the configuration to behave the same for Vue Component
as it does for Javascript
?
Auto-pairing of matching characters as well as wrapping selected text in appropriate pairs is a function of key bindings that are configured to apply in only specific situations via the use of a key binding context
.
For example, this is the key binding that provides the functionality for wrapping selected text in double quotes by selecting text and pressing "
, which you can view by using Preferences > Key Bindings
and searching in the default bindings in the left pane:
{ "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 }
]
},
Here the key binding is saying that the "
character should insert a snippet whose content is the selection wrapped in double quotes; the context
says that the key binding is available only while the auto_match_enabled
setting is turned on and there is a selection.
The default bindings also include other similar bindings for wrapping things in parenthesis, for inserting the paired character, and so on.
In the case of the key bindings that you're asking about, those key bindings are provided by the shipped JavaScript
package; you can view them by using View Package File
from the command palette and then selecting the JavaScript/Default.sublime-keymap
file.
There are (as of the time of this writing) 6 bindings provided by this package; the one you want to use is the second one, which looks like this:
{ "keys": ["`"], "command": "insert_snippet", "args": {"contents": "`${0:$SELECTION}`"}, "context":
[
{ "key": "selector", "operator": "equal", "operand": "source.js - string" },
{ "key": "setting.auto_match_enabled", "operator": "equal", "operand": true },
{ "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }
]
},
This is very similar to the default above, but note that the context
contains an additional item that requires that the scope selector
at the current cursor position match source.js - string
in order to be active. This is what constrains the key binding to only be active in JavaScript files (and specifically everywhere but inside of strings).
In order to make these bindings active in your Vue components, you need to copy the bindings from this file into your User key bindings and then modify the selector
so that it matches the scope at the point where you want to use it.
You can determine what the scope at the current cursor location is by using Tools > Developer > Show Scope Name
from the menu (or press the associated key, visible in the menu). That will show you the full scope of the character the cursor is sitting on.
A full discussion of scopes and how they work is out of scope here (pun mildly intended) but this video (disclaimer: I am the video author) goes over the topic. For here it's enough to know that generally speaking the more of the displayed scope you use the more specific the match becomes, so you generally want the top level scope, which for the Vue Syntax Highlight package that you're using is text.html.vue
.
If you use the command above to view the scope of code, you'll see that inside of <script>
tags in a Vue Component file, the key binding works as expected without your having to do anything; this is because the Vue syntax embeds the JavaScript syntax to highlight code there.
I'm not a Vue developer, but it also appears as though the attributes to some tags are also considered JavaScript, such as the strings in:
<li
v-for="page in pages"
:data-test="`page-link-${page}`"
:key="page"
:class="paginationClass(page)"
@click.prevent="changePage(page);"
>
Here you'll find that if you view the scope while the cursor is inside of one of the strings, the scope contains source.js
and the default binding triggers; however if you select the entire contents of the string (i.e. the cursor is sitting on the closing "
character, the scope no longer contains source.js
and the binding doesn't trigger. Selecting text from right to left instead leaves the cursor sitting inside of the string, so the binding will work.
That's a bit of a workflow drag; the easiest way to handle that would be to use the scope text.html.vue
, which will match everywhere inside of a Vue component file no matter what; thus the altered context line would be:
{ "key": "selector", "operator": "equal", "operand": "text.html.vue" },
You could also use the scope text.html.vue punctuation.definition.string.end
, which will match the closing "
character of a string only, in which case the binding will trigger when the entire contents of the string is selected. The downside there being that it will also trigger even when the text of an attribute that's not supposed to be JavaScript is selected.
Alternatively you can delete the selector
line entirely and your custom binding will do this in any file that you happen to edit (so long as the auto_match_enabled
setting is turned on).
Which way you go depends on your workflow and how things work best for you.