Skip to content

Semantic Snippets #56541

@jmarolf

Description

@jmarolf

Snippets as they exist today for .NET developers only do text expansion (cw -> Console.WriteLine) and have lacked any significant improvements in the last 10+ years. We would like to iterate on this experience for .NET developers using Visual Studio. Snippets based on the rich analysis capabilities of the roslyn api that can change based on context.

Proposal

  1. Do not show snippets in the completion list for C#
  2. For the set of some 40 snippets we ship for the language replace them with "snippets" written using the completion provider API
  3. Work with same teams as we do no roslyn-analyzer to write api-specific snippets and ship them in the SDK
  4. Still ship snippets so muscle memory like cw -> Console.WriteLine(); will work

Advantages

image

The CompletionProvider api has lots of advanced capabilities that allow us to be semantically aware of what is around the snippet. I've been playing around with what the API is capable of and there are a lot of possibilities.

  • Only show completion for snippets when it is syntactically correct to do so
  • Advanced filtering and sorting capabilities available to us via the completion api
  • Populate values from members that are in scope (automatically include cancellation tokens etc.)
  • Have different behavior depending on where the cursor is (surround with vs. expansion can just be where on the line you are typing)
  • completion providers can come from nuget packages so we can have other teams such as the BCL

Open Implementation Questions

  • In our own implementations we would use the Speculative Semantic Model and other tricks to ensure good performance, some of these helpers may need to get ported to like what was done for roslyn-analyzers if we want other teams to contribute
  • We currently can create tab stop behavior by synthesizing an xml snippet on demand. Its unclear if this approach is something we want to continue doing.

Open Design Questions

  1. What should the pre-selection and filtering behavior be? If the snippets are targeted enough, it may be sufficient for their behavior to be simple.
  2. If the user has third-party snippets installed, we should still show those but the ~40 we own should not appear
  3. Potentially add a Tools->Option toggle to show snippets again and/or disable this feature. Its unclear how people will react to the old snippets not being in the completion list.

Appendix

C# Snippet Behavior in Visual Studio

List of all snippets C# included in Visual Studio Here is a list of all 39 "Expansion" snippets we ship for C# today. These appear in the completion list as their snippet text entry.

image

Snippet Text Description
#if if region directing
#region region directive
attribute define new attribute class (25 lines)
checked checked statement
class define new class
ctor define new parameter-less constructor
cw Console.WriteLine
do do…while loop
else else statement
enum define a new enum
equals override Equals(object obj) and `GetHashCode() methods (27 lines)
exception define a new exception type (10 lines)
for for loop
foreach foreach block
forr Reverse for loop
if if statement
indexer indexer property
interface define a new interface
iterindex define an interator class with and index property (43 lines)
iterator define IEnumerator<ElementType> GetEnumerator()method
lock lock statement
mbox show message box (System.Windows.Forms.MessageBox.Show("Test");)
namespace define a new namespace
prop public int MyProperty { get; set; }
propfull property with backing field (8 lines)
propg Property with a get accessor and private set accessor
sim int Main method
struct define a new struct
svm void Main method
switch switch statement
testc define new MSTest Test Class
testm define new MSTest Test Method
try try catch
tryf try finally
unchecked unchecked block
unsafe unsafe block
using using statement
while while loop
~ deconstructor

Here is a list of all 21 "Surrounds With" snippets we ship for C# today. These require the user to select text and press te "Surround With" cord (Ctrl+K+S) and are never shown in the completion list.

image

Snippet Text Description
#if if region directing
#region region directive
checked checked statement
class define new class
do do…while loop
else else statement
enum define a new enum
for for loop
foreach foreach block
forr Reverse for loop
if if statement
interface define a new interface
lock lock statement
namespace define a new namespace
struct define a new struct
try try catch
tryf try finally
unchecked unchecked block
unsafe unsafe block
using using statement
while while loop

Visual Basic Snippet Behavior in Visual Studio

The VB snippet design is different they ship about 100 snippets that offer a more expansive set of capabilities however, they do not show them in the completion list and it is not expected that the user types out text such as cw or prop and gets the text expanded. Instead the user is expected to invoke the snippet menu (Ctrl+K+X).

Visual Studio Snippet Capabilities

Some relevant details pulled from docs

See the snipper schema docs here for more information.
There are two kinds of snippets that exist today in Visual Studio

  • Surrounds With: surrounds a users selection
  • Expansion: text expansion (i.e. cw -> Console.WriteLine();)

These VS snippets are allowed to have the following pre-defined variables

  • $selected$ is a predefined variable. It represents the text that was selected in the editor before invoking the snippet. The placement of this variable determines where the selected text appears in the code snippet that surrounds that selection.
  • $end$ is a predefined variable. When the user presses Enter to finish editing the code snippet fields, this variable determines where the caret (^) is moved to.

You can also define your own variables in the VS snippet engine and give them a default text but allow the user to tab to them and edit them on snippet insertion.

Each snippets can also have an Assembly element indicating that an <AssemblyReference> should be added to the project file if not present and an Import element that adds usings to the top of the file if not present.

VS Code Snippet Capabilities

Some relevant details pulled from docs

For more information see the docs here

VS Code's snippet system is similar to Visual Studio's in that it has a "trigger phrase" (such as for) that it uses as the decider for when to begin expansion, however its text transformation capabilities are a more dynamic as it allows regular expressions to be run over the output of a snippet.

The EBNF form of the regex syntax is defined as follows:

any         ::= tabstop | placeholder | choice | variable | text
tabstop     ::= '$' int
                | '${' int '}'
                | '${' int  transform '}'
placeholder ::= '${' int ':' any '}'
choice      ::= '${' int '|' text (',' text)* '|}'
variable    ::= '$' var | '${' var '}'
                | '${' var ':' any '}'
                | '${' var transform '}'
transform   ::= '/' regex '/' (format | text)+ '/' options
format      ::= '$' int | '${' int '}'
                | '${' int ':' '/upcase' | '/downcase' | '/capitalize' | '/camelcase' | '/pascalcase' '}'
                | '${' int ':+' if '}'
                | '${' int ':?' if ':' else '}'
                | '${' int ':-' else '}' | '${' int ':' else '}'
regex       ::= JavaScript Regular Expression value (ctor-string)
options     ::= JavaScript Regular Expression option (ctor-options)
var         ::= [_a-zA-Z] [_a-zA-Z0-9]*
int         ::= [0-9]+
text        ::= .*

They have the following predefined variables:

  • TM_SELECTED_TEXT The currently selected text or the empty string
  • TM_CURRENT_LINE The contents of the current line
  • TM_CURRENT_WORD The contents of the word under cursor or the empty string
  • TM_LINE_INDEX The zero-index based line number
  • TM_LINE_NUMBER The one-index based line number
  • TM_FILENAME The filename of the current document
  • TM_FILENAME_BASE The filename of the current document without its extensions
  • TM_DIRECTORY The directory of the current document
  • TM_FILEPATH The full file path of the current document
  • RELATIVE_FILEPATH The relative (to the opened workspace or folder) file path of the current document
  • CLIPBOARD The contents of your clipboard
  • WORKSPACE_NAME The name of the opened workspace or folder
  • WORKSPACE_FOLDER The path of the opened workspace or folde
Example Output Explanation
${TM_FILENAME/[\\.]/_/} example-123_456-TEST.js Replace the first . with _
${TM_FILENAME/[\\.-]/_/g} example_123_456_TEST_js Replace each . or - with _
${TM_FILENAME/(.*)/${1:/upcase}/} EXAMPLE-123.456-TEST.JS Change to all uppercase
${TM_FILENAME/[^0-9^a-z]//gi} example123456TESTjs Remove non-alphanumeric characters

User Experiences

1.1 Snippet Discovery

A user starts typing new so they can start writing a new constructor. They are unaware of the built-in snippets Visual Studio provides but start typing the code for what they are trying to accomplish:

image

We will show the snippet for a constructer as soon as the type new as it is on our list of synonyms for that snippet:

image

If the user commits the snippet, it is persisted to the buffer as the “ghost text” preview had shown:

image

If the user dismisses the completion list then the editor is placed back as it was with new being left alone:

image

1.2 Using an Existing Snippet

A user types ctor (the text expansion for an existing snippet into the editor):

image

When completion comes up, we will do custom filtering to: A. Select the new constructor completion item even though none of the text they’ve entered matches the text in this item. B. Use the ghost text API to show the user what will happen if they commit the completion item.

image

If the user commits the snippet, it is persisted to the buffer as the ghost text preview had shown:

image

If the user dismissed the completion list then the editor is placed back as it was with ctor being left alone:

image

1.3 Semantic Awareness

Today the editor shows and commits snippets even if they do not make sense. Here the developer is being suggested the “for” snippet even though it can never be valid inside of a type:

image

In the new experience snippets will be aware of the context they are appearing in and only show if
they are reasonable. Notice we will now suggest FormattableString as a completion item assuming the
user is writing out a new property or field in this type:

image

However if the user is in a situation where this snippet makes sense we will: A. Filter to it in the completion list based on hard-coded synonyms. B. Show a ghost text preview of what the committed code will look like. C. Populate the snippet based on the semantic context in which it appears (notice how points.Length is automatically used in the snippet).

image

If the user commits the snippet, we will use the existing snippet expansion experience that Visual Studio offers today to allow the user to fill in the placeholders that are automatically generated for them:

image

If the user dismisses completion, they are left with the same contents in their buffer as they had before:

image


Tracking Issues

Metadata

Metadata

Assignees

Type

No type

Projects

Status

Complete

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions