Skip to content

Commit e66783b

Browse files
Changes for #162 correct how text document sync settings are configured
1 parent 7f2064c commit e66783b

File tree

5 files changed

+166
-57
lines changed

5 files changed

+166
-57
lines changed

src/Protocol/Client/Capabilities/SynchronizationCapability.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace OmniSharp.Extensions.LanguageServer.Protocol.Client.Capabilities
77
{
8-
public class SynchronizationCapability : DynamicCapability, ConnectedCapability<IDidChangeTextDocumentHandler>, ConnectedCapability<IDidCloseTextDocumentHandler>, ConnectedCapability<IDidOpenTextDocumentHandler>, ConnectedCapability<IDidSaveTextDocumentHandler>, ConnectedCapability<IWillSaveTextDocumentHandler>, ConnectedCapability<IWillSaveWaitUntilTextDocumentHandler>
8+
public class SynchronizationCapability : DynamicCapability, ConnectedCapability<IDidChangeTextDocumentHandler>
99
{
1010
/// <summary>
1111
/// The client supports sending will save notifications.

src/Protocol/Server/Capabilities/TextDocumentSyncOptions.cs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Linq;
34
using Newtonsoft.Json;
@@ -36,19 +37,29 @@ public class TextDocumentSyncOptions : ITextDocumentSyncOptions
3637
[Optional]
3738
public SaveOptions Save { get; set; }
3839

39-
public static TextDocumentSyncOptions Of(IEnumerable<ITextDocumentSyncOptions> options)
40+
public static Func<IEnumerable<ITextDocumentSyncOptions>, TextDocumentSyncOptions> Of(TextDocumentSyncOptions @default)
4041
{
41-
return new TextDocumentSyncOptions() {
42-
OpenClose = options.Any(z => z.OpenClose),
43-
Change = options
44-
.Where(x => x.Change != TextDocumentSyncKind.None)
45-
.Min(z => z.Change),
46-
WillSave = options.Any(z => z.WillSave),
47-
WillSaveWaitUntil = options.Any(z => z.WillSaveWaitUntil),
48-
Save = new SaveOptions() {
49-
IncludeText = options.Any(z => z.Save?.IncludeText == true)
42+
return options =>
43+
{
44+
var change = @default.Change;
45+
if (@default.Change == TextDocumentSyncKind.None && options.Any())
46+
{
47+
change = @default.Change > 0 ? @default.Change : options
48+
.Where(x => x.Change != TextDocumentSyncKind.None)
49+
.Min(z => z.Change);
5050
}
51-
};
51+
return new TextDocumentSyncOptions()
52+
{
53+
OpenClose = @default.OpenClose || options.Any(z => z.OpenClose),
54+
Change = change,
55+
WillSave = @default.WillSave || options.Any(z => z.WillSave),
56+
WillSaveWaitUntil = @default.WillSaveWaitUntil || options.Any(z => z.WillSaveWaitUntil),
57+
Save = new SaveOptions()
58+
{
59+
IncludeText = @default.Save?.IncludeText ?? options.Any(z => z.Save?.IncludeText == true)
60+
}
61+
};
62+
};
5263
}
5364
}
5465
}

src/Server/LanguageServer.cs

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,8 @@ private IDisposable RegisterHandlers(LspHandlerDescriptorDisposable handlerDispo
329329
{
330330
var registrations = handlerDisposable.Descriptors
331331
.Where(d => d.AllowsDynamicRegistration)
332-
.Select(d => new Registration() {
332+
.Select(d => new Registration()
333+
{
333334
Id = d.Id.ToString(),
334335
Method = d.Method,
335336
RegisterOptions = d.RegistrationOptions
@@ -409,6 +410,17 @@ async Task<InitializeResult> IRequestHandler<InitializeParams, InitializeResult>
409410

410411
var ccp = new ClientCapabilityProvider(_collection);
411412

413+
var defaultTextDocumentSyncOptions = new TextDocumentSyncOptions()
414+
{
415+
Change = TextDocumentSyncKind.None,
416+
OpenClose = _collection.ContainsHandler(typeof(IDidOpenTextDocumentHandler)) || _collection.ContainsHandler(typeof(IDidCloseTextDocumentHandler)),
417+
Save = _collection.ContainsHandler(typeof(IDidSaveTextDocumentHandler)) ?
418+
new SaveOptions() { IncludeText = true /* TODO: Make configurable */ } :
419+
null,
420+
WillSave = _collection.ContainsHandler(typeof(IWillSaveTextDocumentHandler)),
421+
WillSaveWaitUntil = _collection.ContainsHandler(typeof(IWillSaveWaitUntilTextDocumentHandler))
422+
};
423+
412424
var serverCapabilities = new ServerCapabilities()
413425
{
414426
CodeActionProvider = ccp.GetStaticOptions(textDocumentCapabilities.CodeAction).Get<ICodeActionOptions, CodeActionOptions>(CodeActionOptions.Of),
@@ -422,7 +434,7 @@ async Task<InitializeResult> IRequestHandler<InitializeParams, InitializeResult>
422434
DocumentRangeFormattingProvider = ccp.HasStaticHandler(textDocumentCapabilities.RangeFormatting),
423435
DocumentSymbolProvider = ccp.HasStaticHandler(textDocumentCapabilities.DocumentSymbol),
424436
ExecuteCommandProvider = ccp.GetStaticOptions(workspaceCapabilities.ExecuteCommand).Reduce<IExecuteCommandOptions, ExecuteCommandOptions>(ExecuteCommandOptions.Of),
425-
TextDocumentSync = ccp.GetStaticOptions(textDocumentCapabilities.Synchronization).Reduce<ITextDocumentSyncOptions, TextDocumentSyncOptions>(TextDocumentSyncOptions.Of),
437+
TextDocumentSync = ccp.GetStaticOptions(textDocumentCapabilities.Synchronization).Reduce<ITextDocumentSyncOptions, TextDocumentSyncOptions>(TextDocumentSyncOptions.Of(defaultTextDocumentSyncOptions)),
426438
HoverProvider = ccp.HasStaticHandler(textDocumentCapabilities.Hover),
427439
ReferencesProvider = ccp.HasStaticHandler(textDocumentCapabilities.References),
428440
RenameProvider = ccp.GetStaticOptions(textDocumentCapabilities.Rename).Get<IRenameOptions, RenameOptions>(RenameOptions.Of),
@@ -434,6 +446,10 @@ async Task<InitializeResult> IRequestHandler<InitializeParams, InitializeResult>
434446
FoldingRangeProvider = ccp.GetStaticOptions(textDocumentCapabilities.FoldingRange).Get<IFoldingRangeOptions, FoldingRangeOptions>(FoldingRangeOptions.Of),
435447
DeclarationProvider = ccp.GetStaticOptions(textDocumentCapabilities.Declaration).Get<IDeclarationOptions, DeclarationOptions>(DeclarationOptions.Of),
436448
};
449+
if (serverCapabilities.TextDocumentSync.HasOptions)
450+
{
451+
defaultTextDocumentSyncOptions = serverCapabilities.TextDocumentSync.Options;
452+
}
437453

438454
if (_collection.ContainsHandler(typeof(IDidChangeWorkspaceFoldersHandler)))
439455
{
@@ -449,32 +465,36 @@ async Task<InitializeResult> IRequestHandler<InitializeParams, InitializeResult>
449465

450466
if (ccp.HasStaticHandler(textDocumentCapabilities.Synchronization))
451467
{
452-
var textDocumentSyncKind = _collection.ContainsHandler(typeof(IDidChangeTextDocumentHandler))
453-
? _collection
468+
var textDocumentSyncKind = TextDocumentSyncKind.None;
469+
if (_collection.ContainsHandler(typeof(IDidChangeTextDocumentHandler)))
470+
{
471+
var kinds = _collection
454472
.Select(x => x.Handler)
455473
.OfType<IDidChangeTextDocumentHandler>()
456-
.Where(x => x.GetRegistrationOptions()?.SyncKind != TextDocumentSyncKind.None)
457-
.Min(z => z.GetRegistrationOptions()?.SyncKind)
458-
: TextDocumentSyncKind.None;
474+
.Select(x => x.GetRegistrationOptions()?.SyncKind ?? TextDocumentSyncKind.None)
475+
.Where(x => x != TextDocumentSyncKind.None)
476+
.ToArray();
477+
if (kinds.Any())
478+
{
479+
textDocumentSyncKind = kinds.Min(z => z);
480+
}
481+
}
459482

460483
if (_clientVersion == ClientVersion.Lsp2)
461484
{
462485
serverCapabilities.TextDocumentSync = textDocumentSyncKind;
463486
}
464487
else
465488
{
466-
serverCapabilities.TextDocumentSync = new TextDocumentSyncOptions()
467-
{
468-
Change = textDocumentSyncKind ?? TextDocumentSyncKind.None,
469-
OpenClose = _collection.ContainsHandler(typeof(IDidOpenTextDocumentHandler)) || _collection.ContainsHandler(typeof(IDidCloseTextDocumentHandler)),
470-
Save = _collection.ContainsHandler(typeof(IDidSaveTextDocumentHandler)) ?
471-
new SaveOptions() { IncludeText = true /* TODO: Make configurable */ } :
472-
null,
473-
WillSave = _collection.ContainsHandler(typeof(IWillSaveTextDocumentHandler)),
474-
WillSaveWaitUntil = _collection.ContainsHandler(typeof(IWillSaveWaitUntilTextDocumentHandler))
475-
};
489+
defaultTextDocumentSyncOptions.Change = textDocumentSyncKind;
490+
serverCapabilities.TextDocumentSync = defaultTextDocumentSyncOptions;
476491
}
477492
}
493+
else
494+
{
495+
defaultTextDocumentSyncOptions.Change = TextDocumentSyncKind.None;
496+
serverCapabilities.TextDocumentSync = defaultTextDocumentSyncOptions;
497+
}
478498

479499
// TODO: Need a call back here
480500
// serverCapabilities.Experimental;

test/Lsp.Tests/ClientCapabilityProviderTests.cs

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ public void Should_AllowSupportedCapabilities(IJsonRpcHandler handler, object in
3636

3737
public static IEnumerable<object[]> AllowSupportedCapabilities()
3838
{
39-
return GetItems(Capabilities, type => {
39+
return GetItems(Capabilities, type =>
40+
{
4041
var handlerTypes = GetHandlerTypes(type);
4142
var handler = Substitute.For(handlerTypes.ToArray(), new object[0]);
4243
return new[] { handler, Activator.CreateInstance(typeof(Supports<>).MakeGenericType(type), true, Activator.CreateInstance(type)) };
@@ -56,7 +57,8 @@ public void Should_AllowUnsupportedCapabilities(IJsonRpcHandler handler, object
5657

5758
public static IEnumerable<object[]> AllowUnsupportedCapabilities()
5859
{
59-
return GetItems(Capabilities, type => {
60+
return GetItems(Capabilities, type =>
61+
{
6062
var handlerTypes = GetHandlerTypes(type);
6163
var handler = Substitute.For(handlerTypes.ToArray(), new object[0]);
6264
return new[] { handler, Activator.CreateInstance(typeof(Supports<>).MakeGenericType(type), false) };
@@ -104,7 +106,8 @@ public void Should_AllowNullSupportedCapabilities(IJsonRpcHandler handler, objec
104106

105107
public static IEnumerable<object[]> AllowNullSupportsCapabilities()
106108
{
107-
return GetItems(Capabilities, type => {
109+
return GetItems(Capabilities, type =>
110+
{
108111
var handlerTypes = GetHandlerTypes(type);
109112
var handler = Substitute.For(handlerTypes.ToArray(), new object[0]);
110113
return new[] { handler, Activator.CreateInstance(typeof(Supports<>).MakeGenericType(type), true) };
@@ -125,7 +128,8 @@ public void Should_DisallowDynamicSupportedCapabilities(IJsonRpcHandler handler,
125128

126129
public static IEnumerable<object[]> DisallowDynamicSupportsCapabilities()
127130
{
128-
return GetItems(Capabilities, type => {
131+
return GetItems(Capabilities, type =>
132+
{
129133
var handlerTypes = GetHandlerTypes(type);
130134
var handler = Substitute.For(handlerTypes.ToArray(), new object[0]);
131135
var capability = Activator.CreateInstance(type);
@@ -145,12 +149,16 @@ public void Should_Handle_Mixed_Capabilities()
145149

146150
var collection = new HandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, new TextDocumentIdentifiers()) { textDocumentSyncHandler, codeActionHandler, definitionHandler, typeDefinitionHandler };
147151
var provider = new ClientCapabilityProvider(collection);
148-
var capabilities = new ClientCapabilities() {
149-
TextDocument = new TextDocumentClientCapabilities() {
150-
CodeAction = new Supports<CodeActionCapability>(true, new CodeActionCapability() {
152+
var capabilities = new ClientCapabilities()
153+
{
154+
TextDocument = new TextDocumentClientCapabilities()
155+
{
156+
CodeAction = new Supports<CodeActionCapability>(true, new CodeActionCapability()
157+
{
151158
DynamicRegistration = false,
152159
}),
153-
TypeDefinition = new Supports<TypeDefinitionCapability>(true, new TypeDefinitionCapability() {
160+
TypeDefinition = new Supports<TypeDefinitionCapability>(true, new TypeDefinitionCapability()
161+
{
154162
DynamicRegistration = true,
155163
})
156164
}
@@ -161,6 +169,57 @@ public void Should_Handle_Mixed_Capabilities()
161169
provider.HasStaticHandler(capabilities.TextDocument.TypeDefinition).Should().BeFalse();
162170
}
163171

172+
[Fact]
173+
public void GH162_TextDocumentSync_Should_Work_Without_WillSave_Or_WillSaveWaitUntil()
174+
{
175+
var textDocumentSyncHandler = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cs"), "csharp");
176+
177+
var collection = new HandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, new TextDocumentIdentifiers()) { textDocumentSyncHandler };
178+
var provider = new ClientCapabilityProvider(collection);
179+
var capabilities = new ClientCapabilities()
180+
{
181+
TextDocument = new TextDocumentClientCapabilities()
182+
{
183+
Synchronization = new SynchronizationCapability()
184+
{
185+
DidSave = true,
186+
DynamicRegistration = false,
187+
WillSave = true,
188+
WillSaveWaitUntil = true
189+
},
190+
}
191+
};
192+
193+
provider.HasStaticHandler(capabilities.TextDocument.Synchronization).Should().BeTrue();
194+
}
195+
196+
[Fact]
197+
public void GH162_TextDocumentSync_Should_Work_With_WillSave_Or_WillSaveWaitUntil()
198+
{
199+
var textDocumentSyncHandler = TextDocumentSyncHandlerExtensions.With(DocumentSelector.ForPattern("**/*.cs"), "csharp");
200+
var willSaveTextDocumentHandler = Substitute.For<IWillSaveTextDocumentHandler>();
201+
var willSaveWaitUntilTextDocumentHandler = Substitute.For<IWillSaveWaitUntilTextDocumentHandler>();
202+
var didSaveTextDocumentHandler = Substitute.For<IDidSaveTextDocumentHandler>();
203+
204+
var collection = new HandlerCollection(SupportedCapabilitiesFixture.AlwaysTrue, new TextDocumentIdentifiers()) { textDocumentSyncHandler, willSaveTextDocumentHandler, willSaveWaitUntilTextDocumentHandler, didSaveTextDocumentHandler };
205+
var provider = new ClientCapabilityProvider(collection);
206+
var capabilities = new ClientCapabilities()
207+
{
208+
TextDocument = new TextDocumentClientCapabilities()
209+
{
210+
Synchronization = new SynchronizationCapability()
211+
{
212+
DidSave = true,
213+
DynamicRegistration = false,
214+
WillSave = true,
215+
WillSaveWaitUntil = true
216+
},
217+
}
218+
};
219+
220+
provider.HasStaticHandler(capabilities.TextDocument.Synchronization).Should().BeTrue();
221+
}
222+
164223
private static bool HasHandler(ClientCapabilityProvider provider, object instance)
165224
{
166225
return (bool)typeof(ClientCapabilityProviderTests).GetTypeInfo()

0 commit comments

Comments
 (0)