Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to register language servers for file names instead of extensions #5107 #5156

Merged
merged 12 commits into from
Jul 3, 2017

Conversation

tsmaeder
Copy link
Contributor

@tsmaeder tsmaeder commented May 22, 2017

What does this PR do?

This PR allows language server plugins to register language servers for specific file names (for example pom.xml). Until now, it is only possible to register for filename extension (like *.xml).
The PR introduces two major changes:

  1. Multiple extensions/or and file names can be mapped to a single mime type and language id. This solves the problem that the mime type was only registered for the first extension when multiples were given.
  2. Language registration and language server launcher registration are separated. This way, we can still mimic the old behavior and register multiple mime types for the same language id. As an additional advantage, this change will be useful when allowing to register multiple language servers for the same language in the future.

What issues does this PR fix or reference?

fixes #5107

Changelog

Ability to register language servers for file names instead of extensions

Release Notes

Language Server Support: Add ability to register language servers for file names instead of extensions

Docs PR

eclipse-che/che-docs#230

@codenvy-ci
Copy link

Can one of the admins verify this patch?

@@ -149,7 +149,7 @@
<version>${project.version}</version>
<executions>
<execution>
<phase>process-sources</phase>
<phase>generate-sources</phase>
Copy link
Contributor

Choose a reason for hiding this comment

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

last time we are not able to release with <phase>generate-sources</phase> I think there is a compilation issue in this phase. Why do you want to change it? Can you compile this artifact in offline mode if you local is empty or doesn't contains che-plugin-languageserver-ide artifact

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The maven generator plugin is using compiled dependencies to generate the dto classes. Since no dto's are generated from classes in the project itself, this should be fine. In the core.api.languageserver project, there used to be a precompile step in order to be able to generate from classes in the project itself.

Copy link
Contributor

Choose a reason for hiding this comment

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

Have you checked that I've asked? Because we had troubles with this last time during release.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's impossible to build in offline mode when the local maven cache is empty. I can remove che stuff from the cache, though.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's impossible to build in offline mode when the local maven cache is empty

Ok. I wasn't really clear. I mean remove only org.eclipse.che artifacts or to be more precise che-plugin-languageserver-ide and when rebuilt che-plugin-languageserver-ide in offline mode. It's should be possible. Same situation we have when maven release plugin is releasing.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok. I've checked looks like it's working

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thx. I was having trouble building from scratch all last week (some tests failing in unrelated projects)

/**
* @author Evgen Vidolob
*/
@Singleton
public class LanguageServerFileTypeRegister implements WsAgentComponent {

private static Logger logger = Logger.getLogger(LanguageServerFileTypeRegister.class.getName());
Copy link
Contributor

Choose a reason for hiding this comment

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

LOGGER?

private static final String[] EXTENSIONS = new String[]{ProjectAttributes.PYTHON_EXT};
private static final String MIME_TYPE = "text/x-python";
static {
}
Copy link
Contributor

Choose a reason for hiding this comment

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

redundant code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True.

@@ -58,8 +49,7 @@ public boolean isAbleToLaunch() {
}

protected LanguageServer connectToLanguageServer(final Process languageServerProcess, LanguageClient client) {
Launcher<LanguageServer> launcher = Launcher.createLauncher(client, LanguageServer.class,
Copy link
Contributor

Choose a reason for hiding this comment

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

if this is only formatting, I would include in a seperated PR or commit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would mean that I have to create an issue and open a PR just for a formatting change? I politely disagree here. My favorite solution would be to automate formatting completely, then formatting changes would never occur.

Copy link
Contributor

Choose a reason for hiding this comment

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

at least seperate in 2 commits in the same PR

this.resources = resources;
this.editorRegistry = editorRegistry;
this.contentTypeRegistrant = contentTypeRegistrant;
this.orionHoverRegistrant = orionHoverRegistrant;
this.orionHoverRegistrant = orionHoverRegistrant;
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess this is not wanted

@Inject
public LanguageServerFileTypeRegister(LanguageServerRegistryServiceClient serverLanguageRegistry,
FileTypeRegistry fileTypeRegistry,
Copy link
Contributor

Choose a reason for hiding this comment

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

mixing format and changes makes review and merge harder

Copy link
Contributor Author

@tsmaeder tsmaeder Jun 2, 2017

Choose a reason for hiding this comment

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

I see what you mean here. And Githubs line based diff algorithm does not help at all.

final NotificationManager notificationManager,
final EventBus eventBus) {
protected void subscribeToInitializeEvent(final DtoUnmarshallerFactory unmarshallerFactory, final MessageBusProvider messageBusProvider,
final NotificationManager notificationManager, final EventBus eventBus) {
Copy link
Contributor

Choose a reason for hiding this comment

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

just format ?

Unmarshallable<LanguageServerInitializeEvent> unmarshaller =
unmarshallerFactory.newWSUnmarshaller(LanguageServerInitializeEvent.class);
Unmarshallable<LanguageServerInitializeEvent> unmarshaller = unmarshallerFactory
.newWSUnmarshaller(LanguageServerInitializeEvent.class);
Copy link
Contributor

Choose a reason for hiding this comment

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

just format ?

initializeEvent.getServerCapabilities());
}

@Override
protected void onErrorReceived(Throwable exception) {
notificationManager.notify(exception.getMessage(),
StatusNotification.Status.FAIL,
notificationManager.notify(exception.getMessage(), StatusNotification.Status.FAIL,
Copy link
Contributor

Choose a reason for hiding this comment

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

just format ?

}

Copy link
Contributor

Choose a reason for hiding this comment

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

trailing space/tab not to be included

}

Copy link
Contributor

Choose a reason for hiding this comment

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

trailing space/tab not to be included

InitializeParams initializeParams = prepareInitializeParams(projectPath);

LanguageServer server;
try {
server = launcher.launch(projectPath, languageClient);
} catch (LanguageServerException e) {
throw new LanguageServerException(
"Can't initialize Language Server " + languageId + " on " + projectPath + ". " + e.getMessage(), e);
"Can't initialize Language Server " + languageId + " on " + projectPath + ". " + e.getMessage(), e);
Copy link
Contributor

Choose a reason for hiding this comment

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

just format ?

@@ -172,7 +162,7 @@ protected LanguageServer doInitialize(LanguageServerLauncher launcher, String pr
protected void registerCallbacks(LanguageServer server) {

if (server instanceof ServerInitializerObserver) {
addObserver((ServerInitializerObserver)server);
addObserver((ServerInitializerObserver) server);
Copy link
Contributor

Choose a reason for hiding this comment

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

just format ?

@slemeur slemeur self-requested a review May 29, 2017 08:08
@slemeur slemeur added the kind/enhancement A feature request - must adhere to the feature request template. label May 29, 2017
@tsmaeder
Copy link
Contributor Author

I'll get back to this second half of this week.



static {
}
Copy link
Contributor

Choose a reason for hiding this comment

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

unused block

"babelrc"};
private static final String MIME_TYPE = "application/json";


Copy link
Contributor

Choose a reason for hiding this comment

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

extra spaces

description.setMimeTypes(Arrays.asList(MIME_TYPES));
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

extra spaces


GinMapBinder<String, WsAgentComponent> wsAgentComponentsBinder =
GinMapBinder.newMapBinder(binder(), String.class, WsAgentComponent.class);
wsAgentComponentsBinder.addBinding("Load Language Server file types.").to(LanguageServerFileTypeRegister.class);

bind(PublishDiagnosticsReceiver.class).asEagerSingleton();
bind(ShowMessageJsonRpcReceiver.class).asEagerSingleton();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why you delete this bindings?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is a mistake. It also explains why notifications don't work in my current working branch. Good catch!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@@ -15,15 +15,29 @@

import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.plugin.json.languageserver.JsonLanguageServerLauncher;

import static java.util.Arrays.asList;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the static import is not correctly sorted

HoverProvider hoverProvider,
OccurrencesProvider occurrencesProvider) {
OccurrencesProvider occurrencesProvider) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we have trailing spaces ?

Copy link
Contributor

Choose a reason for hiding this comment

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

@tsmaeder you have to be consistent, sometimes you add trailing spaces, sometimes not. Can you remove them ?


/**
* Register file type for a language description
* @param type
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe add description around these type and description or remove the javadoc ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fair enough.

/**
* Get the language that is registered for this file. May return null if none is found.
* @param file
* @return
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe add description around file and what is returned or remove the javadoc ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yup

Provider<ProjectManager> projectManagerProvider, ServerInitializer initializer) {
this.languages.addAll(languages);
this.launchers.putAll(languageServerLaunchers.stream()
.collect(Collectors.toMap(LanguageServerLauncher::getLanguageId, launcher -> launcher)));
Copy link
Contributor

Choose a reason for hiding this comment

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

if languages and launchers are initialized there, do we need to initialize the fields and use addAll/putAll after instead of just using the new directly there

this.languages = new ArrayList<>(languages);

vs

List list = new ArrayList<>()   (when declaring field)
list.addAll(myElements)  (in constructor)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, generally, I like to initialized fields that are not passed as init parameters in the field declaration; it makes sure the field has a usefule value.

Copy link
Contributor

Choose a reason for hiding this comment

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

as it's final fields, we're sure that they're forced to be initialized

}
return false;
}
Copy link
Contributor

@benoitf benoitf Jun 27, 2017

Choose a reason for hiding this comment

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

return Optional.ofNullable(language.getFileExtensions()).map(Collection::stream).orElse(Stream.empty()).anyMatch(extension -> path.endsWith(extension)); ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, but I strongly disagree here: while your version has fewer characters, I find it enormously more difficult to read. When did we decide that if and for are "harmful" (with my apologies to the late Professor Goto)?

Copy link
Contributor

Choose a reason for hiding this comment

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

It may be subjective but as JDK8 is out since a long time, I assume that ppl are fluent on using streams.
BTW if getFileExtensions would always return something (like empty) it could have been updated to only

return language.getFileExtensions().stream().anyMatch(extension -> path.endsWith(extension));

which is definitely better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apart from the difficulty enforcing the non-nullness, I disagree with the statement that the stream version is "better". It's shorter, I agree, but try putting a breakpoint into your version, for example.

}
}
}
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

could java streams make it more readable/smaller ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same comment as above: smaller, maybe, but not more readable.

Signed-off-by: Thomas Mäder <tmader@redhat.com>
Signed-off-by: Thomas Mäder <tmader@redhat.com>
Signed-off-by: Thomas Mäder <tmader@redhat.com>
Signed-off-by: Thomas Mäder <tmader@redhat.com>
Signed-off-by: Thomas Mäder <tmader@redhat.com>
Signed-off-by: Thomas Mäder <tmader@redhat.com>
Signed-off-by: Thomas Mäder <tmader@redhat.com>
Signed-off-by: Thomas Mäder <tmader@redhat.com>
Copy link
Contributor Author

@tsmaeder tsmaeder left a comment

Choose a reason for hiding this comment

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

???

}
return false;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, but I strongly disagree here: while your version has fewer characters, I find it enormously more difficult to read. When did we decide that if and for are "harmful" (with my apologies to the late Professor Goto)?

}
}
}
return false;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same comment as above: smaller, maybe, but not more readable.

fileTypeRegistry.registerFileType(fileType);
editorRegistry.registerDefaultEditor(fileType, editorProvider);
ext2langId.put(ext, lang.getLanguageId());
if (lang.getFileExtensions() != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

could we make getFileExtensions() always returning non-null list

We could avoid almost useless if (lang.getFileExtensions() != null)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That sounds good, but it's not easily possible since LanguageDescription has a setter for the file extensions. So I can't really guarantee that it's not null.

Copy link
Contributor

Choose a reason for hiding this comment

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

@tsmaeder yes LanguageDescription has a setter.
But the setter could modify the internal list only if the given element is not null instead of setting the given value directly without any check.

for example in LanguageDescription we could have
constructor : initialize fileExtensions to an empty list

in the setter: update this internal list only if the provided list is not null. (we could also make it immutable list by copying the elements of the provided list to the internal list and not reusing the provided list but it's another topic. Getter returning a copy of the list as well)

we could also in getter, return Collections.emptyList if the internal list is null. (either change setter logic or getter logic)

so, even with a setter, getFileExtensions is always returning a not null list

List<String> mimeTypes = lang.getMimeTypes();
if (mimeTypes.isEmpty()) {
mimeTypes = newArrayList("text/x-" + lang.getLanguageId());
if (lang.getFileNames() != null) {
Copy link
Contributor

Choose a reason for hiding this comment

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

same remark here for getFileNames()

Copy link
Contributor Author

Choose a reason for hiding this comment

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

again, I can't enforce non-null here.

@gazarenkov
Copy link
Contributor

gazarenkov commented Jun 29, 2017

@sunix @tsmaeder as I can see there are requested changes. What is the status of this issue? Are you guys able to come into agreement?

@benoitf benoitf changed the title 5107 register by filename Ability to register language servers for file names instead of extensions #5107 Jun 29, 2017
Copy link
Contributor

@sunix sunix left a comment

Choose a reason for hiding this comment

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

LGTM

Signed-off-by: Thomas Mäder <tmader@redhat.com>
Signed-off-by: Thomas Mäder <tmader@redhat.com>
*/
private List<String> fileExtensions;
private List<String> fileExtensions= Collections.emptyList();
Copy link
Contributor

@benoitf benoitf Jul 3, 2017

Choose a reason for hiding this comment

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

fileExtensions<space>=

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok

ServerInitializer initializer) {
public LanguageServerRegistryImpl(Set<LanguageServerLauncher> languageServerLaunchers, Set<LanguageDescription> languages,
Provider<ProjectManager> projectManagerProvider, ServerInitializer initializer) {
this.languages= new ArrayList<>(languages);
Copy link
Contributor

Choose a reason for hiding this comment

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

there is a missing space before the =

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for making my point about auto-formatting ;-)

@benoitf
Copy link
Contributor

benoitf commented Jul 3, 2017

ci-build

@codenvy-ci
Copy link

Build # 2976 - FAILED

Please check console output at https://ci.codenvycorp.com/job/che-pullrequests-build/2976/ to view the results.

public void setFileExtensions(final List<String> fileExtensions) {
this.fileExtensions = fileExtensions;
this.fileExtensions = new ArrayList<>(fileExtensions);
Copy link
Contributor

@benoitf benoitf Jul 3, 2017

Choose a reason for hiding this comment

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

it seems per the test result that we should do the assignment only if fileExtensions is not null (or update the test to comply with javadoc)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Test is bogus.

Signed-off-by: Thomas Mäder <tmader@redhat.com>
@benoitf
Copy link
Contributor

benoitf commented Jul 3, 2017

ci-build

@codenvy-ci
Copy link

@benoitf benoitf merged commit 9027aa4 into eclipse-che:master Jul 3, 2017
@benoitf benoitf added this to the 5.15.0 milestone Jul 3, 2017
JPinkney pushed a commit to JPinkney/che that referenced this pull request Aug 17, 2017
…ions eclipse-che#5107 (eclipse-che#5156)

* Register languages by file name

Signed-off-by: Thomas Mäder <tmader@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/enhancement A feature request - must adhere to the feature request template.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Register Language Server for File Name
9 participants