Skip to content

Commit

Permalink
feat: Investigate using OSProcessHandler
Browse files Browse the repository at this point in the history
Fixes #153

Signed-off-by: azerr <azerr@redhat.com>
  • Loading branch information
angelozerr committed Sep 18, 2024
1 parent 9d1e0fe commit 5142610
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,14 @@ public synchronized void start() throws LanguageServerException {
this.launcherFuture = new CompletableFuture<>();
this.initializeFuture = CompletableFuture.supplyAsync(() -> {
this.lspStreamProvider = serverDefinition.createConnectionProvider(initialProject);

initParams.setInitializationOptions(this.lspStreamProvider.getInitializationOptions(rootURI));

// Add error log
lspStreamProvider.addLogErrorHandler(error -> {
ServerMessageHandler.logMessage(this, new MessageParams(MessageType.Error, error));
});

// Starting process...
updateStatus(ServerStatus.starting);
getLanguageServerLifecycleManager().onStatusChanged(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.redhat.devtools.lsp4ij.console.LSPConsoleToolWindowPanel;
import com.redhat.devtools.lsp4ij.features.documentation.MarkdownConverter;
import com.redhat.devtools.lsp4ij.internal.StringUtils;
import com.redhat.devtools.lsp4ij.server.definition.LanguageServerDefinition;
import org.eclipse.lsp4j.*;
import org.jetbrains.annotations.NotNull;

Expand All @@ -54,7 +55,17 @@ private ServerMessageHandler() {
* @param params the message request parameters
*/
public static void logMessage(LanguageServerWrapper serverWrapper, MessageParams params) {
LSPConsoleToolWindowPanel.showLog(serverWrapper.getServerDefinition(), params, serverWrapper.getProject() );
logMessage(serverWrapper.getServerDefinition(), params, serverWrapper.getProject() );
}

/**
* Implements the LSP <a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#window_logMessage">window/logMessage</a> specification.
*
* @param serverWrapper the language server wrapper
* @param params the message request parameters
*/
public static void logMessage(LanguageServerDefinition serverDefinition, MessageParams params, Project project) {
LSPConsoleToolWindowPanel.showLog(serverDefinition, params, project );
}

private static Icon messageTypeToIcon(MessageType type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,9 @@
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.ServerCapabilities;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import static com.redhat.devtools.lsp4ij.internal.CompletableFutures.isDoneNormally;

Expand All @@ -39,8 +36,6 @@
*/
public abstract class AbstractLSPGoToAction extends AnAction {

private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLSPGoToAction.class);

@NotNull
private final LSPUsageType usageType;

Expand Down Expand Up @@ -112,14 +107,8 @@ private boolean canSupportAction(@NotNull AnActionEvent e) {
return false;
}
// Check if the file can support the feature
try {
return !LanguageServiceAccessor.getInstance(project)
.getLanguageServers(file, this::canSupportFeature)
.get(200, TimeUnit.MILLISECONDS)
.isEmpty();
} catch (Exception ex) {
return false;
}
return LanguageServiceAccessor.getInstance(project)
.hasAny(file, ls -> canSupportFeature(ls.getServerCapabilitiesSync()));
}

protected abstract boolean canSupportFeature(ServerCapabilities serverCapabilities);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@
*******************************************************************************/
package com.redhat.devtools.lsp4ij.server;

import java.io.IOException;

/**
* Language server exception when start process cannot be done.
*/
public class CannotStartProcessException extends LanguageServerException {

public CannotStartProcessException(IOException e) {
public CannotStartProcessException(Exception e) {
super(e);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.redhat.devtools.lsp4ij.server;

import com.intellij.execution.impl.ExecutionManagerImpl;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessListener;
import com.intellij.execution.process.ProcessOutputType;
import com.intellij.openapi.util.Key;
import org.jetbrains.annotations.NotNull;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class LSPProcessListener implements ProcessListener {

private final PipedOutputStream outputStream;
private final OutputStreamWriter outputStreamWriter;
private final PipedInputStream inputStream;
private final @NotNull List<LanguageServerLogErrorHandler> errorHandlers;

public LSPProcessListener(List<LanguageServerLogErrorHandler> errorHandlers) throws IOException {
this.outputStream = new PipedOutputStream();
this.outputStreamWriter = new OutputStreamWriter(this.outputStream, StandardCharsets.UTF_8);
this.inputStream = new PipedInputStream(this.outputStream);
this.errorHandlers = errorHandlers;
}

@NotNull
public final InputStream getInputStream() {
return this.inputStream;
}

@Override
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
ProcessListener.super.onTextAvailable(event, outputType);

if (ProcessOutputType.isStdout(outputType)) {
try {
this.outputStreamWriter.write(event.getText());
this.outputStreamWriter.flush();
} catch (IOException e) {
ExecutionManagerImpl.stopProcess(event.getProcessHandler());
}
} else if (ProcessOutputType.isStderr(outputType)) {
for (var handler : errorHandlers) {
handler.logError(event.getText());
}
}
}

@Override
public void processTerminated(@NotNull ProcessEvent event) {
try {
outputStreamWriter.close();
outputStream.close();
} catch (IOException e) {
// Do nothing
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.redhat.devtools.lsp4ij.server;

public interface LanguageServerLogErrorHandler {

void logError(String error);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.redhat.devtools.lsp4ij.server;

import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.impl.ExecutionManagerImpl;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.util.io.BaseOutputReader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class OSProcessStreamConnectionProvider implements StreamConnectionProvider {

private GeneralCommandLine commandLine;
private OSProcessHandler processHandler;
private @NotNull InputStream inputStream;
private List<LanguageServerLogErrorHandler> handlers;

public OSProcessStreamConnectionProvider(@Nullable GeneralCommandLine commandLine) {
this.commandLine = commandLine;
this.handlers = new ArrayList<>();
}

public void setCommandLine(GeneralCommandLine commandLine) {
this.commandLine = commandLine;
}

public GeneralCommandLine getCommandLine() {
return commandLine;
}

@Override
public void addLogErrorHandler(LanguageServerLogErrorHandler handler) {
handlers.add(handler);
}

@Override
public void start() throws CannotStartProcessException {
if (this.commandLine == null) {
throw new CannotStartProcessException("Unable to start language server: " + this);
}
try {
processHandler = new OSProcessHandler(commandLine) {
@Override
protected BaseOutputReader.@NotNull Options readerOptions() {
// If it's a long-running mostly idle daemon process,
// consider overriding OSProcessHandler#readerOptions
// with 'BaseOutputReader.Options.forMostlySilentProcess()'
// to reduce CPU usage.
return BaseOutputReader.Options.forMostlySilentProcess();
}
};
LSPProcessListener processListener = new LSPProcessListener(handlers);
processHandler.addProcessListener(processListener);
inputStream = processListener.getInputStream();
processHandler.startNotify();
} catch (Exception e) {
throw new CannotStartProcessException(e);
}
}

@Override
public boolean isAlive() {
return processHandler != null && processHandler.isStartNotified() && !processHandler.isProcessTerminated();
}

@Override
public InputStream getInputStream() {
return inputStream;
}

@Override
public OutputStream getOutputStream() {
return processHandler.getProcessInput();
}

@Override
public void stop() {
if (processHandler != null && !processHandler.isProcessTerminated()) {
ExecutionManagerImpl.stopProcess(processHandler);
}
}

@Override
public int hashCode() {
return Objects.hash(this.getCommandLine());
}

@Override
public String toString() {
return "OSProcessStreamConnectionProvider [commandLine=" + this.getCommandLine() + "]";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,6 @@ InputStream getInputStream() {
return p == null ? null : p.getInputStream();
}

@Override
public @Nullable
InputStream getErrorStream() {
Process p = process;
return p == null ? null : p.getErrorStream();
}

@Override
public @Nullable
OutputStream getOutputStream() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import com.intellij.openapi.vfs.VirtualFile;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.services.LanguageServer;
import org.jetbrains.annotations.Nullable;

import java.io.InputStream;
import java.io.OutputStream;
Expand All @@ -33,18 +32,10 @@ public interface StreamConnectionProvider {

OutputStream getOutputStream();

/**
* Returns the {@link InputStream} connected to the error output of the process
* running the language server. If the error output is redirected to standard
* output it returns <code>null</code>.
*
* @return the {@link InputStream} connected to the error output of the language
* server process or <code>null</code> if it's redirected or process not
* yet started.
*/
@Nullable
InputStream getErrorStream();

default void addLogErrorHandler(LanguageServerLogErrorHandler handler) {

}
/**
* User provided initialization options.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public UserDefinedLanguageServerDefinition(@NotNull String id,
return new UserDefinedStreamConnectionProvider(resolvedCommandLine,
userEnvironmentVariables,
includeSystemEnvironmentVariables,
this);
this,
project);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,47 @@
*******************************************************************************/
package com.redhat.devtools.lsp4ij.server.definition.launching;

import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.EnvironmentUtil;
import com.redhat.devtools.lsp4ij.server.OSProcessStreamConnectionProvider;
import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Map;

/**
* {@link ProcessStreamConnectionProvider} implementation to start a language server with a
* process command defined by the user.
*/
public class UserDefinedStreamConnectionProvider extends ProcessStreamConnectionProvider {
public class UserDefinedStreamConnectionProvider extends OSProcessStreamConnectionProvider {

private final @NotNull UserDefinedLanguageServerDefinition serverDefinition;

public UserDefinedStreamConnectionProvider(@NotNull String commandLine,
@NotNull Map<String, String> userEnvironmentVariables,
boolean includeSystemEnvironmentVariables,
@NotNull UserDefinedLanguageServerDefinition serverDefinition) {
super.setCommands(CommandUtils.createCommands(commandLine));
super.setUserEnvironmentVariables(userEnvironmentVariables);
super.setIncludeSystemEnvironmentVariables(includeSystemEnvironmentVariables);
@NotNull UserDefinedLanguageServerDefinition serverDefinition,
@NotNull Project project) {
super(createCommandLine(commandLine, userEnvironmentVariables, includeSystemEnvironmentVariables));
this.serverDefinition = serverDefinition;
}

@NotNull
private static GeneralCommandLine createCommandLine(@NotNull String commandLine,
@NotNull Map<String, String> userEnvironmentVariables,
boolean includeSystemEnvironmentVariables) {
Map<String, String> environmentVariables = new HashMap<>(userEnvironmentVariables);
// Add System environment variables
if (includeSystemEnvironmentVariables) {
environmentVariables.putAll(EnvironmentUtil.getEnvironmentMap());
}
return new GeneralCommandLine(CommandUtils.createCommands(commandLine))
.withEnvironment(environmentVariables);
}

@Override
public Object getInitializationOptions(VirtualFile rootUri) {
return serverDefinition.getLanguageServerInitializationOptions();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version='1.0'?>
<list>
<option name="LSP_TYPE_PARAMETER">
<value>
<option name="FOREGROUND" value="37cccc" />
<option name="FONT_TYPE" value="1" />
</value>
</option>
<?xml version='1.0'?>
<list>
<option name="LSP_TYPE_PARAMETER">
<value>
<option name="FOREGROUND" value="37cccc" />
<option name="FONT_TYPE" value="1" />
</value>
</option>
</list>

0 comments on commit 5142610

Please sign in to comment.