-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
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
- Do not show snippets in the completion list for C#
- For the set of some 40 snippets we ship for the language replace them with "snippets" written using the completion provider API
- Work with same teams as we do no roslyn-analyzer to write api-specific snippets and ship them in the SDK
- Still ship snippets so muscle memory like
cw->Console.WriteLine();will work
Advantages
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
- 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.
- If the user has third-party snippets installed, we should still show those but the ~40 we own should not appear
- 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.| 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.
| 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_TEXTThe currently selected text or the empty stringTM_CURRENT_LINEThe contents of the current lineTM_CURRENT_WORDThe contents of the word under cursor or the empty stringTM_LINE_INDEXThe zero-index based line numberTM_LINE_NUMBERThe one-index based line numberTM_FILENAME Thefilename of the current documentTM_FILENAME_BASEThe filename of the current document without its extensionsTM_DIRECTORYThe directory of the current documentTM_FILEPATHThe full file path of the current documentRELATIVE_FILEPATHThe relative (to the opened workspace or folder) file path of the current documentCLIPBOARDThe contents of your clipboardWORKSPACE_NAMEThe name of the opened workspace or folderWORKSPACE_FOLDERThe 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:
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:
If the user commits the snippet, it is persisted to the buffer as the “ghost text” preview had shown:
If the user dismisses the completion list then the editor is placed back as it was with new being left alone:
1.2 Using an Existing Snippet
A user types ctor (the text expansion for an existing snippet into the editor):
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.
If the user commits the snippet, it is persisted to the buffer as the ghost text preview had shown:
If the user dismissed the completion list then the editor is placed back as it was with ctor being left alone:
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:
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:
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).
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:
If the user dismisses completion, they are left with the same contents in their buffer as they had before:
Tracking Issues
- Update snippets to use modern language features (eg "nameof") #5574
-
foreachautocompletion should be offered when ctrl+. is pressed on a variable representing a collection #60628 - Snippets: If snippet and use of LSP's SnippetExpander #60860
- adding draft of detailed spec #57346
- Semantic Snippets: Console snippet first look #59303
Metadata
Metadata
Assignees
Labels
Type
Projects
Status















