DEV Community

ArkfulDodger
ArkfulDodger

Posted on • Edited on

Transforming VS Code Snippets

VS Code Snippets

If you're here, you probably know what snippets are, and have maybe even dabbled in making your own. As such, you're familiar with some or all of the basic features/benefits of snippets outlined below:

Snippet Features/Benefits

  1. quickly output pre-written code (save repetitive typing)
  2. use tabstops to navigate your cursor to predetermined locations in your snippet using the tab key
  3. use multiple occurrences of the same tab stop to enter recurring values only once
  4. use placeholder values to provide default values to tabstops
  5. give placeholder choice options (as from a dropdown) to be selected when the snippet is inserted
  6. insert certain variable values into your snippets

If you're new to making your own snippets, I highly recommend working on each of these in turn, as they build on each other well and are simple enough to pick up. With only these 6, you can create a great many incredibly useful snippets.

Here, I am going to cover one of the tricker, but more powerful customizations you can add to your snippets to further streamline your own coding process:

7. Variable and Placeholder Transformations

Transformations

Transformations in our snippets take either a variable or placeholder and modify it in some way.

Why would we do this? Let's look at one practical use-case example.

Use-Case Example: React Use State Declaration

Let's say you're programming in React and want to declare a new state. Not familiar with React? Don't worry about it. For this example, just know that declaring state involves creating a variable as well as a method used to set the value of that variable. This is declared as such:

const [fooBar, setFooBar] = useState()
Enter fullscreen mode Exit fullscreen mode

We have our variable "fooBar" and our method "setFooBar". By convention, the method is named as "set____" using the name of the variable. Both are in camelCase.

If we were making a snippet for this, we might write our snippet body as something like:

"body": "const [$1, set$2] = useState();"
Enter fullscreen mode Exit fullscreen mode

This snippet is functional, but note that we are typing the same variable name twice. We could replace the "$2" tabstop with "$1", but we will quickly run into the issue that the second occurrence comes out as setfooBar and no longer adheres to our camelCase.

What we want is a way to type the variable once, and capitalize the first letter of the second occurrence.

To achieve this we use a placeholder transform to insert tabstop $1 and change it to our specifications. The new snippet code is as such:

"body": "const [$1, set${1/(.*)/${1:/capitalize}/}] = useState()"
Enter fullscreen mode Exit fullscreen mode

Okay, it looks like there's a lot going on in there, so let's break it down into pieces and put it back together.

Transformation Structure/Syntax

To start, let's think of a transform in terms of a series of questions:

  1. What are we transforming?
  2. What part(s) of that thing are we transforming?
  3. How are we changing each part?
  4. Are there any additional details or modifications?

Let's get our bearings in the code by looking at the syntax of a transformation:

${«variable|tabstop»/«regexp»/«text|format»/«options»}

The transform is contained inside "{}" curly brackets preceded by a "$" dollar sign and is divided into four sections by "/" forward slashes. Each section directly relates back to our questions thusly:

  1. What are we transforming?
    • variable or tabstop: the variable or tabstop input being transformed
  2. What part(s) of that thing are we transforming?
    • regex: a regular expression that searches for matches in the variable/tabstop
  3. How are we changing each part?
    • text or format: the text to insert in place of the match(es) found by the regex, or the format indicating how to return or act upon the match(es) found by the regex
  4. Are there any additional details or modifications?
    • options: additional options for the regex, often blank (we won't use or go into this in this example)

The Structure of our Example Transformation

Now let's see how these sections present themselves in the transformation we used above:

${1/(.*)/${1:/capitalize}/}    // the full transformation
${ /    /                /}    // container and dividers
  1                            // tabstop reference
    (.*)                       // regex
         ${1:/capitalize}      // format
Enter fullscreen mode Exit fullscreen mode

Let's touch on each of the three sections in play (section 4, options, is not used here):

Tabstop - What is being transformed?
The tabstop reference is relatively self explanatory. We are wanting to transform whatever we are entering into our first tabstop (our variable name from up above), so we list 1.

If we were transforming a variable instead of a tabstop input, we would put the variable name here instead.

So the answer to our question 1: Tabstop 1's input

Regex - What part is being transformed?
If you are unfamiliar with regex syntax, there are some resources listed at the bottom of this post that might be helpful. For now, just know that this particular expression (.*) indicates that we are searching within our tabstop input string for any characters in any combination; aka: return the whole string.

So the answer to question 2 is: All of Tabstop 1's input

An important thing to note here before moving on is that the use of parentheses around the entire regex means that it is all part of the same regex "capture" – the first capture to be specific. This is important, because the format section cares about captures and capture order.

Again, if regex and captures are foreign to you, don't worry about it too much for this example. You can think of regex/captures this way: regex is a way to search a string for specific substrings, and captures are ways to further break those substrings into chunks that can be referenced. Here, the whole regex is grouped as a single chunk.

Format - How is it being transformed?

Format references the results/captures from our regex and can act upon captures individually. Depending on the change being made, format can have the following syntaxes:

Format Syntax

insertion: $«int» or ${«int»}
method: ${«int»:/«method»}
conditional insertions:

  • if/else ${«int»:?«if insertion»:«else insertion»}
  • if ${«int»:+«if insertion»}
  • else ${«int»:-«else insertion»} or ${«int»:«else insertion»}

Above, "int" is the capture number being acted upon, "method" is the change being applied, and "if/else insertion" are strings to be inserted if the regex/capture does or does not return a match respectively.

This can be quite a lot to get into, so let's limit our scope to our example. Recall that our format above looks like this:

${1:/capitalize}

So our format appears to be calling a method of "capitalize" on capture number 1.

Sidenote: It would be easy to confuse the "1" here for referencing our tabstop $1, but they are entirely unrelated. In format, "1" indicates that this format is acting on the first capture in our regex. Note that if you wish to act on the entire regex collectively, you can list an int of "0" in your format. In our example, we only have one capture, and it is the entirety of our regex, so we could technically use "0" or "1".

The methods listed in the VS Code documentation are:

  • /upcase - converts string to upper case
  • /downcase - converts string to lower case
  • /capitalize - capitalizes first character of string
  • /camelcase - converts snake_case to camelCase
  • /pascalcase - converts snake_case to PascalCase (aka upper-camelcase)

So our format of ${1:/capitalize} takes the first capture and capitalizes the first letter. This is exactly what we were aiming for!

The answer to our third question then is: First Letter is Capitalized

Recap

That was a fairly in-depth look, so let's zoom back out to look at our snippet, and see if the pieces make more sense to us now.

"body": "const [$1, set${1/(.*)/${1:/capitalize}/}] = useState()"
Enter fullscreen mode Exit fullscreen mode

Bringing it back to our questions, we:

  1. What? - 1 - are taking the input from tabstop 1
  2. Part? - (.*) - are modifying the whole input string
  3. Changes? - ${1:/capitalize} - capitalize the first letter of that string
  4. And...? and are done

The result is a snippet which lets you type a variable one time, and as soon as you tab out of the first tabstop (you must tab out!) the second occurrence will adjust its capitalization automatically!

It seems like a lot of work for something very small, but the more familiar you can become with snippet syntax, regex, format, etc, the easier it will become to implement.

This is a complex topic, and this single use-case could only cover so much. I recommend looking through the resources below if you want to look into this further.

Resources:

Snippets

Regex

Top comments (0)