mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[terminal] IJPL-163095 launch local/WSL shell with EelApi
Enable `terminal.use.EelApi` registry key for that (by default, it's disabled). GitOrigin-RevId: 1ff0343d8919b0ddf13642626210a2e40f93d72c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
457e48fca6
commit
20dd88bceb
@@ -111,5 +111,7 @@
|
||||
<orderEntry type="library" scope="TEST" name="assertJ" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.kernel" />
|
||||
<orderEntry type="module" module-name="intellij.platform.project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.eel" />
|
||||
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -63,6 +63,9 @@
|
||||
<registryKey key="terminal.classic.refresh.vfs.on.shell.command.finished" defaultValue="true"
|
||||
description="Refresh VFS when shell commands are finished in the Classic Terminal.
|
||||
May not work 100% reliably as it uses heuristics to determine when command is finished."/>
|
||||
<registryKey key="terminal.use.EelApi" defaultValue="false"
|
||||
description="Use EelApi to launch a shell process. Launching a local shell works the same as before.
|
||||
For WSL environments, the shell process in WSL will be launched via IJEnt instead of using wsl.exe."/>
|
||||
|
||||
<experimentalFeature id="terminal.shell.command.handling" percentOfUsers="100"/>
|
||||
<terminal.shellCommandHandler implementation="org.jetbrains.plugins.terminal.RunAnythingTerminalBridge"/>
|
||||
|
||||
+29
-13
@@ -36,6 +36,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jetbrains.plugins.terminal.LocalBlockTerminalRunner.*;
|
||||
import static org.jetbrains.plugins.terminal.TerminalStartupKt.startProcess;
|
||||
import static org.jetbrains.plugins.terminal.util.ShellNameUtil.*;
|
||||
|
||||
public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess> {
|
||||
@@ -110,21 +111,33 @@ public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess
|
||||
TerminalUsageLocalStorage.getInstance().recordBlockTerminalUsed();
|
||||
}
|
||||
|
||||
Path workingDirPath = null;
|
||||
try {
|
||||
workingDirPath = Path.of(workingDir);
|
||||
}
|
||||
catch (InvalidPathException ignored) {
|
||||
}
|
||||
try {
|
||||
long startNano = System.nanoTime();
|
||||
PtyProcess process = (PtyProcess)ProcessService.getInstance().startPtyProcess(
|
||||
command,
|
||||
workingDir,
|
||||
envs,
|
||||
LocalPtyOptions.defaults().builder()
|
||||
.initialColumns(initialTermSize != null ? initialTermSize.getColumns() : -1)
|
||||
.initialRows(initialTermSize != null ? initialTermSize.getRows() : -1)
|
||||
.build(),
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
PtyProcess process;
|
||||
if (workingDirPath != null && shouldUseEelApi()) {
|
||||
process = startProcess(List.of(command), envs, workingDirPath, Objects.requireNonNull(initialTermSize));
|
||||
}
|
||||
else {
|
||||
process = (PtyProcess)ProcessService.getInstance().startPtyProcess(
|
||||
command,
|
||||
workingDir,
|
||||
envs,
|
||||
LocalPtyOptions.defaults().builder()
|
||||
.initialColumns(initialTermSize != null ? initialTermSize.getColumns() : -1)
|
||||
.initialRows(initialTermSize != null ? initialTermSize.getRows() : -1)
|
||||
.build(),
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
LOG.info("Started " + process.getClass().getName() + " in " + TimeoutUtil.getDurationMillis(startNano) + " ms from "
|
||||
+ stringifyProcessInfo(command, workingDir, initialTermSize, envs, !LOG.isDebugEnabled()));
|
||||
return process;
|
||||
@@ -266,4 +279,7 @@ public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess
|
||||
|| shellName.equals(FISH_NAME) && Registry.is(BLOCK_TERMINAL_FISH_REGISTRY, false);
|
||||
}
|
||||
|
||||
private static boolean shouldUseEelApi() {
|
||||
return Registry.is("terminal.use.EelApi", false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package org.jetbrains.plugins.terminal.arrangement;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.configurations.GeneralCommandLine;
|
||||
import com.intellij.execution.ijent.IjentChildPtyProcessAdapter;
|
||||
import com.intellij.execution.process.CapturingProcessRunner;
|
||||
import com.intellij.execution.process.OSProcessHandler;
|
||||
import com.intellij.execution.process.ProcessOutput;
|
||||
@@ -38,6 +39,8 @@ public final class ProcessInfoUtil {
|
||||
}
|
||||
|
||||
private static @Nullable String doGetCwd(@NotNull Process process) throws Exception {
|
||||
// use shell integration instead
|
||||
if (process instanceof IjentChildPtyProcessAdapter) return null;
|
||||
if (SystemInfo.isUnix) {
|
||||
int pid = (int)process.pid();
|
||||
String result = tryGetCwdFastOnUnix(pid);
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.terminal
|
||||
|
||||
import com.intellij.execution.wsl.WSLDistribution
|
||||
import com.intellij.execution.wsl.WslPath
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.progress.runBlockingMaybeCancellable
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.EelExecApi
|
||||
import com.intellij.platform.eel.EelExecApi.ExecuteProcessError
|
||||
import com.intellij.platform.eel.EelResult
|
||||
import com.intellij.platform.eel.provider.asEelPath
|
||||
import com.intellij.platform.eel.provider.getEelDescriptor
|
||||
import com.intellij.util.PathUtil
|
||||
import com.jediterm.core.util.TermSize
|
||||
import com.jediterm.terminal.TtyConnector
|
||||
import com.pty4j.PtyProcess
|
||||
import org.jetbrains.plugins.terminal.runner.LocalTerminalStartCommandBuilder
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
@@ -32,4 +47,87 @@ internal fun logCommonStartupInfo(
|
||||
", time to process created: ${durationBetweenStartupAndConnectorCreated.toMillis()} ms")
|
||||
}
|
||||
|
||||
@Throws(ErrnoException::class)
|
||||
internal fun startProcess(
|
||||
command: List<String>,
|
||||
envs: Map<String, String>,
|
||||
initialWorkingDirectory: Path,
|
||||
initialTermSize: TermSize,
|
||||
): PtyProcess {
|
||||
return runBlockingMaybeCancellable {
|
||||
val (eelApi, workingDirectory) = getEelApi(initialWorkingDirectory, command)
|
||||
val remoteCommand = convertCommandToRemote(eelApi, command)
|
||||
doStartProcess(eelApi, remoteCommand, envs, workingDirectory, initialTermSize)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun convertCommandToRemote(eelApi: EelApi, command: List<String>): List<String> {
|
||||
if (isWslCommand(command)) {
|
||||
val shell = eelApi.exec.fetchLoginShellEnvVariables()["SHELL"] ?: "/bin/sh"
|
||||
return listOf(shell, LocalTerminalDirectRunner.LOGIN_CLI_OPTION, LocalTerminalStartCommandBuilder.INTERACTIVE_CLI_OPTION)
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
private fun isWslCommand(command: List<String>): Boolean {
|
||||
if (SystemInfo.isWindows) {
|
||||
val exePath = command.getOrNull(0) ?: return false
|
||||
val exeFileName = PathUtil.getFileName(exePath)
|
||||
return exeFileName.equals("wsl.exe", true) || exeFileName.equals("wsl", true)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private suspend fun getEelApi(
|
||||
workingDirectory: Path,
|
||||
command: List<String>,
|
||||
): Pair<EelApi, Path> {
|
||||
val wslDistribNameFromCommandline = getWslDistributionNameFromCommand(command)
|
||||
if (wslDistribNameFromCommandline != null) {
|
||||
val wslDistribNameFromWorkingDirectory = WslPath.parseWindowsUncPath(workingDirectory.toString())?.distributionId
|
||||
if (wslDistribNameFromCommandline != wslDistribNameFromWorkingDirectory) {
|
||||
val wslRootPath = WSLDistribution(wslDistribNameFromCommandline).getUNCRootPath()
|
||||
val eelApi = wslRootPath.getEelDescriptor().upgrade()
|
||||
val userHome = runCatching { eelApi.exec.fetchLoginShellEnvVariables()["HOME"] }.getOrNull()
|
||||
return eelApi to wslRootPath.resolve(userHome ?: ".")
|
||||
}
|
||||
}
|
||||
return workingDirectory.getEelDescriptor().upgrade() to workingDirectory
|
||||
}
|
||||
|
||||
private fun getWslDistributionNameFromCommand(command: List<String>): String? {
|
||||
if (isWslCommand(command)) {
|
||||
val distributionOptionName = command.getOrNull(1)
|
||||
if (distributionOptionName == "-d" || distributionOptionName == "--distribution") {
|
||||
return command.getOrNull(2)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@Throws(ErrnoException::class)
|
||||
private suspend fun doStartProcess(
|
||||
eelApi: EelApi,
|
||||
command: List<String>,
|
||||
envs: Map<String, String>,
|
||||
workingDirectory: Path,
|
||||
initialTermSize: TermSize,
|
||||
): PtyProcess {
|
||||
// TODO migrate to generated builders in 252 (not available in 251)
|
||||
@Suppress("DEPRECATION")
|
||||
val execOptions = EelExecApi.ExecuteProcessOptions.Builder(command.first())
|
||||
.args(command.takeLast(command.size - 1))
|
||||
.env(envs)
|
||||
.workingDirectory(workingDirectory.asEelPath())
|
||||
.ptyOrStdErrSettings(EelExecApi.Pty(initialTermSize.columns, initialTermSize.rows, true))
|
||||
.build()
|
||||
val processResult = eelApi.exec.execute(execOptions)
|
||||
return when (processResult) {
|
||||
is EelResult.Ok -> processResult.value.convertToJavaProcess() as PtyProcess
|
||||
is EelResult.Error -> throw ErrnoException(processResult.error)
|
||||
}
|
||||
}
|
||||
|
||||
internal class ErrnoException(val error: ExecuteProcessError): Exception(error.message)
|
||||
|
||||
private val log: Logger = logger<AbstractTerminalRunner<*>>()
|
||||
|
||||
Reference in New Issue
Block a user