I want to create a list of items from a string that would be useful in yaml json or python, so ["1","2"]
from 1 2
(and back again). First understand you have to match a pattern before you can manipulate it. Second understand exactly what that pattern is.
My pattern is a list of yum packages I'd like to quickly move to a yaml list for ansible. Here I have alphanumeric words with 1 sometimes 2 dashes. I have a word with no dashes and one with a dot .
.
python2-mock python-zope-interface pytz pyOpenSSL.x86_64
The answer is, or at least my answer is
[
{
"key": "ctrl+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "$1[${TM_SELECTED_TEXT/(\\w+-?\\.?\\w+-?\\w+)(\\s)?/\"$1\"${2:+, }/g}]"
}
}
]
# ctrl+l
["python2-mock", "python-zope-interface", "pytz", "pyOpenSSL.x86_64"]
This is a specific match, not a fuzzy one. I'll explain how I got here. You can add snippets to the snippets section and tie them to a keybinding. Or you can create a keybinding and inline the snippet, which is what I'll do here.
cmd+shift+p
for command palette and enter "short" open "Open Keyboard Shortcuts (JSON)" not "Open Default Keyboard Shortcuts (JSON)"
[
{
"key": "ctrl+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "${TM_SELECTED_TEXT}"
}
}
]
${TM_SELECTED_TEXT}
is the highlighted text the snippet will act on. Selecting the list and pressing ctrl+l
I just get the same list echoed back at me.
python2-mock python-zope-interface pytz pyOpenSSL.x86_64
In order to manipulate this text I have a standard sed like syntax s/something/tosomethingelse/g
without the inital s
, this goes inside the braces {}
of the variable.
[
{
"key": "ctrl+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "${TM_SELECTED_TEXT/python/I-changed-the-thing/g}"
}
}
]
# ctrl+l
I-changed-the-thing2-mock I-changed-the-thing-zope-interface pytz pyOpenSSL.x86_64
Now lets talk about $1
$2
. These are confusingly used for two things in the snippet:
line. Outside of the ${VAR}
they're called "tabstops". They're where you want your cursor to focus after you use the key binding and again after each tab key is pressed. Inside ${VAR/(regex)/${1}/g}
they're positionally used to reference regex capture groups, i.e. /(group1)(group2)(group3)/$1 $2 $3/g
.
[
{
"key": "ctrl+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "$1${TM_SELECTED_TEXT/python/I-changed-the-thing/g}"
}
}
]
With $1
at the start of the line,using keybinding ctrl+l
my cursor will be at the start of the line instead of the end or wherever I left it.
So how do we make a list? Well since plaintext is honored wrappering the text in whatever you'd like is easy. Just wrap it in []
[
{
"key": "ctrl+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "$1[${TM_SELECTED_TEXT/python/I-changed-the-thing/g}]"
}
}
]
This would be fine for a bash array using ()
, but I need quotes and commas.
# ctrl+l
[I-changed-the-thing2-mock I-changed-the-thing-zope-interface pytz pyOpenSSL.x86_64]
If we get rid of the "I-changed-the-thing" shtick, we can get back to "understand what the pattern is" we're trying to match. Regex gets complicated REALLY fast so like anything else we just need to break up the problem and reduce it to its simplest terms later.
Note the regex here needs to be escaped so where \s
and \w
would be normal it'll be \\s
and \\w
.
If you start by replacing all spaces with comma-space ,
, it's a start. This will eventually be capture group2 futher down.
[
{
"key": "ctrl+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "$1[${TM_SELECTED_TEXT/\\s/, /g}]"
}
}
]
# ctrl+l
[python2-mock, python-zope-interface, pytz, pyOpenSSL.x86_64]
For determining the specific pattern I just googled regex playground and went to the first one that came up, using this interface and the list in this gist I could start to find what matched best for the pattern I needed. I'm also going to wrap the regex in a caputre group ()
so that I can reference the items with $number
variable.
-
\w
alphanumeric word based on its charactersw o r d
, meaning any changes, like wrapping in""
or adding a,
will be applied to the chars (not what we want, think"mystring".split()
in python.) -
\w+
alphanumeric word as a blockword
so"my string".split(" ")
in python. -
\w+-\w+
will matchword-word
but notword-word-word
-
\w+-\w+-\w+
will matchword-word-word
but notword-word
and neither of them will match word
or word.word
which we need. The ?
is used to tell the matching engine that part of the pattern is optional.
\w+-?\w+-?\w+
will match word word-word and word-word-word wince?\w+-
is now optional
All that's left is to match the word.word
, since the .
comes after the first word we'll place a \.
\w+-?\.?\w+-?\w+
this is an explicit match for the given list, not a fuzzy one and will match all of the above.
[a b c] <--- doesn't account for letters
["abc", "abc", "abc", ]
["abc.def", "ghi.jlm", ]
["this-is", "a-test-with", "dashes", ]
Note again when these are added to the capture group ()
all \
are escaped with \\
and the quotes around the$1
var are also escaped. This wraps the items in quotes then adds ,
[
{
"key": "ctrl+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "$1[${TM_SELECTED_TEXT/(\\w+-?\\.?\\w+-?\\w+)/\"$1\", /g}]"
}
}
]
# ctrl+l
["python2-mock", "python-zope-interface", "pytz", "pyOpenSSL.x86_64", ]
But we still have ,
to deal with at the end. We're wrapping capture group1 in quotes and adding ,
so this output makes sense, each item has ,
appended. We want to be able to control where the comma is added.
Here I've wrapped what capture group one matches in square brackets.
[python2-mock] [python-zope-interface] [pytz] [pyOpenSSL.x86_64]
If I create a second capture group I can identify the remaining spaces inbetween, illustrated here with *
and it's these we wish to manipulate.
[python2-mock]*[python-zope-interface]*[pytz]*[pyOpenSSL.x86_64]
So now I have a way to quote $1
and replace $2
with ,
. The regex is simply \s
and since this space may or may not be there as in the last position, we add ?
.
[
{
"key": "ctrl+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "$1[${TM_SELECTED_TEXT/(\\w+-?\\.?\\w+-?\\w+)(\\s)?/\"$1\", /g}]"
}
}
]
# ctrl+l
["python2-mock", "python-zope-interface", "pytz", "pyOpenSSL.x86_64", ]
However, we still need to reference group2 somehow, replacing ,
with ${2:+,}
is the answer.
[
{
"key": "ctrl+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "$1[${TM_SELECTED_TEXT/(\\w+-?\\.?\\w+-?\\w+)(\\s)?/\"$1\"${2:+, }/g}]"
}
}
]
# ctrl+l
["python2-mock", "python-zope-interface", "pytz", "pyOpenSSL.x86_64"]
The ${number:something}
syntax is the "placeholder" syntax vaguely outlined in the documentation. Essentially we're replacing capture group2 specifically with ,
instead of just adding it onto the end of each element of capture group1.
To convert back from list to something you can paste into a bash terminal for yum
you can use the rudementary delete operation. This needs to be refined but it works for this particular case.
[
{
"key": "ctrl+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "$1[${TM_SELECTED_TEXT/(\\w+-?\\.?\\w+-?\\w+)(\\s)?/\"$1\"${2:+, }/g}]"
}
},
{
"key": "ctrl+shift+l",
"command": "editor.action.insertSnippet",
"args": {
"snippet": "$1${TM_SELECTED_TEXT/([^\\w-\\.])/ /g}"
}
}
]
# ctrl+shift+l
python2-mock python-zope-interface pytz pyOpenSSL.x86_64
[^\\w-\\.]
is saying all special characters but NOT ^
the -
which is escaped here and the .
since those are part of the package names I don't want to delete them.