Skip to content

[LSP] Respect settings passed in by LSP in format handlers#52044

Merged
allisonchou merged 7 commits into
dotnet:mainfrom
allisonchou:UseLSPOptionsForFormatHandlers
Apr 12, 2021
Merged

[LSP] Respect settings passed in by LSP in format handlers#52044
allisonchou merged 7 commits into
dotnet:mainfrom
allisonchou:UseLSPOptionsForFormatHandlers

Conversation

@allisonchou
Copy link
Copy Markdown
Contributor

@allisonchou allisonchou commented Mar 22, 2021

Today, LSP passes in tabs/spaces settings as part of formatting requests. However, we do not respect these settings and instead use the document's options. We should respect the LSP settings.

Follow up from #51916.

Note, I think this will also require a change on the TypeScript side as they implement IEditorFormattingService. I opened a PR on their side here - https://devdiv.visualstudio.com/DevDiv/_git/TypeScript-VS/pullrequest/312160

@allisonchou allisonchou added Area-IDE LSP issues related to the roslyn language server protocol implementation labels Mar 22, 2021
@allisonchou allisonchou requested review from a team as code owners March 22, 2021 10:22
var span = textSpan ?? new TextSpan(0, root.FullSpan.Length);
var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(root, span);

var options = await document.GetDocumentOptionsWithInferredIndentationAsync(explicitFormat: true, indentationManagerService: _indentationManagerService, cancellationToken: cancellationToken).ConfigureAwait(false);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

A bunch of lines in this PR were really long on my laptop, so I broke them up for easier future readability.

/// Returns the text changes necessary to format the document with custom document options.
/// If "textSpan" is provided, only the text changes necessary to format that span are needed.
/// </summary>
Task<IList<TextChange>> GetFormattingChangesAsync(Document document, TextSpan? textSpan, DocumentOptionSet documentOptions, CancellationToken cancellationToken);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TS + F# both use this interface so I had to add new methods instead of modifying the existing ones.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do they not use the ExternalAccess patterns?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe they do for some interfaces, but for this one it looks like they just implement it directly?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand why this is better than adding a parameter? Isn't it a breaking change either way?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If we add the methods and merge the TypeScript change first, I don't think it would be a breaking change, since TypeScript will just have unused members that aren't associated with any interface method. When we then merge the interface changes in from the Roslyn side, those unassociated members will be tied to interface members.

However, if others feel strongly that editing the existing interface methods is the correct way to go here, then maybe a breaking change would be OK? It would just have to be a coordinated insertion with TypeScript (I think).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think it would be a breaking change, since TypeScript will just have unused members that aren't associated with any interface method. When we then merge the interface changes in from the Roslyn side, those unassociated members will be tied to interface members.

This is still a breaking change AFAIK, since it's at the moment TypeScript is compiled that the compiler is sorting out what is and isn't a interface implementation. TypeScript needs to be recompiled no matter what.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Gotcha, in that case I modified the code to just update the existing signature.


// The editor calls this handler for C# and VB comment characters, but we only need to process the one for the language that matches the document
if (autoInsertParams.Character == "\n" || autoInsertParams.Character == service.DocumentationCommentCharacter)
if (request.Character == "\n" || request.Character == service.DocumentationCommentCharacter)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For consistency, I changed this from autoInsertParams -> request since that's the variable name all other handlers use.

Copy link
Copy Markdown
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

See note about tab size vs. indentation size; I suspect this will do something pretty strange if we're not careful. In either case I didn't see any tests that set something other than 4. which we probably should add.

int position,
DocumentOptionSet documentOptions,
CancellationToken cancellationToken)
// We don't need to pass in options here since C#/VB always returns no text changes for this case.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Will we need to at some point though? Or does F#/TypeScript need to? This is more a "if we're breaking the interface, do we need to break this too" sort of question.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm, good question. I guess it makes sense to add it just in case we do add support for this method in the future.

var documentOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false);
var updatedOptions = documentOptions
.WithChangedOption(Formatting.FormattingOptions.UseTabs, !options.InsertSpaces)
.WithChangedOption(Formatting.FormattingOptions.TabSize, options.TabSize);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this also need to set Formatting.FormattingOptions.IndentationSize as well to match tab size? I wouldn't be surprised if VS Code doesn't support the distinction but usually you want these the same unless you have really good reasons not to do so.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah I see. LSP doesn't support indent size currently, only tab size, but I'll update the code so the two match.

Copy link
Copy Markdown
Member

@dibarbet dibarbet left a comment

Choose a reason for hiding this comment

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

the changes here look fine, as mentioned offline though I'm still not sure of the pros/cons of forking the document (for the request) with new options vs passing them in

/// If "textSpan" is provided, only the text changes necessary to format that span are needed.
/// </summary>
Task<IList<TextChange>> GetFormattingChangesAsync(Document document, TextSpan? textSpan, CancellationToken cancellationToken);
Task<IList<TextChange>> GetFormattingChangesAsync(Document document, TextSpan? textSpan, DocumentOptionSet? documentOptions, CancellationToken cancellationToken);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is fine, just wanted to bikeshed a little bit - should this be something like documentOptionsOverride? to clearly indicate that these are optional options to override whatever the default document options are

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would just do OptionsSet options to match how we do this elsewhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The implementations of these methods depend on specifically using DocumentOptionsSet as opposed to OptionsSet, so I don't know if we can switch the types here.

Copy link
Copy Markdown
Contributor

@uniqueiniquity uniqueiniquity left a comment

Choose a reason for hiding this comment

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

Validated the breaking change from the TS side 👍

@allisonchou allisonchou merged commit 00f046a into dotnet:main Apr 12, 2021
@ghost ghost added this to the Next milestone Apr 12, 2021
@allisonchou allisonchou deleted the UseLSPOptionsForFormatHandlers branch April 12, 2021 18:15
@dibarbet dibarbet modified the milestones: Next, 16.10.P3 Apr 26, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-IDE LSP issues related to the roslyn language server protocol implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants