backport: 19.3.0

This commit is contained in:
FalsePattern 2024-10-27 16:16:37 +01:00
parent 81efb0624c
commit 0531230d1b
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]
## [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]
### 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
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
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.1.6
clionVersion=2024.1.5
pluginVersion=19.2.0
pluginVersion=19.3.0
# Gradle Releases -> https://github.com/gradle/gradle/releases
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).withWorkDirectory(workDir.toFile());
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.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
@ -27,6 +29,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
public class FileUtil {
@ -132,10 +135,10 @@ public class FileUtil {
return path != null ? sanitizeURI(path.toUri().toString()) : null;
}
public static Optional<Path> findExecutableOnPATH(String exe) {
var exeName = SystemInfo.isWindows ? exe + ".exe" : exe;
var PATH = System.getenv("PATH").split(File.pathSeparator);
for (var dir: PATH) {
public static @NotNull Optional<Path> findExecutableOnPATH(@NotNull Map<String, String> extraEnv, @NotNull String exe) {
val exeName = SystemInfo.isWindows ? exe + ".exe" : exe;
val PATH = extraEnv.getOrDefault("PATH", System.getenv("PATH")).split(File.pathSeparator);
for (val dir: PATH) {
var path = Path.of(dir);
try {
path = path.toAbsolutePath();
@ -145,7 +148,7 @@ public class FileUtil {
if (!Files.exists(path) || !Files.isDirectory(path)) {
continue;
}
var exePath = path.resolve(exeName).toAbsolutePath();
val exePath = path.resolve(exeName).toAbsolutePath();
if (!Files.isRegularFile(exePath) || !Files.isExecutable(exePath)) {
continue;
}

View file

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

View file

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

View file

@ -2,8 +2,6 @@ package com.falsepattern.zigbrains.debugger.toolchain;
import com.falsepattern.zigbrains.ZigBundle;
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.NotificationType;
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.ui.BrowserHyperlinkListener;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBPanel;
import com.intellij.util.download.DownloadableFileService;
import com.intellij.util.io.Decompressor;
@ -42,9 +39,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
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;
@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()));
cli.setCharset(StandardCharsets.UTF_8);
cli.addParameters(configuration.buildCommandLineArgs(debug));
return cli;
return configuration.patchCommandLine(cli, toolchain);
}
public T configuration() {

View file

@ -16,6 +16,7 @@
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.ZigFilePathPanel;
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.options.ConfigurationException;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.ui.components.JBCheckBox;
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);
}
public static CheckboxConfigurable direnvConfigurable(String serializedName, Project project) {
return new CheckboxConfigurable(serializedName, "Use direnv", ZigProjectSettingsService.getInstance(project).getState().direnv);
}
@RequiredArgsConstructor
public static class OptimizationConfigurable implements ZigConfigurable<OptimizationConfigurable> {
private transient final String serializedName;

View file

@ -16,10 +16,13 @@
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.intellij.execution.ExecutionException;
import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.LocatableConfigurationBase;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.project.Project;
@ -34,13 +37,17 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional;
import static com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor.direnvConfigurable;
@Getter
public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends LocatableConfigurationBase<ProfileStateBase<T>> {
private ZigConfigEditor.WorkDirectoryConfigurable workingDirectory = new ZigConfigEditor.WorkDirectoryConfigurable("workingDirectory");
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) {
super(project, factory, name);
workingDirectory.setPath(getProject().isDefault() ? null : ProjectUtil.guessProjectDir(getProject()));
direnv = direnvConfigurable("direnv", project);
}
@Override
@ -62,6 +69,13 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
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() {
return pty.value;
}
@ -71,6 +85,7 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
val myClone = (ZigExecConfigBase<?>) super.clone();
myClone.workingDirectory = workingDirectory.clone();
myClone.pty = pty.clone();
myClone.direnv = direnv.clone();
return (T) myClone;
}
@ -82,6 +97,6 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
throws ExecutionException;
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) {
this.handleGit = handleGit;
projConf = new ZigProjectSettingsPanel();
zlsConf = new ZLSSettingsPanel();
projConf = new ZigProjectSettingsPanel(null);
zlsConf = new ZLSSettingsPanel(null);
}
public ZigProjectConfigurationData getData() {

View file

@ -53,7 +53,7 @@ public record ZigProjectConfigurationData(boolean git, ZigProjectSettings projCo
svc.loadState(this.projConf());
ZLSProjectSettingsService.getInstance(project).loadState(this.zlsConf());
val toolchain = svc.getState().getToolchain();
val toolchain = svc.getState().getToolchain(project);
val template = this.selectedTemplate();
@ -65,7 +65,7 @@ public record ZigProjectConfigurationData(boolean git, ZigProjectSettings projCo
return;
}
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()) {
Notifications.Bus.notify(new Notification("ZigBrains.Project",
"Failed to invoke \"zig init\"!",

View file

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

View file

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

View file

@ -40,8 +40,9 @@ public final class ZigProjectSettingsService extends AbstractZigProjectSettingsS
public boolean isModified(ZigProjectSettings otherData) {
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.overrideStdPath, otherData.overrideStdPath);
myData.overrideStdPath != otherData.overrideStdPath;
}
}

View file

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

View file

@ -45,6 +45,7 @@ public final class ZigStepDiscoveryService {
val zig = toolchain.zig();
val result = zig.callWithArgs(
ProjectUtil.guessProjectDir(project), CURRENT_TIMEOUT_SEC * 1000,
toolchain.getDataForSelfRuns(),
"build", "-l");
if (result.isPresent()) {
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.tools.ZigCompilerTool;
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.RequiredArgsConstructor;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
@RequiredArgsConstructor
@Getter
public abstract class AbstractZigToolchain {
private final Path location;
private final @Nullable Project project;
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() {
return suggest(null);
}
public static @Nullable AbstractZigToolchain suggest(@Nullable Path projectDir) {
return AbstractZigToolchainFlavour.getApplicableFlavours()
.stream()
.flatMap(it -> it.suggestHomePaths().stream())
.filter(Objects::nonNull)
.map(it -> ZigToolchainProvider.findToolchain(it.toAbsolutePath()))
.filter(Objects::nonNull)
.findFirst().orElse(null);
public static @NotNull CompletableFuture<@Nullable AbstractZigToolchain> suggest(@NotNull UserDataHolder workDir) {
val project = workDir.getUserData(PROJECT_KEY);
if (workDir.getUserData(WORK_DIR_KEY) == null) {
if (project != null) {
val projectDir = ProjectUtil.guessProjectDir(project);
if (projectDir != null) {
workDir.putUserData(WORK_DIR_KEY, projectDir.toNioPath());
}
}
}
val exec = AppExecutorUtil.getAppExecutorService();
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() {
@ -54,10 +79,14 @@ public abstract class AbstractZigToolchain {
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 @NotNull UserDataHolder getDataForSelfRuns() {
return new UserDataHolderBase();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AbstractZigToolchain azt) {

View file

@ -16,15 +16,22 @@
package com.falsepattern.zigbrains.project.toolchain;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.falsepattern.zigbrains.common.util.PathUtil;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.intellij.execution.ExecutionException;
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;
public class LocalZigToolchain extends AbstractZigToolchain{
public LocalZigToolchain(Path location) {
super(location);
public LocalZigToolchain(Path location, @Nullable Project project) {
super(location, project);
}
@Override
@ -33,7 +40,12 @@ public class LocalZigToolchain extends AbstractZigToolchain{
}
@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;
}
@ -42,6 +54,16 @@ public class LocalZigToolchain extends AbstractZigToolchain{
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 {
if (!(toolchain instanceof LocalZigToolchain $toolchain)) {
throw new ExecutionException("The debugger only supports local zig toolchains!");

View file

@ -17,6 +17,7 @@
package com.falsepattern.zigbrains.project.toolchain;
import com.intellij.execution.wsl.WslPath;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.Nullable;
@ -27,11 +28,11 @@ import java.util.Map;
public class LocalZigToolchainProvider implements ZigToolchainProvider {
private static final Map<Path, LocalZigToolchain> tcCache = ContainerUtil.createWeakKeyWeakValueMap();
@Override
public @Nullable AbstractZigToolchain getToolchain(Path homePath) {
public @Nullable AbstractZigToolchain getToolchain(Path homePath, @Nullable Project project) {
if (SystemInfo.isWindows && WslPath.isWslUncPath(homePath.toString())) {
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;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.zig.environment.ZLSConfig;
import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider;
@ -24,6 +25,7 @@ import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.UserDataHolderBase;
import lombok.val;
import java.nio.file.Files;
@ -35,16 +37,20 @@ public class ToolchainZLSConfigProvider implements ZLSConfigProvider {
public void getEnvironment(Project project, ZLSConfig.ZLSConfigBuilder builder) {
val svc = ZigProjectSettingsService.getInstance(project);
val state = svc.getState();
var toolchain = state.getToolchain();
var toolchain = state.getToolchain(project);
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) {
return;
}
state.setToolchain(toolchain);
}
val projectDir = ProjectUtil.guessProjectDir(project);
val oEnv = toolchain.zig().getEnv(projectDir == null ? null : projectDir.toNioPath());
val oEnv = toolchain.zig().getEnv();
if (oEnv.isEmpty()) {
return;
}

View file

@ -17,6 +17,7 @@
package com.falsepattern.zigbrains.project.toolchain;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
@ -26,14 +27,14 @@ public interface ZigToolchainProvider {
ExtensionPointName<ZigToolchainProvider> EXTENSION_POINT_NAME =
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()
.stream()
.map(it -> it.getToolchain(homePath))
.map(it -> it.getToolchain(homePath, project))
.filter(Objects::nonNull)
.findFirst()
.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.project.toolchain.tools.ZigCompilerTool;
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.Nullable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public abstract class AbstractZigToolchainFlavour {
private static final ExtensionPointName<AbstractZigToolchainFlavour> EXTENSION_POINT_NAME =
@ -44,13 +49,14 @@ public abstract class AbstractZigToolchainFlavour {
.orElse(null);
}
public List<Path> suggestHomePaths() {
return getHomePathCandidates().stream()
.filter(this::isValidToolchainPath)
.toList();
public CompletableFuture<List<Path>> suggestHomePaths(@NotNull UserDataHolder data) {
return getHomePathCandidates(data).thenApplyAsync(it -> it.stream()
.filter(this::isValidToolchainPath)
.toList(),
AppExecutorUtil.getAppExecutorService());
}
protected abstract List<Path> getHomePathCandidates();
protected abstract CompletableFuture<List<Path>> getHomePathCandidates(@NotNull UserDataHolder data);
protected boolean isApplicable() {
return true;

View file

@ -16,7 +16,10 @@
package com.falsepattern.zigbrains.project.toolchain.flavours;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.intellij.openapi.util.UserDataHolder;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.nio.file.Files;
@ -24,18 +27,33 @@ import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
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{
@Override
protected List<Path> getHomePathCandidates() {
val PATH = System.getenv("PATH");
if (PATH == null) {
return Collections.emptyList();
protected CompletableFuture<List<Path>> getHomePathCandidates(@NotNull UserDataHolder data) {
val workDir = data.getUserData(WORK_DIR_KEY);
val direnv = data.getUserData(DirenvCmd.DIRENV_KEY);
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))
.filter(it -> !it.isEmpty())
.map(Path::of)
.filter(Files::isDirectory)
.toList();
return direnvFuture.thenApplyAsync(env -> {
val PATH = env.getOrDefault("PATH", System.getenv("PATH"));
if (PATH == null) {
return Collections.emptyList();
}
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;
import com.falsepattern.zigbrains.common.util.PathUtil;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.zig.Icons;
import com.intellij.navigation.ItemPresentation;
@ -51,7 +50,7 @@ public class ZigSyntheticLibrary extends SyntheticLibrary implements ItemPresent
val service = ZigProjectSettingsService.getInstance(project);
val state = service.getState();
this.roots = ApplicationManager.getApplication().executeOnPooledThread(() -> {
val toolchain = state.getToolchain();
val toolchain = state.getToolchain(project);
blk: try {
val ePathStr = state.getExplicitPathToStd();
if (ePathStr == null) {
@ -84,7 +83,7 @@ public class ZigSyntheticLibrary extends SyntheticLibrary implements ItemPresent
});
this.name = ApplicationManager.getApplication()
.executeOnPooledThread(() -> Optional.ofNullable(state.getToolchain())
.executeOnPooledThread(() -> Optional.ofNullable(state.getToolchain(project))
.flatMap(tc -> tc.zig().queryVersion())
.map(version -> "Zig " + version)
.orElse("Zig"));

View file

@ -20,6 +20,7 @@ import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.util.UserDataHolder;
import kotlin.text.Charsets;
import lombok.AllArgsConstructor;
import lombok.val;
@ -41,35 +42,39 @@ public abstract class AbstractZigTool {
return toolchain.pathToExecutable(toolName);
}
public final Optional<ProcessOutput> callWithArgs(@Nullable Path workingDirectory, int timeoutMillis, String... parameters) {
return CLIUtil.execute(createBaseCommandLine(workingDirectory, parameters),
public final Optional<ProcessOutput> callWithArgs(@Nullable Path workingDirectory, int timeoutMillis, @NotNull UserDataHolder data, String @NotNull... parameters) {
return CLIUtil.execute(createBaseCommandLine(workingDirectory, data, parameters),
timeoutMillis);
}
protected final GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory,
@NotNull UserDataHolder data,
String @NotNull... parameters) {
return createBaseCommandLine(workingDirectory, Collections.emptyMap(), parameters);
return createBaseCommandLine(workingDirectory, Collections.emptyMap(), data, parameters);
}
protected final GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory,
@NotNull Map<String, String> environment,
@NotNull UserDataHolder data,
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,
@NotNull UserDataHolder data,
@NotNull List<String> parameters) {
return createBaseCommandLine(workingDirectory, Collections.emptyMap(), parameters);
return createBaseCommandLine(workingDirectory, Collections.emptyMap(), data, parameters);
}
protected GeneralCommandLine createBaseCommandLine(@Nullable Path workingDirectory,
@NotNull Map<String, String> environment,
@NotNull UserDataHolder data,
@NotNull List<String> parameters) {
val cli = new GeneralCommandLine(executable().toString())
.withWorkDirectory(workingDirectory == null ? null : workingDirectory.toString())
.withParameters(parameters)
.withEnvironment(environment)
.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.openapi.application.ApplicationManager;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
@ -38,7 +37,7 @@ public class ZigCompilerTool extends AbstractZigTool{
public ZigCompilerTool(AbstractZigToolchain toolchain) {
super(toolchain, TOOL_NAME);
val app = ApplicationManager.getApplication();
val baseFuture = app.executeOnPooledThread(() -> getEnv(toolchain.getLocation()));
val baseFuture = app.executeOnPooledThread(() -> getEnv());
version = new Lazy<>(() -> {
try {
return baseFuture.get().map(ZigToolchainEnvironmentSerializable::version);
@ -70,8 +69,8 @@ public class ZigCompilerTool extends AbstractZigTool{
});
}
public Optional<ZigToolchainEnvironmentSerializable> getEnv(@Nullable Path workingDirectory) {
return callWithArgs(workingDirectory, toolchain.executionTimeoutInMilliseconds(), "env")
public Optional<ZigToolchainEnvironmentSerializable> getEnv() {
return callWithArgs(toolchain.getLocation(), toolchain.executionTimeoutInMilliseconds(), toolchain.getDataForSelfRuns(), "env")
.map(ProcessOutput::getStdoutLines)
.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 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) {

View file

@ -3,35 +3,18 @@ package com.falsepattern.zigbrains.zig.editing;
import com.falsepattern.zigbrains.zig.parser.ZigFile;
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
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.ZigStringUtil;
import com.intellij.application.options.CodeStyle;
import com.intellij.codeInsight.editorActions.JavaLikeQuoteHandler;
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.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
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.StringEscapesTokenTypes;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.tree.IElementType;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.io.StringReader;
import java.util.ArrayList;
@RequiredArgsConstructor
public class ZigEnterInQuotedStringHandler extends EnterHandlerDelegateAdapter {
@Override

View file

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

View file

@ -1,7 +1,6 @@
package com.falsepattern.zigbrains.zig.intentions;
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
import com.falsepattern.zigbrains.zig.psi.ZigTypes;
import com.falsepattern.zigbrains.zig.util.PsiTextUtil;
import com.intellij.codeInsight.intention.IntentionAction;
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.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import lombok.val;

View file

@ -1,24 +1,17 @@
package com.falsepattern.zigbrains.zig.intentions;
import com.falsepattern.zigbrains.zig.psi.ZigStringLiteral;
import com.falsepattern.zigbrains.zig.util.PsiTextUtil;
import com.falsepattern.zigbrains.zig.util.ZigStringUtil;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.codeInspection.util.IntentionFamilyName;
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.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.MathUtil;
import lombok.val;
import org.jetbrains.annotations.NotNull;

View file

@ -1,8 +1,6 @@
package com.falsepattern.zigbrains.zig.lsp;
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.psi.PsiFile;
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.LSPInlayHintFeature;
import com.redhat.devtools.lsp4ij.server.StreamConnectionProvider;
import com.redhat.devtools.lsp4ij.server.capabilities.InlayHintCapabilityRegistry;
import lombok.val;
import org.jetbrains.annotations.NotNull;
@ -54,7 +51,7 @@ public class ZLSLanguageServerFactory implements LanguageServerFactory, Language
@Override
public boolean isEnabled(@NotNull Project project) {
return enabled && ZLSStreamConnectionProvider.doGetCommand(project, false) != null;
return enabled && ZLSStreamConnectionProvider.getCommandSync(project, false) != null;
}
@Override

View file

@ -10,8 +10,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
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_MEMBER_DECL;
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.PROPERTY_DECL;
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_REF;
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.Definition;
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.ZLSSemanticTokenTypes.Builtin;
import static com.falsepattern.zigbrains.zig.lsp.ZLSSemanticTokenTypes.Comment;

View file

@ -1,5 +1,6 @@
package com.falsepattern.zigbrains.zig.lsp;
import com.falsepattern.zigbrains.common.direnv.DirenvCmd;
import com.falsepattern.zigbrains.common.util.StringUtil;
import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider;
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.util.io.FileUtil;
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.ProcessStreamConnectionProvider;
import lombok.val;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage;
import org.eclipse.lsp4j.services.LanguageServer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
@ -29,10 +32,9 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvider {
@ -40,7 +42,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
private final Project project;
public ZLSStreamConnectionProvider(Project project) {
this.project = project;
val command = getCommand(project);
val command = getCommandAsync(project, true);
val projectDir = ProjectUtil.guessProjectDir(project);
GeneralCommandLine commandLine = null;
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);
val state = svc.getState();
var zlsPath = state.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 (safe) {
if (full) {
Notifications.Bus.notify(
new Notification("ZigBrains.ZLS", "Could not detect ZLS binary! Please configure it!",
NotificationType.ERROR));
}
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;
}
var configPath = state.zlsConfigPath;
boolean configOK = true;
if (!configPath.isBlank() && !validatePath("ZLS Config", configPath, false, safe)) {
if (safe) {
if (!configPath.isBlank() && !validatePath("ZLS Config", configPath, false, full)) {
if (full) {
Notifications.Bus.notify(
new Notification("ZigBrains.ZLS", "Using default config path.", NotificationType.INFORMATION));
}
configPath = null;
}
if ((configPath == null || configPath.isBlank()) && safe) {
if ((configPath == null || configPath.isBlank()) && full) {
blk:
try {
val tmpFile = FileUtil.createTempFile("zigbrains-zls-autoconf", ".json", true).toPath();
@ -133,7 +162,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
}
configPath = tmpFile.toAbsolutePath().toString();
} catch (IOException e) {
if (safe) {
if (full) {
Notifications.Bus.notify(
new Notification("ZigBrains.ZLS", "Failed to create automatic zls config file",
NotificationType.WARNING));
@ -166,19 +195,11 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
return cmd;
}
public static Future<List<String>> getCommand(Project project) {
val future = new CompletableFuture<List<String>>();
ApplicationManager.getApplication().executeOnPooledThread(() -> {
try {
future.complete(doGetCommand(project, true));
} catch (Throwable t) {
future.completeExceptionally(t);
}
});
return future;
public static CompletableFuture<List<String>> getCommandAsync(Project project, boolean full) {
return CompletableFuture.supplyAsync(() -> getCommandSync(project, full), AppExecutorUtil.getAppExecutorService());
}
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()) {
return false;
}
@ -186,7 +207,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
try {
path = Path.of(pathTxt);
} catch (InvalidPathException e) {
if (safe) {
if (full) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name,
"Invalid " + name + " at path \"" + pathTxt + "\"",
NotificationType.ERROR));
@ -194,7 +215,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
return false;
}
if (!Files.exists(path)) {
if (safe) {
if (full) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name,
"The " + name + " at \"" + pathTxt + "\" doesn't exist!",
NotificationType.ERROR));
@ -202,7 +223,7 @@ public class ZLSStreamConnectionProvider extends OSProcessStreamConnectionProvid
return false;
}
if (Files.isDirectory(path) != dir) {
if (safe) {
if (full) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name,
"The " + name + " at \"" + pathTxt + "\" is a " +
(Files.isDirectory(path) ? "directory" : "file") +

View file

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

View file

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

View file

@ -1,14 +1,7 @@
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.psi.LiteralTextEscaper;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.impl.source.tree.LeafElement;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.util.List;

View file

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

View file

@ -24,6 +24,7 @@ import org.jetbrains.annotations.Nullable;
@Data
@AllArgsConstructor
public final class ZLSSettings {
public boolean direnv;
public @Nullable String zlsPath;
public @NotNull String zlsConfigPath;
public boolean debug;
@ -36,6 +37,6 @@ public final class ZLSSettings {
public boolean inlayHintsCompact;
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
public void createComponent(JavaPanel panel) {
appSettingsComponent = new ZLSSettingsPanel();
appSettingsComponent = new ZLSSettingsPanel(project);
appSettingsComponent.attachPanelTo(panel);
}

View file

@ -16,26 +16,37 @@
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.TextFieldUtil;
import com.falsepattern.zigbrains.common.util.dsl.JavaPanel;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.ui.TextBrowseFolderListener;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.components.fields.ExtendableTextField;
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.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static com.falsepattern.zigbrains.common.util.KtUtil.$f;
public class ZLSSettingsPanel implements Disposable {
private final @Nullable Project project;
private final TextFieldWithBrowseButton zlsPath = TextFieldUtil.pathToFileTextField(this,
"Path to the ZLS Binary",
() -> {});
@ -52,6 +63,7 @@ public class ZLSSettingsPanel implements Disposable {
private final JBCheckBox messageTrace = 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");
@ -66,12 +78,36 @@ public class ZLSSettingsPanel implements Disposable {
autodetect();
}
public void autodetect() {
FileUtil.findExecutableOnPATH("zls").map(Path::toString).ifPresent(zlsPath::setText);
private @Nullable Path tryWorkDirFromProject() {
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)));
this.project = project;
}
public void attachPanelTo(JavaPanel panel) {
@ -81,6 +117,9 @@ public class ZLSSettingsPanel implements Disposable {
panel.group("ZLS Settings", true, p -> {
p.row("Executable path", r -> {
r.cell(zlsPath).resizableColumn().align(AlignX.FILL);
if (DirenvCmd.direnvInstalled() && project != null) {
r.cell(direnv);
}
r.button("Autodetect", $f(this::autodetect));
});
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() {
return new ZLSSettings(zlsPath.getText(),
return new ZLSSettings(direnv.isSelected(),
zlsPath.getText(),
zlsConfigPath.getText(),
debug.isSelected(),
messageTrace.isSelected(),
@ -113,6 +153,7 @@ public class ZLSSettingsPanel implements Disposable {
}
public void setData(ZLSSettings value) {
direnv.setSelected(value.direnv);
zlsPath.setText(value.zlsPath == null ? "" : value.zlsPath);
zlsConfigPath.setText(value.zlsConfigPath);
debug.setSelected(value.debug);

View file

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