/// <summary> /// If there was no $end$ token, place it at the end of the snippet code. Otherwise, it /// defaults to the beginning of the snippet code. /// </summary> private static bool SetEndPositionIfNoneSpecified(IExpansionSession pSession) { if (pSession.GetSnippetNode() is not XElement snippetNode) { return(false); } var ns = snippetNode.Name.NamespaceName; var codeNode = snippetNode.Element(XName.Get("Code", ns)); if (codeNode == null) { return(false); } var delimiterAttribute = codeNode.Attribute("Delimiter"); var delimiter = delimiterAttribute != null ? delimiterAttribute.Value : "$"; if (codeNode.Value.IndexOf(string.Format("{0}end{0}", delimiter), StringComparison.OrdinalIgnoreCase) != -1) { return(false); } var snippetSpan = pSession.GetSnippetSpan(); var newEndSpan = new SnapshotSpan(snippetSpan.End, 0); pSession.EndSpan = newEndSpan; return(true); }
public void FormatSpan(SnapshotSpan span) { // Formatting a snippet isn't cancellable. var cancellationToken = CancellationToken.None; // At this point, the $selection$ token has been replaced with the selected text and // declarations have been replaced with their default text. We need to format the // inserted snippet text while carefully handling $end$ position (where the caret goes // after Return is pressed). The IExpansionSession keeps a tracking point for this // position but we do the tracking ourselves to properly deal with virtual space. To // ensure the end location is correct, we take three extra steps: // 1. Insert an empty comment ("/**/" or "'") at the current $end$ position (prior // to formatting), and keep a tracking span for the comment. // 2. After formatting the new snippet text, find and delete the empty multiline // comment (via the tracking span) and notify the IExpansionSession of the new // $end$ location. If the line then contains only whitespace (due to the formatter // putting the empty comment on its own line), then delete the white space and // remember the indentation depth for that line. // 3. When the snippet is finally completed (via Return), and PositionCaretForEditing() // is called, check to see if the end location was on a line containing only white // space in the previous step. If so, and if that line is still empty, then position // the caret in virtual space. // This technique ensures that a snippet like "if($condition$) { $end$ }" will end up // as: // if ($condition$) // { // $end$ // } if (!TryGetSubjectBufferSpan(span, out var snippetSpan)) { return; } // Insert empty comment and track end position var snippetTrackingSpan = snippetSpan.CreateTrackingSpan(SpanTrackingMode.EdgeInclusive); var fullSnippetSpan = ExpansionSession.GetSnippetSpan(); var isFullSnippetFormat = fullSnippetSpan == span; var endPositionTrackingSpan = isFullSnippetFormat ? InsertEmptyCommentAndGetEndPositionTrackingSpan() : null; var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(SubjectBuffer.CurrentSnapshot, snippetTrackingSpan.GetSpan(SubjectBuffer.CurrentSnapshot)); SubjectBuffer.CurrentSnapshot.FormatAndApplyToBuffer(formattingSpan, CancellationToken.None); if (isFullSnippetFormat) { CleanUpEndLocation(endPositionTrackingSpan); // Unfortunately, this is the only place we can safely add references and imports // specified in the snippet xml. In OnBeforeInsertion we have no guarantee that the // snippet xml will be available, and changing the buffer during OnAfterInsertion can // cause the underlying tracking spans to get out of sync. var currentStartPosition = snippetTrackingSpan.GetStartPoint(SubjectBuffer.CurrentSnapshot).Position; AddReferencesAndImports( ExpansionSession, currentStartPosition, cancellationToken); SetNewEndPosition(endPositionTrackingSpan); } }