[platform] replacing PathExecLazyValue (IJPL-7387 prerequisite)

GitOrigin-RevId: edc1f4884790a873780346c3d6e7e7540ea3d9d6
This commit is contained in:
Roman Shevchenko
2025-07-04 12:21:02 +00:00
committed by intellij-monorepo-bot
parent 30cb09e0c9
commit 6e9bacb07e
16 changed files with 175 additions and 150 deletions
@@ -1,18 +1,18 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.fileTypes;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.PathEnvironmentVariableUtil;
import com.intellij.icons.AllIcons;
import com.intellij.ide.IdeCoreBundle;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.system.OS;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
public final class NativeFileType implements INativeFileType {
public static final NativeFileType INSTANCE = new NativeFileType();
@@ -64,16 +64,16 @@ public final class NativeFileType implements INativeFileType {
throw new IllegalArgumentException("Non-local file: " + file + "; FS=" + file.getFileSystem());
}
List<String> commands = new ArrayList<>();
if (SystemInfo.isWindows) {
var commands = new ArrayList<String>();
if (OS.CURRENT == OS.Windows) {
//noinspection SpellCheckingInspection
commands.add("rundll32.exe");
commands.add("url.dll,FileProtocolHandler");
}
else if (SystemInfo.isMac) {
else if (OS.CURRENT == OS.macOS) {
commands.add("/usr/bin/open");
}
else if (SystemInfo.hasXdgOpen()) {
else if (PathEnvironmentVariableUtil.isOnPath("xdg-open")) {
commands.add("xdg-open");
}
else {
@@ -4,6 +4,7 @@ package com.intellij.ide.browsers
import com.intellij.execution.CommandLineUtil
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PathEnvironmentVariableUtil
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.util.ExecUtil
import com.intellij.ide.BrowserUtil
@@ -17,11 +18,11 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.NlsContexts.NotificationContent
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.util.PathUtil
import com.intellij.util.io.URLUtil
import com.intellij.util.system.OS
import kotlinx.coroutines.*
import org.jetbrains.annotations.ApiStatus
import java.awt.Desktop
@@ -36,7 +37,7 @@ open class BrowserLauncherAppless : BrowserLauncher() {
@JvmStatic
fun canUseSystemDefaultBrowserPolicy(): Boolean =
isDesktopActionSupported(Desktop.Action.BROWSE) || SystemInfo.isWindows || SystemInfo.isMac || SystemInfo.hasXdgOpen()
isDesktopActionSupported(Desktop.Action.BROWSE) || OS.CURRENT == OS.Windows || OS.CURRENT == OS.macOS || PathEnvironmentVariableUtil.isOnPath("xdg-open")
private fun isDesktopActionSupported(action: Desktop.Action): Boolean =
Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(action)
@@ -77,13 +78,13 @@ open class BrowserLauncherAppless : BrowserLauncher() {
@Suppress("UsagesOfObsoleteApi")
override fun browse(file: java.io.File) {
val path = file.absolutePath
val absPath = if (SystemInfo.isWindows && path[0] != '/') "/${path}" else path
val absPath = if (OS.CURRENT == OS.Windows && path[0] != '/') "/${path}" else path
browse("${StandardFileSystems.FILE_PROTOCOL_PREFIX}${absPath}", browser = null, project = null)
}
override fun browse(file: Path) {
val path = file.toAbsolutePath().toString()
val absPath = if (SystemInfo.isWindows && path[0] != '/') "/${path}" else path
val absPath = if (OS.CURRENT == OS.Windows && path[0] != '/') "/${path}" else path
browse("${StandardFileSystems.FILE_PROTOCOL_PREFIX}${absPath}", browser = null, project = null)
}
@@ -189,7 +190,7 @@ open class BrowserLauncherAppless : BrowserLauncher() {
}
catch (e: Exception) {
LOG.warn("[${uri}]", e)
if (SystemInfo.isMac && e.message!!.contains("Error code: -10814")) {
if (OS.CURRENT == OS.macOS && e.message!!.contains("Error code: -10814")) {
// if "No application knows how to open" the URL, there is no sense in retrying with the 'open' command
return@launch
}
@@ -260,9 +261,9 @@ open class BrowserLauncherAppless : BrowserLauncher() {
private val defaultBrowserCommand: List<String>?
get() = when {
SystemInfo.isWindows -> listOf(CommandLineUtil.getWinShellName(), "/c", "start", GeneralCommandLine.inescapableQuote(""))
SystemInfo.isMac -> listOf(ExecUtil.openCommandPath)
SystemInfo.hasXdgOpen() -> listOf("xdg-open")
OS.CURRENT == OS.Windows -> listOf(CommandLineUtil.getWinShellName(), "/c", "start", GeneralCommandLine.inescapableQuote(""))
OS.CURRENT == OS.macOS -> listOf(ExecUtil.openCommandPath)
PathEnvironmentVariableUtil.isOnPath("xdg-open") -> listOf("xdg-open")
else -> null
}
@@ -1,8 +1,9 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.actions;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.PathEnvironmentVariableUtil;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.util.ExecUtil;
import com.intellij.ide.IdeBundle;
@@ -22,12 +23,12 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.updateSettings.impl.ExternalUpdateManager;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.AppUIUtil;
import com.intellij.ui.AppUIUtilKt;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.Restarter;
import com.intellij.util.system.OS;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -44,7 +45,7 @@ public final class CreateDesktopEntryAction extends DumbAwareAction implements A
private static final Logger LOG = Logger.getInstance(CreateDesktopEntryAction.class);
public static boolean isAvailable() {
return SystemInfo.isUnix && !SystemInfo.isMac && !ExternalUpdateManager.isCreatingDesktopEntries() && SystemInfo.hasXdgOpen();
return OS.isGenericUnix() &&!ExternalUpdateManager.isCreatingDesktopEntries() && PathEnvironmentVariableUtil.isOnPath("xdg-desktop-menu");
}
@Override
@@ -1,6 +1,7 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.actions;
import com.intellij.execution.configurations.PathEnvironmentVariableUtil;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessIOExecutorService;
import com.intellij.ide.IdeBundle;
@@ -22,10 +23,10 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.NlsActions.ActionText;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.ArchiveFileSystem;
import com.intellij.util.SystemProperties;
import com.intellij.util.system.OS;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.*;
@@ -107,12 +108,12 @@ public class RevealFileAction extends DumbAwareAction implements LightEditCompat
/** Whether a system is able to open a directory in a file manager and highlight a file in it. */
public static boolean isSupported() {
return SystemInfo.isWindows || SystemInfo.isMac || Holder.fileManagerApp != null;
return OS.CURRENT == OS.Windows || OS.CURRENT == OS.macOS || Holder.fileManagerApp != null;
}
/** Whether a system is able to open a directory in a file manager. */
public static boolean isDirectoryOpenSupported() {
return SystemInfo.isWindows || SystemInfo.isMac || Holder.fileManagerApp != null;
return OS.CURRENT == OS.Windows || OS.CURRENT == OS.macOS || Holder.fileManagerApp != null;
}
public static @ActionText @NotNull String getActionName() {
@@ -125,7 +126,7 @@ public class RevealFileAction extends DumbAwareAction implements LightEditCompat
}
private static @ActionText String getActionName(boolean skipDetection) {
return SystemInfo.isMac ? ActionsBundle.message("action.RevealIn.name.mac") : ActionsBundle.message("action.RevealIn.name.other", getFileManagerName(skipDetection));
return OS.CURRENT == OS.macOS ? ActionsBundle.message("action.RevealIn.name.mac") : ActionsBundle.message("action.RevealIn.name.other", getFileManagerName(skipDetection));
}
@Override
@@ -143,8 +144,8 @@ public class RevealFileAction extends DumbAwareAction implements LightEditCompat
}
public static @NotNull @ActionText String getFileManagerName(boolean skipDetection) {
return SystemInfo.isMac ? IdeBundle.message("action.finder.text") :
SystemInfo.isWindows ? IdeBundle.message("action.explorer.text") :
return OS.CURRENT == OS.Windows ? IdeBundle.message("action.explorer.text") :
OS.CURRENT == OS.macOS ? IdeBundle.message("action.finder.text") :
skipDetection ? IdeBundle.message("action.file.manager.text") :
requireNonNullElseGet(Holder.fileManagerName, () -> IdeBundle.message("action.file.manager.text"));
}
@@ -202,7 +203,7 @@ public class RevealFileAction extends DumbAwareAction implements LightEditCompat
var toSelect = _toSelect != null ? canonicalize(_toSelect).normalize().toString() : null;
String fmApp;
if (SystemInfo.isWindows) {
if (OS.CURRENT == OS.Windows) {
if (JnaLoader.isLoaded()) {
openViaShellApi(dir, toSelect);
}
@@ -210,7 +211,7 @@ public class RevealFileAction extends DumbAwareAction implements LightEditCompat
openViaExplorerCall(dir, toSelect);
}
}
else if (SystemInfo.isMac) {
else if (OS.CURRENT == OS.macOS) {
if (toSelect != null) {
spawn("open", "-R", toSelect);
}
@@ -229,7 +230,7 @@ public class RevealFileAction extends DumbAwareAction implements LightEditCompat
spawn(fmApp, toSelect != null ? toSelect : dir);
}
}
else if (toSelect == null && SystemInfo.hasXdgOpen()) {
else if (toSelect == null && PathEnvironmentVariableUtil.isOnPath("xdg-open")) {
spawn("xdg-open", dir);
}
else {
@@ -298,7 +299,7 @@ public class RevealFileAction extends DumbAwareAction implements LightEditCompat
ProcessIOExecutorService.INSTANCE.execute(() -> {
try {
var process = SystemInfo.isWindows ? Runtime.getRuntime().exec(command[0]) : new ProcessBuilder(command).start();
var process = OS.CURRENT == OS.Windows ? Runtime.getRuntime().exec(command[0]) : new ProcessBuilder(command).start();
new CapturingProcessHandler.Silent(process, null, command[0])
.runProcess(10000, false)
.checkSuccess(LOG);
@@ -315,7 +316,7 @@ public class RevealFileAction extends DumbAwareAction implements LightEditCompat
static {
String fmApp = null, fmName = null;
if (SystemInfo.hasXdgMime()) {
if (PathEnvironmentVariableUtil.isOnPath("xdg-mime")) {
try (var reader = new ProcessBuilder("xdg-mime", "query", "default", "inode/directory").start().inputReader()) {
var desktopEntryName = reader.readLine();
if (desktopEntryName != null && desktopEntryName.endsWith(".desktop")) {
@@ -1,6 +1,7 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.fileChooser.actions;
import com.intellij.execution.configurations.PathEnvironmentVariableUtil;
import com.intellij.execution.wsl.WSLDistribution;
import com.intellij.execution.wsl.WSLUtil;
import com.intellij.execution.wsl.WslDistributionManager;
@@ -18,7 +19,6 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.util.PopupUtil;
import com.intellij.openapi.util.io.NioFiles;
import com.intellij.openapi.util.io.PathExecLazyValue;
import com.intellij.ui.UIBundle;
import org.jetbrains.annotations.NotNull;
@@ -26,15 +26,15 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Supplier;
final class GoToWslHomeAction extends FileChooserAction implements LightEditCompatible {
private static final Supplier<Boolean> ourHasWsl = PathExecLazyValue.create("wsl.exe");
@Override
protected void update(@NotNull FileChooserPanel panel, @NotNull AnActionEvent e) {
var allowed = WSLUtil.isSystemCompatible() && ourHasWsl.get() && Experiments.getInstance().isFeatureEnabled("wsl.p9.show.roots.in.file.chooser");
e.getPresentation().setEnabledAndVisible(allowed);
e.getPresentation().setEnabledAndVisible(
WSLUtil.isSystemCompatible() &&
Experiments.getInstance().isFeatureEnabled("wsl.p9.show.roots.in.file.chooser") &&
PathEnvironmentVariableUtil.isOnPath("wsl.exe")
);
}
@Override
@@ -1,7 +1,8 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.fileChooser.actions;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.PathEnvironmentVariableUtil;
import com.intellij.execution.util.ExecUtil;
import com.intellij.ide.lightEdit.LightEditCompatible;
import com.intellij.jna.JnaLoader;
@@ -10,12 +11,12 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooserPanel;
import com.intellij.openapi.fileChooser.FileSystemTree;
import com.intellij.openapi.util.NullableLazyValue;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.mac.foundation.Foundation;
import com.intellij.ui.mac.foundation.ID;
import com.intellij.util.SystemProperties;
import com.intellij.util.system.OS;
import com.sun.jna.platform.win32.*;
import org.jetbrains.annotations.NotNull;
@@ -65,7 +66,7 @@ final class GotoDesktopDirAction extends FileChooserAction implements LightEditC
}
private static Path getDesktopDirectory() {
if (SystemInfo.isWindows && JnaLoader.isLoaded()) {
if (OS.CURRENT == OS.Windows && JnaLoader.isLoaded()) {
char[] path = new char[WinDef.MAX_PATH];
WinNT.HRESULT res = Shell32.INSTANCE.SHGetFolderPath(null, ShlObj.CSIDL_DESKTOP, null, ShlObj.SHGFP_TYPE_CURRENT, path);
if (WinError.S_OK.equals(res)) {
@@ -74,7 +75,7 @@ final class GotoDesktopDirAction extends FileChooserAction implements LightEditC
return Path.of(new String(path, 0, len));
}
}
else if (SystemInfo.isMac && JnaLoader.isLoaded()) {
else if (OS.CURRENT == OS.macOS && JnaLoader.isLoaded()) {
ID manager = Foundation.invoke(Foundation.getObjcClass("NSFileManager"), "defaultManager");
ID url = Foundation.invoke(manager, "URLForDirectory:inDomain:appropriateForURL:create:error:",
12 /*NSDesktopDirectory*/, 1 /*NSUserDomainMask*/, null, false, null);
@@ -83,8 +84,8 @@ final class GotoDesktopDirAction extends FileChooserAction implements LightEditC
return Path.of(path);
}
}
else if (SystemInfo.hasXdgOpen()) {
String path = ExecUtil.execAndReadLine(new GeneralCommandLine("xdg-user-dir", "DESKTOP"));
else if (PathEnvironmentVariableUtil.isOnPath("xdg-user-dir")) {
var path = ExecUtil.execAndReadLine(new GeneralCommandLine("xdg-user-dir", "DESKTOP"));
if (path != null && !path.isBlank()) {
try {
return Path.of(path);
@@ -0,0 +1,27 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.configurations;
import com.intellij.util.system.OS;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
class PathEnvironmentVariableUtilTest {
@Test void isOnPathContract() {
assertThatCode(() -> PathEnvironmentVariableUtil.isOnPath("bad\\path")).isInstanceOf(IllegalArgumentException.class);
}
@Test void isOnPathPositive() {
var shell = Path.of(OS.CURRENT == OS.Windows ? "C:\\Windows\\System32\\cmd.exe" : "/bin/sh");
assumeTrue(Files.isExecutable(shell));
assertThat(PathEnvironmentVariableUtil.isOnPath(shell.getFileName().toString())).isTrue();
}
@Test void isOnPathNegative() {
assertThat(PathEnvironmentVariableUtil.isOnPath("no-one-in-his-right-mind-names-an-exec-like-this")).isFalse();
}
}
+1
View File
@@ -151,6 +151,7 @@ f:com.intellij.execution.configurations.PathEnvironmentVariableUtil
- s:getPathDirs(java.lang.String):java.util.List
- s:getPathVariableValue():java.lang.String
- s:getWindowsExecutableFileExtensions():java.util.List
- s:isOnPath(java.lang.String):Z
c:com.intellij.execution.configurations.PtyCommandLine
- com.intellij.execution.configurations.GeneralCommandLine
- com.intellij.execution.configurations.CommandLineWithSuspendedProcessCallback
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.configurations;
import com.intellij.openapi.util.SystemInfo;
@@ -10,15 +10,19 @@ import org.jetbrains.annotations.*;
import java.io.File;
import java.io.FileFilter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A collection of utility methods for working with PATH environment variable.
*/
@SuppressWarnings("ALL")
public final class PathEnvironmentVariableUtil {
private static final String PATH = "PATH";
private static final Map<String, Boolean> ourOnPathCache = Collections.synchronizedMap(new HashMap<>());
private PathEnvironmentVariableUtil() { }
@@ -143,7 +147,7 @@ public final class PathEnvironmentVariableUtil {
* Retrieves the value of PATH environment variable
*/
public static @Nullable String getPathVariableValue() {
return EnvironmentUtil.getValue(PATH);
return EnvironmentUtil.getValue("PATH");
}
public static @Nullable File findExecutableInPathOnAnyOS(@NotNull @NonNls String fileBaseName) {
@@ -157,4 +161,29 @@ public final class PathEnvironmentVariableUtil {
return findInPath(fileBaseName);
}
}
/**
* Checks whether the given file is in one of the directories listed in the PATH environment variable.
* The first call might be slow, but the result is cached.
*
* @since 2025.3
*/
public static boolean isOnPath(@NotNull String name) {
if (name.indexOf('\\') >= 0 || name.indexOf('/') >= 0) throw new IllegalArgumentException(name);
var result = ourOnPathCache.get(name);
if (result == null) {
result = Boolean.FALSE;
var path = getPathVariableValue();
if (path != null) {
for (var dir : StringUtil.tokenize(path, File.pathSeparator)) {
if (Files.isExecutable(Path.of(dir, name))) {
result = Boolean.TRUE;
break;
}
}
}
ourOnPathCache.put(name, result);
}
return result;
}
}
@@ -1,11 +1,11 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.sudo
import com.intellij.execution.CommandLineUtil
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PathEnvironmentVariableUtil
import com.intellij.execution.util.ExecUtil
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.util.io.PathExecLazyValue
import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.io.IdeUtilIoBundle
import com.intellij.util.system.OS
@@ -14,10 +14,6 @@ import org.jetbrains.annotations.Nls
@ApiStatus.Internal
open class LocalSudoCommandProvider : SudoCommandProvider {
private val hasGkSudo = PathExecLazyValue.create("gksudo")
private val hasKdeSudo = PathExecLazyValue.create("kdesudo")
private val hasPkExec = PathExecLazyValue.create("pkexec")
override fun isAvailable(): Boolean = true
override fun sudoCommand(wrappedCommand: GeneralCommandLine, prompt: @Nls String): GeneralCommandLine? {
@@ -37,13 +33,13 @@ open class LocalSudoCommandProvider : SudoCommandProvider {
do shell script ${escapedCommand} with prompt "${escapedPrompt}" with administrator privileges without altering line endings
end tell""".trimIndent())
}
hasGkSudo.get() -> {
PathEnvironmentVariableUtil.isOnPath("gksudo") -> {
GeneralCommandLine(listOf("gksudo", "--message", prompt, "--") + envCommand(wrappedCommand) + command)
}
hasKdeSudo.get() -> {
PathEnvironmentVariableUtil.isOnPath("kdesudo") -> {
GeneralCommandLine(listOf("kdesudo", "--comment", prompt, "--") + envCommand(wrappedCommand) + command)
}
hasPkExec.get() -> {
PathEnvironmentVariableUtil.isOnPath("pkexec") -> {
GeneralCommandLine(listOf("pkexec") + envCommand(wrappedCommand) + command)
}
ExecUtil.hasTerminalApp() -> {
@@ -4,21 +4,21 @@ package com.intellij.execution.util
import com.intellij.execution.CommandLineUtil
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PathEnvironmentVariableUtil
import com.intellij.execution.process.*
import com.intellij.execution.sudo.SudoCommandProvider
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.PathExecLazyValue
import com.intellij.platform.eel.EelExecApi
import com.intellij.platform.eel.provider.asEelPath
import com.intellij.platform.eel.spawnProcess
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.intellij.util.io.IdeUtilIoBundle
import com.intellij.util.io.SuperUserStatus
import com.intellij.util.system.OS
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import java.io.BufferedReader
@@ -27,20 +27,19 @@ import java.io.InputStreamReader
import java.nio.file.Path
object ExecUtil {
private val hasGnomeTerminal = PathExecLazyValue.create("gnome-terminal")
@Suppress("SpellCheckingInspection")
private val hasKdeTerminal = PathExecLazyValue.create("konsole")
@Suppress("SpellCheckingInspection")
private val hasUrxvt = PathExecLazyValue.create("urxvt")
private val hasXTerm = PathExecLazyValue.create("xterm")
private val hasSupportedTerminals = lazy {
@Suppress("SpellCheckingInspection")
PathEnvironmentVariableUtil.isOnPath("konsole") ||
PathEnvironmentVariableUtil.isOnPath("gnome-terminal") ||
PathEnvironmentVariableUtil.isOnPath("urxvt") ||
PathEnvironmentVariableUtil.isOnPath("xterm")
}
@JvmStatic
val osascriptPath: @NlsSafe String
get() = "/usr/bin/osascript"
val osascriptPath: @NlsSafe String get() = "/usr/bin/osascript"
@JvmStatic
val openCommandPath: @NlsSafe String
get() = "/usr/bin/open"
val openCommandPath: @NlsSafe String get() = "/usr/bin/open"
@ApiStatus.Internal
@JvmStatic
@@ -167,17 +166,16 @@ object ExecUtil {
@ApiStatus.Internal
@JvmStatic
fun hasTerminalApp(): Boolean =
SystemInfoRt.isWindows || SystemInfoRt.isMac || hasKdeTerminal.get() || hasGnomeTerminal.get() || hasUrxvt.get() || hasXTerm.get()
fun hasTerminalApp(): Boolean = OS.CURRENT == OS.Windows || OS.CURRENT == OS.macOS || hasSupportedTerminals.value
@ApiStatus.Internal
@JvmStatic
@Suppress("SpellCheckingInspection")
fun getTerminalCommand(@Nls(capitalization = Nls.Capitalization.Title) title: String?, command: String): List<@NlsSafe String> = when {
SystemInfoRt.isWindows -> {
OS.CURRENT == OS.Windows -> {
listOf(CommandLineUtil.getWinShellName(), "/c", "start", GeneralCommandLine.inescapableQuote(title?.replace('"', '\'') ?: ""), command)
}
SystemInfoRt.isMac -> {
OS.CURRENT == OS.macOS -> {
val prefix = if (title != null) "\"echo -n \" & " + escapeAppleScriptArgument("\\0033]0;${title}\\007") + " & \" ; \" & " else ""
val script = prefix + "\"clear ; exec \" & " + escapeAppleScriptArgument(command)
listOf(osascriptPath, "-e", """
@@ -187,24 +185,24 @@ object ExecUtil {
|end tell
""".trimMargin())
}
hasKdeTerminal.get() -> {
PathEnvironmentVariableUtil.isOnPath("konsole") -> {
if (title != null) listOf("konsole", "-p", "tabtitle=\"${title.replace('"', '\'')}\"", "-e", command)
else listOf("konsole", "-e", command)
}
hasGnomeTerminal.get() -> {
PathEnvironmentVariableUtil.isOnPath("gnome-terminal") -> {
if (title != null) listOf("gnome-terminal", "-t", title, "-x", command)
else listOf("gnome-terminal", "-x", command)
}
hasUrxvt.get() -> {
PathEnvironmentVariableUtil.isOnPath("urxvt") -> {
if (title != null) listOf("urxvt", "-title", title, "-e", command)
else listOf("urxvt", "-e", command)
}
hasXTerm.get() -> {
PathEnvironmentVariableUtil.isOnPath("xterm") -> {
if (title != null) listOf("xterm", "-T", title, "-e", command)
else listOf("xterm", "-e", command)
}
else -> {
throw UnsupportedOperationException("Unsupported OS/desktop: ${SystemInfoRt.OS_NAME}/${System.getenv("XDG_CURRENT_DESKTOP")}")
throw UnsupportedOperationException("Unsupported OS/desktop: ${OS.CURRENT.name}/${System.getenv("XDG_CURRENT_DESKTOP")}")
}
}
+2
View File
@@ -423,6 +423,8 @@ com.intellij.util.keyFMap.KeyFMap
- a:minus(com.intellij.openapi.util.Key):com.intellij.util.keyFMap.KeyFMap
- a:plus(com.intellij.openapi.util.Key,java.lang.Object):com.intellij.util.keyFMap.KeyFMap
- a:size():I
e:com.intellij.util.system.OS
- s:isGenericUnix():Z
com.intellij.util.xmlb.Accessor
- a:getAnnotation(java.lang.Class):java.lang.annotation.Annotation
- a:getGenericType():java.lang.reflect.Type
@@ -1,7 +1,6 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.util;
import com.intellij.openapi.util.io.PathExecLazyValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.Strings;
import com.intellij.util.containers.ContainerUtil;
@@ -12,8 +11,11 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.function.Supplier;
import static com.intellij.openapi.util.NotNullLazyValue.lazy;
/**
* Provides information about operating system, system-wide settings, and Java Runtime.
@@ -68,18 +70,16 @@ public final class SystemInfo {
public static final boolean isWin11OrNewer = isWindows && isOsVersionAtLeast("11.0");
/**
* Set to true if we are running in a Wayland environment, either through
* XWayland or using Wayland directly.
* Set to true if we are running in a Wayland environment, either through XWayland or using Wayland directly.
*/
public static final boolean isWayland;
public static final boolean isXWindow = SystemInfoRt.isUnix && !SystemInfoRt.isMac;
public static final boolean isGNOME, isKDE, isXfce, isI3;
static {
// http://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running/227669#227669
// https://userbase.kde.org/KDE_System_Administration/Environment_Variables#KDE_FULL_SESSION
if (SystemInfoRt.isUnix && !SystemInfoRt.isMac) {
if (!isWindows && !isMac) {
isWayland = System.getenv("WAYLAND_DISPLAY") != null;
@SuppressWarnings("SpellCheckingInspection") String desktop = System.getenv("XDG_CURRENT_DESKTOP"), gdmSession = System.getenv("GDMSESSION");
@SuppressWarnings({"SpellCheckingInspection", "RedundantSuppression"}) String desktop = System.getenv("XDG_CURRENT_DESKTOP"), gdmSession = System.getenv("GDMSESSION");
isGNOME = desktop != null && desktop.contains("GNOME") || gdmSession != null && gdmSession.contains("gnome");
isKDE = !isGNOME && (desktop != null && desktop.contains("KDE") || System.getenv("KDE_FULL_SESSION") != null);
isXfce = !isGNOME && !isKDE && (desktop != null && desktop.contains("XFCE"));
@@ -94,14 +94,16 @@ public final class SystemInfo {
public static final boolean isFileSystemCaseSensitive = SystemInfoRt.isFileSystemCaseSensitive;
private static final Supplier<Boolean> ourHasXdgOpen = SystemInfoRt.isUnix && !SystemInfoRt.isMac
? PathExecLazyValue.create("xdg-open") : () -> false;
/** @deprecated use {@link com.intellij.execution.configurations.PathEnvironmentVariableUtil#isOnPath} instead */
@Deprecated
@ApiStatus.ScheduledForRemoval
public static boolean hasXdgOpen() {
return ourHasXdgOpen.get();
}
private static final Supplier<Boolean> ourHasXdgMime = SystemInfoRt.isUnix && !SystemInfoRt.isMac
? PathExecLazyValue.create("xdg-mime") : () -> false;
/** @deprecated use {@link com.intellij.execution.configurations.PathEnvironmentVariableUtil#isOnPath} instead */
@Deprecated
@ApiStatus.ScheduledForRemoval
public static boolean hasXdgMime() {
return ourHasXdgMime.get();
}
@@ -203,5 +205,25 @@ public final class SystemInfo {
@Deprecated
@ApiStatus.ScheduledForRemoval
public static final boolean is64Bit = CpuArch.CURRENT.width == 64;
/** @deprecated misleading; consider using {@link com.intellij.util.system.OS#isGenericUnix} instead, if appropriate */
@Deprecated
@ApiStatus.ScheduledForRemoval
public static final boolean isXWindow = SystemInfoRt.isUnix && !SystemInfoRt.isMac;
private static final NotNullLazyValue<Boolean> ourHasXdgOpen = isUnix && !isMac ? lazy(() -> isOnPath("xdg-open")) : NotNullLazyValue.createConstantValue(false);
private static final NotNullLazyValue<Boolean> ourHasXdgMime = isUnix && !isMac ? lazy(() -> isOnPath("xdg-mime")) : NotNullLazyValue.createConstantValue(false);
private static boolean isOnPath(String name) {
String path = System.getenv("PATH");
if (path != null) {
for (String dir : StringUtil.tokenize(path, ":")) {
if (Files.isExecutable(Paths.get(dir, name))) {
return true;
}
}
}
return false;
}
//</editor-fold>
}
@@ -1,37 +0,0 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.util.io;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.Strings;
import com.intellij.util.EnvironmentUtil;
import com.intellij.util.concurrency.SynchronizedClearableLazy;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.function.Supplier;
@ApiStatus.Internal
public final class PathExecLazyValue {
private PathExecLazyValue() { }
public static @NotNull Supplier<Boolean> create(@NlsSafe @NotNull String name) {
if (Strings.containsAnyChar(name, "/\\")) {
throw new IllegalArgumentException(name);
}
return new SynchronizedClearableLazy<>(() -> {
String path = EnvironmentUtil.getValue("PATH");
if (path != null) {
for (String dir : StringUtil.tokenize(path, File.pathSeparator)) {
if (new File(dir, name).canExecute()) {
return true;
}
}
}
return false;
});
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.util.system;
import com.intellij.execution.Platform;
@@ -49,4 +49,11 @@ public enum OS {
public @NotNull Platform getPlatform() {
return this == Windows ? Platform.WINDOWS : Platform.UNIX;
}
/**
* Returns {@code true} if the current operating system is a generic Unix-like system (not Windows or macOS).
*/
public static boolean isGenericUnix() {
return CURRENT != Windows && CURRENT != macOS;
}
}
@@ -1,24 +0,0 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.util.io
import com.intellij.openapi.util.SystemInfo
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import java.io.File
class PathExecLazyValueTest {
@Test fun positive() {
val shell = File(if (SystemInfo.isWindows) "C:\\Windows\\System32\\cmd.exe" else "/bin/sh")
assertTrue(shell.canExecute())
assertTrue(PathExecLazyValue.create(shell.name).get())
}
@Test fun negative() {
assertFalse(PathExecLazyValue.create("no-one-in-his-right-mind-names-an-exec-like-this").get())
}
@Test(expected = IllegalArgumentException::class) fun contract() {
PathExecLazyValue.create("bad\\path")
}
}