Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Snippets: If snippet and use of LSP's SnippetExpander #60860

Merged

Conversation

akhera99
Copy link
Member

In this PR:

  • Uses reflection to call upon the LanguageServerSnippetExpander
  • Adds a Placeholders piece to the SnippetChange
  • Adds a service to convert the data held by the SnippetChange into an LSP formatted snippet
  • Adds testing to determine if the LSP formatted snippet is something that we expect the LanguageServerSnippetExpander to handle
  • Adds an if snippet
  • Adds functionality to put trivia on all of the tokens of the generated snippet syntax so that it may be formatted accordingly
  • Adds an overload to Create in the CompletionChange that holds onto the LSP formatted snippet (internal)

@akhera99 akhera99 changed the title Snippets: If snippet and use of LanguageServerSnippetExpander Snippets: If snippet and use of LSP's SnippetExpander Apr 20, 2022
lspSnippetString.Append(str);

// Skip past the entire identifier in the TextChange text
i += placeholderInfo.identifier.Length;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

something feels very funky here. in the above if check, you end with a ++... but then you lookup in this dictionary using that position that you mutated. so if you have a caretposition and placeholder at the same position, it seems like this will fail.

DO you have testws for that?

/// Preprocesses the list of placeholders into a dictionary that maps the insertion position
/// in the string to the placeholder's identifier and the priority associated with it.
/// </summary>
private static void GetMapOfSpanStartsToLSPStringItem(ref PooledDictionary<int, (string identifier, int priority)> dictionary, ImmutableArray<SnippetPlaceholder> placeholders, int textChangeStart)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private static void GetMapOfSpanStartsToLSPStringItem(ref PooledDictionary<int, (string identifier, int priority)> dictionary, ImmutableArray<SnippetPlaceholder> placeholders, int textChangeStart)
private static void GetMapOfSpanStartsToLSPStringItem(Dictionary<int, (string identifier, int priority)> dictionary, ImmutableArray<SnippetPlaceholder> placeholders, int textChangeStart)

{
if (textChanges.IsEmpty)
{
throw new ArgumentException($"{ textChanges.Length } must not be empty");
throw new ArgumentException($"textChanges must not be empty");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new ArgumentException($"textChanges must not be empty");
throw new ArgumentException($"{nameof(textChanges)} must not be empty");

/// Example:
/// For loop would have two placeholders:
/// for (var {1:i} = 0; {1:i} &lt; {2:length}; {i}++)
/// Identifier: i, 3 associated positions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Identifier: i, 3 associated positions
/// Identifier: i, 3 associated positions

/// For loop would have two placeholders:
/// for (var {1:i} = 0; {1:i} &lt; {2:length}; {i}++)
/// Identifier: i, 3 associated positions
/// IdentifierL length, 1 associated position
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something is weird here.

syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _);
return openParenToken.Span.End;
var openParenToken = GetOpenParenToken(caretTarget, syntaxFacts);
Contract.ThrowIfNull(openParenToken);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we contract-throwing here? how do you know this must insert an open paren? (consdier VB, which would not have an open paren here).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Actually, the VB comment applies to if-statements, not console.writeline) (though in VB the parens are optinoal there too :))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm creating the syntax, so I can ensure there are parentheses created. Not sure what you mean by it applying to if-statements, I don't get any open parentheses tokens in the if statement provider.


syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _);

return openParenToken;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't get why this method is nullable just to have the caller throw if it gets null.

@CyrusNajmabadi
Copy link
Member

Done with pass.

{
var lspSnippetText = change.Properties[SnippetCompletionItem.LSPSnippetKey];

_roslynLSPSnippetExpander.TryExpand(change.TextChange.Span, lspSnippetText, _textView, triggerSnapshot);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: if return value is not checked. consider just making "Expand" and not "TryExpand"

}
}

public void TryExpand(TextSpan textSpan, string lspSnippetText, ITextView textView, ITextSnapshot textSnapshot)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: as a void method, consider just namign 'Expand'.

Comment on lines 57 to 60
if (expandMethodResult is null)
{
throw new Exception("The result of the invoked LSP snippet expander is null.");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (expandMethodResult is null)
{
throw new Exception("The result of the invoked LSP snippet expander is null.");
}
if (expandMethodResult is not boolean resultValue)
{
throw new Exception("The result of the invoked LSP snippet expander was not a boolean.");
}

Comment on lines 62 to 65
if (!(bool)expandMethodResult)
{
throw new Exception("The invoked LSP snippet expander came back as false.");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!(bool)expandMethodResult)
{
throw new Exception("The invoked LSP snippet expander came back as false.");
}
if (!resultValue)
{
throw new Exception("The invoked LSP snippet expander came back as false.");
}

}

return lspSnippetString.ToString();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

if (identifier.Length == 0)
{
throw new ArgumentException($"{nameof(identifier)} must not be an empty string.");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice.

@akhera99 akhera99 enabled auto-merge May 10, 2022 01:06
@akhera99 akhera99 disabled auto-merge May 10, 2022 01:06
@@ -119,7 +119,7 @@ private static async Task<TextChange> ExtendSnippetTextChangeAsync(Document docu

var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
var newString = documentText.ToString(extendedSpan);
var newTextChange = new TextChange(new TextSpan(extendedSpan.Start, 0), newString);
var newTextChange = new TextChange(TextSpan.FromBounds(extendedSpan.Start, extendedSpan.End), newString);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't TextSpan.FromBounds(extendedSpan.Start, extendedSpan.End) exactly the same as extendedSpan?

@akhera99 akhera99 enabled auto-merge May 10, 2022 15:43
@akhera99 akhera99 merged commit f6212d7 into dotnet:features/semantic-snippets May 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants