| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.java.lsp.server; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.PrintWriter; |
| import java.net.MalformedURLException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.ExecutionException; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.eclipse.lsp4j.CompletionOptions; |
| import org.eclipse.lsp4j.InitializeParams; |
| import org.eclipse.lsp4j.InitializeResult; |
| import org.eclipse.lsp4j.MessageParams; |
| import org.eclipse.lsp4j.MessageType; |
| import org.eclipse.lsp4j.ServerCapabilities; |
| import org.eclipse.lsp4j.TextDocumentSyncKind; |
| import org.eclipse.lsp4j.WorkspaceFolder; |
| import org.eclipse.lsp4j.jsonrpc.Launcher; |
| import org.eclipse.lsp4j.launch.LSPLauncher; |
| import org.eclipse.lsp4j.services.LanguageClient; |
| import org.eclipse.lsp4j.services.LanguageClientAware; |
| import org.eclipse.lsp4j.services.LanguageServer; |
| import org.eclipse.lsp4j.services.TextDocumentService; |
| import org.eclipse.lsp4j.services.WorkspaceService; |
| import org.netbeans.api.java.classpath.ClassPath; |
| import org.netbeans.api.java.source.ClasspathInfo; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.api.project.ProjectUtils; |
| import org.netbeans.api.project.Sources; |
| import org.netbeans.api.project.ui.OpenProjects; |
| import org.netbeans.api.sendopts.CommandException; |
| import org.netbeans.modules.java.lsp.server.text.TextDocumentServiceImpl; |
| import org.netbeans.modules.java.lsp.server.workspace.WorkspaceServiceImpl; |
| import org.netbeans.spi.sendopts.Arg; |
| import org.netbeans.spi.sendopts.ArgsProcessor; |
| import org.netbeans.spi.sendopts.Description; |
| import org.netbeans.spi.sendopts.Env; |
| import org.netbeans.spi.sendopts.Option; |
| import org.netbeans.spi.sendopts.OptionProcessor; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle.Messages; |
| import org.openide.util.lookup.ServiceProvider; |
| |
| /** |
| * |
| * @author lahvac |
| */ |
| public class Server implements ArgsProcessor { |
| |
| @Arg(longName="start-java-language-server") |
| @Description(shortDescription="whatever") |
| @Messages("DESC_StartJavaLanguageServer=Starts the Java Language Server") |
| public boolean enable; |
| |
| @Override |
| public void process(Env env) throws CommandException { |
| try { |
| run(env.getInputStream(), env.getOutputStream()); |
| } catch (Exception ex) { |
| throw (CommandException) new CommandException(1).initCause(ex); |
| } |
| } |
| |
| @ServiceProvider(service=OptionProcessor.class) |
| public static class OptionProcessorImpl extends OptionProcessor { |
| |
| @Override |
| protected Set<Option> getOptions() { |
| return new HashSet<>(Arrays.asList(Option.withoutArgument('\0', "--start-java-language-server"))); |
| } |
| |
| @Override |
| protected void process(Env env, Map<Option, String[]> optionValues) throws CommandException { |
| try { |
| run(env.getInputStream(), env.getOutputStream()); |
| } catch (Exception ex) { |
| throw (CommandException) new CommandException(1).initCause(ex); |
| } |
| } |
| |
| } |
| |
| private static void run(InputStream in, OutputStream out) throws Exception { |
| LanguageServerImpl server = new LanguageServerImpl(); |
| Launcher<LanguageClient> serverLauncher = LSPLauncher.createServerLauncher(server, in, out); |
| ((LanguageClientAware) server).connect(serverLauncher.getRemoteProxy()); |
| serverLauncher.startListening(); |
| |
| while (true) { |
| try { |
| Thread.sleep(100000); |
| } catch (InterruptedException ex) { |
| //ignore |
| } |
| } |
| } |
| |
| private static class LanguageServerImpl implements LanguageServer, LanguageClientAware { |
| |
| private static final Logger LOG = Logger.getLogger(LanguageServerImpl.class.getName()); |
| private LanguageClient client; |
| private final TextDocumentService textDocumentService = new TextDocumentServiceImpl(); |
| |
| @Override |
| public CompletableFuture<InitializeResult> initialize(InitializeParams init) { |
| List<FileObject> projectCandidates = new ArrayList<>(); |
| List<WorkspaceFolder> folders = init.getWorkspaceFolders(); |
| if (folders != null) { |
| for (WorkspaceFolder w : folders) { |
| try { |
| projectCandidates.add(TextDocumentServiceImpl.fromUri(w.getUri())); |
| } catch (MalformedURLException ex) { |
| LOG.log(Level.FINE, null, ex); |
| } |
| } |
| } else { |
| String root = init.getRootUri(); |
| |
| if (root != null) { |
| try { |
| projectCandidates.add(TextDocumentServiceImpl.fromUri(root)); |
| } catch (MalformedURLException ex) { |
| LOG.log(Level.FINE, null, ex); |
| } |
| } else { |
| //TODO: use getRootPath()? |
| } |
| } |
| List<Project> projects = new ArrayList<>(); |
| for (FileObject candidate : projectCandidates) { |
| Project prj = FileOwnerQuery.getOwner(candidate); |
| if (prj != null) { |
| projects.add(prj); |
| } |
| } |
| //XXX: ensure project opened: |
| try { |
| Class.forName("org.netbeans.modules.project.ui.OpenProjectList", false, Lookup.getDefault().lookup(ClassLoader.class)).getDeclaredMethod("waitProjectsFullyOpen").invoke(null); |
| } catch (Exception ex) { |
| throw new IllegalStateException(ex); |
| } |
| OpenProjects.getDefault().open(projects.toArray(new Project[0]), false); |
| try { |
| OpenProjects.getDefault().openProjects().get(); |
| } catch (InterruptedException | ExecutionException ex) { |
| throw new IllegalStateException(ex); |
| } |
| for (Project prj : projects) { |
| //init source groups/FileOwnerQuery: |
| ProjectUtils.getSources(prj).getSourceGroups(Sources.TYPE_GENERIC); |
| } |
| try { |
| JavaSource.create(ClasspathInfo.create(ClassPath.EMPTY, ClassPath.EMPTY, ClassPath.EMPTY)) |
| .runWhenScanFinished(cc -> { |
| client.showMessage(new MessageParams(MessageType.Info, INDEXING_COMPLETED)); |
| //todo: refresh diagnostics all open editor? |
| }, true); |
| } catch (IOException ex) { |
| throw new IllegalStateException(ex); |
| } |
| ServerCapabilities capabilities = new ServerCapabilities(); |
| capabilities.setTextDocumentSync(TextDocumentSyncKind.Incremental); |
| CompletionOptions completionOptions = new CompletionOptions(); |
| completionOptions.setResolveProvider(true); |
| capabilities.setCompletionProvider(completionOptions); |
| capabilities.setCodeActionProvider(true); |
| capabilities.setDocumentSymbolProvider(true); |
| capabilities.setDefinitionProvider(true); |
| capabilities.setDocumentHighlightProvider(true); |
| return CompletableFuture.completedFuture(new InitializeResult(capabilities)); |
| } |
| |
| @Override |
| public CompletableFuture<Object> shutdown() { |
| return CompletableFuture.completedFuture(null); |
| } |
| |
| @Override |
| public void exit() { |
| System.exit(1); |
| } |
| |
| @Override |
| public TextDocumentService getTextDocumentService() { |
| return textDocumentService; |
| } |
| |
| @Override |
| public WorkspaceService getWorkspaceService() { |
| return new WorkspaceServiceImpl(); |
| } |
| |
| @Override |
| public void connect(LanguageClient client) { |
| this.client = client; |
| ((LanguageClientAware) getTextDocumentService()).connect(client); |
| } |
| } |
| |
| static final String INDEXING_COMPLETED = "Indexing completed."; |
| } |