Skip to content

Commit

Permalink
#1799 implement DidChangeWatchedFiles Notification (#6007)
Browse files Browse the repository at this point in the history
Signed-off-by: Yevhen Vydolob <evidolob@codenvy.com>
  • Loading branch information
evidolob committed Aug 16, 2017
1 parent 770701c commit d7d734d
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher;
import org.eclipse.che.api.languageserver.messager.PublishDiagnosticsParamsJsonRpcTransmitter;
import org.eclipse.che.api.languageserver.messager.ShowMessageJsonRpcTransmitter;
import org.eclipse.che.api.languageserver.registry.LanguageServerFileWatcher;
import org.eclipse.che.api.languageserver.registry.LanguageServerRegistry;
import org.eclipse.che.api.languageserver.registry.LanguageServerRegistryImpl;
import org.eclipse.che.api.languageserver.registry.ServerInitializer;
Expand All @@ -41,5 +42,6 @@ protected void configure() {
Multibinder.newSetBinder(binder(), LanguageDescription.class);

bind(LanguageServerInitializationHandler.class).asEagerSingleton();
bind(LanguageServerFileWatcher.class).asEagerSingleton();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,31 @@
*******************************************************************************/
package org.eclipse.che.api.languageserver.registry;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static java.util.Collections.emptyList;

public class LanguageServerDescription {
private final String id;
private final List<String> languageIds;
private final List<DocumentFilter> documentFilters;

/**
* The file name patters, format described there {@link java.nio.file.FileSystem#getPathMatcher(String)}
*/
private List<String> fileWatchPatterns = emptyList();

public LanguageServerDescription(String id, List<String> languageIds, List<DocumentFilter> documentFilters) {
this(id, languageIds, documentFilters, Collections.emptyList());
}

public LanguageServerDescription(String id, List<String> languageIds, List<DocumentFilter> documentFilters, List<String> fileWatchPatterns) {
this.id = id;
this.languageIds = languageIds;
this.documentFilters = documentFilters;
this.fileWatchPatterns = fileWatchPatterns;
}

public String getId() {
Expand All @@ -35,4 +49,18 @@ public List<DocumentFilter> getDocumentFilters() {
return documentFilters;
}


public List<String> getFileWatchPatterns() {
return fileWatchPatterns;
}

/**
* @param fileWatchPatterns
* must not be null
*/
public void setFileWatchPatterns(List<String> fileWatchPatterns) {
this.fileWatchPatterns = new ArrayList<>(fileWatchPatterns);
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*******************************************************************************/

package org.eclipse.che.api.languageserver.registry;

import com.google.common.annotations.VisibleForTesting;

import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher;
import org.eclipse.che.api.vfs.watcher.FileWatcherManager;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.FileChangeType;
import org.eclipse.lsp4j.FileEvent;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.services.LanguageServer;

import javax.annotation.PreDestroy;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.util.Collections;
import java.util.concurrent.CopyOnWriteArrayList;

import static org.eclipse.che.api.languageserver.service.LanguageServiceUtils.prefixURI;

/**
* Implement <a href="https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#workspace_didChangeWatchedFiles">DidChangeWatchedFiles
* Notification</a>
*/
@Singleton
public class LanguageServerFileWatcher {


private final FileWatcherManager watcherManager;

private CopyOnWriteArrayList<Integer> watcherIds = new CopyOnWriteArrayList<>();

@Inject
public LanguageServerFileWatcher(FileWatcherManager watcherManager, ServerInitializer serverInitializer) {
this.watcherManager = watcherManager;
serverInitializer.addObserver(this::onServerInitialized);
}

private void send(LanguageServer server, String filePath, FileChangeType changeType) {
DidChangeWatchedFilesParams params =
new DidChangeWatchedFilesParams(Collections.singletonList(new FileEvent(prefixURI(filePath), changeType)));
server.getWorkspaceService().didChangeWatchedFiles(params);
}

@PreDestroy
@VisibleForTesting
public void removeAllWatchers() {
for (Integer watcherId : watcherIds) {
watcherManager.unRegisterByMatcher(watcherId);
}
}

private void onServerInitialized(LanguageServerLauncher launcher,
LanguageServer server,
ServerCapabilities capabilities,
String projectPath) {
LanguageServerDescription description = launcher.getDescription();
FileSystem fileSystem = FileSystems.getDefault();
for (String pattern : description.getFileWatchPatterns()) {
PathMatcher matcher = fileSystem.getPathMatcher(pattern);
int watcherId = watcherManager.registerByMatcher(matcher,
s -> send(server, s, FileChangeType.Created),
s -> send(server, s, FileChangeType.Changed),
s -> send(server, s, FileChangeType.Deleted));

watcherIds.add(watcherId);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,15 @@ public ServerCapabilities initialize(String fileUri) throws LanguageServerExcept

for (LanguageServerLauncher launcher : new ArrayList<>(launchers)) {
synchronized (initializedServers) {
List<LanguageServerLauncher> servers = launchedServers.get(projectPath);
List<LanguageServerLauncher> servers = launchedServers.computeIfAbsent(projectPath, k -> new ArrayList<>());

if (servers == null) {
servers = new ArrayList<>();
launchedServers.put(projectPath, servers);
}
List<LanguageServerLauncher> servers2 = servers;
if (!servers2.contains(launcher)) {
servers2.add(launcher);
if (!servers.contains(launcher)) {
servers.add(launcher);
String id = String.valueOf(serverId.incrementAndGet());
initializer.initialize(launcher, new CheLanguageClient(eventService, id), projectPath).thenAccept(pair -> {
synchronized (initializedServers) {
List<InitializedLanguageServer> initialized = initializedServers.get(projectPath);
if (initialized == null) {
initialized = new ArrayList<>();
initializedServers.put(projectPath, initialized);
}
List<InitializedLanguageServer> initialized =
initializedServers.computeIfAbsent(projectPath, k -> new ArrayList<>());
initialized.add(new InitializedLanguageServer(id, pair.first, pair.second, launcher));
launchers.remove(launcher);
initializedServers.notifyAll();
Expand All @@ -132,7 +124,7 @@ public ServerCapabilities initialize(String fileUri) throws LanguageServerExcept
LOG.error("Error launching language server " + launcher, t);
synchronized (initializedServers) {
launchers.remove(launcher);
servers2.remove(launcher);
servers.remove(launcher);
initializedServers.notifyAll();
}
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*******************************************************************************
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*******************************************************************************/

package org.eclipse.che.api.languageserver;

import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher;
import org.eclipse.che.api.languageserver.registry.LanguageServerDescription;
import org.eclipse.che.api.languageserver.registry.LanguageServerFileWatcher;
import org.eclipse.che.api.languageserver.registry.ServerInitializer;
import org.eclipse.che.api.languageserver.registry.ServerInitializerObserver;
import org.eclipse.che.api.vfs.watcher.FileWatcherManager;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.lsp4j.services.WorkspaceService;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import java.io.File;
import java.nio.file.PathMatcher;
import java.util.Collections;
import java.util.function.Consumer;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertTrue;

/**
*
*/
@Listeners(MockitoTestNGListener.class)
public class LanguageServerFileWatcherTest {

@Mock
private LanguageServerLauncher launcher;
@Mock
private LanguageServer server;
@Mock
private FileWatcherManager watcherManager;
@Mock
private ServerInitializer initializer;
@Captor
private ArgumentCaptor<Consumer<String>> changedCaptor;

private LanguageServerFileWatcher watcher;

@AfterMethod
public void tearDown() throws Exception {
if (watcher != null) {
watcher.removeAllWatchers();
}
}

@Test
public void testShouldAddObserver() throws Exception {
watcher = new LanguageServerFileWatcher(watcherManager, initializer);
verify(initializer).addObserver(any());
}

@Test
public void testRegisterFileWatcher() throws Exception {
ArgumentCaptor<ServerInitializerObserver> argumentCaptor = ArgumentCaptor.forClass(ServerInitializerObserver.class);
watcher = new LanguageServerFileWatcher(watcherManager, initializer);
verify(initializer).addObserver(argumentCaptor.capture());
ServerInitializerObserver value = argumentCaptor.getValue();

LanguageServerDescription description =
new LanguageServerDescription("foo", Collections.singletonList("bar"), Collections.emptyList(),
Collections.singletonList("glob:*.foo"));
when(launcher.getDescription()).thenReturn(description);
value.onServerInitialized(launcher, server, null, null);

ArgumentCaptor<PathMatcher> pathMatcherCaptor = ArgumentCaptor.forClass(PathMatcher.class);
verify(watcherManager).registerByMatcher(pathMatcherCaptor.capture(), any(), any(), any());
assertTrue(pathMatcherCaptor.getValue().matches(new File("bar.foo").toPath()));
}

@Test
public void testSendNotification() throws Exception {
ArgumentCaptor<ServerInitializerObserver> argumentCaptor = ArgumentCaptor.forClass(ServerInitializerObserver.class);
watcher = new LanguageServerFileWatcher(watcherManager, initializer);
verify(initializer).addObserver(argumentCaptor.capture());
ServerInitializerObserver value = argumentCaptor.getValue();

LanguageServerDescription description =
new LanguageServerDescription("foo", Collections.singletonList("bar"), Collections.emptyList(),
Collections.singletonList("glob:*.foo"));
when(launcher.getDescription()).thenReturn(description);

WorkspaceService workspaceService = mock(WorkspaceService.class);
when(server.getWorkspaceService()).thenReturn(workspaceService);

value.onServerInitialized(launcher, server, null, null);

verify(watcherManager).registerByMatcher(any(), any(), changedCaptor.capture(), any());

changedCaptor.getValue().accept("/p/bar.foo");

verify(workspaceService).didChangeWatchedFiles(any());

}


}

0 comments on commit d7d734d

Please sign in to comment.