diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs index d5f02c4108ddd..7d132721ef047 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/InProcLanguageServer.cs @@ -171,6 +171,11 @@ public Task ResolveCompletionItemAsync(CompletionItem completion => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentCompletionResolveName, completionItem, _clientCapabilities, _clientName, cancellationToken); + [JsonRpcMethod(Methods.TextDocumentFoldingRangeName, UseSingleObjectParameterDeserialization = true)] + public Task GetTextDocumentFoldingRangeAsync(FoldingRangeParams textDocumentFoldingRangeParams, CancellationToken cancellationToken) + => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentFoldingRangeName, + textDocumentFoldingRangeParams, _clientCapabilities, _clientName, cancellationToken); + [JsonRpcMethod(Methods.TextDocumentDocumentHighlightName, UseSingleObjectParameterDeserialization = true)] public Task GetTextDocumentDocumentHighlightsAsync(TextDocumentPositionParams textDocumentPositionParams, CancellationToken cancellationToken) => _requestHandlerProvider.ExecuteRequestAsync(Methods.TextDocumentDocumentHighlightName, diff --git a/src/VisualStudio/Xaml/Impl/Features/AutoInsert/IXamlAutoInsertService.cs b/src/VisualStudio/Xaml/Impl/Features/AutoInsert/IXamlAutoInsertService.cs new file mode 100644 index 0000000000000..63fd1f932ee0f --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Features/AutoInsert/IXamlAutoInsertService.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.AutoInsert +{ + internal interface IXamlAutoInsertService : ILanguageService + { + public Task GetAutoInsertAsync(TextDocument document, char typedChar, int position, CancellationToken cancellationToken); + } +} diff --git a/src/VisualStudio/Xaml/Impl/Features/AutoInsert/XamlAutoInsertResult.cs b/src/VisualStudio/Xaml/Impl/Features/AutoInsert/XamlAutoInsertResult.cs new file mode 100644 index 0000000000000..5877aeff02d8f --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Features/AutoInsert/XamlAutoInsertResult.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.AutoInsert +{ + internal class XamlAutoInsertResult + { + public TextChange TextChange { get; set; } + public int? CaretOffset { get; set; } + } +} diff --git a/src/VisualStudio/Xaml/Impl/Features/Completion/IXamlCompletionItem.cs b/src/VisualStudio/Xaml/Impl/Features/Completion/IXamlCompletionItem.cs deleted file mode 100644 index e90fac2cd5f9b..0000000000000 --- a/src/VisualStudio/Xaml/Impl/Features/Completion/IXamlCompletionItem.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Immutable; -using Microsoft.VisualStudio.Text.Adornments; - -namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Completion -{ - internal interface IXamlCompletionItem - { - string[] CommitCharacters { get; } - string DisplayText { get; } - string InsertText { get; } - string Detail { get; } - string FilterText { get; } - string SortText { get; } - bool? Preselect { get; } - XamlCompletionKind Kind { get; } - ClassifiedTextElement Description { get; } - ImageElement Icon { get; } - } -} diff --git a/src/VisualStudio/Xaml/Impl/Features/Completion/IXamlCompletionService.cs b/src/VisualStudio/Xaml/Impl/Features/Completion/IXamlCompletionService.cs index 58f08fc1b8b41..08d1bad1087c9 100644 --- a/src/VisualStudio/Xaml/Impl/Features/Completion/IXamlCompletionService.cs +++ b/src/VisualStudio/Xaml/Impl/Features/Completion/IXamlCompletionService.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -12,7 +11,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Completion { internal interface IXamlCompletionService : ILanguageService { - Task> GetCompletionsAsync(TextDocument document, int offset, CancellationToken cancellationToken); - Task GetSymbolAsync(TextDocument document, int offset, string label, CancellationToken cancellationToken); + Task GetCompletionsAsync(XamlCompletionContext completionContext, CancellationToken cancellationToken); + Task GetSymbolAsync(XamlCompletionContext completionContext, string label, CancellationToken cancellationToken); } } diff --git a/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionContext.cs b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionContext.cs new file mode 100644 index 0000000000000..a3aa7f3c31074 --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionContext.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Completion +{ + /// + /// Contextual information needed for processing completion requests. + /// + internal struct XamlCompletionContext + { + public XamlCompletionContext(TextDocument document, int offset, char triggerChar = '\0') + { + Document = document; + Offset = offset; + TriggerChar = triggerChar; + } + + public TextDocument Document { get; } + public int Offset { get; } + public char TriggerChar { get; } + } +} diff --git a/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs new file mode 100644 index 0000000000000..7a2e2c2f51dfb --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionItem.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Text.Adornments; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Completion +{ + internal class XamlCompletionItem + { + public string[] CommitCharacters { get; set; } + public string DisplayText { get; set; } + public string InsertText { get; set; } + public string Detail { get; set; } + public string FilterText { get; set; } + public string SortText { get; set; } + public bool? Preselect { get; set; } + public TextSpan? Span { get; set; } + public XamlCompletionKind Kind { get; set; } + public ClassifiedTextElement Description { get; set; } + public ImageElement Icon { get; set; } + public ISymbol Symbol { get; set; } + } +} diff --git a/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionResult.cs b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionResult.cs new file mode 100644 index 0000000000000..91ae961b038b0 --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Features/Completion/XamlCompletionResult.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Completion +{ + internal class XamlCompletionResult + { + public ImmutableArray Completions { get; set; } + public TextSpan? ApplicableToSpan { get; set; } + } +} diff --git a/src/VisualStudio/Xaml/Impl/Features/Formatting/IXamlFormattingService.cs b/src/VisualStudio/Xaml/Impl/Features/Formatting/IXamlFormattingService.cs new file mode 100644 index 0000000000000..b1a327ae38527 --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Features/Formatting/IXamlFormattingService.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Formatting +{ + internal interface IXamlFormattingService : ILanguageService + { + Task> GetFormattingChangesAsync(TextDocument document, XamlFormattingOptions options, TextSpan? textSpan, CancellationToken cancellationToken); + Task> GetFormattingChangesAsync(TextDocument document, XamlFormattingOptions options, char typedChar, int position, CancellationToken cancellationToken); + } +} diff --git a/src/VisualStudio/Xaml/Impl/Features/Formatting/XamlFormattingOptions.cs b/src/VisualStudio/Xaml/Impl/Features/Formatting/XamlFormattingOptions.cs new file mode 100644 index 0000000000000..ebd1237167a25 --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Features/Formatting/XamlFormattingOptions.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Formatting +{ + internal class XamlFormattingOptions + { + public bool InsertSpaces { get; set; } + public int TabSize { get; set; } + public IDictionary OtherOptions { get; set; } + } +} diff --git a/src/VisualStudio/Xaml/Impl/Features/Structure/IXamlStructureService.cs b/src/VisualStudio/Xaml/Impl/Features/Structure/IXamlStructureService.cs index 62b8354341b62..298b617f6aeb7 100644 --- a/src/VisualStudio/Xaml/Impl/Features/Structure/IXamlStructureService.cs +++ b/src/VisualStudio/Xaml/Impl/Features/Structure/IXamlStructureService.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; @@ -13,6 +12,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Structure { internal interface IXamlStructureService : ILanguageService { - Task> GetStructureTagsAsync(TextDocument document, CancellationToken cancellationToken); + Task> GetStructureTagsAsync(TextDocument document, CancellationToken cancellationToken); } } diff --git a/src/VisualStudio/Xaml/Impl/Features/Structure/IXamlStructureTag.cs b/src/VisualStudio/Xaml/Impl/Features/Structure/XamlStructureTag.cs similarity index 72% rename from src/VisualStudio/Xaml/Impl/Features/Structure/IXamlStructureTag.cs rename to src/VisualStudio/Xaml/Impl/Features/Structure/XamlStructureTag.cs index 1d7b8133dbab3..e7fdfa69eb038 100644 --- a/src/VisualStudio/Xaml/Impl/Features/Structure/IXamlStructureTag.cs +++ b/src/VisualStudio/Xaml/Impl/Features/Structure/XamlStructureTag.cs @@ -6,9 +6,9 @@ namespace Microsoft.VisualStudio.LanguageServices.Xaml.Features.Structure { - internal interface IXamlStructureTag + internal class XamlStructureTag { - string Type { get; } - TextSpan TextSpan { get; } + public string Type { get; set; } + public TextSpan TextSpan { get; set; } } } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs index 0797a33767427..bb3f4e5bd648e 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionHandler.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable + using System; using System.Composition; using System.Diagnostics; @@ -13,6 +15,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.LanguageServices.Xaml.Features.Completion; @@ -40,23 +43,37 @@ public override async Task HandleRequestAsync(CompletionParams } var completionService = document.Project.LanguageServices.GetRequiredService(); - var offset = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); - var completions = await completionService.GetCompletionsAsync(document, offset, cancellationToken: cancellationToken).ConfigureAwait(false); - if (completions == null) + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var offset = text.Lines.GetPosition(ProtocolConversions.PositionToLinePosition(request.Position)); + var completionResult = await completionService.GetCompletionsAsync(new XamlCompletionContext(document, offset, request.Context.TriggerCharacter?.FirstOrDefault() ?? '\0'), cancellationToken: cancellationToken).ConfigureAwait(false); + if (completionResult?.Completions == null) { return Array.Empty(); } - return completions.Select(c => CreateCompletionItem(c, document.Id, request.Position)).ToArray(); + return completionResult.Completions.Select(c => CreateCompletionItem(c, document.Id, text, request.Position)).ToArray(); } - private static CompletionItem CreateCompletionItem(IXamlCompletionItem xamlCompletion, DocumentId documentId, Position position) - => new VSCompletionItem + private static CompletionItem CreateCompletionItem(XamlCompletionItem xamlCompletion, DocumentId documentId, SourceText text, Position position) + { + TextEdit? textEdit = null; + + if (xamlCompletion.Span.HasValue) + { + textEdit = new TextEdit + { + NewText = xamlCompletion.InsertText, + Range = ProtocolConversions.LinePositionToRange(text.Lines.GetLinePositionSpan(xamlCompletion.Span.Value)) + }; + } + + return new VSCompletionItem { Label = xamlCompletion.DisplayText, CommitCharacters = xamlCompletion.CommitCharacters, Detail = xamlCompletion.Detail, InsertText = xamlCompletion.InsertText, + TextEdit = textEdit, Preselect = xamlCompletion.Preselect, SortText = xamlCompletion.SortText, FilterText = xamlCompletion.FilterText, @@ -65,8 +82,9 @@ private static CompletionItem CreateCompletionItem(IXamlCompletionItem xamlCompl Icon = xamlCompletion.Icon, Data = new CompletionResolveData { ProjectGuid = documentId.ProjectId.Id, DocumentGuid = documentId.Id, Position = position, DisplayText = xamlCompletion.DisplayText } }; + } - private static CompletionItem[] CreateErrorItem(string message, string details = null) + private static CompletionItem[] CreateErrorItem(string message, string? details = null) { var item = new CompletionItem { diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs index 34a62935a3b85..6dec9c68ba9e1 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs @@ -51,7 +51,7 @@ public CompletionResolveHandler(ILspSolutionProvider solutionProvider) : base(so int offset = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(data.Position), cancellationToken).ConfigureAwait(false); var completionService = document.Project.LanguageServices.GetRequiredService(); - var symbol = await completionService.GetSymbolAsync(document, offset, completionItem.Label, cancellationToken: cancellationToken).ConfigureAwait(false); + var symbol = await completionService.GetSymbolAsync(new XamlCompletionContext(document, offset), completionItem.Label, cancellationToken: cancellationToken).ConfigureAwait(false); if (symbol == null) { return completionItem; diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs new file mode 100644 index 0000000000000..83bd270d7ee75 --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.LanguageServices.Xaml.Features.Formatting; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler +{ + internal abstract class AbstractFormatDocumentHandlerBase : AbstractRequestHandler + { + protected AbstractFormatDocumentHandlerBase(ILspSolutionProvider solutionProvider) : base(solutionProvider) + { + } + + protected async Task GetTextEditsAsync(LSP.TextDocumentIdentifier documentIdentifier, LSP.FormattingOptions formattingOptions, RequestContext context, CancellationToken cancellationToken, LSP.Range? range = null) + { + using var _ = ArrayBuilder.GetInstance(out var edits); + + var document = SolutionProvider.GetTextDocument(documentIdentifier, context.ClientName); + var formattingService = document?.Project.LanguageServices.GetService(); + + if (document != null && formattingService != null) + { + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + TextSpan? textSpan = null; + if (range != null) + { + textSpan = ProtocolConversions.RangeToTextSpan(range, text); + } + + var options = new XamlFormattingOptions { InsertSpaces = formattingOptions.InsertSpaces, TabSize = formattingOptions.TabSize, OtherOptions = formattingOptions.OtherOptions }; + var textChanges = await formattingService.GetFormattingChangesAsync(document, options, textSpan, cancellationToken).ConfigureAwait(false); + edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text))); + } + + return edits.ToArray(); + } + } +} diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs new file mode 100644 index 0000000000000..f4c7e42c66f64 --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Xaml; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler +{ + [Shared] + [ExportLspMethod(LSP.Methods.TextDocumentFormattingName, StringConstants.XamlLanguageName)] + internal class FormatDocumentHandler : AbstractFormatDocumentHandlerBase + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public FormatDocumentHandler(ILspSolutionProvider solutionProvider) : base(solutionProvider) + { + } + + public override Task HandleRequestAsync(LSP.DocumentFormattingParams request, RequestContext context, CancellationToken cancellationToken) + => GetTextEditsAsync(request.TextDocument, request.Options, context, cancellationToken); + } +} diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs new file mode 100644 index 0000000000000..c3ebec227ae29 --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Xaml; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.VisualStudio.LanguageServices.Xaml.Features.Formatting; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler +{ + [Shared] + [ExportLspMethod(Methods.TextDocumentOnTypeFormattingName, StringConstants.XamlLanguageName)] + internal class FormatDocumentOnTypeHandler : AbstractRequestHandler + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public FormatDocumentOnTypeHandler(ILspSolutionProvider solutionProvider) : base(solutionProvider) + { + } + + public override async Task HandleRequestAsync(DocumentOnTypeFormattingParams request, RequestContext context, CancellationToken cancellationToken) + { + var edits = new ArrayBuilder(); + if (string.IsNullOrEmpty(request.Character)) + { + return edits.ToArrayAndFree(); + } + + var document = SolutionProvider.GetTextDocument(request.TextDocument, context.ClientName); + var formattingService = document?.Project.LanguageServices.GetService(); + if (document != null && formattingService != null) + { + var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); + var options = new XamlFormattingOptions { InsertSpaces = request.Options.InsertSpaces, TabSize = request.Options.TabSize, OtherOptions = request.Options.OtherOptions }; + IList? textChanges = await formattingService.GetFormattingChangesAsync(document, options, request.Character[0], position, cancellationToken).ConfigureAwait(false); + if (textChanges != null) + { + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text))); + } + } + + return edits.ToArrayAndFree(); + } + } +} diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs new file mode 100644 index 0000000000000..65639c286b5b8 --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Xaml; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler +{ + [Shared] + [ExportLspMethod(Methods.TextDocumentRangeFormattingName, StringConstants.XamlLanguageName)] + internal class FormatDocumentRangeHandler : AbstractFormatDocumentHandlerBase + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public FormatDocumentRangeHandler(ILspSolutionProvider solutionProvider) : base(solutionProvider) + { + } + + public override Task HandleRequestAsync(DocumentRangeFormattingParams request, RequestContext context, CancellationToken cancellationToken) + => GetTextEditsAsync(request.TextDocument, request.Options, context, cancellationToken, range: request.Range); + } +} diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Initialize/InitializeHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Initialize/InitializeHandler.cs index f0d55221a73a1..d99cb6783efde 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Initialize/InitializeHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Initialize/InitializeHandler.cs @@ -30,11 +30,15 @@ public Task HandleRequestAsync(InitializeParams request, Reque return Task.FromResult(new InitializeResult { - Capabilities = new ServerCapabilities + Capabilities = new VSServerCapabilities { CompletionProvider = new CompletionOptions { ResolveProvider = true, TriggerCharacters = new string[] { "<", " ", ":", ".", "=", "\"", "'", "{", ",", "(" } }, HoverProvider = true, FoldingRangeProvider = new FoldingRangeProviderOptions { }, + DocumentFormattingProvider = true, + DocumentRangeFormattingProvider = true, + DocumentOnTypeFormattingProvider = new DocumentOnTypeFormattingOptions { FirstTriggerCharacter = ">", MoreTriggerCharacter = new string[] { "\n" } }, + OnAutoInsertProvider = new DocumentOnAutoInsertOptions { TriggerCharacters = new[] { "=", "/", ">" } }, TextDocumentSync = new TextDocumentSyncOptions { Change = TextDocumentSyncKind.None diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs new file mode 100644 index 0000000000000..3ee04de534a50 --- /dev/null +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Xaml; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.VisualStudio.LanguageServices.Xaml.Features.AutoInsert; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.VisualStudio.LanguageServices.Xaml.LanguageServer.Handler +{ + [Shared] + [ExportLspMethod(LSP.MSLSPMethods.OnAutoInsertName, StringConstants.XamlLanguageName)] + internal class OnAutoInsertHandler : AbstractRequestHandler + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public OnAutoInsertHandler(ILspSolutionProvider solutionProvider) : base(solutionProvider) + { + } + + public override async Task HandleRequestAsync(LSP.DocumentOnAutoInsertParams autoInsertParams, RequestContext context, CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var response); + + var document = SolutionProvider.GetTextDocument(autoInsertParams.TextDocument, context.ClientName); + if (document == null) + { + return response.ToArray(); + } + + var insertService = document.Project.LanguageServices.GetService(); + if (insertService == null) + { + return response.ToArray(); + } + + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var offset = text.Lines.GetPosition(ProtocolConversions.PositionToLinePosition(autoInsertParams.Position)); + var result = await insertService.GetAutoInsertAsync(document, autoInsertParams.Character[0], offset, cancellationToken).ConfigureAwait(false); + if (result == null) + { + return response.ToArray(); + } + + var insertText = result.TextChange.NewText; + var insertFormat = LSP.InsertTextFormat.Plaintext; + if (result.CaretOffset.HasValue) + { + insertFormat = LSP.InsertTextFormat.Snippet; + insertText = insertText?.Insert(result.CaretOffset.Value, "$0"); + } + + response.Add(new LSP.DocumentOnAutoInsertResponseItem + { + TextEditFormat = insertFormat, + TextEdit = new LSP.TextEdit + { + NewText = insertText, + Range = ProtocolConversions.TextSpanToRange(result.TextChange.Span, text) + } + }); + + return response.ToArray(); + } + } +}