2828
2929import java .net .URI ;
3030import java .util .List ;
31+ import java .util .Map ;
32+ import java .util .Objects ;
33+ import java .util .Set ;
3134import java .util .concurrent .CompletableFuture ;
35+ import java .util .concurrent .ConcurrentHashMap ;
36+ import java .util .concurrent .CopyOnWriteArraySet ;
37+ import java .util .concurrent .ExecutorService ;
38+ import java .util .function .Predicate ;
39+ import java .util .stream .Collectors ;
3240import org .apache .logging .log4j .LogManager ;
3341import org .apache .logging .log4j .Logger ;
3442import org .checkerframework .checker .nullness .qual .Nullable ;
4149import org .eclipse .lsp4j .ProgressParams ;
4250import org .eclipse .lsp4j .PublishDiagnosticsParams ;
4351import org .eclipse .lsp4j .Range ;
52+ import org .eclipse .lsp4j .Registration ;
4453import org .eclipse .lsp4j .RegistrationParams ;
4554import org .eclipse .lsp4j .ShowDocumentParams ;
4655import org .eclipse .lsp4j .ShowDocumentResult ;
4756import org .eclipse .lsp4j .ShowMessageRequestParams ;
4857import org .eclipse .lsp4j .TextDocumentContentRefreshParams ;
58+ import org .eclipse .lsp4j .Unregistration ;
4959import org .eclipse .lsp4j .UnregistrationParams ;
5060import org .eclipse .lsp4j .WorkDoneProgressCreateParams ;
5161import org .eclipse .lsp4j .WorkspaceFolder ;
5262import org .eclipse .lsp4j .services .LanguageClient ;
5363import org .rascalmpl .uri .remote .jsonrpc .ISourceLocationChanged ;
5464import org .rascalmpl .vscode .lsp .IBaseLanguageClient ;
5565import org .rascalmpl .vscode .lsp .parametric .LanguageRegistry .LanguageParameter ;
66+ import org .rascalmpl .vscode .lsp .util .concurrent .CompletableFutureUtils ;
5667
5768import io .usethesource .vallang .IInteger ;
5869import io .usethesource .vallang .IString ;
@@ -65,9 +76,16 @@ public class MultipleClientProxy implements IBaseLanguageClient {
6576 private static final Logger logger = LogManager .getLogger (MultipleClientProxy .class );
6677
6778 private final IBaseLanguageClient client ;
79+ private final ExecutorService exec ;
80+ private final CompletableFuture <Void > noop ;
6881
69- protected MultipleClientProxy (LanguageClient client ) {
82+ private final Map <String , Set <Registration >> currentRegistrations = new ConcurrentHashMap <>();
83+
84+
85+ protected MultipleClientProxy (LanguageClient client , ExecutorService exec ) {
7086 this .client = (IBaseLanguageClient ) client ;
87+ this .exec = exec ;
88+ this .noop = CompletableFutureUtils .completedFuture (null , exec );
7189 }
7290
7391 @ Override
@@ -194,14 +212,74 @@ public CompletableFuture<ShowDocumentResult> showDocument(ShowDocumentParams par
194212
195213 @ Override
196214 public CompletableFuture <Void > registerCapability (RegistrationParams params ) {
197- // TODO Collect/maintain capabilities of all delegate servers, combine, and unregister capabilities if necessary based on that.
198- return client .registerCapability (params );
215+ return CompletableFutureUtils
216+ .reduce (params
217+ .getRegistrations ()
218+ .parallelStream ()
219+ .map (this ::registerCapability ), exec )
220+ .thenAccept (v -> {}); // convert to Void
221+ }
222+
223+ private CompletableFuture <Void > registerCapability (Registration r ) {
224+ var c = currentRegistrations .computeIfAbsent (r .getMethod (), k -> new CopyOnWriteArraySet <>());
225+ // Lock on the registrations for this method for a moment
226+ synchronized (c ) {
227+ var similarRegOpt = c .stream ().filter (rr -> Objects .equals (rr .getRegisterOptions (), r .getRegisterOptions ())).findAny ();
228+ if (similarRegOpt .isPresent ()) {
229+ logger .trace ("We already have a registration for {} with the same options; ignoring this one" , r .getMethod ());
230+ return noop ;
231+ }
232+
233+ c .add (r );
234+ return client .registerCapability (new RegistrationParams (List .of (r )))
235+ .exceptionally (t -> {
236+ logger .error ("Exception while registering {}" , r , t );
237+ c .remove (r );
238+ return null ;
239+ });
240+ }
199241 }
200242
201243 @ Override
202244 public CompletableFuture <Void > unregisterCapability (UnregistrationParams params ) {
203- // TODO Collect/maintain capabilities of all delegate servers, combine, and unregister capabilities if necessary based on that.
204- return client .unregisterCapability (params );
245+ return CompletableFutureUtils
246+ .reduce (params
247+ .getUnregisterations ()
248+ .parallelStream ()
249+ .map (this ::unregisterCapability ), exec )
250+ .thenAccept (v -> {}); // convert to Void
251+ }
252+
253+ private boolean matches (Registration r , Unregistration u ) {
254+ return r .getId ().equals (u .getId ())
255+ && r .getMethod ().equals (u .getMethod ());
256+ }
257+
258+ private Predicate <Registration > matches (Unregistration u ) {
259+ return r -> matches (r , u );
260+ }
261+
262+ private CompletableFuture <Void > unregisterCapability (Unregistration u ) {
263+ var c = currentRegistrations .get (u .getMethod ());
264+ synchronized (c ) {
265+ if (c == null ) {
266+ return noop ;
267+ }
268+
269+ var cs = c .stream ().filter (matches (u )).collect (Collectors .toSet ());
270+ if (cs .isEmpty ()) {
271+ // No registrations => nothing to unregister
272+ return noop ;
273+ }
274+
275+ c .removeAll (cs );
276+ return client .unregisterCapability (new UnregistrationParams (List .of (u )))
277+ .exceptionally (t -> {
278+ logger .error ("Exception while unregistering {} ({})" , u .getMethod (), u , t );
279+ c .addAll (cs );
280+ return null ;
281+ });
282+ }
205283 }
206284
207285 @ Override
0 commit comments