backport: 17.0.0

This commit is contained in:
FalsePattern 2024-07-31 20:14:27 +02:00
parent 4ca75e38ae
commit 58201b4868
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
19 changed files with 523 additions and 11 deletions

View file

@ -17,6 +17,25 @@ Changelog structure reference:
## [Unreleased]
## [17.0.0]
### Added
- Project
- Zig Build integrated into an IDE tool window. Currently only supports running single steps, for more complex steps,
create a custom build configuration as before.
### Changed
- Project
- Increased internal zig tool timeout to 10 seconds. Note that tasks don't have timeout, this is only used for
ZigBrains getting metadata about the compiler and the buildscript.
### Fixed
- Project
- Toolchain working directory was not set when requesting compiler metadata
## [16.1.3]
### Changed

View file

@ -14,7 +14,7 @@ plugins {
java
`maven-publish`
`java-library`
id("org.jetbrains.intellij.platform") version("2.0.0-rc1")
id("org.jetbrains.intellij.platform") version("2.0.0")
id("org.jetbrains.changelog") version("2.2.1")
id("org.jetbrains.grammarkit") version("2022.3.2.2")
id("de.undercouch.download") version("5.6.0")

View file

@ -11,7 +11,7 @@ baseIDE=clion
ideaVersion=2023.2.7
clionVersion=2023.2.4
pluginVersion=16.1.3
pluginVersion=17.0.0
# Gradle Releases -> https://github.com/gradle/gradle/releases
gradleVersion=8.9

View file

@ -24,3 +24,11 @@ settings.debugger.toolchain.update.comment=Need to be <a>updated</a>
settings.debugger.toolchain.use.clion.toolchains=Use Clion toolchains instead
notification.content.debugger.need.download=You need to download/update the debugger before you can run debugging! (Settings | Build, Execution, Deployment | Debugging -> Zig)
unable.to.run.debugger=Unable to Run Debugger
toolwindow.stripe.zigbrains.build=Zig Build
build.tool.window.tree.steps.label=Steps
build.tool.window.status.not-scanned=Build steps not yet scanned. Click the refresh button.
build.tool.window.status.loading=Running zig build -l
build.tool.window.status.error.missing-build-zig=No build.zig file found
build.tool.window.status.error.missing-toolchain=No zig toolchain configured
build.tool.window.status.error.general=Error while running zig build -l
build.tool.window.status.timeout=zig build -l timed out after {0} seconds.

View file

@ -149,8 +149,23 @@ The <a href="https://github.com/Zigtools/ZLS">Zig Language Server</a>, for ZigBr
<analyzeStacktraceFilter implementation="com.falsepattern.zigbrains.project.console.ZigSourceFileFilter"/>
<additionalLibraryRootsProvider implementation="com.falsepattern.zigbrains.project.toolchain.stdlib.ZigLibraryRootProvider"/>
<toolWindow factoryClass="com.falsepattern.zigbrains.project.steps.ui.BuildToolWindowFactory"
anchor="right"
icon="/icons/zig_build_tool.svg"
id="zigbrains.build"/>
</extensions>
<actions>
<action
id="ZigBrains.Reload"
class="com.falsepattern.zigbrains.project.steps.discovery.ZigDiscoverStepsAction"
text="Reload Zig Build Steps"
description="Scan the project and detect build.zig steps using the current toolchain"
icon="AllIcons.Actions.Refresh"
/>
</actions>
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
<toolchainFlavour implementation="com.falsepattern.zigbrains.project.toolchain.flavours.ZigSystemPathToolchainFlavour"/>
<toolchainProvider implementation="com.falsepattern.zigbrains.project.toolchain.LocalZigToolchainProvider"/>

View file

@ -17,10 +17,10 @@
package com.falsepattern.zigbrains.project.console;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.project.util.ProjectUtil;
import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.OpenFileHyperlinkInfo;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.vfs.VirtualFile;
import kotlin.Pair;
import lombok.RequiredArgsConstructor;
@ -63,7 +63,7 @@ public class ZigSourceFileFilter implements Filter {
@Override
public Result applyFilter(@NotNull String line, int entireLength) {
val lineStart = entireLength - line.length();
val projectPath = Optional.ofNullable(ProjectUtil.guessProjectDir(project)).map(VirtualFile::toNioPath).orElse(null);
val projectPath = ProjectUtil.guessProjectDir(project);
val results = new ArrayList<ResultItem>();
val matcher = LEN_REGEX.matcher(line);
while (matcher.find()) {

View file

@ -16,16 +16,15 @@
package com.falsepattern.zigbrains.project.execution.base;
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.LocatableConfigurationBase;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.NlsActions;
import com.intellij.openapi.vfs.VirtualFile;
import lombok.Getter;
import lombok.val;
import org.jdom.Element;
@ -41,9 +40,7 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
private ZigConfigEditor.CheckboxConfigurable pty = new ZigConfigEditor.CheckboxConfigurable("pty", "Emulate Terminal", false);
public ZigExecConfigBase(@NotNull Project project, @NotNull ConfigurationFactory factory, @Nullable String name) {
super(project, factory, name);
workingDirectory.setPath(getProject().isDefault() ? null : Optional.ofNullable(ProjectUtil.guessProjectDir(getProject()))
.map(VirtualFile::toNioPath)
.orElse(null));
workingDirectory.setPath(getProject().isDefault() ? null : ProjectUtil.guessProjectDir(getProject()));
}
@Override

View file

@ -0,0 +1,15 @@
package com.falsepattern.zigbrains.project.steps.discovery;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import lombok.val;
import org.jetbrains.annotations.NotNull;
public class ZigDiscoverStepsAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
val project = e.getProject();
if (project == null) return;
ZigStepDiscoveryService.getInstance(project).triggerReload();
}
}

View file

@ -0,0 +1,22 @@
package com.falsepattern.zigbrains.project.steps.discovery;
import com.intellij.util.messages.Topic;
import kotlin.Pair;
import java.util.List;
public interface ZigStepDiscoveryListener {
@Topic.ProjectLevel
Topic<ZigStepDiscoveryListener> TOPIC = new Topic<>(ZigStepDiscoveryListener.class);
enum ErrorType {
MissingToolchain,
MissingBuildZig,
GeneralError
}
default void preReload() {}
default void postReload(List<Pair<String, String>> steps) {}
default void errorReload(ErrorType type) {}
default void timeoutReload(int seconds) {}
}

View file

@ -0,0 +1,112 @@
package com.falsepattern.zigbrains.project.steps.discovery;
import com.falsepattern.zigbrains.ZigBundle;
import com.falsepattern.zigbrains.project.util.ProjectUtil;
import com.intellij.ide.impl.TrustedProjects;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import kotlin.Pair;
import lombok.val;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@Service(Service.Level.PROJECT)
public final class ZigStepDiscoveryService {
private static final Logger LOG = Logger.getInstance(ZigStepDiscoveryService.class);
private static final int DEFAULT_TIMEOUT_SEC = 10;
private final Project project;
public ZigStepDiscoveryService(Project project) {
this.project = project;
}
private final AtomicBoolean reloading = new AtomicBoolean(false);
private final AtomicBoolean reloadScheduled = new AtomicBoolean(false);
private int CURRENT_TIMEOUT_SEC = DEFAULT_TIMEOUT_SEC;
private void doReload() {
val bus = project.getMessageBus().syncPublisher(ZigStepDiscoveryListener.TOPIC);
bus.preReload();
try {
val toolchain = ProjectUtil.getToolchain(project);
if (toolchain == null) {
bus.errorReload(ZigStepDiscoveryListener.ErrorType.MissingToolchain);
return;
}
val zig = toolchain.zig();
val result = zig.callWithArgs(
ProjectUtil.guessProjectDir(project), CURRENT_TIMEOUT_SEC * 1000,
"build", "-l");
if (result.isPresent()) {
val res = result.get();
if (res.getExitCode() == 0) {
if (!res.isTimeout()) {
CURRENT_TIMEOUT_SEC = DEFAULT_TIMEOUT_SEC;
val lines = res.getStdoutLines();
val steps = new ArrayList<Pair<String, String>>();
for (val line : lines) {
val parts = line.trim().split("\\s+", 2);
if (parts.length == 2) {
steps.add(new Pair<>(parts[0], parts[1]));
} else {
steps.add(new Pair<>(parts[0], null));
}
}
bus.postReload(steps);
} else {
bus.timeoutReload(CURRENT_TIMEOUT_SEC);
CURRENT_TIMEOUT_SEC *= 2;
}
} else if (res.getStderrLines()
.stream()
.anyMatch(line -> line.contains(
"error: no build.zig file found, in the current directory or any parent directories"))) {
bus.errorReload(ZigStepDiscoveryListener.ErrorType.MissingBuildZig);
} else {
bus.errorReload(ZigStepDiscoveryListener.ErrorType.GeneralError);
}
}
} catch (Throwable t) {
LOG.error("Error while reloading zig build steps", t);
}
synchronized (reloading) {
if (reloadScheduled.getAndSet(false)) {
dispatchReload();
return;
}
reloading.set(false);
}
}
private void dispatchReload() {
val manager = ApplicationManager.getApplication();
manager.invokeLater(() -> {
FileDocumentManager.getInstance().saveAllDocuments();
manager.executeOnPooledThread(this::doReload);
});
}
public void triggerReload() {
synchronized (reloading) {
if (reloading.get()) {
reloadScheduled.set(true);
return;
}
reloading.set(true);
}
dispatchReload();
}
public static ZigStepDiscoveryService getInstance(Project project) {
return project.getService(ZigStepDiscoveryService.class);
}
}

View file

@ -0,0 +1,36 @@
package com.falsepattern.zigbrains.project.steps.ui;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.util.treeView.PresentableNodeDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.ui.SimpleTextAttributes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.Icon;
public class BaseNodeDescriptor<T> extends PresentableNodeDescriptor<T> {
private String description;
public BaseNodeDescriptor(@Nullable Project project, String displayName, Icon displayIcon) {
this(project, displayName, null, displayIcon);
}
public BaseNodeDescriptor(@Nullable Project project, String displayName, String description, Icon displayIcon) {
super(project, null);
setIcon(displayIcon);
myName = displayName;
this.description = description;
update();
}
@Override
protected void update(@NotNull PresentationData presentation) {
presentation.setIcon(getIcon());
presentation.addText(myName, SimpleTextAttributes.REGULAR_ATTRIBUTES);
presentation.setTooltip(description);
}
@Override
public T getElement() {
return null;
}
}

View file

@ -0,0 +1,221 @@
package com.falsepattern.zigbrains.project.steps.ui;
import com.falsepattern.zigbrains.ZigBundle;
import com.falsepattern.zigbrains.project.execution.build.ConfigTypeBuild;
import com.falsepattern.zigbrains.project.execution.build.ZigExecConfigBuild;
import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListener;
import com.falsepattern.zigbrains.zig.Icons;
import com.intellij.execution.ProgramRunnerUtil;
import com.intellij.execution.RunManager;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.SimpleToolWindowPanel;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.AnimatedIcon;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import com.intellij.ui.treeStructure.Tree;
import kotlin.Pair;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
public class BuildToolWindowContext implements Disposable {
private static final Key<JBScrollPane> VIEWPORT = Key.create("MODEL");
public final DefaultMutableTreeNode rootNode;
private final DefaultMutableTreeNode buildZig;
private final Project project;
public static void create(Project project, ToolWindow window) {
val context = new BuildToolWindowContext(project);
project.getMessageBus().connect(context).subscribe(ZigStepDiscoveryListener.TOPIC, context.new BuildReloadListener());
Disposer.register(window.getDisposable(), context);
window.getContentManager().addContent(context.createContentPanel());
}
private class BuildReloadListener implements ZigStepDiscoveryListener {
@Override
public void preReload() {
val viewport = getViewport(project);
if (viewport == null)
return;
setViewportLoading(viewport);
}
@Override
public void postReload(List<Pair<String, String>> steps) {
buildZig.removeAllChildren();
for (val step : steps) {
val icon = switch (step.component1()) {
case "install" -> AllIcons.Actions.Install;
case "uninstall" -> AllIcons.Actions.Uninstall;
default -> AllIcons.RunConfigurations.TestState.Run;
};
buildZig.add(new DefaultMutableTreeNode(new StepNodeDescriptor(project, step.component1(), step.component2(), icon)));
}
invokeLaterWithViewport(BuildToolWindowContext.this::setViewportTree);
}
@Override
public void errorReload(ErrorType errorType) {
invokeLaterWithViewport(viewport -> {
setViewportError(viewport, ZigBundle.message(switch (errorType) {
case MissingToolchain -> "build.tool.window.status.error.missing-toolchain";
case MissingBuildZig -> "build.tool.window.status.error.missing-build-zig";
case GeneralError -> "build.tool.window.status.error.general";
}));
});
}
@Override
public void timeoutReload(int seconds) {
invokeLaterWithViewport(viewport -> {
setViewportError(viewport, ZigBundle.message("build.tool.window.status.timeout", seconds));
});
}
}
private void invokeLaterWithViewport(Consumer<JBScrollPane> callback) {
ToolWindowManager.getInstance(project).invokeLater(() -> {
val viewport = getViewport(project);
if (viewport == null)
return;
callback.accept(viewport);
});
}
public BuildToolWindowContext(Project project) {
this.project = project;
rootNode = new DefaultMutableTreeNode(new BaseNodeDescriptor<>(project, project.getName(), AllIcons.Actions.ProjectDirectory));
buildZig = new DefaultMutableTreeNode(new BaseNodeDescriptor<>(project, ZigBundle.message("build.tool.window.tree.steps.label"), Icons.ZIG));
rootNode.add(buildZig);
}
private static @Nullable JBScrollPane getViewport(Project project) {
val toolWindow = ToolWindowManager.getInstance(project).getToolWindow("zigbrains.build");
if (toolWindow == null)
return null;
val cm = toolWindow.getContentManager();
val content = cm.getContent(0);
if (content == null)
return null;
return content.getUserData(VIEWPORT);
}
private static @Nullable RunnerAndConfigurationSettings getExistingRunConfig(RunManager manager, String stepName) {
for (val config : manager.getConfigurationSettingsList(ConfigTypeBuild.class)) {
if (!(config.getConfiguration() instanceof ZigExecConfigBuild build))
continue;
val steps = build.getBuildSteps().args;
if (steps == null || steps.length != 1)
continue;
if (!(Objects.equals(steps[0], stepName)))
continue;
return config;
}
return null;
}
private void setViewportTree(JBScrollPane viewport) {
val model = new DefaultTreeModel(rootNode);
val tree = new Tree(model);
tree.expandPath(new TreePath(model.getPathToRoot(buildZig)));
viewport.setViewportView(tree);
tree.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
val node = tree.getLastSelectedPathComponent();
if (node == null)
return;
if (!(node instanceof DefaultMutableTreeNode mut))
return;
if (!(mut.getUserObject() instanceof StepNodeDescriptor step))
return;
val stepName = step.getElement().name();
val manager = RunManager.getInstance(project);
val config = Objects.requireNonNullElseGet(getExistingRunConfig(manager, stepName), () -> {
val factory = ConfigTypeBuild.getInstance().getConfigurationFactories()[0];
val newConfig = manager.createConfiguration("zig build " + stepName, factory);
((ZigExecConfigBuild)newConfig.getConfiguration()).getBuildSteps().args = new String[]{stepName};
manager.addConfiguration(newConfig);
return newConfig;
});
manager.setSelectedConfiguration(config);
ProgramRunnerUtil.executeConfiguration(config, DefaultRunExecutor.getRunExecutorInstance());
}
}
});
}
private void setViewportLoading(JBScrollPane viewport) {
viewport.setViewportView(new JBLabel(ZigBundle.message("build.tool.window.status.loading"), new AnimatedIcon.Default(), SwingConstants.CENTER));
}
private void setViewportNoContent(JBScrollPane viewport) {
viewport.setViewportView(new JBLabel(ZigBundle.message("build.tool.window.status.not-scanned"), AllIcons.General.Information, SwingConstants.CENTER));
}
private void setViewportError(JBScrollPane viewport, String msg) {
viewport.setViewportView(new JBLabel(msg, AllIcons.General.Error, SwingConstants.CENTER));
}
private Content createContentPanel() {
val contentPanel = new SimpleToolWindowPanel(false);
val body = new JPanel(new GridBagLayout());
contentPanel.setLayout(new GridBagLayout());
val c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.weighty = 1;
contentPanel.add(body, c);
c.fill = GridBagConstraints.HORIZONTAL;
c.anchor = GridBagConstraints.NORTHWEST;
c.weightx = 1;
c.weighty = 0;
val toolbar = ActionManager.getInstance().createActionToolbar("ZigToolbar", new DefaultActionGroup(ActionManager.getInstance().getAction("ZigBrains.Reload")), true);
toolbar.setTargetComponent(null);
body.add(toolbar.getComponent(), c);
c.gridwidth = 1;
c.gridy = 1;
c.weighty = 1;
c.fill = GridBagConstraints.BOTH;
val viewport = new JBScrollPane();
setViewportNoContent(viewport);
body.add(viewport, c);
val content = ContentFactory.getInstance().createContent(contentPanel, "", false);
content.putUserData(VIEWPORT, viewport);
return content;
}
@Override
public void dispose() {
}
}

View file

@ -0,0 +1,13 @@
package com.falsepattern.zigbrains.project.steps.ui;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import org.jetbrains.annotations.NotNull;
public class BuildToolWindowFactory implements ToolWindowFactory {
@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
BuildToolWindowContext.create(project, toolWindow);
}
}

View file

@ -0,0 +1,4 @@
package com.falsepattern.zigbrains.project.steps.ui;
public record StepDetails(String name) {
}

View file

@ -0,0 +1,19 @@
package com.falsepattern.zigbrains.project.steps.ui;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.Nullable;
import javax.swing.Icon;
public class StepNodeDescriptor extends BaseNodeDescriptor<StepDetails> {
private final String stepName;
public StepNodeDescriptor(@Nullable Project project, String stepName, String description, Icon displayIcon) {
super(project, stepName, description, displayIcon);
this.stepName = stepName;
}
@Override
public StepDetails getElement() {
return new StepDetails(stepName);
}
}

View file

@ -29,7 +29,7 @@ public class LocalZigToolchain extends AbstractZigToolchain{
@Override
public int executionTimeoutInMilliseconds() {
return 1000;
return 10000;
}
@Override

View file

@ -37,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(null));
val baseFuture = app.executeOnPooledThread(() -> getEnv(toolchain.getLocation()));
version = new Lazy<>(() -> {
try {
return baseFuture.get().map(ZigToolchainEnvironmentSerializable::version);

View file

@ -19,11 +19,21 @@ package com.falsepattern.zigbrains.project.util;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.intellij.openapi.project.Project;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
public class ProjectUtil {
public static @Nullable AbstractZigToolchain getToolchain(Project project) {
return ZigProjectSettingsService.getInstance(project).getState().getToolchain();
}
public static @Nullable Path guessProjectDir(Project project) {
val dir = com.intellij.openapi.project.ProjectUtil.guessProjectDir(project);
if (dir == null)
return null;
return dir.toNioPath();
}
}

View file

@ -0,0 +1,21 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 154 140">
<g fill="#AFB1B3">
<g>
<polygon points="46,22 28,44 19,30"/>
<polygon points="46,22 33,33 28,44 22,44 22,95 31,95 20,100 12,117 0,117 0,22" shape-rendering="crispEdges"/>
<polygon points="31,95 12,117 4,106"/>
</g>
<g>
<polygon points="56,22 62,36 37,44"/>
<polygon points="56,22 111,22 111,44 37,44 56,32" shape-rendering="crispEdges"/>
<polygon points="116,95 97,117 90,104"/>
<polygon points="116,95 100,104 97,117 42,117 42,95" shape-rendering="crispEdges"/>
<polygon points="150,0 52,117 3,140 101,22"/>
</g>
<g>
<polygon points="141,22 140,40 122,45"/>
<polygon points="153,22 153,117 106,117 120,105 125,95 131,95 131,45 122,45 132,36 141,22" shape-rendering="crispEdges"/>
<polygon points="125,95 130,110 106,117"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 850 B