backport: 14.0.0

This commit is contained in:
FalsePattern 2024-04-20 00:45:50 +02:00
parent 255829c3d7
commit bb6dd0d83a
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
68 changed files with 1873 additions and 1277 deletions

View file

@ -18,6 +18,38 @@ Changelog structure reference:
## [Unreleased]
## [14.0.0]
### Added
- LSP
- The status widget now auto-hides itself when the selected editor is not a zig file in the current window
- Project
- Completely overhauled the configuration system and the new project creation window. All the configs have been unified
into a single screen, and project creation has been fully integrated as a mainline feature, instead of just a "nice to have".
### Changed
- LSP
- The injection of the various language actions (Go to declaration/implementation, reformat, etc.) has been
reimplemented from the ground up to be much more reliable and compatible in the presence of other languages and plugins.
- Zig, ZLS
- The configurations have been unified into a single cohesive interface
- Improved auto-detection for both Zig and ZLS
### Fixed
- LSP
- Putting the caret on a diagnostics error now no longer highlights the whole file
- Project
- Fixed invalid --colored command line argument for zig tasks
- Zig
- More robust indentation logic, also works with semi-invalid syntax now
## [13.2.0]
### Added

View file

@ -1,5 +1,9 @@
# ZigBrains
### [Website](https://falsepattern.com/zigbrains)
### [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/22456-zigbrains)
## Developer guide
### All platforms
@ -50,24 +54,15 @@ and might as well utilize the full semver string for extra information.
# Description
<!-- Plugin description -->
A multifunctional Zig Programming Language plugin for the IDEA platform.
Adds support for the Zig Language, utilizing the ZLS language server for advanced coding assistance.
Core features:
- Uses ZLS (Zig Language Server) for code assistance, syntax highlighting, and anything to do with coding assistance
- Supports build.zig.zon files with autocomplete
- Per-project Zig toolchain integration
- Debugging support for CLion (builtin), and IDEA Ultimate [With this plugin](https://plugins.jetbrains.com/plugin/12775-native-debugging-support)
- Gutter icon for running main(), tests, and build
## Quick setup guide for Zig and ZLS
## Setting up the language server
If you have `zls` available on PATH, ZigBrains will automatically discover it. If not, follow this guide:
1. Download or compile the ZLS language server, available at https://github.com/zigtools/zls
2. Go to `Settings` -> `Languages & Frameworks` -> `ZLS` -> `ZLS path` -> set the path to the `zls` executable you downloaded or compiled
3. Open a .zig file, and wait for the circle in the bottom status bar to turn Green (empty).
See below for an explanation on what the circle means.
1. Download the latest version of Zig from https://ziglang.org/download
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
4. Open a .zig file, and wait for the circle in the bottom status bar to turn Green (empty).
See below (`LSP status icon explanation`) for an explanation on what the circle means.
### LSP status icon explanation
Red (X symbol):
@ -81,6 +76,13 @@ LSP server is running.
## Debugging
### Note
Debugging on Linux/MacOS/Unix is only available in CLion, as ZigBrains depends on the C++ toolchains system.
On Windows, debugging is also available with the help of the
[Native Debugging Support](https://plugins.jetbrains.com/plugin/12775-native-debugging-support), which is unfortunately
only compatible with paid IDEs.
### Windows
Due to technical limitations, the C++ toolchains cannot be used for debugging zig code on windows.
@ -102,39 +104,4 @@ Note: There is a small issue with the LLDB debugger which does not happen with G
instruction (usually, deep inside the zig standard library's startup code). Unfortunately, we have not found a fix for
this yet, but fortunately it doesn't break anything, just a bit of inconvenience.
## Feature tracker:
### .zig files:
- Code completion
- Code folding
- Code formatting
- Syntax highlighting
- Inlay hints
- Basic error diagnostics
- Go to definition
- Rename symbol
- Hover documentation
- Go to implementations / find usages
- Brace/Parenthesis/Bracket matching
- Debugging (CLion/CLion Nova)
- File creation prompt
- Gutter launch buttons
- Commenter (thanks @MarioAriasC !)
- TODO:
- Workspace Symbols
### .zon files:
- Syntax highlighting
- Formatting and indentation
- Code completion
- Brace folding
- Automatic brace and quote pairing
### Toolchain:
- Basic per-project toolchain management
- Run configurations
- Debugging (CLion/IDEA Ultimate)
- Project generation (thanks @JensvandeWiel !)
<!-- Plugin description end -->

View file

@ -0,0 +1,74 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.common;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.ui.JBColor;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import javax.swing.JComponent;
import java.util.Arrays;
import static com.falsepattern.zigbrains.common.util.dsl.JavaPanel.newPanel;
public abstract class MultiConfigurable implements Configurable {
private final SubConfigurable[] configurables;
protected MultiConfigurable(SubConfigurable... configurables) {
this.configurables = Arrays.copyOf(configurables, configurables.length);
}
@Override
public @Nullable JComponent createComponent() {
return newPanel(p -> {
for (val configurable: configurables) {
configurable.createComponent(p);
}
});
}
@Override
public boolean isModified() {
for (val configurable: configurables) {
if (configurable.isModified())
return true;
}
return false;
}
@Override
public void apply() throws ConfigurationException {
for (val config: configurables) {
config.apply();
}
}
@Override
public void reset() {
for (val config: configurables) {
config.reset();
}
}
@Override
public void disposeUIResources() {
for (val config: configurables) {
config.disposeUIResources();
}
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.common;
import com.falsepattern.zigbrains.common.util.dsl.JavaPanel;
import com.intellij.openapi.options.ConfigurationException;
public interface SubConfigurable {
void createComponent(JavaPanel panel);
boolean isModified();
void apply() throws ConfigurationException;
void reset();
void disposeUIResources();
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.common;
import com.intellij.openapi.components.PersistentStateComponent;
import org.jetbrains.annotations.NotNull;
public abstract class WrappingStateComponent<T> implements PersistentStateComponent<T> {
private T state;
public WrappingStateComponent(@NotNull T initialState) {
this.state = initialState;
}
@Override
public @NotNull T getState() {
return state;
}
@Override
public void loadState(@NotNull T state) {
this.state = state;
}
}

View file

@ -17,6 +17,7 @@
package com.falsepattern.zigbrains.common.util;
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 org.jetbrains.annotations.Nullable;
@ -24,7 +25,9 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
public class FileUtil {
private static final Logger LOG = Logger.getInstance(FileUtil.class);
@ -96,6 +99,10 @@ public class FileUtil {
return LocalFileSystem.getInstance().findFileByIoFile(new File(uri));
}
public static VirtualFile virtualFileFromPath(Path path) {
return LocalFileSystem.getInstance().findFileByNioFile(path);
}
/**
* Transforms an URI string into a VFS file
*
@ -125,6 +132,28 @@ 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) {
var path = Path.of(dir);
try {
path = path.toAbsolutePath();
} catch (Exception ignored) {
continue;
}
if (!Files.exists(path) || !Files.isDirectory(path)) {
continue;
}
var exePath = path.resolve(exeName).toAbsolutePath();
if (!Files.isRegularFile(exePath) || !Files.isExecutable(exePath)) {
continue;
}
return Optional.of(exePath);
}
return Optional.empty();
}
/**
* Object representing the OS type (Windows or Unix)
*/

View file

@ -0,0 +1,31 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.common.util;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import java.util.function.Consumer;
public class KtUtil {
public static <T> Function1<T, Unit> $f(Consumer<T> f) {
return (x) -> {
f.accept(x);
return null;
};
}
}

View file

@ -17,8 +17,11 @@
package com.falsepattern.zigbrains.common.util;
import com.intellij.openapi.util.SystemInfo;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
public class PathUtil {
@ -30,4 +33,22 @@ public class PathUtil {
var exeName = SystemInfo.isWindows ? toolName + ".exe" : toolName;
return path.resolve(exeName).toAbsolutePath();
}
public static @Nullable Path pathFromString(@Nullable String pathString) {
if (pathString == null || pathString.isBlank()) {
return null;
}
try {
return Path.of(pathString);
} catch (InvalidPathException e) {
return null;
}
}
public static @NotNull String stringFromPath(@Nullable Path path) {
if (path == null) {
return "";
}
return path.toString();
}
}

View file

@ -23,6 +23,7 @@ import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.components.fields.ExtendableTextField;
import lombok.val;
import org.jetbrains.annotations.NotNull;
@ -53,7 +54,7 @@ public class TextFieldUtil {
Disposable disposable,
@NlsContexts.DialogTitle String dialogTitle,
Runnable onTextChanged) {
val component = new TextFieldWithBrowseButton(null, disposable);
val component = new TextFieldWithBrowseButton(new ExtendableTextField(), null, disposable);
component.addBrowseFolderListener(dialogTitle, null, null, fileChooserDescriptor, TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
addTextChangeListener(component.getChildComponent(), ignored -> onTextChanged.run());

View file

@ -0,0 +1,103 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.common.util.dsl;
import com.falsepattern.zigbrains.common.util.KtUtil;
import com.intellij.openapi.ui.DialogPanel;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.dsl.builder.Align;
import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.ui.dsl.builder.BuilderKt;
import com.intellij.ui.dsl.builder.Panel;
import com.intellij.ui.dsl.builder.RightGap;
import com.intellij.ui.dsl.builder.Row;
import com.intellij.ui.dsl.builder.RowsRange;
import lombok.RequiredArgsConstructor;
import javax.swing.JComponent;
import javax.swing.JLabel;
import java.awt.Color;
import java.util.function.Consumer;
import static com.falsepattern.zigbrains.common.util.KtUtil.$f;
public class JavaPanel {
private final Panel panel;
public JavaPanel(Panel p) {
this.panel = p;
}
public void cell(String label, JComponent component, Align align) {
row(label, row -> row.cell(component).align(align));
}
public void cell(String label, JComponent component) {
row(label, row -> row.cell(component));
}
public void cell(JComponent component) {
cell("", component);
}
public void label(String label) {
panel.row((JLabel) null, $f(r -> r.label(label)));
}
public void gap() {
panel.gap(RightGap.SMALL);
}
public void row(Consumer<Row> row) {
panel.row((JLabel) null, (r) -> {
row.accept(r);
return null;
});
}
public void row(String text, Consumer<Row> row) {
panel.row(text, $f(row));
}
public void separator() {
panel.separator(null);
}
public void separator(Color color) {
panel.separator(color);
}
public void panel(Consumer<JavaPanel> c) {
panel.panel($f(p -> {
c.accept(new JavaPanel(p));
}));
}
public static DialogPanel newPanel(Consumer<JavaPanel> c) {
return BuilderKt.panel((p) -> {
c.accept(new JavaPanel(p));
return null;
});
}
public void group(String title, boolean open, Consumer<JavaPanel> c) {
panel.collapsibleGroup(title, true, p -> {
c.accept(new JavaPanel(p));
return null;
}).setExpanded(open);
}
}

View file

@ -40,8 +40,8 @@ public class ZigExecConfigBinary extends ZigExecConfigBase<ZigExecConfigBinary>
}
@Override
public String[] buildCommandLineArgs() {
return args.args;
public List<String> buildCommandLineArgs(boolean debug) {
return List.of(args.args);
}
@Override

View file

@ -36,7 +36,6 @@ import org.eclipse.lsp4j.DidChangeConfigurationParams;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

View file

@ -0,0 +1,79 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.actions;
import com.intellij.codeInsight.actions.ReformatCodeAction;
import com.intellij.codeInsight.actions.ShowReformatFileDialog;
import com.intellij.codeInsight.hint.actions.ShowImplementationsAction;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
import com.intellij.codeInsight.navigation.actions.GotoImplementationAction;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.impl.DynamicActionConfigurationCustomizer;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public class ActionCustomizer implements DynamicActionConfigurationCustomizer {
@RequiredArgsConstructor
private static class ActionWrapper<T extends AnAction> {
public final Class<T> klass;
public final Function<T, WrappedAction<T>> wrapper;
}
private static final Map<String, ActionWrapper<?>> actions = new HashMap<>();
static {
actions.put("GotoDeclaration", new ActionWrapper<>(GotoDeclarationAction.class, LSPGotoDeclarationAction::new));
actions.put("GotoImplementation", new ActionWrapper<>(GotoImplementationAction.class, LSPGotoImplementationAction::new));
actions.put("ReformatCode", new ActionWrapper<>(ReformatCodeAction.class, LSPReformatAction::new));
actions.put("QuickImplementations", new ActionWrapper<>(ShowImplementationsAction.class, LSPShowImplementationsAction::new));
actions.put("ShowReformatFileDialog", new ActionWrapper<>(ShowReformatFileDialog.class, LSPShowReformatDialogAction::new));
}
@Override
public void registerActions(@NotNull ActionManager manager) {
for (val entry: actions.entrySet()) {
wrap(manager, entry.getKey(), entry.getValue());
}
}
@Override
public void unregisterActions(@NotNull ActionManager manager) {
for (val name: actions.keySet()) {
unwrap(manager, name);
}
}
private static <T extends AnAction> void wrap(ActionManager manager, String name, ActionWrapper<T> constructor) {
val oldAction = manager.getAction(name);
if (constructor.klass.isInstance(oldAction)) {
val wrapped = constructor.wrapper.apply(constructor.klass.cast(oldAction));
wrapped.copyFrom(oldAction);
manager.replaceAction(name, wrapped);
}
}
private static void unwrap(ActionManager manager, String name) {
val oldAction = manager.getAction(name);
if (oldAction instanceof WrappedAction<?> w) {
manager.replaceAction(name, w.wrapped);
}
}
}

View file

@ -16,42 +16,26 @@
package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.psi.PsiFile;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class LSPGotoDeclarationAction extends WrappedAction<GotoDeclarationAction> implements DumbAware {
public LSPGotoDeclarationAction(GotoDeclarationAction wrapped) {
super(wrapped);
}
public class LSPGotoDeclarationAction extends GotoDeclarationAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getData(CommonDataKeys.PROJECT);
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor == null || project == null) {
super.actionPerformed(e);
return;
}
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null || !IntellijLanguageClient.isExtensionSupported(file.getVirtualFile())) {
super.actionPerformed(e);
return;
}
EditorEventManager manager = EditorEventManagerBase.forEditor(editor);
if (manager == null) {
super.actionPerformed(e);
return;
}
val offset = editor.getCaretModel().getOffset();
public void actionPerformedLSP(@NotNull AnActionEvent e, EditorEventManager manager, PsiFile file) {
val offset = manager.editor.getCaretModel().getOffset();
val psiElement = file.findElementAt(offset);
if (psiElement == null) {
super.actionPerformed(e);
return;
}
manager.gotoDeclarationOrUsages(psiElement);

View file

@ -1,61 +0,0 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.intellij.codeInsight.hint.actions.ShowImplementationsAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import lombok.val;
import org.jetbrains.annotations.NotNull;
public class LSPGotoDefinitionAction extends ShowImplementationsAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
Project project = e.getData(CommonDataKeys.PROJECT);
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor == null || project == null) {
super.actionPerformed(e);
return;
}
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null || !IntellijLanguageClient.isExtensionSupported(file.getVirtualFile())) {
super.actionPerformed(e);
return;
}
EditorEventManager manager = EditorEventManagerBase.forEditor(editor);
if (manager == null) {
super.actionPerformed(e);
return;
}
val offset = editor.getCaretModel().getOffset();
val psiElement = file.findElementAt(offset);
if (psiElement == null) {
super.actionPerformed(e);
return;
}
if (!manager.gotoDefinition(psiElement)) {
super.actionPerformed(e);
}
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.intellij.codeInsight.navigation.actions.GotoImplementationAction;
import com.intellij.idea.ActionsBundle;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PerformWithDocumentsCommitted;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiFile;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class LSPGotoImplementationAction extends WrappedAction<GotoImplementationAction> implements PerformWithDocumentsCommitted {
public LSPGotoImplementationAction(GotoImplementationAction wrapped) {
super(wrapped);
}
@Override
protected void actionPerformedLSP(AnActionEvent e, EditorEventManager manager, PsiFile file) {
val offset = manager.editor.getCaretModel().getOffset();
val psiElement = file.findElementAt(offset);
if (psiElement == null) {
return;
}
manager.gotoDefinition(psiElement);
}
@Override
protected void updateLSP(AnActionEvent e, EditorEventManager manager, PsiFile file) {
if (e.getPresentation().getTextWithMnemonic() == null) {
e.getPresentation().setText(ActionsBundle.actionText("GotoImplementation"));
e.getPresentation().setDescription(ActionsBundle.actionDescription("GotoImplementation"));
}
}
}

View file

@ -16,39 +16,28 @@
package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.requests.ReformatHandler;
import com.intellij.codeInsight.actions.ReformatCodeAction;
import com.intellij.ide.lightEdit.LightEditCompatible;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import lombok.val;
/**
* Action overriding the default reformat action
* Fallback to the default action if the language is already supported or not supported by any language server
*/
public class LSPReformatAction extends ReformatCodeAction implements DumbAware {
private Logger LOG = Logger.getInstance(LSPReformatAction.class);
public class LSPReformatAction extends WrappedAction<ReformatCodeAction> implements DumbAware, LightEditCompatible {
public LSPReformatAction(ReformatCodeAction wrapped) {
super(wrapped);
}
@Override
public void actionPerformed(AnActionEvent e) {
Project project = e.getData(CommonDataKeys.PROJECT);
Editor editor = e.getData(CommonDataKeys.EDITOR);
if (editor == null || project == null) {
super.actionPerformed(e);
return;
}
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null || !IntellijLanguageClient.isExtensionSupported(file.getVirtualFile())) {
super.actionPerformed(e);
return;
}
public void actionPerformedLSP(AnActionEvent e, EditorEventManager manager, PsiFile file) {
val editor = manager.editor;
ApplicationUtil.writeAction(() -> FileDocumentManager.getInstance().saveDocument(editor.getDocument()));
// if editor hasSelection, only reformat selection, not reformat the whole file
if (editor.getSelectionModel().hasSelection()) {
@ -57,9 +46,4 @@ public class LSPReformatAction extends ReformatCodeAction implements DumbAware {
ReformatHandler.reformatFile(editor);
}
}
@Override
public void update(AnActionEvent event) {
super.update(event);
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.intellij.codeInsight.hint.actions.ShowImplementationsAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.DumbAware;
import com.intellij.psi.PsiFile;
import lombok.val;
import org.jetbrains.annotations.NotNull;
public class LSPShowImplementationsAction extends WrappedAction<ShowImplementationsAction> implements DumbAware {
public LSPShowImplementationsAction(ShowImplementationsAction wrapped) {
super(wrapped);
}
@Override
public void actionPerformedLSP(@NotNull AnActionEvent e, EditorEventManager manager, PsiFile file) {
val offset = manager.editor.getCaretModel().getOffset();
val psiElement = file.findElementAt(offset);
if (psiElement == null) {
return;
}
manager.gotoDefinition(psiElement);
}
}

View file

@ -23,41 +23,33 @@ import com.intellij.codeInsight.actions.LayoutCodeOptions;
import com.intellij.codeInsight.actions.ShowReformatFileDialog;
import com.intellij.codeInsight.actions.TextRangeType;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import lombok.val;
/**
* Class overriding the default action handling the Reformat dialog event (CTRL+ALT+SHIFT+L by default)
* Fallback to the default action if the language is already supported or not supported by any language server
*/
public class LSPShowReformatDialogAction extends ShowReformatFileDialog implements DumbAware {
public class LSPShowReformatDialogAction extends WrappedAction<ShowReformatFileDialog> implements DumbAware {
private String HELP_ID = "editing.codeReformatting";
private Logger LOG = Logger.getInstance(LSPShowReformatDialogAction.class);
@Override
public void actionPerformed(AnActionEvent e) {
Editor editor = e.getData(CommonDataKeys.EDITOR);
Project project = e.getData(CommonDataKeys.PROJECT);
public LSPShowReformatDialogAction(ShowReformatFileDialog wrapped) {
super(wrapped);
}
if (editor == null || project == null) {
super.actionPerformed(e);
return;
}
PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (psiFile == null) {
super.actionPerformed(e);
return;
}
@Override
public void actionPerformedLSP(AnActionEvent e, EditorEventManager manager, PsiFile psiFile) {
val editor = manager.editor;
val project = manager.getProject();
VirtualFile virFile = FileDocumentManager.getInstance().getFile(editor.getDocument());
if (!IntellijLanguageClient.isExtensionSupported(virFile)) {
super.actionPerformed(e);
wrapped.actionPerformed(e);
return;
}
boolean hasSelection = editor.getSelectionModel().hasSelection();
@ -79,10 +71,5 @@ public class LSPShowReformatDialogAction extends ShowReformatFileDialog implemen
}
}
}
@Override
public void update(AnActionEvent event) {
super.update(event);
}
}

View file

@ -0,0 +1,247 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.OverridingAction;
import com.intellij.openapi.actionSystem.ShortcutSet;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import lombok.val;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.List;
import java.util.function.Supplier;
public abstract class WrappedAction<T extends AnAction> extends AnAction implements OverridingAction {
public final T wrapped;
// private static class Reflector {
// static Field templatePresentation;
// static Field myShortcutSet;
// static Field myEnabledInModalContext;
// static Field myIsDefaultIcon;
// static Field myWorksInInjected;
// static Field myActionTextOverrides;
// static Field mySynonyms;
// static Field[] fields;
//
// static {
// val theClass = AnAction.class;
// val validFields = new ArrayList<Field>();
// try {
// templatePresentation = theClass.getDeclaredField("templatePresentation");
// templatePresentation.setAccessible(true);
// validFields.add(templatePresentation);
// } catch (NoSuchFieldException e) {
// e.printStackTrace();
// }
// try {
// myShortcutSet = theClass.getDeclaredField("myShortcutSet");
// myShortcutSet.setAccessible(true);
// validFields.add(myShortcutSet);
// } catch (NoSuchFieldException e) {
// e.printStackTrace();
// }
// try {
// myEnabledInModalContext = theClass.getDeclaredField("myEnabledInModalContext");
// myEnabledInModalContext.setAccessible(true);
// validFields.add(myEnabledInModalContext);
// } catch (NoSuchFieldException e) {
// e.printStackTrace();
// }
// try {
// myIsDefaultIcon = theClass.getDeclaredField("myIsDefaultIcon");
// myIsDefaultIcon.setAccessible(true);
// validFields.add(myIsDefaultIcon);
// } catch (NoSuchFieldException e) {
// e.printStackTrace();
// }
// try {
// myWorksInInjected = theClass.getDeclaredField("myWorksInInjected");
// myWorksInInjected.setAccessible(true);
// validFields.add(myWorksInInjected);
// } catch (NoSuchFieldException e) {
// e.printStackTrace();
// }
// try {
// myActionTextOverrides = theClass.getDeclaredField("myActionTextOverrides");
// myActionTextOverrides.setAccessible(true);
// validFields.add(myActionTextOverrides);
// } catch (NoSuchFieldException e) {
// e.printStackTrace();
// }
// try {
// mySynonyms = theClass.getDeclaredField("mySynonyms");
// mySynonyms.setAccessible(true);
// validFields.add(mySynonyms);
// } catch (NoSuchFieldException e) {
// e.printStackTrace();
// }
// fields = validFields.toArray(new Field[0]);
// }
// }
public WrappedAction(T wrapped) {
this.wrapped = wrapped;
// if (Reflector.fields != null) {
// for (val field: Reflector.fields) {
// assimilate(field);
// }
// }
}
private void assimilate(Field field) {
if (field == null)
return;
try {
field.set(this, field.get(wrapped));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
@Override
public boolean isDumbAware() {
return wrapped.isDumbAware();
}
@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
return wrapped.getActionUpdateThread();
}
@Override
public boolean displayTextInToolbar() {
return super.displayTextInToolbar();
}
@Override
public boolean useSmallerFontForTextInToolbar() {
return super.useSmallerFontForTextInToolbar();
}
@Override
public void setDefaultIcon(boolean isDefaultIconSet) {
wrapped.setDefaultIcon(isDefaultIconSet);
}
@Override
public boolean isDefaultIcon() {
return wrapped.isDefaultIcon();
}
@Override
public void setInjectedContext(boolean worksInInjected) {
wrapped.setInjectedContext(worksInInjected);
}
@Override
public boolean isInInjectedContext() {
return wrapped.isInInjectedContext();
}
@Override
public void addSynonym(@NotNull Supplier<@Nls String> text) {
wrapped.addSynonym(text);
}
@Override
public @NotNull List<Supplier<String>> getSynonyms() {
return wrapped.getSynonyms();
}
@Override
public final void update(@NotNull AnActionEvent e) {
val manager = tryGetEventManager(e);
if (manager == null) {
wrapped.update(e);
return;
}
val file = tryGetPSIFileLSPAware(manager);
if (file == null) {
wrapped.update(e);
return;
}
updateLSP(e, manager, file);
}
@Override
public final void beforeActionPerformedUpdate(@NotNull AnActionEvent e) {
val manager = tryGetEventManager(e);
if (manager == null) {
wrapped.beforeActionPerformedUpdate(e);
return;
}
val file = tryGetPSIFileLSPAware(manager);
if (file == null) {
wrapped.beforeActionPerformedUpdate(e);
return;
}
beforeActionPerformedUpdateLSP(e, manager, file);
}
@Override
public final void actionPerformed(@NotNull AnActionEvent e) {
val manager = tryGetEventManager(e);
if (manager == null) {
wrapped.actionPerformed(e);
return;
}
val file = tryGetPSIFileLSPAware(manager);
if (file == null) {
wrapped.actionPerformed(e);
return;
}
actionPerformedLSP(e, manager, file);
}
private static EditorEventManager tryGetEventManager(AnActionEvent e) {
val editor = e.getData(CommonDataKeys.EDITOR);
if (editor == null)
return null;
return EditorEventManagerBase.forEditor(editor);
}
private static PsiFile tryGetPSIFileLSPAware(EditorEventManager manager) {
val project = manager.getProject();
val editor = manager.editor;
if (project == null)
return null;
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null || !IntellijLanguageClient.isExtensionSupported(file.getVirtualFile()))
return null;
return file;
}
protected abstract void actionPerformedLSP(AnActionEvent e, EditorEventManager manager, PsiFile file);
protected void beforeActionPerformedUpdateLSP(AnActionEvent e, EditorEventManager manager, PsiFile file) {
}
protected void updateLSP(AnActionEvent e, EditorEventManager manager, PsiFile file) {
}
}

View file

@ -47,7 +47,6 @@ import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.remoteServer.util.CloudNotifier;
@ -261,7 +260,7 @@ public class LanguageServerWrapper {
}
public void notifyResult(Timeouts timeouts, boolean success) {
getWidget().ifPresent(widget -> widget.notifyResult(timeouts, success));
getWidgets().forEach(widget -> widget.notifyResult(timeouts, success));
}
public void notifySuccess(Timeouts timeouts) {
@ -624,7 +623,7 @@ public class LanguageServerWrapper {
private void setStatus(ServerStatus status) {
this.status = status;
getWidget().ifPresent(widget -> widget.setStatus(status));
getWidgets().forEach(widget -> widget.setStatus(status));
synchronized (shutdownHook) {
if ((status == STARTED || status == INITIALIZED) && shutdownHook.get() == null) {
shutdownHook.set(new Thread(() -> {
@ -694,10 +693,6 @@ public class LanguageServerWrapper {
return connected;
}
public void removeWidget() {
getWidget().ifPresent(LSPServerStatusWidget::dispose);
}
/**
* Disconnects an editor from the LanguageServer
*
@ -726,7 +721,7 @@ public class LanguageServerWrapper {
if (connectedEditors.isEmpty()) {
stop(true);
getWidget().ifPresent(widget -> widget.setStatus(ServerStatus.NONEXISTENT));
getWidgets().forEach(widget -> widget.setStatus(ServerStatus.NONEXISTENT));
}
}
@ -764,13 +759,12 @@ public class LanguageServerWrapper {
if (connectedEditors.isEmpty()) {
stop(true);
getWidget().ifPresent(widget -> widget.setStatus(ServerStatus.NONEXISTENT));
getWidgets().forEach(widget -> widget.setStatus(ServerStatus.NONEXISTENT));
}
}
public void removeServerWrapper() {
stop(true);
removeWidget();
IntellijLanguageClient.removeWrapper(this);
}
@ -805,13 +799,8 @@ public class LanguageServerWrapper {
});
}
private Optional<LSPServerStatusWidget> getWidget() {
LSPServerStatusWidgetFactory factory = ((LSPServerStatusWidgetFactory) project.getService(StatusBarWidgetsManager.class).findWidgetFactory("LSP"));
if (factory != null) {
return Optional.of(factory.getWidget(project));
} else {
return Optional.empty();
}
private List<LSPServerStatusWidget> getWidgets() {
return Optional.ofNullable(project.getUserData(LSPServerStatusWidgetFactory.LSP_WIDGETS)).orElse(List.of());
}
/**

View file

@ -143,20 +143,17 @@ public class LSPAnnotator extends ExternalAnnotator<Object, Object> {
annotations.forEach(annotation -> {
if (annotation.getQuickFixes() != null && !annotation.getQuickFixes().isEmpty()) {
AnnotationBuilder builder = holder.newAnnotation(annotation.getSeverity(), annotation.getMessage());
boolean range = true;
builder = builder.range(TextRange.create(annotation.getStartOffset(), annotation.getEndOffset()));
for (Annotation.QuickFixInfo quickFixInfo : annotation.getQuickFixes()) {
if (range) {
builder = builder.range(quickFixInfo.textRange);
range = false;
}
builder = builder.withFix(quickFixInfo.quickFix);
builder = builder.newFix(quickFixInfo.quickFix).range(quickFixInfo.textRange).key(quickFixInfo.key).registerFix();
}
builder.create();
} else if (requests.containsKey(annotation)) {
AnnotationBuilder builder = holder.newAnnotation(annotation.getSeverity(), annotation.getMessage());
AnnotationBuilder builder = holder.newAnnotation(annotation.getSeverity(), annotation.getMessage());
builder = builder.range(TextRange.create(annotation.getStartOffset(), annotation.getEndOffset()));
var request = requests.remove(annotation);
for (var quickFixInfo: request) {
builder = builder.withFix(quickFixInfo.action());
builder = builder.newFix(quickFixInfo.action()).range(quickFixInfo.range()).registerFix();
}
builder.create();
}

View file

@ -34,7 +34,6 @@ public class LSPProjectManagerListener implements ProjectManagerListener {
Set<LanguageServerWrapper> languageServerWrappers = IntellijLanguageClient.getAllServerWrappersFor(FileUtils.projectToUri(project));
languageServerWrappers.forEach(wrapper -> {
wrapper.stop(false);
wrapper.removeWidget();
IntellijLanguageClient.removeWrapper(wrapper);
});
}

View file

@ -15,12 +15,12 @@
*/
package com.falsepattern.zigbrains.lsp.statusbar;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper;
import com.falsepattern.zigbrains.lsp.contributors.icon.LSPDefaultIconProvider;
import com.falsepattern.zigbrains.lsp.requests.Timeouts;
import com.falsepattern.zigbrains.lsp.utils.GUIUtils;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataContext;
@ -30,40 +30,67 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.StatusBarWidget;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.Consumer;
import com.intellij.openapi.wm.impl.status.EditorBasedStatusBarPopup;
import lombok.val;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class LSPServerStatusWidget implements StatusBarWidget {
public class LSPServerStatusWidget extends EditorBasedStatusBarPopup {
private final Map<Timeouts, Pair<Integer, Integer>> timeouts = new HashMap<>();
private final Project project;
private final String projectName;
private ServerStatus status = ServerStatus.NONEXISTENT;
private static final List<WeakReference<LSPServerStatusWidget>> widgets = Collections.synchronizedList(new ArrayList<>());
private static final ScheduledExecutorService refresher = Executors.newSingleThreadScheduledExecutor();
static {
refresher.scheduleWithFixedDelay(() -> {
synchronized (widgets) {
val iter = widgets.iterator();
while (iter.hasNext()) {
val weakWidget = iter.next();
val widget = weakWidget.get();
if (widget == null) {
iter.remove();
continue;
}
widget.update();
}
}
}, 1, 1, TimeUnit.SECONDS);
}
public Project project() {
return super.getProject();
}
LSPServerStatusWidget(Project project) {
this.project = project;
super(project, false);
this.projectName = project.getName();
for (Timeouts t : Timeouts.values()) {
timeouts.put(t, new MutablePair<>(0, 0));
}
widgets.add(new WeakReference<>(this));
}
public void notifyResult(Timeouts timeout, Boolean success) {
@ -75,14 +102,6 @@ public class LSPServerStatusWidget implements StatusBarWidget {
}
}
public IconPresentation getPresentation() {
return new IconPresentation();
}
@Override
public void install(@NotNull StatusBar statusBar) {
}
/**
* Sets the status of the server
*
@ -90,152 +109,194 @@ public class LSPServerStatusWidget implements StatusBarWidget {
*/
public void setStatus(ServerStatus status) {
this.status = status;
updateWidget();
update();
}
private void updateWidget() {
WindowManager manager = WindowManager.getInstance();
if (manager != null && project != null && !project.isDisposed()) {
StatusBar statusBar = manager.getStatusBar(project);
if (statusBar != null) {
statusBar.updateWidget(ID());
}
}
}
@Override
public void dispose() {}
@NotNull
@Override
public String ID() {
return "LSP";
}
public Project getProject() {
return project;
@NotNull
@Override
protected StatusBarWidget createInstance(@NotNull Project project) {
return new LSPServerStatusWidget(project);
}
private class IconPresentation implements StatusBarWidget.IconPresentation {
@Nullable
@Override
public Icon getIcon() {
LanguageServerWrapper wrapper = LanguageServerWrapper.forProject(project);
Map<ServerStatus, Icon> icons = new LSPDefaultIconProvider().getStatusIcons();
if (wrapper != null) {
icons = GUIUtils.getIconProviderFor(wrapper.getServerDefinition())
.getStatusIcons();
}
return icons.get(status);
private LSPServerStatusPanel component;
@NotNull
@Override
protected JPanel createComponent() {
component = new LSPServerStatusPanel();
updateComponent();
return component;
}
@Override
protected void updateComponent(@NotNull EditorBasedStatusBarPopup.WidgetState state) {
updateComponent();
}
private void updateComponent() {
if (component != null) {
component.setToolTipText(getTooltipText());
component.setIcon(getIcon());
}
}
@SuppressWarnings("UsagesOfObsoleteApi")
@Nullable
@Override
public Consumer<MouseEvent> getClickConsumer() {
return (MouseEvent t) -> {
JBPopupFactory.ActionSelectionAid mnemonics = JBPopupFactory.ActionSelectionAid.MNEMONICS;
Component component = t.getComponent();
var wrapper = LanguageServerWrapper.forProject(project);
if (wrapper == null) {
var popup = JBPopupFactory.getInstance().createMessage("No language server active.");
Dimension dimension = popup.getContent().getPreferredSize();
Point at = new Point(0, -dimension.height);
popup.show(new RelativePoint(t.getComponent(), at));
return;
}
List<AnAction> actions = new ArrayList<>();
if (wrapper.getStatus() == ServerStatus.INITIALIZED) {
actions.add(new ShowConnectedFiles());
}
actions.add(new ShowTimeouts());
@Nullable
@Override
public WidgetPresentation getPresentation() {
return super.getPresentation();
}
actions.add(new Restart());
String title = "Server Actions";
DataContext context = DataManager.getInstance().getDataContext(component);
DefaultActionGroup group = new DefaultActionGroup(actions);
ListPopup popup = JBPopupFactory.getInstance()
.createActionGroupPopup(title, group, context, mnemonics, true);
Dimension dimension = popup.getContent().getPreferredSize();
Point at = new Point(0, -dimension.height);
popup.show(new RelativePoint(t.getComponent(), at));
};
@Nullable
private Icon getIcon() {
LanguageServerWrapper wrapper = LanguageServerWrapper.forProject(project());
Map<ServerStatus, Icon> icons = new LSPDefaultIconProvider().getStatusIcons();
if (wrapper != null) {
icons = GUIUtils.getIconProviderFor(wrapper.getServerDefinition())
.getStatusIcons();
}
return icons.get(status);
}
class ShowConnectedFiles extends AnAction implements DumbAware {
ShowConnectedFiles() {
super("&Show Connected Files", "Show the files connected to the server", null);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
var wrapper = LanguageServerWrapper.forProject(project);
if (wrapper == null) {
return;
}
StringBuilder connectedFiles = new StringBuilder("Connected files :");
wrapper.getConnectedFiles().forEach(f -> connectedFiles.append(System.lineSeparator()).append(f));
Messages.showInfoMessage(connectedFiles.toString(), "Connected Files");
}
private String getTooltipText() {
LanguageServerWrapper wrapper = LanguageServerWrapper.forProject(project());
if (wrapper == null) {
return "Language server, project " + projectName;
} else {
return "Language server for extension " + wrapper.getServerDefinition().ext + ", project " + projectName;
}
}
class ShowTimeouts extends AnAction implements DumbAware {
ShowTimeouts() {
super("&Show Timeouts", "Show the timeouts proportions of the server", null);
}
@Override
protected boolean isEmpty() {
return false;
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
StringBuilder message = new StringBuilder();
message.append("<html>");
message.append("Timeouts (failed requests) :<br>");
timeouts.forEach((t, v) -> {
int timeouts = v.getRight();
message.append(t.name(), 0, 1).append(t.name().substring(1).toLowerCase()).append(" => ");
int total = v.getLeft() + timeouts;
if (total != 0) {
if (timeouts > 0) {
message.append("<font color=\"red\">");
}
message.append(timeouts).append("/").append(total).append(" (")
.append(100 * (double) timeouts / total).append("%)<br>");
if (timeouts > 0) {
message.append("</font>");
}
} else {
message.append("0/0 (0%)<br>");
}
});
message.append("</html>");
Messages.showInfoMessage(message.toString(), "Timeouts");
}
@Nullable
@Override
protected ListPopup createPopup(@NotNull DataContext dataContext) {
var wrapper = LanguageServerWrapper.forProject(project());
if (wrapper == null) {
return null;
}
List<AnAction> actions = new ArrayList<>();
if (wrapper.getStatus() == ServerStatus.INITIALIZED) {
actions.add(new ShowConnectedFiles());
}
actions.add(new ShowTimeouts());
class Restart extends AnAction implements DumbAware {
actions.add(new Restart());
Restart() {
super("&Restart", "Restarts the language server.", null);
}
JBPopupFactory.ActionSelectionAid mnemonics = JBPopupFactory.ActionSelectionAid.MNEMONICS;
String title = "Server Actions";
DefaultActionGroup group = new DefaultActionGroup(actions);
return JBPopupFactory.getInstance()
.createActionGroupPopup(title, group, dataContext, mnemonics, true);
}
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
var wrapper = LanguageServerWrapper.forProject(project);
if (wrapper != null) {
wrapper.restart();
}
}
@NotNull
@Override
protected WidgetState getWidgetState(@Nullable VirtualFile virtualFile) {
if (virtualFile == null) {
return WidgetState.HIDDEN;
}
val manager = IntellijLanguageClient.getExtensionManagerFor(virtualFile.getExtension());
if (manager != null) {
val ws = new WidgetState(getTooltipText(), null, true);
ws.setIcon(getIcon());
return ws;
} else {
return WidgetState.HIDDEN;
}
}
class ShowConnectedFiles extends AnAction implements DumbAware {
ShowConnectedFiles() {
super("&Show Connected Files", "Show the files connected to the server", null);
}
@Override
public String getTooltipText() {
LanguageServerWrapper wrapper = LanguageServerWrapper.forProject(project);
public void actionPerformed(@NotNull AnActionEvent e) {
var wrapper = LanguageServerWrapper.forProject(project());
if (wrapper == null) {
return "Language server, project " + projectName;
} else {
return "Language server for extension " + wrapper.getServerDefinition().ext + ", project " + projectName;
return;
}
StringBuilder connectedFiles = new StringBuilder("Connected files :");
wrapper.getConnectedFiles().forEach(f -> connectedFiles.append(System.lineSeparator()).append(f));
Messages.showInfoMessage(connectedFiles.toString(), "Connected Files");
}
}
class ShowTimeouts extends AnAction implements DumbAware {
ShowTimeouts() {
super("&Show Timeouts", "Show the timeouts proportions of the server", null);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
StringBuilder message = new StringBuilder();
message.append("<html>");
message.append("Timeouts (failed requests) :<br>");
timeouts.forEach((t, v) -> {
int timeouts = v.getRight();
message.append(t.name(), 0, 1).append(t.name().substring(1).toLowerCase()).append(" => ");
int total = v.getLeft() + timeouts;
if (total != 0) {
if (timeouts > 0) {
message.append("<font color=\"red\">");
}
message.append(timeouts).append("/").append(total).append(" (")
.append(100 * (double) timeouts / total).append("%)<br>");
if (timeouts > 0) {
message.append("</font>");
}
} else {
message.append("0/0 (0%)<br>");
}
});
message.append("</html>");
Messages.showInfoMessage(message.toString(), "Timeouts");
}
}
class Restart extends AnAction implements DumbAware {
Restart() {
super("&Restart", "Restarts the language server.", null);
}
@Override
public void actionPerformed(@NotNull AnActionEvent anActionEvent) {
var wrapper = LanguageServerWrapper.forProject(project());
if (wrapper != null) {
wrapper.restart();
}
}
}
private class LSPServerStatusPanel extends JPanel {
private final JLabel myIconLabel;
LSPServerStatusPanel() {
super();
setOpaque(false);
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
setAlignmentY(Component.CENTER_ALIGNMENT);
myIconLabel = new JLabel("");
add(myIconLabel);
}
public void setIcon(@Nullable Icon icon) {
myIconLabel.setIcon(icon);
myIconLabel.setVisible(icon != null);
}
}
}

View file

@ -16,19 +16,19 @@
package com.falsepattern.zigbrains.lsp.statusbar;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.wm.StatusBarWidget;
import com.intellij.openapi.wm.StatusBarWidgetFactory;
import com.intellij.openapi.wm.impl.status.widget.StatusBarEditorBasedWidgetFactory;
import lombok.val;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
public class LSPServerStatusWidgetFactory implements StatusBarWidgetFactory {
private final Map<Project, LSPServerStatusWidget> widgetForProject = new HashMap<>();
import java.util.ArrayList;
import java.util.List;
public class LSPServerStatusWidgetFactory extends StatusBarEditorBasedWidgetFactory {
public static final Key<List<LSPServerStatusWidget>> LSP_WIDGETS = Key.create("ZB_LSP_KEYS");
@Override
public @NonNls
@NotNull
@ -44,29 +44,26 @@ public class LSPServerStatusWidgetFactory implements StatusBarWidgetFactory {
}
@Override
public boolean isAvailable(@NotNull Project project) {
return true;
}
@Override
public
StatusBarWidget createWidget(@NotNull Project project) {
return widgetForProject.computeIfAbsent(project, (k) -> new LSPServerStatusWidget(project));
}
@Override
public void disposeWidget(@NotNull StatusBarWidget statusBarWidget) {
if (statusBarWidget instanceof LSPServerStatusWidget) {
widgetForProject.remove(((LSPServerStatusWidget) statusBarWidget).getProject());
public @NotNull StatusBarWidget createWidget(@NotNull Project project) {
var keys = project.getUserData(LSP_WIDGETS);
if (keys == null) {
keys = new ArrayList<>();
project.putUserData(LSP_WIDGETS, keys);
}
val widget = new LSPServerStatusWidget(project);
keys.add(widget);
return widget;
}
@Override
public boolean canBeEnabledOn(@NotNull StatusBar statusBar) {
return true;
}
public LSPServerStatusWidget getWidget(Project project) {
return widgetForProject.get(project);
public void disposeWidget(@NotNull StatusBarWidget widget) {
if (widget instanceof LSPServerStatusWidget w) {
val project = w.project();
val keys = project.getUserData(LSP_WIDGETS);
if (keys != null) {
keys.remove(widget);
}
}
super.disposeWidget(widget);
}
}

View file

@ -25,7 +25,6 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectUtil;

View file

@ -59,7 +59,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.setRedirectErrorStream(true);
cli.addParameters(debug ? configuration.buildDebugCommandLineArgs() : configuration.buildCommandLineArgs());
cli.addParameters(configuration.buildCommandLineArgs(debug));
return cli;
}

View file

@ -63,11 +63,7 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
getConfigurables().forEach(cfg -> cfg.writeExternal(element));
}
public abstract String[] buildCommandLineArgs() throws ExecutionException;
public String[] buildDebugCommandLineArgs() throws ExecutionException {
return buildCommandLineArgs();
}
public abstract List<String> buildCommandLineArgs(boolean debug) throws ExecutionException;
@Override
public T clone() {

View file

@ -20,6 +20,7 @@ import com.falsepattern.zigbrains.common.ZBFeatures;
import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory;
@ -44,30 +45,28 @@ public class ZigExecConfigBuild extends ZigExecConfigBase<ZigExecConfigBuild> {
super(project, factory, "Zig Build");
}
private String[] buildWithSteps(String[] steps) throws ExecutionException {
val base = new String[]{"build", "--color", colored.value ? "on" : "off"};
return CollectionUtil.concat(base, steps, extraArgs.args).toArray(String[]::new);
}
@Override
public String[] buildCommandLineArgs() throws ExecutionException {
return buildWithSteps(buildSteps.args);
}
@Override
public String[] buildDebugCommandLineArgs() throws ExecutionException {
var steps = buildSteps.args;
val truncatedSteps = new ArrayList<String>();
for (int i = 0; i < steps.length; i++) {
if (steps[i].equals("run")) {
continue;
public List<String> buildCommandLineArgs(boolean debug) throws ExecutionException {
val result = new ArrayList<String>();
result.add("build");
var steps = List.of(buildSteps.args);
if (debug) {
val truncatedSteps = new ArrayList<String>();
for (int i = 0, size = steps.size(); i < size; i++) {
if (steps.get(i).equals("run")) {
continue;
}
if (steps.get(i).equals("test")) {
throw new ExecutionException("Debugging \"zig build test\" is not supported yet.");
}
truncatedSteps.add(steps.get(i));
}
if (steps[i].equals("test")) {
throw new ExecutionException("Debugging \"zig build test\" is not supported yet.");
}
truncatedSteps.add(steps[i]);
steps = truncatedSteps;
}
return buildWithSteps(truncatedSteps.toArray(String[]::new));
result.addAll(steps);
result.addAll(CLIUtil.colored(colored.value));
result.addAll(List.of(extraArgs.args));
return result;
}
@Override

View file

@ -19,6 +19,7 @@ package com.falsepattern.zigbrains.project.execution.run;
import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.runners.ExecutionEnvironment;
@ -28,6 +29,7 @@ import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@Getter
@ -41,17 +43,19 @@ public class ZigExecConfigRun extends ZigExecConfigBase<ZigExecConfigRun> {
}
@Override
public String[] buildCommandLineArgs() {
return CollectionUtil.concat(new String[]{"run", "--color", colored.value ? "on" : "off", filePath.getPathOrThrow().toString(), "-O", optimization.level.name(), "--"}, exeArgs.args).toArray(String[]::new);
}
@Override
public String[] buildDebugCommandLineArgs() {
if (optimization.forced) {
return new String[]{"build-exe", "--color", colored.value ? "on" : "off", filePath.getPathOrThrow().toString(), "-O", optimization.level.name()};
} else {
return new String[]{"build-exe", "--color", colored.value ? "on" : "off", filePath.getPathOrThrow().toString()};
public List<String> buildCommandLineArgs(boolean debug) {
val result = new ArrayList<String>();
result.add("run");
result.addAll(CLIUtil.colored(colored.value));
result.add(filePath.getPathOrThrow().toString());
if (!debug || optimization.forced) {
result.addAll(List.of("-O", optimization.level.name()));
}
if (!debug) {
result.add("--");
result.addAll(List.of(exeArgs.args));
}
return result;
}
@Override

View file

@ -20,6 +20,7 @@ import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.runners.ExecutionEnvironment;
@ -29,6 +30,7 @@ import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@Getter
@ -41,17 +43,18 @@ public class ZigExecConfigTest extends ZigExecConfigBase<ZigExecConfigTest> {
}
@Override
public String[] buildCommandLineArgs() {
return new String[]{"test", "--color", colored.value ? "on" : "off", filePath.getPathOrThrow().toString(), "-O", optimization.level.name()};
}
@Override
public String[] buildDebugCommandLineArgs() {
if (optimization.forced) {
return new String[]{"test", "--color", colored.value ? "on" : "off", filePath.getPathOrThrow().toString(), "--test-no-exec", "-O", optimization.level.name()};
} else {
return new String[]{"test", "--color", colored.value ? "on" : "off", filePath.getPathOrThrow().toString(), "--test-no-exec"};
public List<String> buildCommandLineArgs(boolean debug) {
val result = new ArrayList<String>();
result.add("test");
result.addAll(CLIUtil.colored(colored.value));
result.add(filePath.getPathOrThrow().toString());
if (!debug || optimization.forced) {
result.addAll(List.of("-O", optimization.level.name()));
}
if (debug) {
result.add("--test-no-exec");
}
return result;
}
@Override

View file

@ -0,0 +1,34 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.ide.config;
import com.falsepattern.zigbrains.common.MultiConfigurable;
import com.falsepattern.zigbrains.project.ide.project.ZigProjectConfigurable;
import com.falsepattern.zigbrains.zig.settings.ZLSSettingsConfigurable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsContexts;
import org.jetbrains.annotations.NotNull;
public class ZigConfigurable extends MultiConfigurable {
public ZigConfigurable(@NotNull Project project) {
super(new ZigProjectConfigurable(project), new ZLSSettingsConfigurable(project));
}
@Override
public @NlsContexts.ConfigurableName String getDisplayName() {
return "Zig";
}
}

View file

@ -0,0 +1,82 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.ide.newproject;
import com.falsepattern.zigbrains.project.ide.newproject.ZigProjectConfigurationData;
import com.falsepattern.zigbrains.project.ide.newproject.ZigProjectGeneratorPeer;
import com.falsepattern.zigbrains.project.ide.project.ZigDefaultTemplate;
import com.falsepattern.zigbrains.project.ide.newproject.ZigProjectSettingsStep;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.zig.Icons;
import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService;
import com.intellij.facet.ui.ValidationResult;
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep;
import com.intellij.ide.util.projectWizard.CustomStepProjectGenerator;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.impl.welcomeScreen.AbstractActionWithPanel;
import com.intellij.platform.DirectoryProjectGenerator;
import com.intellij.platform.ProjectGeneratorPeer;
import com.intellij.util.ResourceUtil;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.Icon;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ZigDirectoryProjectGenerator implements DirectoryProjectGenerator<ZigProjectConfigurationData>,
CustomStepProjectGenerator<ZigProjectConfigurationData> {
@Override
public @NotNull @NlsContexts.Label String getName() {
return "Zig";
}
@Override
public @Nullable Icon getLogo() {
return Icons.ZIG;
}
@Override
public @NotNull ProjectGeneratorPeer<ZigProjectConfigurationData> createPeer() {
return new ZigProjectGeneratorPeer(true);
}
@Override
public @NotNull ValidationResult validate(@NotNull String baseDirPath) {
return ValidationResult.OK;
}
@Override
public void generateProject(@NotNull Project project, @NotNull VirtualFile baseDir, @NotNull ZigProjectConfigurationData data, @NotNull Module module) {
data.generateProject(this, project, baseDir, false);
}
@Override
public AbstractActionWithPanel createStep(DirectoryProjectGenerator<ZigProjectConfigurationData> projectGenerator, AbstractNewProjectStep.AbstractCallback<ZigProjectConfigurationData> callback) {
return new ZigProjectSettingsStep(projectGenerator);
}
}

View file

@ -14,24 +14,29 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.ide.util.projectwizard;
package com.falsepattern.zigbrains.project.ide.newproject;
import com.falsepattern.zigbrains.project.ide.newproject.ZigProjectConfigurationData;
import com.falsepattern.zigbrains.project.openapi.module.ZigModuleType;
import com.falsepattern.zigbrains.project.util.ExperimentUtil;
import com.intellij.ide.NewProjectWizardLegacy;
import com.intellij.ide.util.projectWizard.ModuleBuilder;
import com.intellij.ide.util.projectWizard.ModuleWizardStep;
import com.intellij.ide.util.projectWizard.WizardContext;
import com.intellij.ide.wizard.CommitStepException;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.ui.JBUI;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.JComponent;
public class ZigModuleBuilder extends ModuleBuilder {
public @Nullable ZigProjectConfigurationData configurationData = null;
public boolean forceGitignore = false;
@Override
public ModuleType<?> getModuleType() {
@ -45,7 +50,7 @@ public class ZigModuleBuilder extends ModuleBuilder {
@Override
public void setupRootModel(@NotNull ModifiableRootModel modifiableRootModel) {
createProject(modifiableRootModel, "git");
createProject(modifiableRootModel);
}
@Override
@ -55,8 +60,8 @@ public class ZigModuleBuilder extends ModuleBuilder {
return step;
}
public void createProject(ModifiableRootModel modifiableRootModel, @Nullable String vcs) {
val contentEntry = doAddContentEntry(modifiableRootModel);
public void createProject(ModifiableRootModel rootModel) {
val contentEntry = doAddContentEntry(rootModel);
if (contentEntry == null) {
return;
}
@ -64,7 +69,36 @@ public class ZigModuleBuilder extends ModuleBuilder {
if (root == null) {
return;
}
modifiableRootModel.inheritSdk();
if (configurationData == null) {
return;
}
configurationData.generateProject(this, rootModel.getProject(), root, forceGitignore);
root.refresh(false, true);
}
public class ZigModuleWizardStep extends ModuleWizardStep {
private final ZigProjectGeneratorPeer peer = new ZigProjectGeneratorPeer(true);
@Override
public JComponent getComponent() {
return withBorderIfNeeded(peer.getComponent());
}
@Override
public void disposeUIResources() {
peer.dispose();
}
@Override
public void updateDataModel() {
ZigModuleBuilder.this.configurationData = peer.getSettings();
}
private <T extends JComponent> T withBorderIfNeeded(T component) {
if (ExperimentUtil.isNewWizard()) {
component.setBorder(JBUI.Borders.empty(14, 20));
}
return component;
}
}
}

View file

@ -16,19 +16,23 @@
package com.falsepattern.zigbrains.project.ide.newproject;
import com.falsepattern.zigbrains.common.util.dsl.JavaPanel;
import com.falsepattern.zigbrains.project.ide.project.ZigDefaultTemplate;
import com.falsepattern.zigbrains.project.ide.project.ZigProjectSettingsPanel;
import com.falsepattern.zigbrains.project.ide.project.ZigProjectTemplate;
import com.falsepattern.zigbrains.zig.settings.ZLSSettingsPanel;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionToolbarPosition;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.ColoredListCellRenderer;
import com.intellij.ui.JBColor;
import com.intellij.ui.ToolbarDecorator;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBList;
import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.ui.dsl.builder.AlignY;
import com.intellij.ui.dsl.builder.Panel;
import com.intellij.util.ui.JBUI;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import javax.swing.DefaultListModel;
@ -40,11 +44,20 @@ import java.util.List;
import java.util.Optional;
public class ZigNewProjectPanel implements Disposable {
private final ZigProjectSettingsPanel projectSettingsPanel = new ZigProjectSettingsPanel();
private boolean handleGit;
private JBCheckBox git = new JBCheckBox();
private ZigProjectSettingsPanel projConf;
private ZLSSettingsPanel zlsConf;
public ZigNewProjectPanel(boolean handleGit) {
this.handleGit = handleGit;
projConf = new ZigProjectSettingsPanel();
zlsConf = new ZLSSettingsPanel();
}
public ZigProjectConfigurationData getData() {
ZigProjectTemplate selectedTemplate = templateList.getSelectedValue();
return new ZigProjectConfigurationData(projectSettingsPanel.getData(), selectedTemplate);
return new ZigProjectConfigurationData(handleGit && git.isSelected(), projConf.getData(), zlsConf.getData(), selectedTemplate);
}
private final List<ZigProjectTemplate> defaultTemplates = Arrays.asList(
@ -75,23 +88,29 @@ public class ZigNewProjectPanel implements Disposable {
.disableAddAction()
.disableRemoveAction();
public void attachPanelTo(Panel panel) {
projectSettingsPanel.attachPanelTo(panel);
panel.groupRowsRange("Zig Project Template", false, null, null, (p) -> {
p.row((JLabel) null, (r) -> {
public void attachPanelTo(JavaPanel p) {
if (handleGit) {
p.row("Create Git repository", r -> r.cell(git));
}
p.group("Zig Project Template", true, (p2) -> {
p2.row((r) -> {
r.resizableRow();
r.cell(templateToolbar.createPanel())
.align(AlignX.FILL)
.align(AlignY.FILL);
return null;
});
return null;
});
projConf.attachPanelTo(p);
zlsConf.attachPanelTo(p);
projConf.autodetect();
zlsConf.autodetect();
}
@Override
public void dispose() {
Disposer.dispose(projectSettingsPanel);
projConf.dispose();
zlsConf.dispose();
projConf = null;
zlsConf = null;
}
}

View file

@ -16,26 +16,20 @@
package com.falsepattern.zigbrains.project.ide.newproject;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.project.ide.util.projectwizard.ZigModuleBuilder;
import com.falsepattern.zigbrains.project.platform.ZigProjectGeneratorPeer;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.intellij.ide.wizard.AbstractNewProjectWizardStep;
import com.intellij.ide.wizard.GitNewProjectWizardData;
import com.intellij.ide.wizard.LanguageNewProjectWizard;
import com.intellij.ide.wizard.NewProjectWizardLanguageStep;
import com.intellij.ide.wizard.NewProjectWizardStep;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ModuleRootModificationUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.ui.dsl.builder.Panel;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import javax.swing.JLabel;
import java.io.IOException;
import java.nio.file.Path;
public class ZigNewProjectWizard implements LanguageNewProjectWizard {
@NotNull
@ -56,7 +50,7 @@ public class ZigNewProjectWizard implements LanguageNewProjectWizard {
}
private static class ZigNewProjectWizardStep extends AbstractNewProjectWizardStep {
private final ZigProjectGeneratorPeer peer = new ZigProjectGeneratorPeer();
private final ZigProjectGeneratorPeer peer = new ZigProjectGeneratorPeer(false);
public ZigNewProjectWizardStep(@NotNull NewProjectWizardStep parentStep) {
super(parentStep);
@ -74,42 +68,10 @@ public class ZigNewProjectWizard implements LanguageNewProjectWizard {
@Override
public void setupProject(@NotNull Project project) {
val builder = new ZigModuleBuilder();
val modList = builder.commit(project);
if (modList == null || modList.size() == 0) {
return;
}
val module = modList.get(0);
//noinspection UsagesOfObsoleteApi
ModuleRootModificationUtil.updateModel(module, rootModel -> {
builder.configurationData = peer.getSettings();
builder.createProject(rootModel, "none");
var gitData = GitNewProjectWizardData.Companion.getGitData(this);
if (gitData == null) {
return;
}
if (gitData.getGit()) {
ApplicationUtil.writeAction(() -> createGitIgnoreFile(getContext().getProjectDirectory(), module));
}
});
}
private static final String GITIGNORE = ".gitignore";
private static void createGitIgnoreFile(Path projectDir, Module module) {
try {
val directory = VfsUtil.createDirectoryIfMissing(projectDir.toString());
if (directory == null) {
return;
}
val existingFile = directory.findChild(GITIGNORE);
if (existingFile != null) {
return;
}
directory.createChildData(module, GITIGNORE);
} catch (IOException e) {
e.printStackTrace();
}
builder.configurationData = peer.getSettings();
var gitData = GitNewProjectWizardData.Companion.getGitData(this);
builder.forceGitignore = gitData != null && gitData.getGit();
builder.commit(project);
}
}
}

View file

@ -16,8 +16,132 @@
package com.falsepattern.zigbrains.project.ide.newproject;
import com.falsepattern.zigbrains.project.ide.project.ZigProjectSettingsPanel;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.project.ide.project.ZigDefaultTemplate;
import com.falsepattern.zigbrains.project.ide.project.ZigProjectTemplate;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettings;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService;
import com.falsepattern.zigbrains.zig.settings.ZLSSettings;
import com.intellij.ide.wizard.GitNewProjectWizardData;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.GitRepositoryInitializer;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ResourceUtil;
import lombok.Cleanup;
import lombok.val;
import org.jetbrains.annotations.NotNull;
public record ZigProjectConfigurationData(ZigProjectSettingsPanel.SettingsData settings, ZigProjectTemplate selectedTemplate) {
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
public record ZigProjectConfigurationData(boolean git, ZigProjectSettings projConf, ZLSSettings zlsConf, ZigProjectTemplate selectedTemplate) {
private static String getResourceString(String path) throws IOException {
byte[] data = ResourceUtil.getResourceAsBytes(path, ZigDirectoryProjectGenerator.class.getClassLoader());
if (data == null)
throw new IOException("Could not find resource " + path + "!");
return new String(data, StandardCharsets.UTF_8);
}
public void generateProject(@NotNull Object requestor, @NotNull Project project, @NotNull VirtualFile baseDir, boolean forceGitignore) {
val svc = ZigProjectSettingsService.getInstance(project);
svc.loadState(this.projConf());
ZLSProjectSettingsService.getInstance(project).loadState(this.zlsConf());
val toolchain = svc.getState().getToolchain();
val template = this.selectedTemplate();
if (template instanceof ZigDefaultTemplate.ZigInitTemplate) {
if (toolchain == null) {
Notifications.Bus.notify(new Notification("ZigBrains.Project",
"Tried to generate project with zig init, but zig toolchain is invalid!",
NotificationType.ERROR));
return;
}
val zig = toolchain.zig();
val resultOpt = zig.callWithArgs(baseDir.toNioPath(), 10000, "init");
if (resultOpt.isEmpty()) {
Notifications.Bus.notify(new Notification("ZigBrains.Project",
"Failed to invoke \"zig init\"!",
NotificationType.ERROR));
return;
}
val result = resultOpt.get();
if (result.getExitCode() != 0) {
Notifications.Bus.notify(new Notification("ZigBrains.Project",
"\"zig init\" failed with exit code " + result.getExitCode() + "! Check the IDE log files!",
NotificationType.ERROR));
System.err.println(result.getStderr());
}
} else {
try {
val projectName = project.getName();
WriteAction.run(() -> {
for (val fileTemplate : template.fileTemplates().entrySet()) {
var fileName = fileTemplate.getKey();
VirtualFile parentDir;
if (fileName.contains("/")) {
val slashIndex = fileName.indexOf('/');
parentDir = baseDir.createChildDirectory(requestor, fileName.substring(0, slashIndex));
fileName = fileName.substring(slashIndex + 1);
} else {
parentDir = baseDir;
}
val templateDir = fileTemplate.getValue();
val resourceData = getResourceString("project-gen/" + templateDir + "/" + fileName + ".template").replace("@@PROJECT_NAME@@", projectName);
val targetFile = parentDir.createChildData(requestor, fileName);
VfsUtil.saveText(targetFile, resourceData);
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (git) {
ApplicationManager.getApplication().executeOnPooledThread(() -> {
val initializer = GitRepositoryInitializer.getInstance();
if (initializer != null) {
initializer.initRepository(project, baseDir);
}
});
}
if (git || forceGitignore) {
createGitIgnoreFile(baseDir, this);
}
}
private static final String GITIGNORE = ".gitignore";
private static void createGitIgnoreFile(VirtualFile projectDir, Object requestor) {
val existingFile = projectDir.findChild(GITIGNORE);
if (existingFile != null) {
return;
}
WriteAction.run(() -> {
VirtualFile file = null;
try {
file = projectDir.createChildData(requestor, GITIGNORE);
@Cleanup val res = ZigProjectConfigurationData.class.getResourceAsStream("/fileTemplates/internal/gitignore");
if (res == null)
return;
file.setCharset(StandardCharsets.UTF_8);
file.setBinaryContent(res.readAllBytes());
} catch (IOException e) {
e.printStackTrace();
}
});
}
}

View file

@ -14,19 +14,22 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.platform;
package com.falsepattern.zigbrains.project.ide.newproject;
import com.falsepattern.zigbrains.project.ide.newproject.ZigNewProjectPanel;
import com.falsepattern.zigbrains.project.ide.newproject.ZigProjectConfigurationData;
import com.intellij.openapi.util.Disposer;
import com.intellij.platform.GeneratorPeerImpl;
import org.jetbrains.annotations.NotNull;
import javax.swing.JComponent;
import static com.intellij.ui.dsl.builder.BuilderKt.panel;
import static com.falsepattern.zigbrains.common.util.dsl.JavaPanel.newPanel;
public class ZigProjectGeneratorPeer extends GeneratorPeerImpl<ZigProjectConfigurationData> {
private final ZigNewProjectPanel newProjectPanel = new ZigNewProjectPanel();
private final ZigNewProjectPanel newProjectPanel;
public ZigProjectGeneratorPeer(boolean handleGit) {
newProjectPanel = new ZigNewProjectPanel(handleGit);
}
@Override
public @NotNull ZigProjectConfigurationData getSettings() {
@ -35,9 +38,10 @@ public class ZigProjectGeneratorPeer extends GeneratorPeerImpl<ZigProjectConfigu
@Override
public @NotNull JComponent getComponent() {
return panel((p) -> {
newProjectPanel.attachPanelTo(p);
return null;
});
return newPanel(newProjectPanel::attachPanelTo);
}
public void dispose() {
Disposer.dispose(newProjectPanel);
}
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.ide.util.projectwizard;
package com.falsepattern.zigbrains.project.ide.newproject;
import com.falsepattern.zigbrains.project.ide.newproject.ZigProjectConfigurationData;
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep;

View file

@ -16,23 +16,17 @@
package com.falsepattern.zigbrains.project.ide.project;
import com.falsepattern.zigbrains.common.SubConfigurable;
import com.falsepattern.zigbrains.common.util.dsl.JavaPanel;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.zig.lsp.ZLSStartupActivity;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.NlsContexts;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.JComponent;
import java.util.Objects;
import static com.intellij.ui.dsl.builder.BuilderKt.panel;
public class ZigProjectConfigurable implements Configurable {
public class ZigProjectConfigurable implements SubConfigurable {
private ZigProjectSettingsPanel settingsPanel;
private final Project project;
@ -42,36 +36,22 @@ public class ZigProjectConfigurable implements Configurable {
}
@Override
public @NlsContexts.ConfigurableName String getDisplayName() {
return "Zig";
}
@Override
public @Nullable JComponent createComponent() {
public void createComponent(JavaPanel panel) {
settingsPanel = new ZigProjectSettingsPanel();
return panel((p) -> {
settingsPanel.attachPanelTo(p);
return null;
});
settingsPanel.attachPanelTo(panel);
}
@Override
public boolean isModified() {
var zigSettings = ZigProjectSettingsService.getInstance(project);
var settingsData = settingsPanel.getData();
return !Objects.equals(settingsData.toolchain(), zigSettings.getToolchain()) ||
!Objects.equals(settingsData.explicitPathToStd(), zigSettings.getExplicitPathToStd());
return ZigProjectSettingsService.getInstance(project).isModified(settingsPanel.getData());
}
@Override
public void apply() throws ConfigurationException {
val zigSettings = ZigProjectSettingsService.getInstance(project);
val settingsData = settingsPanel.getData();
boolean modified = isModified();
zigSettings.modify((settings) -> {
settings.setToolchain(settingsData.toolchain());
settings.setExplicitPathToStd(settingsData.explicitPathToStd());
});
val service = ZigProjectSettingsService.getInstance(project);
val data = settingsPanel.getData();
val modified = service.isModified(data);
service.loadState(data);
if (modified) {
ZLSStartupActivity.initZLS(project);
}
@ -80,10 +60,7 @@ public class ZigProjectConfigurable implements Configurable {
@Override
public void reset() {
val zigSettings = ZigProjectSettingsService.getInstance(project);
settingsPanel.setData(new ZigProjectSettingsPanel.SettingsData(
zigSettings.getExplicitPathToStd(),
zigSettings.getToolchain()
));
settingsPanel.setData(zigSettings.getState());
}
@Override

View file

@ -16,10 +16,13 @@
package com.falsepattern.zigbrains.project.ide.project;
import com.falsepattern.zigbrains.common.util.PathUtil;
import com.falsepattern.zigbrains.common.util.StringUtil;
import com.falsepattern.zigbrains.common.util.TextFieldUtil;
import com.falsepattern.zigbrains.common.util.dsl.JavaPanel;
import com.falsepattern.zigbrains.project.openapi.MyDisposable;
import com.falsepattern.zigbrains.project.openapi.UIDebouncer;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettings;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider;
@ -29,26 +32,25 @@ import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.ui.JBColor;
import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.ui.dsl.builder.Panel;
import lombok.Getter;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import javax.swing.JLabel;
import java.awt.event.ActionEvent;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import static com.falsepattern.zigbrains.common.util.KtUtil.$f;
public class ZigProjectSettingsPanel implements MyDisposable {
@Getter
private boolean disposed = false;
public record SettingsData(@Nullable String explicitPathToStd,
@Nullable AbstractZigToolchain toolchain) {}
private final UIDebouncer versionUpdateDebouncer = new UIDebouncer(this);
private final ZigToolchainPathChooserComboBox toolchainPathChooserComboBox = new ZigToolchainPathChooserComboBox(this::updateUI);
private final TextFieldWithBrowseButton pathToToolchain = TextFieldUtil.pathToDirectoryTextField(this,
"Path to the Zig Toolchain",
this::updateUI);
private final JLabel toolchainVersion = new JLabel();
@ -56,56 +58,57 @@ public class ZigProjectSettingsPanel implements MyDisposable {
"Path to Standard Library",
() -> {});
public SettingsData getData() {
val toolchain = Optional.ofNullable(toolchainPathChooserComboBox.getSelectedPath())
.map(ZigToolchainProvider::findToolchain)
.orElse(null);
return new SettingsData(StringUtil.blankToNull(pathToStdField.getText()), toolchain);
private void autodetect(ActionEvent e) {
autodetect();
}
public void setData(SettingsData value) {
toolchainPathChooserComboBox.setSelectedPath(Optional.ofNullable(value.toolchain()).map(tc -> tc.location).orElse(null));
public void autodetect() {
val tc = AbstractZigToolchain.suggest();
if (tc != null) {
pathToToolchain.setText(PathUtil.stringFromPath(tc.getLocation()));
updateUI();
}
}
pathToStdField.setText(Optional.ofNullable(value.explicitPathToStd()).orElse(""));
public ZigProjectSettings getData() {
val toolchain = Optional.of(pathToToolchain.getText())
.map(PathUtil::pathFromString)
.map(ZigToolchainProvider::findToolchain)
.orElse(null);
return new ZigProjectSettings(StringUtil.blankToNull(pathToStdField.getText()), toolchain);
}
public void setData(ZigProjectSettings value) {
pathToToolchain.setText(Optional.ofNullable(value.getToolchainHomeDirectory())
.orElse(""));
pathToStdField.setText(Optional.ofNullable(value.getExplicitPathToStd()).orElse(""));
updateUI();
}
public void attachPanelTo(Panel panel) {
setData(new SettingsData(null,
Optional.ofNullable(ProjectManager.getInstance()
.getDefaultProject()
.getService(ZigProjectSettingsService.class))
.map(ZigProjectSettingsService::getToolchain)
.orElse(AbstractZigToolchain.suggest(Paths.get(".")))));
panel.row("Toolchain Location", (r) -> {
r.cell(toolchainPathChooserComboBox)
.align(AlignX.FILL);
return null;
});
panel.row("Toolchain Version", (r) -> {
r.cell(toolchainVersion);
return null;
});
panel.row("Standard Library Location", (r) -> {
r.cell(pathToStdField)
.align(AlignX.FILL);
return null;
public void attachPanelTo(JavaPanel p) {
Optional.ofNullable(ZigProjectSettingsService.getInstance(ProjectManager.getInstance().getDefaultProject()))
.map(ZigProjectSettingsService::getState)
.ifPresent(this::setData);
p.group("Zig Settings", true, p2 -> {
p2.row("Toolchain location", r -> {
r.cell(pathToToolchain).resizableColumn().align(AlignX.FILL);
r.button("Autodetect", $f(this::autodetect));
});
p2.cell("Toolchain version", toolchainVersion);
p2.cell("Standard library location", pathToStdField, AlignX.FILL);
});
}
@Override
public void dispose() {
disposed = true;
Disposer.dispose(toolchainPathChooserComboBox);
Disposer.dispose(pathToToolchain);
}
private void updateUI() {
val pathToToolchain = toolchainPathChooserComboBox.getSelectedPath();
val pathToToolchain = PathUtil.pathFromString(this.pathToToolchain.getText());
versionUpdateDebouncer.run(
() -> {

View file

@ -1,78 +0,0 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.ide.project;
import com.falsepattern.zigbrains.common.util.TextFieldUtil;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.ui.ComboBoxWithWidePopup;
import com.intellij.openapi.ui.ComponentWithBrowseButton;
import com.intellij.ui.AnimatedIcon;
import com.intellij.ui.ComboboxSpeedSearch;
import com.intellij.ui.components.fields.ExtendableTextComponent;
import com.intellij.ui.components.fields.ExtendableTextField;
import lombok.val;
import javax.swing.JTextField;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import java.nio.file.Path;
public class ZigToolchainPathChooserComboBox extends ComponentWithBrowseButton<ComboBoxWithWidePopup<Path>> {
public Runnable onTextChanged;
private final BasicComboBoxEditor comboBoxEditor = new BasicComboBoxEditor() {
@Override
protected JTextField createEditorComponent() {
return new ExtendableTextField();
}
};
private ExtendableTextField getPathTextField() {
return (ExtendableTextField) getChildComponent().getEditor().getEditorComponent();
}
private final ExtendableTextComponent.Extension busyIconExtension = hovered -> AnimatedIcon.Default.INSTANCE;
public Path getSelectedPath() {
return Path.of(getPathTextField().getText());
}
public void setSelectedPath(Path path) {
if (path == null) {
getPathTextField().setText("");
return;
}
getPathTextField().setText(path.toString());
}
public ZigToolchainPathChooserComboBox(Runnable onTextChanged) {
super(new ComboBoxWithWidePopup<>(), null);
this.onTextChanged = onTextChanged;
ComboboxSpeedSearch.installOn(getChildComponent());
getChildComponent().setEditor(comboBoxEditor);
getChildComponent().setEditable(true);
addActionListener(e -> {
val descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
//noinspection UsagesOfObsoleteApi
FileChooser.chooseFile(descriptor, null, null, (file) -> getChildComponent().setSelectedItem(file.toNioPath()));
});
TextFieldUtil.addTextChangeListener(getPathTextField(), ignored -> onTextChanged.run());
}
}

View file

@ -1,56 +0,0 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.ide.util.projectwizard;
import com.falsepattern.zigbrains.project.ide.newproject.ZigNewProjectPanel;
import com.falsepattern.zigbrains.project.util.ExperimentUtil;
import com.intellij.ide.util.projectWizard.ModuleWizardStep;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.ui.JBUI;
import javax.swing.JComponent;
import static com.intellij.ui.dsl.builder.BuilderKt.panel;
public class ZigModuleWizardStep extends ModuleWizardStep {
private final ZigNewProjectPanel newProjectPanel = new ZigNewProjectPanel();
@Override
public JComponent getComponent() {
return withBorderIfNeeded(panel((p) -> {
newProjectPanel.attachPanelTo(p);
return null;
}));
}
@Override
public void disposeUIResources() {
Disposer.dispose(newProjectPanel);
}
@Override
public void updateDataModel() {
throw new UnsupportedOperationException("Not yet implemented");
}
private <T extends JComponent> T withBorderIfNeeded(T component) {
if (ExperimentUtil.isNewWizard()) {
component.setBorder(JBUI.Borders.empty(14, 20));
}
return component;
}
}

View file

@ -16,26 +16,16 @@
package com.falsepattern.zigbrains.project.openapi.components;
import com.falsepattern.zigbrains.common.WrappingStateComponent;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.project.Project;
import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull;
public abstract class AbstractZigProjectSettingsService<T> implements PersistentStateComponent<T> {
public abstract class AbstractZigProjectSettingsService<T> extends WrappingStateComponent<T> {
public final transient Project project;
private final T state;
public AbstractZigProjectSettingsService(Project project, @NotNull T initialState) {
super(initialState);
this.project = project;
this.state = initialState;
}
@Override
public @NotNull T getState() {
return state;
}
@Override
public void loadState(@NotNull T state) {
XmlSerializerUtil.copyBean(state, this.state);
}
}

View file

@ -20,20 +20,23 @@ import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider;
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 String toolchainHomeDirectory = null;
public String explicitPathToStd = null;
public String explicitPathToStd;
public String toolchainHomeDirectory;
public String getExplicitPathToStd() {
return explicitPathToStd;
}
public void setExplicitPathToStd(String value) {
explicitPathToStd = value;
public ZigProjectSettings(String explicitPathToStd, AbstractZigToolchain toolchain) {
this(explicitPathToStd, (String)null);
setToolchain(toolchain);
}
@Transient
@ -50,7 +53,7 @@ public class ZigProjectSettings {
toolchainHomeDirectory = null;
return;
}
var loc = value.location;
var loc = value.getLocation();
if (loc == null) {
toolchainHomeDirectory = null;
return;

View file

@ -16,13 +16,17 @@
package com.falsepattern.zigbrains.project.openapi.components;
import com.falsepattern.zigbrains.project.ide.project.ZigProjectSettingsPanel;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.zig.lsp.ZLSStartupActivity;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.project.Project;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.function.Consumer;
@Service(Service.Level.PROJECT)
@ -39,16 +43,9 @@ public final class ZigProjectSettingsService extends AbstractZigProjectSettingsS
return project.getService(ZigProjectSettingsService.class);
}
public @Nullable AbstractZigToolchain getToolchain() {
return getState().getToolchain();
}
public @Nullable String getExplicitPathToStd() {
return getState().getExplicitPathToStd();
}
public void modify(Consumer<ZigProjectSettings> modifier) {
var state = getState();
modifier.accept(state);
public boolean isModified(ZigProjectSettings otherData) {
val myData = getState();
return !Objects.equals(myData.toolchainHomeDirectory, otherData.toolchainHomeDirectory) ||
!Objects.equals(myData.explicitPathToStd, otherData.explicitPathToStd);
}
}

View file

@ -16,7 +16,7 @@
package com.falsepattern.zigbrains.project.openapi.module;
import com.falsepattern.zigbrains.project.ide.util.projectwizard.ZigModuleBuilder;
import com.falsepattern.zigbrains.project.ide.newproject.ZigModuleBuilder;
import com.falsepattern.zigbrains.zig.Icons;
import com.intellij.openapi.module.ModuleType;
import org.jetbrains.annotations.Nls;

View file

@ -1,140 +0,0 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.project.platform;
import com.falsepattern.zigbrains.project.ide.newproject.ZigProjectConfigurationData;
import com.falsepattern.zigbrains.project.ide.project.ZigDefaultTemplate;
import com.falsepattern.zigbrains.project.ide.util.projectwizard.ZigProjectSettingsStep;
import com.falsepattern.zigbrains.project.openapi.components.ZigProjectSettingsService;
import com.falsepattern.zigbrains.zig.Icons;
import com.intellij.facet.ui.ValidationResult;
import com.intellij.ide.util.projectWizard.AbstractNewProjectStep;
import com.intellij.ide.util.projectWizard.CustomStepProjectGenerator;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.impl.welcomeScreen.AbstractActionWithPanel;
import com.intellij.platform.DirectoryProjectGenerator;
import com.intellij.platform.ProjectGeneratorPeer;
import com.intellij.util.ResourceUtil;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.Icon;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ZigDirectoryProjectGenerator implements DirectoryProjectGenerator<ZigProjectConfigurationData>,
CustomStepProjectGenerator<ZigProjectConfigurationData> {
@Override
public @NotNull @NlsContexts.Label String getName() {
return "Zig";
}
@Override
public @Nullable Icon getLogo() {
return Icons.ZIG;
}
@Override
public @NotNull ProjectGeneratorPeer<ZigProjectConfigurationData> createPeer() {
return new ZigProjectGeneratorPeer();
}
@Override
public @NotNull ValidationResult validate(@NotNull String baseDirPath) {
return ValidationResult.OK;
}
private static String getResourceString(String path) throws IOException {
byte[] data = ResourceUtil.getResourceAsBytes(path, ZigDirectoryProjectGenerator.class.getClassLoader());
if (data == null)
throw new IOException("Could not find resource " + path + "!");
return new String(data, StandardCharsets.UTF_8);
}
@Override
public void generateProject(@NotNull Project project, @NotNull VirtualFile baseDir, @NotNull ZigProjectConfigurationData data, @NotNull Module module) {
val settings = data.settings();
var svc = ZigProjectSettingsService.getInstance(project);
val toolchain = settings.toolchain();
svc.getState().setToolchain(toolchain);
val template = data.selectedTemplate();
if (template instanceof ZigDefaultTemplate.ZigInitTemplate) {
if (toolchain == null) {
Notifications.Bus.notify(new Notification("ZigBrains.Project",
"Tried to generate project with zig init, but zig toolchain is invalid!",
NotificationType.ERROR));
return;
}
val zig = toolchain.zig();
val resultOpt = zig.callWithArgs(baseDir.toNioPath(), 10000, "init");
if (resultOpt.isEmpty()) {
Notifications.Bus.notify(new Notification("ZigBrains.Project",
"Failed to invoke \"zig init\"!",
NotificationType.ERROR));
return;
}
val result = resultOpt.get();
if (result.getExitCode() != 0) {
Notifications.Bus.notify(new Notification("ZigBrains.Project",
"\"zig init\" failed with exit code " + result.getExitCode() + "! Check the IDE log files!",
NotificationType.ERROR));
System.err.println(result.getStderr());
}
} else {
try {
val projectName = project.getName();
WriteAction.run(() -> {
for (val fileTemplate : template.fileTemplates().entrySet()) {
var fileName = fileTemplate.getKey();
VirtualFile parentDir;
if (fileName.contains("/")) {
val slashIndex = fileName.indexOf('/');
parentDir = baseDir.createChildDirectory(this, fileName.substring(0, slashIndex));
fileName = fileName.substring(slashIndex + 1);
} else {
parentDir = baseDir;
}
val templateDir = fileTemplate.getValue();
val resourceData = getResourceString("project-gen/" + templateDir + "/" + fileName + ".template").replace("@@PROJECT_NAME@@", projectName);
val targetFile = parentDir.createChildData(this, fileName);
VfsUtil.saveText(targetFile, resourceData);
}
});
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public AbstractActionWithPanel createStep(DirectoryProjectGenerator<ZigProjectConfigurationData> projectGenerator, AbstractNewProjectStep.AbstractCallback<ZigProjectConfigurationData> callback) {
return new ZigProjectSettingsStep(projectGenerator);
}
}

View file

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

View file

@ -19,6 +19,7 @@ package com.falsepattern.zigbrains.project.toolchain;
import com.falsepattern.zigbrains.project.toolchain.flavours.AbstractZigToolchainFlavour;
import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool;
import com.intellij.execution.configurations.GeneralCommandLine;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.Nullable;
@ -26,8 +27,9 @@ import java.nio.file.Path;
import java.util.Objects;
@RequiredArgsConstructor
@Getter
public abstract class AbstractZigToolchain {
public final Path location;
private final Path location;
public static @Nullable AbstractZigToolchain suggest() {
return suggest(null);

View file

@ -38,6 +38,6 @@ public class LocalZigToolchain extends AbstractZigToolchain{
@Override
public Path pathToExecutable(String toolName) {
return PathUtil.pathToExecutable(location, toolName);
return PathUtil.pathToExecutable(getLocation(), toolName);
}
}

View file

@ -28,8 +28,9 @@ import java.nio.file.Path;
public class ToolchainZLSConfigProvider implements ZLSConfigProvider {
@Override
public void getEnvironment(Project project, ZLSConfig.ZLSConfigBuilder builder) {
val projectSettings = ZigProjectSettingsService.getInstance(project);
val toolchain = projectSettings.getToolchain();
val svc = ZigProjectSettingsService.getInstance(project);
val state = svc.getState();
val toolchain = state.getToolchain();
if (toolchain == null)
return;
val projectDir = ProjectUtil.guessProjectDir(project);

View file

@ -27,6 +27,7 @@ import lombok.val;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.StringTokenizer;
@ -119,4 +120,8 @@ public class CLIUtil {
}
return result.toArray(new String[0]);
}
public static List<String> colored(boolean colored) {
return List.of("--color", colored ? "on" : "off");
}
}

View file

@ -32,13 +32,13 @@
<runLineMarkerContributor language="Zig"
implementationClass="com.falsepattern.zigbrains.project.execution.build.ZigLineMarkerBuild"/>
<directoryProjectGenerator implementation="com.falsepattern.zigbrains.project.platform.ZigDirectoryProjectGenerator"/>
<directoryProjectGenerator implementation="com.falsepattern.zigbrains.project.ide.newproject.ZigDirectoryProjectGenerator"/>
<newProjectWizard.language implementation="com.falsepattern.zigbrains.project.ide.newproject.ZigNewProjectWizard"/>
<moduleBuilder builderClass="com.falsepattern.zigbrains.project.ide.util.projectwizard.ZigModuleBuilder"/>
<projectConfigurable parentId="language"
instance="com.falsepattern.zigbrains.project.ide.project.ZigProjectConfigurable"
id="com.falsepattern.zigbrains.project.ide.project.ZigProjectConfigurable"
instance="com.falsepattern.zigbrains.project.ide.config.ZigConfigurable"
id="com.falsepattern.zigbrains.project.ide.config.ZigConfigurable"
displayName="Zig"/>

View file

@ -0,0 +1,5 @@
zig-cache/
zig-out/
build/
build-*/
docgen_tmp/

View file

@ -172,23 +172,22 @@ private ContainerMembers ::= ContainerDeclarations (ContainerField COMMA)* (Cont
ContainerDeclarations ::= (TestDecl | ComptimeDecl | DOC_COMMENT? KEYWORD_PUB? Decl)*
TestDecl ::= DOC_COMMENT? KEYWORD_TEST (STRING_LITERAL_SINGLE | IDENTIFIER)? Block {pin=2}
TestDecl ::= DOC_COMMENT? KEYWORD_TEST (STRING_LITERAL_SINGLE | IDENTIFIER)? Block
ComptimeDecl ::= KEYWORD_COMPTIME Block
ComptimeDecl ::= KEYWORD_COMPTIME Block {pin=1}
Decl
::= (KEYWORD_EXPORT | KEYWORD_EXTERN STRING_LITERAL_SINGLE? | KEYWORD_INLINE | KEYWORD_NOILINE)? FnProto (SEMICOLON | Block)
| (KEYWORD_EXPORT | KEYWORD_EXTERN STRING_LITERAL_SINGLE?)? KEYWORD_THREADLOCAL? GlobalVarDecl
| KEYWORD_USINGNAMESPACE Expr SEMICOLON
FnProto ::= KEYWORD_FN IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr
FnProto ::= KEYWORD_FN IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr {pin=1}
VarDeclProto ::= (KEYWORD_CONST | KEYWORD_VAR) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?
VarDeclProto ::= (KEYWORD_CONST | KEYWORD_VAR) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection? {pin=1}
GlobalVarDecl ::= VarDeclProto (EQUAL Expr)? SEMICOLON
GlobalVarDecl ::= VarDeclProto (EQUAL Expr)? SEMICOLON {pin=1}
ContainerField ::= DOC_COMMENT? KEYWORD_COMPTUME? !KEYWORD_FN (IDENTIFIER COLON)? TypeExpr ByteAlign? (EQUAL Expr)?
ContainerField ::= DOC_COMMENT? KEYWORD_COMPTUME? !KEYWORD_FN (IDENTIFIER COLON)? TypeExpr ByteAlign? (EQUAL Expr)? {pin=5}
// *** Block Level ***
Statement
@ -207,7 +206,7 @@ ComptimeStatement
IfStatement
::= IfPrefix BlockExpr ( KEYWORD_ELSE Payload? Statement )?
| IfPrefix AssignExpr ( SEMICOLON | KEYWORD_ELSE Payload? Statement )
| IfPrefix AssignExpr ( SEMICOLON | KEYWORD_ELSE Payload? Statement ) {pin(".*")=1}
LabeledStatement ::= BlockLabel? (Block | LoopStatement)
@ -215,22 +214,24 @@ LoopStatement ::= KEYWORD_INLINE? (ForStatement | WhileStatement)
ForStatement
::= ForPrefix BlockExpr ( KEYWORD_ELSE Statement )?
| ForPrefix AssignExpr ( SEMICOLON | KEYWORD_ELSE Statement )
| ForPrefix AssignExpr ( SEMICOLON | KEYWORD_ELSE Statement ) {pin(".*")=1}
WhileStatement
::= WhilePrefix BlockExpr ( KEYWORD_ELSE Payload? Statement )?
| WhilePrefix AssignExpr ( SEMICOLON | KEYWORD_ELSE Payload? Statement)
| WhilePrefix AssignExpr ( SEMICOLON | KEYWORD_ELSE Payload? Statement) {pin(".*") =1}
BlockExprStatement
::= BlockExpr
| AssignExpr SEMICOLON
| ZB_BlockExprStatement_AssignExpr
private ZB_BlockExprStatement_AssignExpr ::= AssignExpr SEMICOLON {pin=1}
BlockExpr ::= BlockLabel? Block
//An expression, assignment, or any destructure, as a statement.
VarDeclExprStatement
::= VarDeclProto (COMMA (VarDeclProto | Expr))* EQUAL Expr SEMICOLON
| Expr (AssignOp Expr | (COMMA (VarDeclProto | Expr))+ EQUAL Expr)? SEMICOLON
| Expr (AssignOp Expr | (COMMA (VarDeclProto | Expr))+ EQUAL Expr)? SEMICOLON {pin(".*")=1}
// *** Expression Level ***
@ -272,9 +273,11 @@ PrimaryExpr
IfExpr ::= IfPrefix Expr (KEYWORD_ELSE Payload? Expr)?
Block ::= LBRACE ZB_Block_Statement* RBRACE {pin(".*")=1}
Block ::= LBRACE ZB_Block_Statement RBRACE {pin(".*")=1}
private ZB_Block_Statement ::= Statement {recoverWhile="#auto"}
private ZB_Block_Statement ::= Statement* {recoverWhile="ZB_Block_Statement_recover"}
private ZB_Block_Statement_recover ::= !(RBRACE)
LoopExpr ::= KEYWORD_INLINE? (ForExpr | WhileExpr)
@ -379,14 +382,16 @@ ParamType
| TypeExpr
// Control flow prefixes
IfPrefix ::= KEYWORD_IF LPAREN Expr RPAREN PtrPayload?
IfPrefix ::= KEYWORD_IF LPAREN Expr RPAREN PtrPayload? {pin=1}
WhilePrefix ::= KEYWORD_WHILE LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?
WhilePrefix ::= KEYWORD_WHILE LPAREN Expr RPAREN PtrPayload? WhileContinueExpr? {pin=1}
ForRange ::= Expr DOT2 Expr?
ForOperand ::= ForRange | Expr
ForPrefix ::= KEYWORD_FOR LPAREN (ForOperand COMMA)* ForOperand RPAREN PtrIndexPayload
ForPrefix ::= KEYWORD_FOR LPAREN ZB_ForPrefix_Operands RPAREN PtrIndexPayload {pin=1}
private ZB_ForPrefix_Operands ::= (ForOperand COMMA)* ForOperand {recoverWhile="#auto"}
// Payloads
Payload ::= PIPE IDENTIFIER PIPE
@ -485,7 +490,11 @@ SuffixOp
| DOTASTERISK
| DOTQUESTIONMARK
FnCallArguments ::= LPAREN ExprList RPAREN
FnCallArguments ::= LPAREN ZB_FnCallArguments_ExprList RPAREN {pin=1}
private ZB_FnCallArguments_ExprList ::= ExprList {recoverWhile="ZB_FnCallArguments_ExprList_recover"}
private ZB_FnCallArguments_ExprList_recover ::= !(RPAREN)
// Ptr specific
SliceTypeStart ::= LBRACKET (COLON Expr)? RBRACKET
@ -498,13 +507,19 @@ PtrTypeStart
ArrayTypeStart ::= LBRACKET Expr (COLON Expr)? RBRACKET
// ContainerDecl specific
ContainerDeclAuto ::= ContainerDeclType LBRACE CONTAINER_DOC_COMMENT? ContainerMembers RBRACE
ContainerDeclAuto ::= ContainerDeclType LBRACE CONTAINER_DOC_COMMENT? ZB_ContainerDeclAuto_ContainerMembers RBRACE {pin=2}
private ZB_ContainerDeclAuto_ContainerMembers ::= ContainerMembers {recoverWhile="ZB_ContainerDeclAuto_ContainerMembers_recover"}
private ZB_ContainerDeclAuto_ContainerMembers_recover ::= !(RBRACE)
ContainerDeclType
::= KEYWORD_STRUCT (LPAREN Expr RPAREN)?
::= KEYWORD_STRUCT (LPAREN ZB_ContainerDeclType_Expr RPAREN)?
| KEYWORD_OPAQUE
| KEYWORD_ENUM (LPAREN Expr RPAREN)?
| KEYWORD_UNION (LPAREN (KEYWORD_ENUM (LPAREN Expr RPAREN)? | Expr) RPAREN)?
| KEYWORD_ENUM (LPAREN ZB_ContainerDeclType_Expr RPAREN)?
| KEYWORD_UNION (LPAREN (KEYWORD_ENUM (LPAREN ZB_ContainerDeclType_Expr RPAREN)? | ZB_ContainerDeclType_Expr) RPAREN)? {pin(".*")=1}
private ZB_ContainerDeclType_Expr ::= Expr {recoverWhile="ZB_ContainerDeclType_Expr_recover"}
private ZB_ContainerDeclType_Expr_recover ::= !(RPAREN)
// Alignment
ByteAlign ::= KEYWORD_ALIGN LPAREN Expr RPAREN

View file

@ -16,7 +16,6 @@
package com.falsepattern.zigbrains.zig.formatter;
import com.falsepattern.zigbrains.zig.psi.ZigTypes;
import com.intellij.formatting.Alignment;
import com.intellij.formatting.Block;
import com.intellij.formatting.Indent;
@ -35,8 +34,16 @@ import java.util.ArrayList;
import java.util.List;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.BLOCK;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.CONTAINER_DECL_AUTO;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.CONTAINER_DECL_TYPE;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.CONTAINER_DOC_COMMENT;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.EXPR_LIST;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.FN_CALL_ARGUMENTS;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.INIT_LIST;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.LBRACE;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.LPAREN;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.RBRACE;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.RPAREN;
public class ZigBlock extends AbstractBlock {
@ -75,25 +82,26 @@ public class ZigBlock extends AbstractBlock {
@Override
protected @Nullable Indent getChildIndent() {
return getIndentBasedOnParentType(getNode().getElementType());
return getIndentBasedOnParentType(getNode().getElementType(), null);
}
@Override
public Indent getIndent() {
val parent = getNode().getTreeParent();
if (parent != null) {
return getIndentBasedOnParentType(parent.getElementType());
return getIndentBasedOnParentType(parent.getElementType(), getNode().getElementType());
}
return Indent.getNoneIndent();
}
private static Indent getIndentBasedOnParentType(IElementType elementType) {
if (elementType == BLOCK ||
elementType == INIT_LIST ||
elementType == EXPR_LIST) {
private static Indent getIndentBasedOnParentType(IElementType parentElementType, IElementType childElementType) {
if ((parentElementType == BLOCK && childElementType != LBRACE && childElementType != RBRACE) ||
(parentElementType == INIT_LIST && childElementType != LBRACE && childElementType != RBRACE) ||
parentElementType == EXPR_LIST ||
(parentElementType == FN_CALL_ARGUMENTS && childElementType != LPAREN && childElementType != RPAREN) ||
(parentElementType == CONTAINER_DECL_AUTO && childElementType != CONTAINER_DECL_TYPE && childElementType != LBRACE && childElementType != CONTAINER_DOC_COMMENT && childElementType != RBRACE)) {
return Indent.getNormalIndent();
}
return Indent.getNoneIndent();
}
}

View file

@ -17,7 +17,7 @@
package com.falsepattern.zigbrains.zig.ide;
import com.falsepattern.zigbrains.lsp.contributors.LSPFoldingRangeProvider;
import com.falsepattern.zigbrains.zig.settings.ZLSSettingsState;
import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService;
import com.intellij.openapi.project.Project;
import org.eclipse.lsp4j.FoldingRange;
import org.jetbrains.annotations.NotNull;
@ -42,6 +42,6 @@ public class ZigFoldingRangeProvider extends LSPFoldingRangeProvider {
@Override
protected boolean async(Project project) {
return ZLSSettingsState.getInstance(project).asyncFolding;
return ZLSProjectSettingsService.getInstance(project).getState().asyncFolding;
}
}

View file

@ -27,7 +27,6 @@ import com.falsepattern.zigbrains.zig.ide.SemaEdit;
import com.falsepattern.zigbrains.zig.util.HighlightingUtil;
import com.falsepattern.zigbrains.zig.util.TokenDecoder;
import com.intellij.lang.annotation.Annotation;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentListener;
import lombok.val;

View file

@ -20,7 +20,7 @@ import com.falsepattern.zigbrains.common.util.StringUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider;
import com.falsepattern.zigbrains.zig.settings.ZLSSettingsState;
import com.falsepattern.zigbrains.zig.settings.ZLSProjectSettingsService;
import com.google.gson.Gson;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
@ -54,23 +54,23 @@ public class ZLSStartupActivity implements ProjectActivity {
for (var wrapper : wrappers) {
if (wrapper.serverDefinition.ext.equals("zig")) {
wrapper.stop(false);
wrapper.removeWidget();
IntellijLanguageClient.removeWrapper(wrapper);
}
}
var settings = ZLSSettingsState.getInstance(project);
var zlsPath = settings.zlsPath;
var svc = ZLSProjectSettingsService.getInstance(project);
val state = svc.getState();
var zlsPath = state.zlsPath;
if (!validatePath("ZLS Binary", zlsPath, false)) {
return;
}
var configPath = settings.zlsConfigPath;
var configPath = state.zlsConfigPath;
boolean configOK = true;
if (!"".equals(configPath) && !validatePath("ZLS Config", configPath, false)) {
if (!configPath.isEmpty() && !validatePath("ZLS Config", configPath, false)) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "Using default config path.",
NotificationType.INFORMATION));
configPath = "";
configPath = null;
}
if ("".equals(configPath)) {
if (configPath == null || configPath.isBlank()) {
blk:
try {
val tmpFile = Files.createTempFile("zigbrains-zls-autoconf", ".json");
@ -103,16 +103,16 @@ public class ZLSStartupActivity implements ProjectActivity {
cmd.add(configPath);
}
// TODO make this properly configurable
if (settings.increaseTimeouts) {
if (state.increaseTimeouts) {
for (var timeout : IntellijLanguageClient.getTimeouts().keySet()) {
IntellijLanguageClient.setTimeout(timeout, 15000);
}
}
if (settings.debug) {
if (state.debug) {
cmd.add("--enable-debug-log");
}
if (settings.messageTrace) {
if (state.messageTrace) {
cmd.add("--enable-message-tracing");
}
for (var wrapper : IntellijLanguageClient.getAllServerWrappersFor("zig")) {
@ -133,12 +133,16 @@ public class ZLSStartupActivity implements ProjectActivity {
}
private static boolean validatePath(String name, String pathTxt, boolean dir) {
if (pathTxt == null || pathTxt.isBlank()) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "Missing " + name, "No path was specified", NotificationType.WARNING));
return false;
}
Path path;
try {
path = Path.of(pathTxt);
} catch (InvalidPathException e) {
Notifications.Bus.notify(
new Notification("ZigBrains.ZLS", "No " + name, "Invalid " + name + " path \"" + pathTxt + "\"",
new Notification("ZigBrains.ZLS", "No " + name, "Invalid " + name + " at path \"" + pathTxt + "\"",
NotificationType.ERROR));
return false;
}
@ -152,7 +156,7 @@ public class ZLSStartupActivity implements ProjectActivity {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No " + name,
"The " + name + " at \"" + pathTxt + "\" is a " +
(Files.isDirectory(path) ? "directory" : "file") +
" , expected a " + (dir ? "directory" : "file"),
", expected a " + (dir ? "directory" : "file"),
NotificationType.ERROR));
return false;
}
@ -162,16 +166,15 @@ public class ZLSStartupActivity implements ProjectActivity {
@Nullable
@Override
public Object execute(@NotNull Project project, @NotNull Continuation<? super Unit> continuation) {
var settings = ZLSSettingsState.getInstance(project);
var zlsPath = settings.zlsPath;
val svc = ZLSProjectSettingsService.getInstance(project);
val state = svc.getState();
var zlsPath = state.zlsPath;
if (zlsPath.isEmpty() && !settings.initialAutodetectHasBeenDone) {
settings.initialAutodetectHasBeenDone = true;
var thePath = ZLSSettingsState.executablePathFinder("zls");
if (thePath.isPresent()) {
zlsPath = settings.zlsPath = thePath.get();
}
if (zlsPath == null) {
//Project creation
return null;
}
if (zlsPath.isEmpty()) {
Notifications.Bus.notify(new Notification("ZigBrains.ZLS", "No ZLS binary",
"Please configure the path to the zls executable in the Zig language configuration menu!",

View file

@ -0,0 +1,60 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.settings;
import com.falsepattern.zigbrains.common.WrappingStateComponent;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.project.Project;
import lombok.val;
import java.util.Objects;
@Service(Service.Level.PROJECT)
@State(name = "ZLSSettings",
storages = @Storage("zigbrains.xml"))
public final class ZLSProjectSettingsService extends WrappingStateComponent<ZLSSettings> {
public ZLSProjectSettingsService() {
super(new ZLSSettings());
}
public static ZLSProjectSettingsService getInstance(Project project) {
return project.getService(ZLSProjectSettingsService.class);
}
public boolean isModified(ZLSSettings otherData) {
val myData = this.getState();
boolean modified = zlsSettingsModified(otherData);
modified |= myData.asyncFolding != otherData.asyncFolding;
return modified;
}
public boolean zlsSettingsModified(ZLSSettings otherData) {
val myData = this.getState();
boolean modified = !Objects.equals(myData.zlsPath, otherData.zlsPath);
modified |= !Objects.equals(myData.zlsConfigPath, otherData.zlsConfigPath);
modified |= myData.debug != otherData.debug;
modified |= myData.messageTrace != otherData.messageTrace;
modified |= myData.increaseTimeouts != otherData.increaseTimeouts;
modified |= myData.buildOnSave != otherData.buildOnSave;
modified |= !Objects.equals(myData.buildOnSaveStep, otherData.buildOnSaveStep);
modified |= myData.highlightGlobalVarDeclarations != otherData.highlightGlobalVarDeclarations;
modified |= myData.dangerousComptimeExperimentsDoNotEnable != otherData.dangerousComptimeExperimentsDoNotEnable;
return modified;
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.settings;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Data
@AllArgsConstructor
public final class ZLSSettings {
public @Nullable String zlsPath;
public @NotNull String zlsConfigPath;
public boolean increaseTimeouts;
public boolean asyncFolding;
public boolean debug;
public boolean messageTrace;
public boolean buildOnSave;
public @NotNull String buildOnSaveStep;
public boolean highlightGlobalVarDeclarations;
public boolean dangerousComptimeExperimentsDoNotEnable;
public ZLSSettings() {
this(null, "", false, true, false, false, false, "install", false, false);
}
}

View file

@ -1,174 +0,0 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.settings;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.ui.TextBrowseFolderListener;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.FormBuilder;
import org.jetbrains.annotations.NotNull;
import javax.swing.JButton;
import javax.swing.JPanel;
public class ZLSSettingsComponent {
private final JPanel myMainPanel;
private final TextFieldWithBrowseButton zlsPathText = new TextFieldWithBrowseButton();
private final TextFieldWithBrowseButton zlsConfigPathText = new TextFieldWithBrowseButton();
private final JBCheckBox asyncFoldingCheckBox = new JBCheckBox();
private final JBCheckBox increaseTimeouts = new JBCheckBox();
private final JBCheckBox buildOnSave = new JBCheckBox();
private final JBTextField buildOnSaveStep = new JBTextField();
private final JBCheckBox highlightGlobalVarDeclarations = new JBCheckBox();
private final JBCheckBox dangerousComptimeExperimentsDoNotEnable = new JBCheckBox();
private final JBCheckBox messageTraceCheckBox = new JBCheckBox();
private final JBCheckBox debugCheckBox = new JBCheckBox();
private final JButton autodetectZls = new JButton("Autodetect");
{
buildOnSave.setToolTipText("Whether to enable build-on-save diagnostics");
buildOnSaveStep.setToolTipText("Select which step should be executed on build-on-save");
highlightGlobalVarDeclarations.setToolTipText("Whether to highlight global var declarations");
dangerousComptimeExperimentsDoNotEnable.setToolTipText("Whether to use the comptime interpreter");
}
public ZLSSettingsComponent() {
zlsPathText.addBrowseFolderListener(
new TextBrowseFolderListener(new FileChooserDescriptor(true, false, false, false, false, false)));
myMainPanel = FormBuilder.createFormBuilder()
.addComponent(new JBLabel("ZLS launch settings"))
.addVerticalGap(10)
.addLabeledComponent(new JBLabel("ZLS path: "), zlsPathText, 1, false)
.addComponent(autodetectZls)
.addLabeledComponent(new JBLabel("ZLS config path (leave empty to use built-in config): "),
zlsConfigPathText, 1, false)
.addLabeledComponent(new JBLabel("Increase timeouts"), increaseTimeouts, 1, false)
.addLabeledComponent(new JBLabel("Asynchronous code folding ranges: "),
asyncFoldingCheckBox, 1, false)
.addSeparator()
.addComponent(new JBLabel("ZLS configuration"))
.addVerticalGap(10)
.addLabeledComponent("Build on save", buildOnSave)
.addLabeledComponent("Build on save step", buildOnSaveStep)
.addLabeledComponent("Highlight global variable declarations", highlightGlobalVarDeclarations)
.addLabeledComponent("Dangerous comptime experiments (do not enable)", dangerousComptimeExperimentsDoNotEnable)
.addSeparator()
.addComponent(new JBLabel(
"Developer settings (only usable when the IDE was launched with " +
"the runIDE gradle task in ZigBrains!)"))
.addVerticalGap(10)
.addLabeledComponent(new JBLabel("ZLS debug log: "), debugCheckBox, 1, false)
.addLabeledComponent(new JBLabel("ZLS message trace: "), messageTraceCheckBox, 1,
false)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
autodetectZls.addActionListener(e -> {
ZLSSettingsState.executablePathFinder("zls").ifPresent(this::setZLSPath);
});
}
public JPanel getPanel() {
return myMainPanel;
}
@NotNull
public String getZLSPath() {
return zlsPathText.getText();
}
public void setZLSPath(@NotNull String newText) {
zlsPathText.setText(newText);
}
@NotNull
public String getZLSConfigPath() {
return zlsConfigPathText.getText();
}
public void setZLSConfigPath(@NotNull String newText) {
zlsConfigPathText.setText(newText);
}
public boolean getIncreaseTimeouts() {
return increaseTimeouts.isSelected();
}
public void setIncreaseTimeouts(boolean state) {
increaseTimeouts.setSelected(state);
}
public boolean getAsyncFolding() {
return asyncFoldingCheckBox.isSelected();
}
public void setAsyncFolding(boolean state) {
asyncFoldingCheckBox.setSelected(state);
}
public boolean getDebug() {
return debugCheckBox.isSelected();
}
public void setDebug(boolean state) {
debugCheckBox.setSelected(state);
}
public boolean getMessageTrace() {
return messageTraceCheckBox.isSelected();
}
public void setMessageTrace(boolean state) {
messageTraceCheckBox.setSelected(state);
}
public boolean getBuildOnSave() {
return buildOnSave.isSelected();
}
public void setBuildOnSave(boolean state) {
buildOnSave.setSelected(state);
}
public String getBuildOnSaveStep() {
return buildOnSaveStep.getText();
}
public void setBuildOnSaveStep(String value) {
buildOnSaveStep.setText(value);
}
public boolean getHighlightGlobalVarDeclarations() {
return highlightGlobalVarDeclarations.isSelected();
}
public void setHighlightGlobalVarDeclarations(boolean state) {
highlightGlobalVarDeclarations.setSelected(state);
}
public boolean getDangerousComptimeExperimentsDoNotEnable() {
return dangerousComptimeExperimentsDoNotEnable.isSelected();
}
public void setDangerousComptimeExperimentsDoNotEnable(boolean state) {
dangerousComptimeExperimentsDoNotEnable.setSelected(state);
}
}

View file

@ -24,7 +24,7 @@ import lombok.val;
public class ZLSSettingsConfigProvider implements ZLSConfigProvider {
@Override
public void getEnvironment(Project project, ZLSConfig.ZLSConfigBuilder builder) {
val state = ZLSSettingsState.getInstance(project);
val state = ZLSProjectSettingsService.getInstance(project).getState();
builder.enable_build_on_save(state.buildOnSave);
builder.build_on_save_step(state.buildOnSaveStep);
builder.highlight_global_var_declarations(state.highlightGlobalVarDeclarations);

View file

@ -16,16 +16,15 @@
package com.falsepattern.zigbrains.zig.settings;
import com.falsepattern.zigbrains.common.SubConfigurable;
import com.falsepattern.zigbrains.common.util.dsl.JavaPanel;
import com.falsepattern.zigbrains.zig.lsp.ZLSStartupActivity;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.project.Project;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.JComponent;
public class ZLSSettingsConfigurable implements Configurable {
private ZLSSettingsComponent appSettingsComponent;
public class ZLSSettingsConfigurable implements SubConfigurable {
private ZLSSettingsPanel appSettingsComponent;
private final Project project;
@ -34,52 +33,24 @@ public class ZLSSettingsConfigurable implements Configurable {
}
@Override
public String getDisplayName() {
return "Zig";
}
@Override
public @Nullable JComponent createComponent() {
appSettingsComponent = new ZLSSettingsComponent();
return appSettingsComponent.getPanel();
public void createComponent(JavaPanel panel) {
appSettingsComponent = new ZLSSettingsPanel();
appSettingsComponent.attachPanelTo(panel);
}
@Override
public boolean isModified() {
var settings = ZLSSettingsState.getInstance(project);
boolean modified = zlsSettingsModified(settings);
modified |= settings.asyncFolding != appSettingsComponent.getAsyncFolding();
return modified;
}
private boolean zlsSettingsModified(ZLSSettingsState settings) {
boolean modified = !settings.zlsPath.equals(appSettingsComponent.getZLSPath());
modified |= !settings.zlsConfigPath.equals(appSettingsComponent.getZLSConfigPath());
modified |= settings.debug != appSettingsComponent.getDebug();
modified |= settings.messageTrace != appSettingsComponent.getMessageTrace();
modified |= settings.increaseTimeouts != appSettingsComponent.getIncreaseTimeouts();
modified |= settings.buildOnSave != appSettingsComponent.getBuildOnSave();
modified |= !settings.buildOnSaveStep.equals(appSettingsComponent.getBuildOnSaveStep());
modified |= settings.highlightGlobalVarDeclarations != appSettingsComponent.getHighlightGlobalVarDeclarations();
modified |= settings.dangerousComptimeExperimentsDoNotEnable != appSettingsComponent.getDangerousComptimeExperimentsDoNotEnable();
return modified;
var settings = ZLSProjectSettingsService.getInstance(project);
val data = appSettingsComponent.getData();
return settings.isModified(data);
}
@Override
public void apply() {
var settings = ZLSSettingsState.getInstance(project);
boolean reloadZLS = zlsSettingsModified(settings);
settings.zlsPath = appSettingsComponent.getZLSPath();
settings.zlsConfigPath = appSettingsComponent.getZLSConfigPath();
settings.asyncFolding = appSettingsComponent.getAsyncFolding();
settings.debug = appSettingsComponent.getDebug();
settings.messageTrace = appSettingsComponent.getMessageTrace();
settings.increaseTimeouts = appSettingsComponent.getIncreaseTimeouts();
settings.buildOnSave = appSettingsComponent.getBuildOnSave();
settings.buildOnSaveStep = appSettingsComponent.getBuildOnSaveStep();
settings.highlightGlobalVarDeclarations = appSettingsComponent.getHighlightGlobalVarDeclarations();
settings.dangerousComptimeExperimentsDoNotEnable = appSettingsComponent.getDangerousComptimeExperimentsDoNotEnable();
var settings = ZLSProjectSettingsService.getInstance(project);
val data = appSettingsComponent.getData();
boolean reloadZLS = settings.zlsSettingsModified(data);
settings.loadState(data);
if (reloadZLS) {
ZLSStartupActivity.initZLS(project);
}
@ -87,19 +58,8 @@ public class ZLSSettingsConfigurable implements Configurable {
@Override
public void reset() {
var settings = ZLSSettingsState.getInstance(project);
appSettingsComponent.setZLSPath(settings.zlsPath);
appSettingsComponent.setZLSConfigPath(settings.zlsConfigPath);
appSettingsComponent.setDebug(settings.debug);
appSettingsComponent.setAsyncFolding(settings.asyncFolding);
appSettingsComponent.setMessageTrace(settings.messageTrace);
appSettingsComponent.setIncreaseTimeouts(settings.increaseTimeouts);
appSettingsComponent.setAsyncFolding(settings.asyncFolding);
appSettingsComponent.setBuildOnSave(settings.buildOnSave);
appSettingsComponent.setBuildOnSaveStep(settings.buildOnSaveStep);
appSettingsComponent.setHighlightGlobalVarDeclarations(settings.highlightGlobalVarDeclarations);
appSettingsComponent.setDangerousComptimeExperimentsDoNotEnable(settings.dangerousComptimeExperimentsDoNotEnable);
var settings = ZLSProjectSettingsService.getInstance(project);
appSettingsComponent.setData(settings.getState());
}
@Override

View file

@ -0,0 +1,133 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.settings;
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.ProjectManager;
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 java.awt.event.ActionEvent;
import java.nio.file.Path;
import java.util.Optional;
import static com.falsepattern.zigbrains.common.util.KtUtil.$f;
public class ZLSSettingsPanel implements Disposable {
private final TextFieldWithBrowseButton zlsPath = TextFieldUtil.pathToFileTextField(this,
"Path to the ZLS Binary",
() -> {});
private final TextFieldWithBrowseButton zlsConfigPath = TextFieldUtil.pathToFileTextField(this,
"Path to the Custom ZLS Config File (Optional)",
() -> {});
private final JBCheckBox asyncFolding = new JBCheckBox();
private final JBCheckBox increaseTimeouts = new JBCheckBox();
private final JBCheckBox buildOnSave = new JBCheckBox();
private final JBTextField buildOnSaveStep = new ExtendableTextField();
private final JBCheckBox highlightGlobalVarDeclarations = new JBCheckBox();
private final JBCheckBox dangerousComptimeExperimentsDoNotEnable = new JBCheckBox();
private final JBCheckBox messageTrace = new JBCheckBox();
private final JBCheckBox debug = new JBCheckBox();
{
buildOnSave.setToolTipText("Whether to enable build-on-save diagnostics");
buildOnSaveStep.setToolTipText("Select which step should be executed on build-on-save");
highlightGlobalVarDeclarations.setToolTipText("Whether to highlight global var declarations");
dangerousComptimeExperimentsDoNotEnable.setToolTipText("Whether to use the comptime interpreter");
}
private void autodetect(ActionEvent e) {
autodetect();
}
public void autodetect() {
FileUtil.findExecutableOnPATH("zls").map(Path::toString).ifPresent(zlsPath::setText);
}
public ZLSSettingsPanel() {
zlsPath.addBrowseFolderListener(new TextBrowseFolderListener(new FileChooserDescriptor(true, false, false, false, false, false)));
}
public void attachPanelTo(JavaPanel panel) {
Optional.ofNullable(ZLSProjectSettingsService.getInstance(ProjectManager.getInstance().getDefaultProject()))
.map(ZLSProjectSettingsService::getState)
.ifPresent(this::setData);
panel.group("ZLS launch settings", true, p -> {
p.row("Executable path", r -> {
r.cell(zlsPath).resizableColumn().align(AlignX.FILL);
r.button("Autodetect", $f(this::autodetect));
});
p.cell("Config path (leave empty to use built-in config)", zlsConfigPath, AlignX.FILL);
p.cell("Increase timeouts", increaseTimeouts);
p.cell("Asynchronous code folding ranges", asyncFolding);
});
panel.group("ZLS Configuration", false, p -> {
p.cell("Build on save", buildOnSave);
p.row("Build on save step", r -> {
r.cell(buildOnSaveStep).resizableColumn().align(AlignX.FILL);
});
p.cell("Highlight global variable declarations", highlightGlobalVarDeclarations);
p.cell("Dangerous comptime experiments (do not enable)", dangerousComptimeExperimentsDoNotEnable);
});
panel.group("ZLS Developer settings", false, p -> {
p.cell("Debug log", debug);
p.cell("Message trace", messageTrace);
});
}
public ZLSSettings getData() {
return new ZLSSettings(zlsPath.getText(),
zlsConfigPath.getText(),
increaseTimeouts.isSelected(),
asyncFolding.isSelected(),
debug.isSelected(),
messageTrace.isSelected(),
buildOnSave.isSelected(),
buildOnSaveStep.getText(),
highlightGlobalVarDeclarations.isSelected(),
dangerousComptimeExperimentsDoNotEnable.isSelected());
}
public void setData(ZLSSettings value) {
zlsPath.setText(value.zlsPath == null ? "" : value.zlsPath);
zlsConfigPath.setText(value.zlsConfigPath);
increaseTimeouts.setSelected(value.increaseTimeouts);
asyncFolding.setSelected(value.asyncFolding);
debug.setSelected(value.debug);
messageTrace.setSelected(value.messageTrace);
buildOnSave.setSelected(value.buildOnSave);
buildOnSaveStep.setText(value.buildOnSaveStep);
highlightGlobalVarDeclarations.setSelected(value.highlightGlobalVarDeclarations);
dangerousComptimeExperimentsDoNotEnable.setSelected(value.dangerousComptimeExperimentsDoNotEnable);
}
@Override
public void dispose() {
zlsPath.dispose();
zlsConfigPath.dispose();
}
}

View file

@ -1,85 +0,0 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.settings;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
@Service(Service.Level.PROJECT)
@State(name = "ZLSSettings",
storages = @Storage("zigbrains.xml"))
public final class ZLSSettingsState implements PersistentStateComponent<ZLSSettingsState> {
public String zlsPath = "";
public String zlsConfigPath = "";
public boolean initialAutodetectHasBeenDone = false;
public boolean increaseTimeouts = false;
public boolean asyncFolding = true;
public boolean debug = false;
public boolean messageTrace = false;
public boolean buildOnSave = false;
public String buildOnSaveStep = "install";
public boolean dangerousComptimeExperimentsDoNotEnable = false;
public boolean highlightGlobalVarDeclarations = false;
public static Optional<String> executablePathFinder(String exe) {
var exeName = SystemInfo.isWindows ? exe + ".exe" : exe;
var PATH = System.getenv("PATH").split(File.pathSeparator);
for (var dir: PATH) {
var path = Path.of(dir);
try {
path = path.toAbsolutePath();
} catch (Exception ignored) {
continue;
}
if (!Files.exists(path) || !Files.isDirectory(path)) {
continue;
}
var exePath = path.resolve(exeName).toAbsolutePath();
if (!Files.isRegularFile(exePath) || !Files.isExecutable(exePath)) {
continue;
}
return Optional.of(exePath.toString());
}
return Optional.empty();
}
public static ZLSSettingsState getInstance(Project project) {
return project.getService(ZLSSettingsState.class);
}
@Override
public ZLSSettingsState getState() {
return this;
}
@Override
public void loadState(@NotNull ZLSSettingsState state) {
XmlSerializerUtil.copyBean(state, this);
}
}

View file

@ -98,11 +98,6 @@
<lang.formatter language="Zig" implementationClass="com.falsepattern.zigbrains.zig.formatter.ZigFormattingModelBuilder"/>
<projectConfigurable parentId="language"
instance="com.falsepattern.zigbrains.zig.settings.ZLSSettingsConfigurable"
id="com.falsepattern.zigbrains.zig.settings.ZLSSettingsConfigurable"
displayName="ZLS"/>
<postStartupActivity implementation="com.falsepattern.zigbrains.zig.lsp.ZLSStartupActivity"/>
<notificationGroup displayType="BALLOON"
bundle="zigbrains.zig.Bundle"
@ -113,6 +108,8 @@
implementationClass="com.falsepattern.zigbrains.zig.completion.ZigParameterInfoHandler"/>
<platform.backend.documentation.linkHandler implementation="com.falsepattern.zigbrains.lsp.contributors.LSPDocumentationLinkHandler"/>
<dynamicActionConfigurationCustomizer implementation="com.falsepattern.zigbrains.lsp.actions.ActionCustomizer"/>
</extensions>
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
@ -130,15 +127,6 @@
<keyboard-shortcut first-keystroke="shift alt F7"
keymap="$default"/>
</action>
<action class="com.falsepattern.zigbrains.lsp.actions.LSPReformatAction" id="ReformatCode" use-shortcut-of="ReformatCode"
overrides="true" text="Reformat Code"/>
<action class="com.falsepattern.zigbrains.lsp.actions.LSPGotoDeclarationAction" id="GotoDeclaration" use-shortcut-of="GotoDeclaration"
overrides="true" text="Go to Declaration or Usages"/>
<action class="com.falsepattern.zigbrains.lsp.actions.LSPGotoDefinitionAction" id="QuickImplementations" use-shortcut-of="QuickImplementations"
overrides="true" text="View Implementations"/>
<action class="com.falsepattern.zigbrains.lsp.actions.LSPShowReformatDialogAction" id="ShowReformatFileDialog"
use-shortcut-of="ShowReformatFileDialog" overrides="true" text="Show Reformat File Dialog"/>
<!-- endregion LSP -->
</actions>