backport: 19.3.0

This commit is contained in:
FalsePattern 2024-10-27 16:16:37 +01:00
parent 8e0b2206a5
commit f6852215ed
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
52 changed files with 529 additions and 172 deletions

View file

@ -17,6 +17,18 @@ Changelog structure reference:
## [Unreleased] ## [Unreleased]
## [19.3.0]
### Added
- Toolchains, Run Configurations
- [Direnv](https://github.com/direnv/direnv) support
### Fixed
- Zig
- Missing description for string conversion intentions
## [19.2.0] ## [19.2.0]
### Added ### Added

View file

@ -85,6 +85,14 @@ Adds support for the Zig Language, utilizing the ZLS language server for advance
2. Download and compile the ZLS language server, available at https://github.com/zigtools/zls 2. Download and compile the ZLS language server, available at https://github.com/zigtools/zls
3. Go to `Settings` -> `Languages & Frameworks` -> `Zig`, and point the `Toolchain Location` and `ZLS path` to the correct places 3. Go to `Settings` -> `Languages & Frameworks` -> `Zig`, and point the `Toolchain Location` and `ZLS path` to the correct places
## Features
- Integration with the Zig Language Server (ZLS)
- Basic syntax highlighting if ZLS is not available
- Run and debug configurations for Zig projects (Debugging has some IDE limitations, and on Windows you need to do some extra setup, see below)
- Direnv support for loading environment variables from `.envrc` files (requires the direnv executable to be installed system-wide)
- Language injection support in Zig strings
## Debugging ## Debugging
Debugger settings are available in the `Settings | Build, Execution, Deployment | Debugger` menu, under the `Zig` section. Debugger settings are available in the `Settings | Build, Execution, Deployment | Debugger` menu, under the `Zig` section.

View file

@ -11,7 +11,7 @@ baseIDE=clion
ideaVersion=2024.2.2 ideaVersion=2024.2.2
clionVersion=2024.2.2 clionVersion=2024.2.2
pluginVersion=19.2.0 pluginVersion=19.3.0
# Gradle Releases -> https://github.com/gradle/gradle/releases # Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion=8.10.2 gradleVersion=8.10.2

View file

@ -0,0 +1,163 @@
package com.falsepattern.zigbrains.common.direnv;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.Key;
import com.intellij.util.concurrency.AppExecutorUtil;
import lombok.val;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
public class DirenvCmd {
private static final String GROUP_DISPLAY_ID = "ZigBrains Direnv";
private static final Logger LOG = Logger.getInstance(DirenvCmd.class);
public static final Key<Boolean> DIRENV_KEY = Key.create("ZIG_DIRENV_KEY");
private final Path workDir;
public DirenvCmd(Path workingDirectory) {
this.workDir = workingDirectory;
}
public static boolean direnvInstalled() {
return FileUtil.findExecutableOnPATH(Map.of(), "direnv").isPresent();
}
public @NotNull Map<String, String> importDirenvSync() {
val emptyMap = Map.<String, String>of();
if (!direnvInstalled()) {
return emptyMap;
}
try {
try {
val runOutput = runSync("export", "json");
if (runOutput.error()) {
if (runOutput.output().contains("is blocked")) {
Notifications.Bus.notify(new Notification(GROUP_DISPLAY_ID, "Direnv not allowed",
"Run `direnv allow` in a terminal inside the project directory" +
" to allow direnv to run", NotificationType.ERROR));
} else {
Notifications.Bus.notify(new Notification(GROUP_DISPLAY_ID, "Direnv error",
"Could not import direnv: " + runOutput.output(),
NotificationType.ERROR));
return emptyMap;
}
}
val type = new TypeToken<Map<String, String>>() {
}.getType();
if (runOutput.output().isEmpty()) {
return emptyMap;
}
return new Gson().fromJson(runOutput.output(), type);
} catch (Exception e) {
LOG.error("Failed to import direnv", e);
return emptyMap;
}
} catch (Exception e) {
LOG.error("Failed to import direnv", e);
return emptyMap;
}
}
public static @NotNull CompletableFuture<@NotNull Map<String, String>> tryGetProjectEnvAsync(@Nullable Project project) {
if (project == null)
return CompletableFuture.completedFuture(Map.of());
val dir = ProjectUtil.guessProjectDir(project);
if (dir == null) {
return CompletableFuture.completedFuture(Map.of());
}
val direnv = new DirenvCmd(dir.toNioPath());
return direnv.importDirenvAsync();
}
public static @NotNull Map<String, String> tryGetProjectEnvSync(@Nullable Project project) {
if (project == null)
return Map.of();
val dir = ProjectUtil.guessProjectDir(project);
if (dir == null) {
return Map.of();
}
val direnv = new DirenvCmd(dir.toNioPath());
return direnv.importDirenvSync();
}
public @NotNull CompletableFuture<Map<String, String>> importDirenvAsync() {
val emptyMap = Map.<String, String>of();
var returnMap = CompletableFuture.completedFuture(emptyMap);
if (!direnvInstalled()) {
return returnMap;
}
return runAsync("export", "json").thenApplyAsync(runOutput -> {
if (runOutput.error()) {
if (runOutput.output().contains("is blocked")) {
Notifications.Bus.notify(new Notification(GROUP_DISPLAY_ID, "Direnv not allowed",
"Run `direnv allow` in a terminal inside the project directory" +
" to allow direnv to run", NotificationType.ERROR));
} else {
Notifications.Bus.notify(new Notification(GROUP_DISPLAY_ID, "Direnv error",
"Could not import direnv: " + runOutput.output(),
NotificationType.ERROR));
return emptyMap;
}
}
val type = new TypeToken<Map<String, String>>() {
}.getType();
if (runOutput.output().isEmpty()) {
return emptyMap;
}
return new Gson().fromJson(runOutput.output(), type);
}, AppExecutorUtil.getAppExecutorService()).exceptionallyAsync((e) -> {
LOG.error("Failed to import direnv", e);
return emptyMap;
}, AppExecutorUtil.getAppExecutorService());
}
private CompletableFuture<DirenvOutput> runAsync(String... args) {
return CompletableFuture.supplyAsync(() -> {
try {
return runSync(args);
} catch (Exception e) {
throw new CompletionException(e);
}
}, AppExecutorUtil.getAppExecutorService());
}
private DirenvOutput runSync(String... args) throws ExecutionException, InterruptedException, IOException {
val commandArgs = new String[args.length + 1];
commandArgs[0] = "direnv";
System.arraycopy(args, 0, commandArgs, 1, args.length);
val cli = new GeneralCommandLine(commandArgs).withWorkingDirectory(workDir);
val process = cli.createProcess();
if (process.waitFor() != 0) {
val stdErr = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8);
return new DirenvOutput(stdErr, true);
}
val stdOut = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);
return new DirenvOutput(stdOut, false);
}
}

View file

@ -0,0 +1,3 @@
package com.falsepattern.zigbrains.common.direnv;
public record DirenvOutput(String output, boolean error) {}

View file

@ -20,6 +20,8 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
@ -27,6 +29,7 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class FileUtil { public class FileUtil {
@ -132,10 +135,10 @@ public class FileUtil {
return path != null ? sanitizeURI(path.toUri().toString()) : null; return path != null ? sanitizeURI(path.toUri().toString()) : null;
} }
public static Optional<Path> findExecutableOnPATH(String exe) { public static @NotNull Optional<Path> findExecutableOnPATH(@NotNull Map<String, String> extraEnv, @NotNull String exe) {
var exeName = SystemInfo.isWindows ? exe + ".exe" : exe; val exeName = SystemInfo.isWindows ? exe + ".exe" : exe;
var PATH = System.getenv("PATH").split(File.pathSeparator); val PATH = extraEnv.getOrDefault("PATH", System.getenv("PATH")).split(File.pathSeparator);
for (var dir: PATH) { for (val dir: PATH) {
var path = Path.of(dir); var path = Path.of(dir);
try { try {
path = path.toAbsolutePath(); path = path.toAbsolutePath();
@ -145,7 +148,7 @@ public class FileUtil {
if (!Files.exists(path) || !Files.isDirectory(path)) { if (!Files.exists(path) || !Files.isDirectory(path)) {
continue; continue;
} }
var exePath = path.resolve(exeName).toAbsolutePath(); val exePath = path.resolve(exeName).toAbsolutePath();
if (!Files.isRegularFile(exePath) || !Files.isExecutable(exePath)) { if (!Files.isRegularFile(exePath) || !Files.isExecutable(exePath)) {
continue; continue;
} }

View file

@ -23,7 +23,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;

View file

@ -43,7 +43,7 @@ public class ZigDebugEmitBinaryInstaller<ProfileState extends ProfileStateBase<?
cli.addParameters(exeArgs); cli.addParameters(exeArgs);
cli.withCharset(StandardCharsets.UTF_8); cli.withCharset(StandardCharsets.UTF_8);
cli.withRedirectErrorStream(true); cli.withRedirectErrorStream(true);
return cli; return profileState.configuration().patchCommandLine(cli, toolchain);
} }
@Override @Override

View file

@ -2,8 +2,6 @@ package com.falsepattern.zigbrains.debugger.toolchain;
import com.falsepattern.zigbrains.ZigBundle; import com.falsepattern.zigbrains.ZigBundle;
import com.falsepattern.zigbrains.common.ZigPathManager; import com.falsepattern.zigbrains.common.ZigPathManager;
import com.falsepattern.zigbrains.debugger.settings.MSVCDownloadPermission;
import com.falsepattern.zigbrains.debugger.settings.ZigDebuggerSettings;
import com.intellij.notification.Notification; import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType; import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications; import com.intellij.notification.Notifications;
@ -17,7 +15,6 @@ import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil;
import com.intellij.ui.BrowserHyperlinkListener; import com.intellij.ui.BrowserHyperlinkListener;
import com.intellij.ui.HyperlinkLabel; import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBPanel; import com.intellij.ui.components.JBPanel;
import com.intellij.util.download.DownloadableFileService; import com.intellij.util.download.DownloadableFileService;
import com.intellij.util.io.Decompressor; import com.intellij.util.io.Decompressor;
@ -42,9 +39,6 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@Service(Service.Level.APP) @Service(Service.Level.APP)

View file

@ -62,7 +62,7 @@ public abstract class ProfileStateBase<T extends ZigExecConfigBase<T>> extends C
workingDirectory.getPath().ifPresent(x -> cli.setWorkDirectory(x.toFile())); workingDirectory.getPath().ifPresent(x -> cli.setWorkDirectory(x.toFile()));
cli.setCharset(StandardCharsets.UTF_8); cli.setCharset(StandardCharsets.UTF_8);
cli.addParameters(configuration.buildCommandLineArgs(debug)); cli.addParameters(configuration.buildCommandLineArgs(debug));
return cli; return configuration.patchCommandLine(cli, toolchain);
} }
public T configuration() { public T configuration() {

View file

@ -16,6 +16,7 @@
package com.falsepattern.zigbrains.project.execution.base; package com.falsepattern.zigbrains.project.execution.base;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.project.ui.WorkingDirectoryComponent; import com.falsepattern.zigbrains.project.ui.WorkingDirectoryComponent;
import com.falsepattern.zigbrains.project.ui.ZigFilePathPanel; import com.falsepattern.zigbrains.project.ui.ZigFilePathPanel;
import com.falsepattern.zigbrains.project.util.CLIUtil; import com.falsepattern.zigbrains.project.util.CLIUtil;
@ -23,6 +24,7 @@ import com.falsepattern.zigbrains.project.util.ElementUtil;
import com.intellij.openapi.Disposable; import com.intellij.openapi.Disposable;
import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.ui.ComboBox;
import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBTextField; import com.intellij.ui.components.JBTextField;
@ -358,6 +360,10 @@ public class ZigConfigEditor<T extends ZigExecConfigBase<T>> extends SettingsEdi
return new CheckboxConfigurable(serializedName, "Colored terminal", true); return new CheckboxConfigurable(serializedName, "Colored terminal", true);
} }
public static CheckboxConfigurable direnvConfigurable(String serializedName, Project project) {
return new CheckboxConfigurable(serializedName, "Use direnv", ZigProjectSettingsService.getInstance(project).getState().direnv);
}
@RequiredArgsConstructor @RequiredArgsConstructor
public static class OptimizationConfigurable implements ZigConfigurable<OptimizationConfigurable> { public static class OptimizationConfigurable implements ZigConfigurable<OptimizationConfigurable> {
private transient final String serializedName; private transient final String serializedName;

View file

@ -16,10 +16,13 @@
package com.falsepattern.zigbrains.project.execution.base; package com.falsepattern.zigbrains.project.execution.base;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.ProjectUtil; import com.falsepattern.zigbrains.project.util.ProjectUtil;
import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionException;
import com.intellij.execution.Executor; import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.LocatableConfigurationBase; import com.intellij.execution.configurations.LocatableConfigurationBase;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
@ -34,13 +37,17 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import static com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor.direnvConfigurable;
@Getter @Getter
public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends LocatableConfigurationBase<ProfileStateBase<T>> { public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends LocatableConfigurationBase<ProfileStateBase<T>> {
private ZigConfigEditor.WorkDirectoryConfigurable workingDirectory = new ZigConfigEditor.WorkDirectoryConfigurable("workingDirectory"); private ZigConfigEditor.WorkDirectoryConfigurable workingDirectory = new ZigConfigEditor.WorkDirectoryConfigurable("workingDirectory");
private ZigConfigEditor.CheckboxConfigurable pty = new ZigConfigEditor.CheckboxConfigurable("pty", "Emulate Terminal", false); private ZigConfigEditor.CheckboxConfigurable pty = new ZigConfigEditor.CheckboxConfigurable("pty", "Emulate Terminal", false);
private ZigConfigEditor.CheckboxConfigurable direnv;
public ZigExecConfigBase(@NotNull Project project, @NotNull ConfigurationFactory factory, @Nullable String name) { public ZigExecConfigBase(@NotNull Project project, @NotNull ConfigurationFactory factory, @Nullable String name) {
super(project, factory, name); super(project, factory, name);
workingDirectory.setPath(getProject().isDefault() ? null : ProjectUtil.guessProjectDir(getProject())); workingDirectory.setPath(getProject().isDefault() ? null : ProjectUtil.guessProjectDir(getProject()));
direnv = direnvConfigurable("direnv", project);
} }
@Override @Override
@ -62,6 +69,13 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
public abstract List<String> buildCommandLineArgs(boolean debug) throws ExecutionException; public abstract List<String> buildCommandLineArgs(boolean debug) throws ExecutionException;
public @NotNull GeneralCommandLine patchCommandLine(@NotNull GeneralCommandLine commandLine, @NotNull AbstractZigToolchain toolchain) {
if (direnv.value) {
commandLine.withEnvironment(DirenvCmd.tryGetProjectEnvSync(toolchain.getProject()));
}
return commandLine;
}
public boolean emulateTerminal() { public boolean emulateTerminal() {
return pty.value; return pty.value;
} }
@ -71,6 +85,7 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
val myClone = (ZigExecConfigBase<?>) super.clone(); val myClone = (ZigExecConfigBase<?>) super.clone();
myClone.workingDirectory = workingDirectory.clone(); myClone.workingDirectory = workingDirectory.clone();
myClone.pty = pty.clone(); myClone.pty = pty.clone();
myClone.direnv = direnv.clone();
return (T) myClone; return (T) myClone;
} }
@ -82,6 +97,6 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
throws ExecutionException; throws ExecutionException;
public @NotNull List<ZigConfigEditor.@NotNull ZigConfigurable<?>> getConfigurables() { public @NotNull List<ZigConfigEditor.@NotNull ZigConfigurable<?>> getConfigurables() {
return List.of(workingDirectory, pty); return List.of(workingDirectory, pty, direnv);
} }
} }

View file

@ -47,8 +47,8 @@ public class ZigNewProjectPanel implements Disposable {
public ZigNewProjectPanel(boolean handleGit) { public ZigNewProjectPanel(boolean handleGit) {
this.handleGit = handleGit; this.handleGit = handleGit;
projConf = new ZigProjectSettingsPanel(); projConf = new ZigProjectSettingsPanel(null);
zlsConf = new ZLSSettingsPanel(); zlsConf = new ZLSSettingsPanel(null);
} }
public ZigProjectConfigurationData getData() { public ZigProjectConfigurationData getData() {

View file

@ -53,7 +53,7 @@ public record ZigProjectConfigurationData(boolean git, ZigProjectSettings projCo
svc.loadState(this.projConf()); svc.loadState(this.projConf());
ZLSProjectSettingsService.getInstance(project).loadState(this.zlsConf()); ZLSProjectSettingsService.getInstance(project).loadState(this.zlsConf());
val toolchain = svc.getState().getToolchain(); val toolchain = svc.getState().getToolchain(project);
val template = this.selectedTemplate(); val template = this.selectedTemplate();
@ -65,7 +65,7 @@ public record ZigProjectConfigurationData(boolean git, ZigProjectSettings projCo
return; return;
} }
val zig = toolchain.zig(); val zig = toolchain.zig();
val resultOpt = zig.callWithArgs(baseDir.toNioPath(), 10000, "init"); val resultOpt = zig.callWithArgs(baseDir.toNioPath(), 10000, toolchain.getDataForSelfRuns(), "init");
if (resultOpt.isEmpty()) { if (resultOpt.isEmpty()) {
Notifications.Bus.notify(new Notification("ZigBrains.Project", Notifications.Bus.notify(new Notification("ZigBrains.Project",
"Failed to invoke \"zig init\"!", "Failed to invoke \"zig init\"!",

View file

@ -37,7 +37,7 @@ public class ZigProjectConfigurable implements SubConfigurable {
@Override @Override
public void createComponent(JavaPanel panel) { public void createComponent(JavaPanel panel) {
settingsPanel = new ZigProjectSettingsPanel(); settingsPanel = new ZigProjectSettingsPanel(project);
settingsPanel.attachPanelTo(panel); settingsPanel.attachPanelTo(panel);
} }

View file

@ -16,6 +16,7 @@
package com.falsepattern.zigbrains.project.ide.project; package com.falsepattern.zigbrains.project.ide.project;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.falsepattern.zigbrains.common.util.PathUtil; import com.falsepattern.zigbrains.common.util.PathUtil;
import com.falsepattern.zigbrains.common.util.StringUtil; import com.falsepattern.zigbrains.common.util.StringUtil;
import com.falsepattern.zigbrains.common.util.TextFieldUtil; import com.falsepattern.zigbrains.common.util.TextFieldUtil;
@ -27,15 +28,18 @@ import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsS
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider; import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider;
import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool; import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.ui.JBColor; import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.dsl.builder.AlignX; import com.intellij.ui.dsl.builder.AlignX;
import lombok.Getter; import lombok.Getter;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.Nullable;
import javax.swing.JLabel; import javax.swing.JLabel;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
@ -44,11 +48,14 @@ import java.util.Optional;
import static com.falsepattern.zigbrains.common.util.KtUtil.$f; import static com.falsepattern.zigbrains.common.util.KtUtil.$f;
public class ZigProjectSettingsPanel implements MyDisposable { public class ZigProjectSettingsPanel implements MyDisposable {
private final @Nullable Project project;
@Getter @Getter
private boolean disposed = false; private boolean disposed = false;
private final UIDebouncer versionUpdateDebouncer = new UIDebouncer(this); private final UIDebouncer versionUpdateDebouncer = new UIDebouncer(this);
private final JBCheckBox direnv = new JBCheckBox("Use direnv");
private final TextFieldWithBrowseButton pathToToolchain = TextFieldUtil.pathToDirectoryTextField(this, private final TextFieldWithBrowseButton pathToToolchain = TextFieldUtil.pathToDirectoryTextField(this,
"Path to the Zig Toolchain", "Path to the Zig Toolchain",
this::updateUI); this::updateUI);
@ -72,27 +79,44 @@ public class ZigProjectSettingsPanel implements MyDisposable {
}); });
} }
public ZigProjectSettingsPanel(@Nullable Project project) {
this.project = project;
}
private void autodetect(ActionEvent e) { private void autodetect(ActionEvent e) {
autodetect(); autodetect();
} }
public void autodetect() { public void autodetect() {
val tc = AbstractZigToolchain.suggest(); val data = new UserDataHolderBase();
if (tc != null) { data.putUserData(DirenvCmd.DIRENV_KEY, direnv.isSelected());
pathToToolchain.setText(PathUtil.stringFromPath(tc.getLocation())); if (project != null) {
updateUI(); data.putUserData(AbstractZigToolchain.PROJECT_KEY, project);
} }
val tc = AbstractZigToolchain.suggest(data);
tc.thenAccept(it -> {
if (it != null) {
if (!disposed) {
pathToToolchain.setText(PathUtil.stringFromPath(it.getLocation()));
updateUI();
}
}
});
} }
public ZigProjectSettings getData() { public ZigProjectSettings getData() {
val toolchain = Optional.of(pathToToolchain.getText()) val toolchain = Optional.of(pathToToolchain.getText())
.map(PathUtil::pathFromString) .map(PathUtil::pathFromString)
.map(ZigToolchainProvider::findToolchain) .map(path -> ZigToolchainProvider.findToolchain(path, project))
.orElse(null); .orElse(null);
return new ZigProjectSettings(stdFieldOverride.isSelected() ? StringUtil.blankToNull(pathToStdField.getText()) : null, toolchain); return new ZigProjectSettings(direnv.isSelected(),
stdFieldOverride.isSelected() ? StringUtil.blankToNull(pathToStdField.getText()) : null,
toolchain);
} }
public void setData(ZigProjectSettings value) { public void setData(ZigProjectSettings value) {
direnv.setSelected(value.direnv);
pathToToolchain.setText(Optional.ofNullable(value.getToolchainHomeDirectory()) pathToToolchain.setText(Optional.ofNullable(value.getToolchainHomeDirectory())
.orElse("")); .orElse(""));
@ -112,6 +136,9 @@ public class ZigProjectSettingsPanel implements MyDisposable {
p.group("Zig Settings", true, p2 -> { p.group("Zig Settings", true, p2 -> {
p2.row("Toolchain location", r -> { p2.row("Toolchain location", r -> {
r.cell(pathToToolchain).resizableColumn().align(AlignX.FILL); r.cell(pathToToolchain).resizableColumn().align(AlignX.FILL);
if (DirenvCmd.direnvInstalled() && project != null) {
r.cell(direnv);
}
r.button("Autodetect", $f(this::autodetect)); r.button("Autodetect", $f(this::autodetect));
}); });
p2.cell("Toolchain version", toolchainVersion); p2.cell("Toolchain version", toolchainVersion);
@ -133,7 +160,7 @@ public class ZigProjectSettingsPanel implements MyDisposable {
versionUpdateDebouncer.run( versionUpdateDebouncer.run(
() -> { () -> {
val toolchain = Optional.ofNullable(pathToToolchain).map(ZigToolchainProvider::findToolchain).orElse(null); val toolchain = Optional.ofNullable(pathToToolchain).map(path -> ZigToolchainProvider.findToolchain(path, project)).orElse(null);
val zig = Optional.ofNullable(toolchain).map(AbstractZigToolchain::zig).orElse(null); val zig = Optional.ofNullable(toolchain).map(AbstractZigToolchain::zig).orElse(null);
val version = Optional.ofNullable(zig).flatMap(ZigCompilerTool::queryVersion).orElse(null); val version = Optional.ofNullable(zig).flatMap(ZigCompilerTool::queryVersion).orElse(null);
val stdPath = Optional.ofNullable(zig).flatMap(ZigCompilerTool::getStdPath).orElse(null); val stdPath = Optional.ofNullable(zig).flatMap(ZigCompilerTool::getStdPath).orElse(null);

View file

@ -18,34 +18,38 @@ package com.falsepattern.zigbrains.project.openapi.components;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider; import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider;
import com.intellij.openapi.project.Project;
import com.intellij.util.io.PathKt; import com.intellij.util.io.PathKt;
import com.intellij.util.xmlb.annotations.Transient; import com.intellij.util.xmlb.annotations.Transient;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.nio.file.Path; import java.nio.file.Path;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor
public class ZigProjectSettings { public class ZigProjectSettings {
public boolean direnv;
public boolean overrideStdPath; public boolean overrideStdPath;
public String explicitPathToStd; public String explicitPathToStd;
public String toolchainHomeDirectory; public String toolchainHomeDirectory;
public ZigProjectSettings(String explicitPathToStd, AbstractZigToolchain toolchain) { public ZigProjectSettings(boolean direnv, String explicitPathToStd, AbstractZigToolchain toolchain) {
this(explicitPathToStd != null, explicitPathToStd, null); this(direnv, explicitPathToStd != null, explicitPathToStd, null);
setToolchain(toolchain); setToolchain(toolchain);
} }
public ZigProjectSettings() {
this(true, false, null, null);
}
@Transient @Transient
public @Nullable AbstractZigToolchain getToolchain() { public AbstractZigToolchain getToolchain(Project project) {
if (toolchainHomeDirectory == null) { if (toolchainHomeDirectory == null) {
return null; return null;
} }
return ZigToolchainProvider.findToolchain(Path.of(toolchainHomeDirectory)); return ZigToolchainProvider.findToolchain(Path.of(toolchainHomeDirectory), project);
} }
@Transient @Transient

View file

@ -40,8 +40,9 @@ public final class ZigProjectSettingsService extends AbstractZigProjectSettingsS
public boolean isModified(ZigProjectSettings otherData) { public boolean isModified(ZigProjectSettings otherData) {
val myData = getState(); val myData = getState();
return !Objects.equals(myData.toolchainHomeDirectory, otherData.toolchainHomeDirectory) || return myData.direnv != otherData.direnv ||
!Objects.equals(myData.toolchainHomeDirectory, otherData.toolchainHomeDirectory) ||
!Objects.equals(myData.explicitPathToStd, otherData.explicitPathToStd) || !Objects.equals(myData.explicitPathToStd, otherData.explicitPathToStd) ||
!Objects.equals(myData.overrideStdPath, otherData.overrideStdPath); myData.overrideStdPath != otherData.overrideStdPath;
} }
} }

View file

@ -51,7 +51,7 @@ public abstract class ZigProgramRunnerBase<ProfileState extends ProfileStateBase
if (state == null) if (state == null)
return Promises.resolvedPromise(); return Promises.resolvedPromise();
val toolchain = ZigProjectSettingsService.getInstance(environment.getProject()).getState().getToolchain(); val toolchain = ZigProjectSettingsService.getInstance(environment.getProject()).getState().getToolchain(environment.getProject());
if (toolchain == null) { if (toolchain == null) {
return Promises.resolvedPromise(); return Promises.resolvedPromise();
} }

View file

@ -45,6 +45,7 @@ public final class ZigStepDiscoveryService {
val zig = toolchain.zig(); val zig = toolchain.zig();
val result = zig.callWithArgs( val result = zig.callWithArgs(
ProjectUtil.guessProjectDir(project), CURRENT_TIMEOUT_SEC * 1000, ProjectUtil.guessProjectDir(project), CURRENT_TIMEOUT_SEC * 1000,
toolchain.getDataForSelfRuns(),
"build", "-l"); "build", "-l");
if (result.isPresent()) { if (result.isPresent()) {
val res = result.get(); val res = result.get();

View file

@ -20,32 +20,57 @@ import com.falsepattern.zigbrains.common.util.Lazy;
import com.falsepattern.zigbrains.project.toolchain.flavours.AbstractZigToolchainFlavour; import com.falsepattern.zigbrains.project.toolchain.flavours.AbstractZigToolchainFlavour;
import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool; import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool;
import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.util.concurrency.AppExecutorUtil;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture;
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
public abstract class AbstractZigToolchain { public abstract class AbstractZigToolchain {
private final Path location; private final Path location;
private final @Nullable Project project;
private final Lazy<ZigCompilerTool> zig = new Lazy<>(() -> new ZigCompilerTool(this)); private final Lazy<ZigCompilerTool> zig = new Lazy<>(() -> new ZigCompilerTool(this));
public static final Key<Path> WORK_DIR_KEY = Key.create("ZIG_TOOLCHAIN_WORK_DIR");
public static final Key<Project> PROJECT_KEY = Key.create("ZIG_TOOLCHAIN_PROJECT");
public static @Nullable AbstractZigToolchain suggest() { public static @NotNull CompletableFuture<@Nullable AbstractZigToolchain> suggest(@NotNull UserDataHolder workDir) {
return suggest(null); val project = workDir.getUserData(PROJECT_KEY);
} if (workDir.getUserData(WORK_DIR_KEY) == null) {
if (project != null) {
public static @Nullable AbstractZigToolchain suggest(@Nullable Path projectDir) { val projectDir = ProjectUtil.guessProjectDir(project);
return AbstractZigToolchainFlavour.getApplicableFlavours() if (projectDir != null) {
.stream() workDir.putUserData(WORK_DIR_KEY, projectDir.toNioPath());
.flatMap(it -> it.suggestHomePaths().stream()) }
.filter(Objects::nonNull) }
.map(it -> ZigToolchainProvider.findToolchain(it.toAbsolutePath())) }
.filter(Objects::nonNull) val exec = AppExecutorUtil.getAppExecutorService();
.findFirst().orElse(null); val homePathFutures = AbstractZigToolchainFlavour.getApplicableFlavours()
.stream()
.map(it -> it.suggestHomePaths(workDir))
.toList();
return CompletableFuture.allOf(homePathFutures.toArray(CompletableFuture[]::new))
.thenApplyAsync(it -> homePathFutures.stream()
.map(CompletableFuture::join)
.flatMap(Collection::stream)
.filter(Objects::nonNull)
.map((dir) -> ZigToolchainProvider.findToolchain(dir, project))
.filter(Objects::nonNull)
.findFirst()
.orElse(null), exec);
} }
public ZigCompilerTool zig() { public ZigCompilerTool zig() {
@ -54,10 +79,14 @@ public abstract class AbstractZigToolchain {
public abstract int executionTimeoutInMilliseconds(); public abstract int executionTimeoutInMilliseconds();
public abstract GeneralCommandLine patchCommandLine(GeneralCommandLine commandLine); public abstract @NotNull GeneralCommandLine patchCommandLine(@NotNull GeneralCommandLine commandLine, @NotNull UserDataHolder data);
public abstract Path pathToExecutable(String toolName); public abstract Path pathToExecutable(String toolName);
public @NotNull UserDataHolder getDataForSelfRuns() {
return new UserDataHolderBase();
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof AbstractZigToolchain azt) { if (obj instanceof AbstractZigToolchain azt) {

View file

@ -16,15 +16,22 @@
package com.falsepattern.zigbrains.project.toolchain; package com.falsepattern.zigbrains.project.toolchain;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.falsepattern.zigbrains.common.util.PathUtil; import com.falsepattern.zigbrains.common.util.PathUtil;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.UserDataHolder;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path; import java.nio.file.Path;
public class LocalZigToolchain extends AbstractZigToolchain{ public class LocalZigToolchain extends AbstractZigToolchain{
public LocalZigToolchain(Path location) { public LocalZigToolchain(Path location, @Nullable Project project) {
super(location); super(location, project);
} }
@Override @Override
@ -33,7 +40,12 @@ public class LocalZigToolchain extends AbstractZigToolchain{
} }
@Override @Override
public GeneralCommandLine patchCommandLine(GeneralCommandLine commandLine) { public @NotNull GeneralCommandLine patchCommandLine(@NotNull GeneralCommandLine commandLine, @NotNull UserDataHolder data) {
val project = getProject();
val direnv = data.getUserData(DirenvCmd.DIRENV_KEY);
if (direnv != null && direnv) {
commandLine.withEnvironment(DirenvCmd.tryGetProjectEnvSync(project));
}
return commandLine; return commandLine;
} }
@ -42,6 +54,16 @@ public class LocalZigToolchain extends AbstractZigToolchain{
return PathUtil.pathToExecutable(getLocation(), toolName); return PathUtil.pathToExecutable(getLocation(), toolName);
} }
@Override
public @NotNull UserDataHolder getDataForSelfRuns() {
val data = super.getDataForSelfRuns();
val project = getProject();
if (project != null) {
data.putUserData(DirenvCmd.DIRENV_KEY, ZigProjectSettingsService.getInstance(project).getState().direnv);
}
return data;
}
public static LocalZigToolchain ensureLocal(AbstractZigToolchain toolchain) throws ExecutionException { public static LocalZigToolchain ensureLocal(AbstractZigToolchain toolchain) throws ExecutionException {
if (!(toolchain instanceof LocalZigToolchain $toolchain)) { if (!(toolchain instanceof LocalZigToolchain $toolchain)) {
throw new ExecutionException("The debugger only supports local zig toolchains!"); throw new ExecutionException("The debugger only supports local zig toolchains!");

View file

@ -17,6 +17,7 @@
package com.falsepattern.zigbrains.project.toolchain; package com.falsepattern.zigbrains.project.toolchain;
import com.intellij.execution.wsl.WslPath; import com.intellij.execution.wsl.WslPath;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.SystemInfo;
import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -27,11 +28,11 @@ import java.util.Map;
public class LocalZigToolchainProvider implements ZigToolchainProvider { public class LocalZigToolchainProvider implements ZigToolchainProvider {
private static final Map<Path, LocalZigToolchain> tcCache = ContainerUtil.createWeakKeyWeakValueMap(); private static final Map<Path, LocalZigToolchain> tcCache = ContainerUtil.createWeakKeyWeakValueMap();
@Override @Override
public @Nullable AbstractZigToolchain getToolchain(Path homePath) { public @Nullable AbstractZigToolchain getToolchain(Path homePath, @Nullable Project project) {
if (SystemInfo.isWindows && WslPath.isWslUncPath(homePath.toString())) { if (SystemInfo.isWindows && WslPath.isWslUncPath(homePath.toString())) {
return null; return null;
} }
return tcCache.computeIfAbsent(homePath, LocalZigToolchain::new); return tcCache.computeIfAbsent(homePath, (path) -> new LocalZigToolchain(path, project));
} }
} }

View file

@ -16,6 +16,7 @@
package com.falsepattern.zigbrains.project.toolchain; package com.falsepattern.zigbrains.project.toolchain;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService; import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.zig.environment.ZLSConfig; import com.falsepattern.zigbrains.zig.environment.ZLSConfig;
import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider; import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider;
@ -24,6 +25,7 @@ import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications; import com.intellij.notification.Notifications;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.UserDataHolderBase;
import lombok.val; import lombok.val;
import java.nio.file.Files; import java.nio.file.Files;
@ -35,16 +37,20 @@ public class ToolchainZLSConfigProvider implements ZLSConfigProvider {
public void getEnvironment(Project project, ZLSConfig.ZLSConfigBuilder builder) { public void getEnvironment(Project project, ZLSConfig.ZLSConfigBuilder builder) {
val svc = ZigProjectSettingsService.getInstance(project); val svc = ZigProjectSettingsService.getInstance(project);
val state = svc.getState(); val state = svc.getState();
var toolchain = state.getToolchain(); var toolchain = state.getToolchain(project);
if (toolchain == null) { if (toolchain == null) {
toolchain = AbstractZigToolchain.suggest(); val data = new UserDataHolderBase();
data.putUserData(AbstractZigToolchain.PROJECT_KEY, project);
data.putUserData(DirenvCmd.DIRENV_KEY, svc.getState().direnv);
toolchain = AbstractZigToolchain.suggest(data).join();
if (toolchain == null) { if (toolchain == null) {
return; return;
} }
state.setToolchain(toolchain); state.setToolchain(toolchain);
} }
val projectDir = ProjectUtil.guessProjectDir(project); val projectDir = ProjectUtil.guessProjectDir(project);
val oEnv = toolchain.zig().getEnv(projectDir == null ? null : projectDir.toNioPath()); val oEnv = toolchain.zig().getEnv();
if (oEnv.isEmpty()) { if (oEnv.isEmpty()) {
return; return;
} }

View file

@ -17,6 +17,7 @@
package com.falsepattern.zigbrains.project.toolchain; package com.falsepattern.zigbrains.project.toolchain;
import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.nio.file.Path; import java.nio.file.Path;
@ -26,14 +27,14 @@ public interface ZigToolchainProvider {
ExtensionPointName<ZigToolchainProvider> EXTENSION_POINT_NAME = ExtensionPointName<ZigToolchainProvider> EXTENSION_POINT_NAME =
ExtensionPointName.create("com.falsepattern.zigbrains.toolchainProvider"); ExtensionPointName.create("com.falsepattern.zigbrains.toolchainProvider");
static @Nullable AbstractZigToolchain findToolchain(Path homePath) { static @Nullable AbstractZigToolchain findToolchain(Path homePath, @Nullable Project project) {
return EXTENSION_POINT_NAME.getExtensionList() return EXTENSION_POINT_NAME.getExtensionList()
.stream() .stream()
.map(it -> it.getToolchain(homePath)) .map(it -> it.getToolchain(homePath, project))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.findFirst() .findFirst()
.orElse(null); .orElse(null);
} }
@Nullable AbstractZigToolchain getToolchain(Path homePath); @Nullable AbstractZigToolchain getToolchain(Path homePath, @Nullable Project project);
} }

View file

@ -19,12 +19,17 @@ package com.falsepattern.zigbrains.project.toolchain.flavours;
import com.falsepattern.zigbrains.common.util.PathUtil; import com.falsepattern.zigbrains.common.util.PathUtil;
import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool; import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool;
import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.util.concurrency.AppExecutorUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
public abstract class AbstractZigToolchainFlavour { public abstract class AbstractZigToolchainFlavour {
private static final ExtensionPointName<AbstractZigToolchainFlavour> EXTENSION_POINT_NAME = private static final ExtensionPointName<AbstractZigToolchainFlavour> EXTENSION_POINT_NAME =
@ -44,13 +49,14 @@ public abstract class AbstractZigToolchainFlavour {
.orElse(null); .orElse(null);
} }
public List<Path> suggestHomePaths() { public CompletableFuture<List<Path>> suggestHomePaths(@NotNull UserDataHolder data) {
return getHomePathCandidates().stream() return getHomePathCandidates(data).thenApplyAsync(it -> it.stream()
.filter(this::isValidToolchainPath) .filter(this::isValidToolchainPath)
.toList(); .toList(),
AppExecutorUtil.getAppExecutorService());
} }
protected abstract List<Path> getHomePathCandidates(); protected abstract CompletableFuture<List<Path>> getHomePathCandidates(@NotNull UserDataHolder data);
protected boolean isApplicable() { protected boolean isApplicable() {
return true; return true;

View file

@ -16,7 +16,10 @@
package com.falsepattern.zigbrains.project.toolchain.flavours; package com.falsepattern.zigbrains.project.toolchain.flavours;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.intellij.openapi.util.UserDataHolder;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
@ -24,18 +27,33 @@ import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain.WORK_DIR_KEY;
public class ZigSystemPathToolchainFlavour extends AbstractZigToolchainFlavour{ public class ZigSystemPathToolchainFlavour extends AbstractZigToolchainFlavour{
@Override @Override
protected List<Path> getHomePathCandidates() { protected CompletableFuture<List<Path>> getHomePathCandidates(@NotNull UserDataHolder data) {
val PATH = System.getenv("PATH"); val workDir = data.getUserData(WORK_DIR_KEY);
if (PATH == null) { val direnv = data.getUserData(DirenvCmd.DIRENV_KEY);
return Collections.emptyList(); CompletableFuture<Map<String, String>> direnvFuture;
if (direnv == Boolean.TRUE && DirenvCmd.direnvInstalled() && workDir != null) {
val cmd = new DirenvCmd(workDir);
direnvFuture = cmd.importDirenvAsync();
} else {
direnvFuture = CompletableFuture.completedFuture(Map.of());
} }
return Arrays.stream(PATH.split(File.pathSeparator)) return direnvFuture.thenApplyAsync(env -> {
.filter(it -> !it.isEmpty()) val PATH = env.getOrDefault("PATH", System.getenv("PATH"));
.map(Path::of) if (PATH == null) {
.filter(Files::isDirectory) return Collections.emptyList();
.toList(); }
return Arrays.stream(PATH.split(File.pathSeparator))
.filter(it -> !it.isEmpty())
.map(Path::of)
.filter(Files::isDirectory)
.toList();
});
} }
} }

View file

@ -16,7 +16,6 @@
package com.falsepattern.zigbrains.project.toolchain.stdlib; package com.falsepattern.zigbrains.project.toolchain.stdlib;
import com.falsepattern.zigbrains.common.util.PathUtil;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService; import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.zig.Icons; import com.falsepattern.zigbrains.zig.Icons;
import com.intellij.navigation.ItemPresentation; import com.intellij.navigation.ItemPresentation;
@ -51,7 +50,7 @@ public class ZigSyntheticLibrary extends SyntheticLibrary implements ItemPresent
val service = ZigProjectSettingsService.getInstance(project); val service = ZigProjectSettingsService.getInstance(project);
val state = service.getState(); val state = service.getState();
this.roots = ApplicationManager.getApplication().executeOnPooledThread(() -> { this.roots = ApplicationManager.getApplication().executeOnPooledThread(() -> {
val toolchain = state.getToolchain(); val toolchain = state.getToolchain(project);
blk: try { blk: try {
val ePathStr = state.getExplicitPathToStd(); val ePathStr = state.getExplicitPathToStd();
if (ePathStr == null) { if (ePathStr == null) {
@ -84,7 +83,7 @@ public class ZigSyntheticLibrary extends SyntheticLibrary implements ItemPresent
}); });
this.name = ApplicationManager.getApplication() this.name = ApplicationManager.getApplication()
.executeOnPooledThread(() -> Optional.ofNullable(state.getToolchain()) .executeOnPooledThread(() -> Optional.ofNullable(state.getToolchain(project))
.flatMap(tc -> tc.zig().queryVersion()) .flatMap(tc -> tc.zig().queryVersion())
.map(version -> "Zig " + version) .map(version -> "Zig " + version)
.orElse("Zig")); .orElse("Zig"));

View file

@ -20,6 +20,7 @@ import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.CLIUtil; import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.ProcessOutput; import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.util.UserDataHolder;
import kotlin.text.Charsets; import kotlin.text.Charsets;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.val; import lombok.val;
@ -41,35 +42,39 @@ public abstract class AbstractZigTool {
return toolchain.pathToExecutable(toolName); return toolchain.pathToExecutable(toolName);
} }
public final Optional<ProcessOutput> callWithArgs(@Nullable Path workingDirectory, int timeoutMillis, String... parameters) { public final Optional<ProcessOutput> callWithArgs(@Nullable Path workingDirectory, int timeoutMillis, @NotNull UserDataHolder data, String @NotNull... parameters) {
return CLIUtil.execute(createBaseCommandLine(workingDirectory, parameters), return CLIUtil.execute(createBaseCommandLine(workingDirectory, data, parameters),
timeoutMillis); timeoutMillis);
} }
protected final GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory, protected final GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory,
@NotNull UserDataHolder data,
String @NotNull... parameters) { String @NotNull... parameters) {
return createBaseCommandLine(workingDirectory, Collections.emptyMap(), parameters); return createBaseCommandLine(workingDirectory, Collections.emptyMap(), data, parameters);
} }
protected final GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory, protected final GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory,
@NotNull Map<String, String> environment, @NotNull Map<String, String> environment,
@NotNull UserDataHolder data,
String @NotNull... parameters) { String @NotNull... parameters) {
return createBaseCommandLine(workingDirectory, environment, List.of(parameters)); return createBaseCommandLine(workingDirectory, environment, data, List.of(parameters));
} }
protected final GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory, protected final GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory,
@NotNull UserDataHolder data,
@NotNull List<String> parameters) { @NotNull List<String> parameters) {
return createBaseCommandLine(workingDirectory, Collections.emptyMap(), parameters); return createBaseCommandLine(workingDirectory, Collections.emptyMap(), data, parameters);
} }
protected GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory, protected GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory,
@NotNull Map<String, String> environment, @NotNull Map<String, String> environment,
@NotNull UserDataHolder data,
@NotNull List<String> parameters) { @NotNull List<String> parameters) {
val cli = new GeneralCommandLine(executable().toString()) val cli = new GeneralCommandLine(executable().toString())
.withWorkDirectory(workingDirectory == null ? null : workingDirectory.toString()) .withWorkDirectory(workingDirectory == null ? null : workingDirectory.toString())
.withParameters(parameters) .withParameters(parameters)
.withEnvironment(environment) .withEnvironment(environment)
.withCharset(Charsets.UTF_8); .withCharset(Charsets.UTF_8);
return toolchain.patchCommandLine(cli); return toolchain.patchCommandLine(cli, data);
} }
} }

View file

@ -23,7 +23,6 @@ import com.google.gson.Gson;
import com.intellij.execution.process.ProcessOutput; import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.Nullable;
import java.nio.file.InvalidPathException; import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
@ -38,7 +37,7 @@ public class ZigCompilerTool extends AbstractZigTool{
public ZigCompilerTool(AbstractZigToolchain toolchain) { public ZigCompilerTool(AbstractZigToolchain toolchain) {
super(toolchain, TOOL_NAME); super(toolchain, TOOL_NAME);
val app = ApplicationManager.getApplication(); val app = ApplicationManager.getApplication();
val baseFuture = app.executeOnPooledThread(() -> getEnv(toolchain.getLocation())); val baseFuture = app.executeOnPooledThread(() -> getEnv());
version = new Lazy<>(() -> { version = new Lazy<>(() -> {
try { try {
return baseFuture.get().map(ZigToolchainEnvironmentSerializable::version); return baseFuture.get().map(ZigToolchainEnvironmentSerializable::version);
@ -70,8 +69,8 @@ public class ZigCompilerTool extends AbstractZigTool{
}); });
} }
public Optional<ZigToolchainEnvironmentSerializable> getEnv(@Nullable Path workingDirectory) { public Optional<ZigToolchainEnvironmentSerializable> getEnv() {
return callWithArgs(workingDirectory, toolchain.executionTimeoutInMilliseconds(), "env") return callWithArgs(toolchain.getLocation(), toolchain.executionTimeoutInMilliseconds(), toolchain.getDataForSelfRuns(), "env")
.map(ProcessOutput::getStdoutLines) .map(ProcessOutput::getStdoutLines)
.map(lines -> new Gson().fromJson(String.join(" ", lines), ZigToolchainEnvironmentSerializable.class)); .map(lines -> new Gson().fromJson(String.join(" ", lines), ZigToolchainEnvironmentSerializable.class));

View file

@ -26,7 +26,7 @@ import java.nio.file.Path;
public class ProjectUtil { public class ProjectUtil {
public static @Nullable AbstractZigToolchain getToolchain(Project project) { public static @Nullable AbstractZigToolchain getToolchain(Project project) {
return ZigProjectSettingsService.getInstance(project).getState().getToolchain(); return ZigProjectSettingsService.getInstance(project).getState().getToolchain(project);
} }
public static @Nullable Path guessProjectDir(Project project) { public static @Nullable Path guessProjectDir(Project project) {

View file

@ -3,35 +3,18 @@ package com.falsepattern.zigbrains.zig.editing;
import com.falsepattern.zigbrains.zig.parser.ZigFile; import com.falsepattern.zigbrains.zig.parser.ZigFile;
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral; import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
import com.falsepattern.zigbrains.zig.psi.ZigTypes; import com.falsepattern.zigbrains.zig.psi.ZigTypes;
import com.falsepattern.zigbrains.zig.stringlexer.ZigStringLexer;
import com.falsepattern.zigbrains.zig.util.PsiTextUtil; import com.falsepattern.zigbrains.zig.util.PsiTextUtil;
import com.falsepattern.zigbrains.zig.util.ZigStringUtil;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.editorActions.JavaLikeQuoteHandler;
import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter; import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter;
import com.intellij.lang.ASTNode;
import com.intellij.lexer.StringLiteralLexer;
import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler; import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.psi.StringEscapesTokenTypes;
import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.tree.IElementType;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.StringReader;
import java.util.ArrayList;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ZigEnterInQuotedStringHandler extends EnterHandlerDelegateAdapter { public class ZigEnterInQuotedStringHandler extends EnterHandlerDelegateAdapter {
@Override @Override

View file

@ -17,7 +17,6 @@
package com.falsepattern.zigbrains.zig.highlighter; package com.falsepattern.zigbrains.zig.highlighter;
import com.falsepattern.zigbrains.zig.lexer.ZigHighlightingLexer; import com.falsepattern.zigbrains.zig.lexer.ZigHighlightingLexer;
import com.falsepattern.zigbrains.zig.lexer.ZigLexerAdapter;
import com.falsepattern.zigbrains.zig.psi.ZigTypes; import com.falsepattern.zigbrains.zig.psi.ZigTypes;
import com.intellij.lexer.Lexer; import com.intellij.lexer.Lexer;
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors; import com.intellij.openapi.editor.DefaultLanguageHighlighterColors;

View file

@ -1,7 +1,6 @@
package com.falsepattern.zigbrains.zig.intentions; package com.falsepattern.zigbrains.zig.intentions;
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral; import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
import com.falsepattern.zigbrains.zig.psi.ZigTypes;
import com.falsepattern.zigbrains.zig.util.PsiTextUtil; import com.falsepattern.zigbrains.zig.util.PsiTextUtil;
import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
@ -10,7 +9,6 @@ import com.intellij.codeInspection.util.IntentionName;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException; import com.intellij.util.IncorrectOperationException;
import lombok.val; import lombok.val;

View file

@ -1,24 +1,17 @@
package com.falsepattern.zigbrains.zig.intentions; package com.falsepattern.zigbrains.zig.intentions;
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral; import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
import com.falsepattern.zigbrains.zig.util.PsiTextUtil;
import com.falsepattern.zigbrains.zig.util.ZigStringUtil; import com.falsepattern.zigbrains.zig.util.ZigStringUtil;
import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.codeInspection.util.IntentionFamilyName; import com.intellij.codeInspection.util.IntentionFamilyName;
import com.intellij.codeInspection.util.IntentionName; import com.intellij.codeInspection.util.IntentionName;
import com.intellij.formatting.CoreFormatterUtil;
import com.intellij.formatting.FormatterEx;
import com.intellij.formatting.FormattingModel;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException; import com.intellij.util.IncorrectOperationException;
import com.intellij.util.MathUtil;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;

View file

@ -1,8 +1,6 @@
package com.falsepattern.zigbrains.zig.lsp; package com.falsepattern.zigbrains.zig.lsp;
import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService; import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService;
import com.falsepattern.zigbrains.zig.settings.ZLSSettings;
import com.falsepattern.zigbrains.zig.settings.ZLSSettingsConfigProvider;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.redhat.devtools.lsp4ij.LanguageServerEnablementSupport; import com.redhat.devtools.lsp4ij.LanguageServerEnablementSupport;
@ -12,7 +10,6 @@ import com.redhat.devtools.lsp4ij.client.features.LSPClientFeatures;
import com.redhat.devtools.lsp4ij.client.features.LSPFormattingFeature; import com.redhat.devtools.lsp4ij.client.features.LSPFormattingFeature;
import com.redhat.devtools.lsp4ij.client.features.LSPInlayHintFeature; import com.redhat.devtools.lsp4ij.client.features.LSPInlayHintFeature;
import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider; import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider;
import com.redhat.devtools.lsp4ij.server.capabilities.InlayHintCapabilityRegistry;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -54,7 +51,7 @@ public class ZLSLanguageServerFactory implements LanguageServerFactory, Language
@Override @Override
public boolean isEnabled(@NotNull Project project) { public boolean isEnabled(@NotNull Project project) {
return enabled && ZLSStreamConnectionProvider.doGetCommand(project, false) != null; return enabled && ZLSStreamConnectionProvider.getCommandSync(project, false) != null;
} }
@Override @Override

View file

@ -10,8 +10,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.BUILTIN; import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.BUILTIN;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.COMMENT;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.COMMENT_DOC;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ENUM_DECL; import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ENUM_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ENUM_MEMBER_DECL; import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ENUM_MEMBER_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ENUM_MEMBER_REF; import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.ENUM_MEMBER_REF;
@ -36,7 +34,6 @@ import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.OP
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.PARAMETER; import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.PARAMETER;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.PROPERTY_DECL; import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.PROPERTY_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.PROPERTY_REF; import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.PROPERTY_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.STRING;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.STRUCT_DECL; import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.STRUCT_DECL;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.STRUCT_REF; import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.STRUCT_REF;
import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.TYPE_DECL; import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.TYPE_DECL;
@ -52,7 +49,6 @@ import static com.falsepattern.zigbrains.zig.highlighter.ZigSyntaxHighlighter.VA
import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenModifiers.Declaration; import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenModifiers.Declaration;
import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenModifiers.Definition; import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenModifiers.Definition;
import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenModifiers.Deprecated; import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenModifiers.Deprecated;
import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenModifiers.Documentation;
import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenModifiers.Generic; import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenModifiers.Generic;
import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenTypes.Builtin; import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenTypes.Builtin;
import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenTypes.Comment; import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenTypes.Comment;

View file

@ -1,5 +1,6 @@
package com.falsepattern.zigbrains.zig.lsp; package com.falsepattern.zigbrains.zig.lsp;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.falsepattern.zigbrains.common.util.StringUtil; import com.falsepattern.zigbrains.common.util.StringUtil;
import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider; import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider;
import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService; import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService;
@ -14,13 +15,15 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.redhat.devtools.lsp4ij.server.OSProcessStreamConnectionProvider; import com.redhat.devtools.lsp4ij.server.OSProcessStreamConnectionProvider;
import com.redhat.devtools.lsp4ij.server.ProcessStreamConnectionProvider;
import lombok.val; import lombok.val;
import org.eclipse.lsp4j.InlayHint; import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.jsonrpc.messages.Message; import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage; import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage;
import org.eclipse.lsp4j.services.LanguageServer; import org.eclipse.lsp4j.services.LanguageServer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -29,10 +32,9 @@ import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvider { public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvider {
@ -40,7 +42,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
private final Project project; private final Project project;
public ZLSStreamConnectionProvider(Project project) { public ZLSStreamConnectionProvider(Project project) {
this.project = project; this.project = project;
val command = getCommand(project); val command = getCommandAsync(project, true);
val projectDir = ProjectUtil.guessProjectDir(project); val projectDir = ProjectUtil.guessProjectDir(project);
GeneralCommandLine commandLine = null; GeneralCommandLine commandLine = null;
try { try {
@ -88,35 +90,62 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
} }
} }
public static List<String> doGetCommand(Project project, boolean safe) { private static @Nullable Path tryWorkDirFromProject(Project project) {
val projectDir = ProjectUtil.guessProjectDir(project);
if (projectDir == null) {
return null;
}
return projectDir.toNioPath();
}
private static @NotNull CompletableFuture<Map<String, String>> getDirenv(Project project) {
val workDir = tryWorkDirFromProject(project);
if (workDir == null)
return CompletableFuture.completedFuture(Map.of());
val direnvCmd = new DirenvCmd(workDir);
//Async if in dispatch thread, sync otherwise
return ApplicationManager.getApplication().isDispatchThread() ? direnvCmd.importDirenvAsync() : CompletableFuture.completedFuture(direnvCmd.importDirenvSync());
}
public static List<String> getCommandSync(Project project, boolean full) {
var svc = ZLSProjectSettingsService.getInstance(project); var svc = ZLSProjectSettingsService.getInstance(project);
val state = svc.getState(); val state = svc.getState();
var zlsPath = state.zlsPath; var zlsPath = state.zlsPath;
if (StringUtil.isEmpty(zlsPath)) { if (StringUtil.isEmpty(zlsPath)) {
zlsPath = com.falsepattern.zigbrains.common.util.FileUtil.findExecutableOnPATH("zls").map(Path::toString).orElse(null); Map<String, String> direnv = Map.of();
if (state.direnv && DirenvCmd.direnvInstalled()) {
try {
direnv = getDirenv(project).get();
} catch (InterruptedException | ExecutionException e) {
LOG.warn(e);
direnv = Map.of();
}
}
zlsPath = com.falsepattern.zigbrains.common.util.FileUtil.findExecutableOnPATH(direnv, "zls").map(Path::toString).orElse(null);
if (zlsPath == null) { if (zlsPath == null) {
if (safe) { if (full) {
Notifications.Bus.notify( Notifications.Bus.notify(
new Notification("ZigBrains.ZLS", "Could not detect ZLS binary! Please configure it!", new Notification("ZigBrains.ZLS", "Could not detect ZLS binary! Please configure it!",
NotificationType.ERROR)); NotificationType.ERROR));
} }
return null; return null;
} }
state.setZlsPath(zlsPath); val zlsPathFinal = zlsPath;
ApplicationManager.getApplication().invokeLater(() -> state.setZlsPath(zlsPathFinal));
} }
if (!validatePath("ZLS Binary", zlsPath, false, safe)) { if (!validatePath("ZLS Binary", zlsPath, false, full)) {
return null; return null;
} }
var configPath = state.zlsConfigPath; var configPath = state.zlsConfigPath;
boolean configOK = true; boolean configOK = true;
if (!configPath.isBlank() && !validatePath("ZLS Config", configPath, false, safe)) { if (!configPath.isBlank() && !validatePath("ZLS Config", configPath, false, full)) {
if (safe) { if (full) {
Notifications.Bus.notify( Notifications.Bus.notify(
new Notification("ZigBrains.ZLS", "Using default config path.", NotificationType.INFORMATION)); new Notification("ZigBrains.ZLS", "Using default config path.", NotificationType.INFORMATION));
} }
configPath = null; configPath = null;
} }
if ((configPath == null || configPath.isBlank()) && safe) { if ((configPath == null || configPath.isBlank()) && full) {
blk: blk:
try { try {
val tmpFile = FileUtil.createTempFile("zigbrains-zls-autoconf", ".json", true).toPath(); val tmpFile = FileUtil.createTempFile("zigbrains-zls-autoconf", ".json", true).toPath();
@ -133,7 +162,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
} }
configPath = tmpFile.toAbsolutePath().toString(); configPath = tmpFile.toAbsolutePath().toString();
} catch (IOException e) { } catch (IOException e) {
if (safe) { if (full) {
Notifications.Bus.notify( Notifications.Bus.notify(
new Notification("ZigBrains.ZLS", "Failed to create automatic zls config file", new Notification("ZigBrains.ZLS", "Failed to create automatic zls config file",
NotificationType.WARNING)); NotificationType.WARNING));
@ -166,19 +195,11 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
return cmd; return cmd;
} }
public static Future<List<String>> getCommand(Project project) { public static CompletableFuture<List<String>> getCommandAsync(Project project, boolean full) {
val future = new CompletableFuture<List<String>>(); return CompletableFuture.supplyAsync(() -> getCommandSync(project, full), AppExecutorUtil.getAppExecutorService());
ApplicationManager.getApplication().executeOnPooledThread(() -> {
try {
future.complete(doGetCommand(project, true));
} catch (Throwable t) {
future.completeExceptionally(t);
}
});
return future;
} }
private static boolean validatePath(String name, String pathTxt, boolean dir, boolean safe) { private static boolean validatePath(String name, String pathTxt, boolean dir, boolean full) {
if (pathTxt == null || pathTxt.isBlank()) { if (pathTxt == null || pathTxt.isBlank()) {
return false; return false;
} }
@ -186,7 +207,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
try { try {
path = Path.of(pathTxt); path = Path.of(pathTxt);
} catch (InvalidPathException e) { } catch (InvalidPathException e) {
if (safe) { if (full) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name, Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name,
"Invalid " + name + " at path \"" + pathTxt + "\"", "Invalid " + name + " at path \"" + pathTxt + "\"",
NotificationType.ERROR)); NotificationType.ERROR));
@ -194,7 +215,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
return false; return false;
} }
if (!Files.exists(path)) { if (!Files.exists(path)) {
if (safe) { if (full) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name, Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name,
"The " + name + " at \"" + pathTxt + "\" doesn't exist!", "The " + name + " at \"" + pathTxt + "\" doesn't exist!",
NotificationType.ERROR)); NotificationType.ERROR));
@ -202,7 +223,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
return false; return false;
} }
if (Files.isDirectory(path) != dir) { if (Files.isDirectory(path) != dir) {
if (safe) { if (full) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name, Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name,
"The " + name + " at \"" + pathTxt + "\" is a " + "The " + name + " at \"" + pathTxt + "\" is a " +
(Files.isDirectory(path) ? "directory" : "file") + (Files.isDirectory(path) ? "directory" : "file") +

View file

@ -11,9 +11,6 @@ import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.stream.Collectors;
public class ZigStringElementManipulator extends AbstractElementManipulator<ZigStringLiteral> { public class ZigStringElementManipulator extends AbstractElementManipulator<ZigStringLiteral> {
private enum InjectTriState { private enum InjectTriState {
NotYet, NotYet,

View file

@ -13,7 +13,6 @@ import com.intellij.psi.impl.source.tree.LeafElement;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public abstract class ZigStringLiteralMixinImpl extends ASTWrapperPsiElement implements ZigStringLiteral { public abstract class ZigStringLiteralMixinImpl extends ASTWrapperPsiElement implements ZigStringLiteral {

View file

@ -1,14 +1,7 @@
package com.falsepattern.zigbrains.zig.psi.mixins; package com.falsepattern.zigbrains.zig.psi.mixins;
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
import com.intellij.extapi.psi.ASTWrapperPsiElement;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.psi.LiteralTextEscaper;
import com.intellij.psi.PsiLanguageInjectionHost; import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.impl.source.tree.LeafElement;
import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.List; import java.util.List;

View file

@ -49,6 +49,7 @@ public final class ZLSProjectSettingsService extends WrappingStateComponent<ZLSS
modified |= myData.dangerousComptimeExperimentsDoNotEnable != otherData.dangerousComptimeExperimentsDoNotEnable; modified |= myData.dangerousComptimeExperimentsDoNotEnable != otherData.dangerousComptimeExperimentsDoNotEnable;
modified |= myData.inlayHints != otherData.inlayHints; modified |= myData.inlayHints != otherData.inlayHints;
modified |= myData.inlayHintsCompact != otherData.inlayHintsCompact; modified |= myData.inlayHintsCompact != otherData.inlayHintsCompact;
modified |= myData.direnv != otherData.direnv;
return modified; return modified;
} }
} }

View file

@ -24,6 +24,7 @@ import org.jetbrains.annotations.Nullable;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public final class ZLSSettings { public final class ZLSSettings {
public boolean direnv;
public @Nullable String zlsPath; public @Nullable String zlsPath;
public @NotNull String zlsConfigPath; public @NotNull String zlsConfigPath;
public boolean debug; public boolean debug;
@ -36,6 +37,6 @@ public final class ZLSSettings {
public boolean inlayHintsCompact; public boolean inlayHintsCompact;
public ZLSSettings() { public ZLSSettings() {
this(null, "", false, false, false, "install", false, false, true, true); this(true, null, "", false, false, false, "install", false, false, true, true);
} }
} }

View file

@ -34,7 +34,7 @@ public class ZLSSettingsConfigurable implements SubConfigurable {
@Override @Override
public void createComponent(JavaPanel panel) { public void createComponent(JavaPanel panel) {
appSettingsComponent = new ZLSSettingsPanel(); appSettingsComponent = new ZLSSettingsPanel(project);
appSettingsComponent.attachPanelTo(panel); appSettingsComponent.attachPanelTo(panel);
} }

View file

@ -16,26 +16,37 @@
package com.falsepattern.zigbrains.zig.settings; package com.falsepattern.zigbrains.zig.settings;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.falsepattern.zigbrains.common.util.FileUtil; import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.common.util.TextFieldUtil; import com.falsepattern.zigbrains.common.util.TextFieldUtil;
import com.falsepattern.zigbrains.common.util.dsl.JavaPanel; import com.falsepattern.zigbrains.common.util.dsl.JavaPanel;
import com.intellij.openapi.Disposable; import com.intellij.openapi.Disposable;
import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.ui.TextBrowseFolderListener; import com.intellij.openapi.ui.TextBrowseFolderListener;
import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBTextField; import com.intellij.ui.components.JBTextField;
import com.intellij.ui.components.fields.ExtendableTextField; import com.intellij.ui.components.fields.ExtendableTextField;
import com.intellij.ui.dsl.builder.AlignX; import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.util.concurrency.AppExecutorUtil;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static com.falsepattern.zigbrains.common.util.KtUtil.$f; import static com.falsepattern.zigbrains.common.util.KtUtil.$f;
public class ZLSSettingsPanel implements Disposable { public class ZLSSettingsPanel implements Disposable {
private final @Nullable Project project;
private final TextFieldWithBrowseButton zlsPath = TextFieldUtil.pathToFileTextField(this, private final TextFieldWithBrowseButton zlsPath = TextFieldUtil.pathToFileTextField(this,
"Path to the ZLS Binary", "Path to the ZLS Binary",
() -> {}); () -> {});
@ -52,6 +63,7 @@ public class ZLSSettingsPanel implements Disposable {
private final JBCheckBox messageTrace = new JBCheckBox(); private final JBCheckBox messageTrace = new JBCheckBox();
private final JBCheckBox debug = new JBCheckBox(); private final JBCheckBox debug = new JBCheckBox();
private final JBCheckBox direnv = new JBCheckBox("Use direnv");
{ {
buildOnSave.setToolTipText("Whether to enable build-on-save diagnostics"); buildOnSave.setToolTipText("Whether to enable build-on-save diagnostics");
@ -66,12 +78,36 @@ public class ZLSSettingsPanel implements Disposable {
autodetect(); autodetect();
} }
public void autodetect() { private @Nullable Path tryWorkDirFromProject() {
FileUtil.findExecutableOnPATH("zls").map(Path::toString).ifPresent(zlsPath::setText); if (project == null)
return null;
val projectDir = ProjectUtil.guessProjectDir(project);
if (projectDir == null) {
return null;
}
return projectDir.toNioPath();
} }
public ZLSSettingsPanel() { private @NotNull CompletableFuture<Map<String, String>> tryGetDirenv() {
if (!DirenvCmd.direnvInstalled() || !direnv.isSelected()) {
return CompletableFuture.completedFuture(Map.of());
}
val workDir = tryWorkDirFromProject();
if (workDir == null)
return CompletableFuture.completedFuture(Map.of());
val direnvCmd = new DirenvCmd(workDir);
return direnvCmd.importDirenvAsync();
}
public void autodetect() {
tryGetDirenv().thenAcceptAsync((env) -> {
FileUtil.findExecutableOnPATH(env, "zls").map(Path::toString).ifPresent(zlsPath::setText);
}, AppExecutorUtil.getAppExecutorService());
}
public ZLSSettingsPanel(@Nullable Project project) {
zlsPath.addBrowseFolderListener(new TextBrowseFolderListener(new FileChooserDescriptor(true, false, false, false, false, false))); zlsPath.addBrowseFolderListener(new TextBrowseFolderListener(new FileChooserDescriptor(true, false, false, false, false, false)));
this.project = project;
} }
public void attachPanelTo(JavaPanel panel) { public void attachPanelTo(JavaPanel panel) {
@ -81,6 +117,9 @@ public class ZLSSettingsPanel implements Disposable {
panel.group("ZLS Settings", true, p -> { panel.group("ZLS Settings", true, p -> {
p.row("Executable path", r -> { p.row("Executable path", r -> {
r.cell(zlsPath).resizableColumn().align(AlignX.FILL); r.cell(zlsPath).resizableColumn().align(AlignX.FILL);
if (DirenvCmd.direnvInstalled() && project != null) {
r.cell(direnv);
}
r.button("Autodetect", $f(this::autodetect)); r.button("Autodetect", $f(this::autodetect));
}); });
p.cell("Config path (leave empty to use built-in config)", zlsConfigPath, AlignX.FILL); p.cell("Config path (leave empty to use built-in config)", zlsConfigPath, AlignX.FILL);
@ -100,7 +139,8 @@ public class ZLSSettingsPanel implements Disposable {
} }
public ZLSSettings getData() { public ZLSSettings getData() {
return new ZLSSettings(zlsPath.getText(), return new ZLSSettings(direnv.isSelected(),
zlsPath.getText(),
zlsConfigPath.getText(), zlsConfigPath.getText(),
debug.isSelected(), debug.isSelected(),
messageTrace.isSelected(), messageTrace.isSelected(),
@ -113,6 +153,7 @@ public class ZLSSettingsPanel implements Disposable {
} }
public void setData(ZLSSettings value) { public void setData(ZLSSettings value) {
direnv.setSelected(value.direnv);
zlsPath.setText(value.zlsPath == null ? "" : value.zlsPath); zlsPath.setText(value.zlsPath == null ? "" : value.zlsPath);
zlsConfigPath.setText(value.zlsConfigPath); zlsConfigPath.setText(value.zlsConfigPath);
debug.setSelected(value.debug); debug.setSelected(value.debug);

View file

@ -1,23 +1,19 @@
package com.falsepattern.zigbrains.zig.util; package com.falsepattern.zigbrains.zig.util;
import com.falsepattern.zigbrains.zig.stringlexer.ZigStringLexer; import com.falsepattern.zigbrains.zig.stringlexer.ZigStringLexer;
import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegate;
import com.intellij.lang.ASTNode; import com.intellij.lang.ASTNode;
import com.intellij.lexer.FlexAdapter; import com.intellij.lexer.FlexAdapter;
import com.intellij.lexer.FlexLexer;
import com.intellij.lexer.Lexer; import com.intellij.lexer.Lexer;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.StringEscapesTokenTypes; import com.intellij.psi.StringEscapesTokenTypes;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.MathUtil; import com.intellij.util.MathUtil;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.StringReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View file

@ -0,0 +1,4 @@
const myText =
\\Hello
\\World!💯
;

View file

@ -0,0 +1 @@
const myText = <spot>"Hello\nWorld!\u{1f4af}"</spot>;

View file

@ -0,0 +1,5 @@
<html>
<body>
Converts quoted strings into multi-line strings, un-escaping any escape sequences.
</body>
</html>

View file

@ -0,0 +1 @@
const myText = "Hello\nWorld!\u{1f4af}";

View file

@ -0,0 +1,4 @@
const myText =
<spot>\\Hello
\\World!💯</spot>
;

View file

@ -0,0 +1,5 @@
<html>
<body>
Converts multi-line strings into quoted strings, escaping characters if necessary.
</body>
</html>