feat!: Huge debugging refactor

- Debug support for Windows
- Debuggable zig build
- Debuggable binaries

This is a squashed commit so ignore the weird author date
This commit is contained in:
FalsePattern 2024-03-05 15:24:56 +01:00
parent b4539c0aa9
commit 3d0dbb8e36
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
52 changed files with 4198 additions and 383 deletions

View file

@ -174,6 +174,9 @@ project(":debugger") {
dependencies { dependencies {
implementation(project(":zig")) implementation(project(":zig"))
implementation(project(":project")) implementation(project(":project"))
implementation(project(":common"))
implementation(project(":lsp-common"))
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.22.0")
} }
intellij { intellij {
version = clionVersion version = clionVersion

View file

@ -0,0 +1,70 @@
/*
* 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 lombok.val;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class CollectionUtil {
public static <T> List<T> concat(List<T> a, List<T> b) {
val res = new ArrayList<>(a);
res.addAll(b);
return res;
}
public static <T> List<T> concat(T[] a, List<T> b) {
val res = new ArrayList<>(List.of(a));
res.addAll(b);
return res;
}
@SafeVarargs
public static <T> List<T> concat(List<T> a, T... b) {
val res = new ArrayList<>(a);
res.addAll(List.of(b));
return res;
}
@SuppressWarnings("unchecked")
public static <T> T[] concat(T[]... arrays) {
if (null != arrays && 0 != arrays.length) {
int resultLength = (Integer)java.util.Arrays.stream(arrays).filter(Objects::nonNull).map((e) -> {
return e.length;
}).reduce(0, Integer::sum);
T[] resultArray = (T[]) Array.newInstance(arrays[0].getClass().getComponentType(), resultLength);
int i = 0;
int n = arrays.length;
for(int curr = 0; i < n; ++i) {
T[] array = arrays[i];
if (null != array) {
int length = array.length;
System.arraycopy(array, 0, resultArray, curr, length);
curr += length;
}
}
return resultArray;
} else {
return null;
}
}
}

View file

@ -40,6 +40,15 @@ public class TextFieldUtil {
onTextChanged); onTextChanged);
} }
public static TextFieldWithBrowseButton pathToFileTextField(Disposable disposable,
@NlsContexts.DialogTitle String dialogTitle,
Runnable onTextChanged) {
return pathTextField(FileChooserDescriptorFactory.createSingleFileDescriptor(),
disposable,
dialogTitle,
onTextChanged);
}
public static TextFieldWithBrowseButton pathTextField(FileChooserDescriptor fileChooserDescriptor, public static TextFieldWithBrowseButton pathTextField(FileChooserDescriptor fileChooserDescriptor,
Disposable disposable, Disposable disposable,
@NlsContexts.DialogTitle String dialogTitle, @NlsContexts.DialogTitle String dialogTitle,

1
modules/debugger/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
!src/**/build/

View file

@ -17,10 +17,12 @@
package com.falsepattern.zigbrains.zig.debugger; package com.falsepattern.zigbrains.zig.debugger;
import com.falsepattern.zigbrains.zig.debugbridge.DebuggerDriverProvider; import com.falsepattern.zigbrains.zig.debugbridge.DebuggerDriverProvider;
import com.falsepattern.zigbrains.zig.debugger.win.WinDebuggerDriverConfiguration;
import com.intellij.notification.Notification; import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType; import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications; import com.intellij.notification.Notifications;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.util.system.OS;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration; import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration; import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration;
import lombok.val; import lombok.val;
@ -28,6 +30,9 @@ import org.jetbrains.annotations.Nullable;
public class Utils { public class Utils {
public static @Nullable DebuggerDriverConfiguration getDebuggerConfiguration(Project project) { public static @Nullable DebuggerDriverConfiguration getDebuggerConfiguration(Project project) {
if (OS.CURRENT == OS.Windows) {
return new WinDebuggerDriverConfiguration();
}
val providedDebugger = DebuggerDriverProvider.findDebuggerConfigurations(project) val providedDebugger = DebuggerDriverProvider.findDebuggerConfigurations(project)
.filter(x -> x instanceof DebuggerDriverConfiguration) .filter(x -> x instanceof DebuggerDriverConfiguration)
.map(x -> (DebuggerDriverConfiguration)x) .map(x -> (DebuggerDriverConfiguration)x)

View file

@ -0,0 +1,62 @@
/*
* 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.debugger.dap;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
class BlockingPipedInputStream extends PipedInputStream {
boolean closed;
public BlockingPipedInputStream(PipedOutputStream pout, int pipeSize) throws IOException {
super(pout, pipeSize);
}
public synchronized int read() throws IOException {
if (this.closed) {
throw new IOException("stream closed");
} else {
while(super.in < 0) {
this.notifyAll();
try {
this.wait(750L);
} catch (InterruptedException var2) {
throw new InterruptedIOException();
}
}
int ret = this.buffer[super.out++] & 255;
if (super.out >= this.buffer.length) {
super.out = 0;
}
if (super.in == super.out) {
super.in = -1;
}
return ret;
}
}
public void close() throws IOException {
this.closed = true;
super.close();
}
}

View file

@ -0,0 +1,62 @@
/*
* 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.debugger.dap;
import com.falsepattern.zigbrains.zig.ZigLanguage;
import com.intellij.execution.ExecutionException;
import com.intellij.lang.Language;
import com.intellij.openapi.util.Expirable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.UserDataHolderEx;
import com.jetbrains.cidr.ArchitectureType;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerCommandException;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import com.jetbrains.cidr.execution.debugger.backend.EvaluationContext;
import com.jetbrains.cidr.execution.debugger.backend.LLFrame;
import com.jetbrains.cidr.execution.debugger.backend.LLThread;
import com.jetbrains.cidr.execution.debugger.backend.LLValue;
import com.jetbrains.cidr.execution.debugger.backend.LLValueData;
import org.eclipse.lsp4j.debug.InitializeRequestArguments;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class DAPDebuggerDriverConfiguration extends DebuggerDriverConfiguration {
@Override
public abstract @NotNull String getDriverName();
@Override
public abstract @NotNull DebuggerDriver createDriver(@NotNull DebuggerDriver.Handler handler,
@NotNull ArchitectureType architectureType) throws ExecutionException;
public abstract void customizeInitializeArguments(InitializeRequestArguments initArgs);
@Override
public @NotNull Language getConsoleLanguage() {
return ZigLanguage.INSTANCE;
}
@Override
public EvaluationContext createEvaluationContext(@NotNull DebuggerDriver debuggerDriver, @Nullable Expirable expirable, @NotNull LLThread llThread, @NotNull LLFrame llFrame, @NotNull UserDataHolderEx userDataHolderEx) {
return new EvaluationContext(debuggerDriver,expirable,llThread,llFrame,userDataHolderEx) {
@Override
public @NotNull String convertToRValue(@NotNull LLValueData llValueData, @NotNull Pair<LLValue, String> pair) throws DebuggerCommandException, ExecutionException {
return cast(pair.getSecond(), pair.getFirst().getType());
}
} ;
}
}

View file

@ -0,0 +1,267 @@
/*
* 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.debugger.dap;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.zig.debugger.ZigDebuggerLanguage;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFileUtil;
import com.jetbrains.cidr.execution.debugger.backend.FileLocation;
import com.jetbrains.cidr.execution.debugger.backend.LLBreakpoint;
import com.jetbrains.cidr.execution.debugger.backend.LLBreakpointLocation;
import com.jetbrains.cidr.execution.debugger.backend.LLFrame;
import com.jetbrains.cidr.execution.debugger.backend.LLInstruction;
import com.jetbrains.cidr.execution.debugger.backend.LLMemoryHunk;
import com.jetbrains.cidr.execution.debugger.backend.LLModule;
import com.jetbrains.cidr.execution.debugger.backend.LLSymbolOffset;
import com.jetbrains.cidr.execution.debugger.backend.LLThread;
import com.jetbrains.cidr.execution.debugger.memory.Address;
import com.jetbrains.cidr.execution.debugger.memory.AddressRange;
import lombok.val;
import org.eclipse.lsp4j.debug.Breakpoint;
import org.eclipse.lsp4j.debug.DisassembledInstruction;
import org.eclipse.lsp4j.debug.Module;
import org.eclipse.lsp4j.debug.ReadMemoryResponse;
import org.eclipse.lsp4j.debug.Source;
import org.eclipse.lsp4j.debug.StackFrame;
import org.eclipse.lsp4j.debug.Thread;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Pattern;
public class Util {
public static LLThread threadJBFromDAP(Thread DAPThread) {
return new LLThread(DAPThread.getId(), null, null, DAPThread.getName(), null);
}
public static Thread threadDAPFromJB(LLThread JBThread) {
val DAPThread = new Thread();
DAPThread.setId((int) JBThread.getId());
return DAPThread;
}
public static LLBreakpoint breakpointJBFromDAP(Breakpoint DAPBreakpoint) {
val source = DAPBreakpoint.getSource();
var sourcePath = source == null ? "": Objects.requireNonNullElseGet(source.getPath(), () -> Objects.requireNonNullElse(source.getOrigin(), "unknown"));
sourcePath = toJBPath(sourcePath);
return new LLBreakpoint(DAPBreakpoint.getId(), sourcePath, Objects.requireNonNullElse(DAPBreakpoint.getLine(), 0) - 1, null);
}
public static @Nullable LLBreakpointLocation getLocation(Breakpoint DAPBreakpoint) {
val ref = DAPBreakpoint.getInstructionReference();
if (ref == null)
return null;
val addr = Long.parseLong(ref.substring(2), 16);
FileLocation fl = null;
val src = DAPBreakpoint.getSource();
if (src != null) {
fl = new FileLocation(src.getPath(), DAPBreakpoint.getLine());
}
return new LLBreakpointLocation(DAPBreakpoint.getId() + "", Address.fromUnsignedLong(addr), fl);
}
public static Breakpoint breakpointDAPFromJB(LLBreakpoint JBBreakpoint) {
val DAPBreakpoint = new Breakpoint();
DAPBreakpoint.setId(JBBreakpoint.getId());
DAPBreakpoint.setLine(JBBreakpoint.getOrigLine() + 1);
val source = new Source();
source.setPath(JBBreakpoint.getOrigFile());
DAPBreakpoint.setSource(source);
DAPBreakpoint.setMessage(JBBreakpoint.getCondition());
return DAPBreakpoint;
}
public static LLModule moduleJBFromDAP(Module DAPModule) {
return new LLModule(toJBPath(DAPModule.getPath()));
}
public static Module moduleDAPFromJB(LLModule JBModule) {
val DAPModule = new Module();
DAPModule.setPath(toJBPath(JBModule.getPath()));
DAPModule.setName(JBModule.getName());
return DAPModule;
}
public static LLFrame frameJBFromDAP(StackFrame DAPFrame, @Nullable DAPDriver.MappedBreakpoint helperBreakpoint, Map<Integer, DAPDriver.MappedModule> modules) {
val ptr = parseAddress(DAPFrame.getInstructionPointerReference());
val name = DAPFrame.getName();
boolean inline = name.startsWith("[Inline Frame] ");
val function = name.substring(name.indexOf('!') + 1, name.indexOf('('));
val moduleID = DAPFrame.getModuleId();
String moduleName = null;
if (moduleID != null) {
if (moduleID.isRight()) {
moduleName = moduleID.getRight();
} else {
val module = modules.get(moduleID.getLeft());
moduleName = module.java().getName();
}
}
var line = DAPFrame.getLine();
String sourcePath;
{
val src = DAPFrame.getSource();
sourcePath = src == null ? null : toJBPath(src.getPath());
}
if (helperBreakpoint != null) {
if (line == 0) {
line = helperBreakpoint.dap().getLine();
}
if (sourcePath == null) {
val src = helperBreakpoint.dap().getSource();
if (src != null) {
sourcePath = toJBPath(src.getPath());
}
}
}
return new LLFrame(DAPFrame.getId(),
function,
sourcePath,
null,
line - 1,
ptr,
ZigDebuggerLanguage.INSTANCE,
false,
inline,
moduleName);
}
public static Source toSource(String path) {
val src = new Source();
val absolute = Path.of(path).toAbsolutePath();
src.setName(absolute.getFileName().toString());
src.setPath(toWinPath(absolute.toString()));
return src;
}
public static String toWinPath(String path) {
if (path == null)
return null;
return path.replace('/', '\\');
}
public static String toJBPath(String path) {
if (path == null)
return null;
return path.replace('\\', '/');
}
public static Long parseAddressNullable(String address) {
if (address == null)
return null;
return parseAddress(address);
}
public static long parseAddress(String address) {
if (address == null)
return 0L;
if (!address.startsWith("0x"))
return Long.parseUnsignedLong(address);
return Long.parseUnsignedLong(address.substring(2), 16);
}
public static String stringifyAddress(long address) {
return "0x" + Long.toHexString(address);
}
private static final Pattern HEX_FIX_REGEX = Pattern.compile("([0-9A-F]+)(?<!\\W)h");
public static LLInstruction instructionJBFromDAP(DisassembledInstruction DAPInstruction, Source loc, Integer startLine, Integer endLine, boolean uniq, LLSymbolOffset symbol) {
val address = Address.parseHexString(DAPInstruction.getAddress());
val byteStrings = DAPInstruction.getInstructionBytes().split(" ");
val bytes = new ArrayList<Byte>(byteStrings.length);
for (val byteString: byteStrings) {
bytes.add((byte) Integer.parseInt(byteString, 16));
}
val result = new ArrayList<LLInstruction>();
String comment = null;
blk:
if (loc != null && startLine != null && endLine != null && uniq) {
val pathStr = Util.toJBPath(loc.getPath());
Path path;
try {
path = Path.of(pathStr);
} catch (InvalidPathException ignored) {
break blk;
}
val text = ApplicationUtil.computableReadAction(() -> {
val file = VfsUtil.findFile(path, true);
if (file == null)
return null;
val doc = VirtualFileUtil.findDocument(file);
if (doc == null)
return null;
return doc.getImmutableCharSequence().toString().split("(\r\n|\r|\n)");
});
if (text == null)
break blk;
startLine -= 1;
endLine -= 1;
if (text.length <= endLine)
break blk;
comment = text[endLine];
}
var nicerDisassembly = new StringBuilder();
var disassembly = DAPInstruction.getInstruction();
val matcher = HEX_FIX_REGEX.matcher(disassembly);
int prevEnd = 0;
while (matcher.find()) {
nicerDisassembly.append(disassembly, prevEnd, matcher.start());
val hex = matcher.group(1).toLowerCase();
nicerDisassembly.append("0x").append(hex);
prevEnd = matcher.end();
}
if (prevEnd < disassembly.length())
nicerDisassembly.append(disassembly, prevEnd, disassembly.length());
return LLInstruction.create(address,
bytes,
nicerDisassembly.toString(),
comment,
symbol);
}
public static LLMemoryHunk memoryJBFromDAP(ReadMemoryResponse DAPMemory) {
val address = Util.parseAddress(DAPMemory.getAddress());
val bytes = Base64.getDecoder().decode(DAPMemory.getData());
val range = new AddressRange(Address.fromUnsignedLong(address), Address.fromUnsignedLong(address + bytes.length - 1));
return new LLMemoryHunk(range, bytes);
}
public static <T> T get(CompletableFuture<T> future) throws ExecutionException {
try {
return future.get(4, TimeUnit.SECONDS);
} catch (InterruptedException | TimeoutException e) {
throw new ExecutionException(e);
} catch (java.util.concurrent.ExecutionException e) {
throw new ExecutionException(e.getCause());
}
}
public static @NotNull String emptyIfNull(@Nullable String str) {
return str == null ? "" : str;
}
}

View file

@ -0,0 +1,518 @@
/*
* 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.debugger.dap;
import com.intellij.execution.ExecutionException;
import lombok.RequiredArgsConstructor;
import org.eclipse.lsp4j.debug.BreakpointLocationsArguments;
import org.eclipse.lsp4j.debug.BreakpointLocationsResponse;
import org.eclipse.lsp4j.debug.CancelArguments;
import org.eclipse.lsp4j.debug.Capabilities;
import org.eclipse.lsp4j.debug.CompletionsArguments;
import org.eclipse.lsp4j.debug.CompletionsResponse;
import org.eclipse.lsp4j.debug.ConfigurationDoneArguments;
import org.eclipse.lsp4j.debug.ContinueArguments;
import org.eclipse.lsp4j.debug.ContinueResponse;
import org.eclipse.lsp4j.debug.DataBreakpointInfoArguments;
import org.eclipse.lsp4j.debug.DataBreakpointInfoResponse;
import org.eclipse.lsp4j.debug.DisassembleArguments;
import org.eclipse.lsp4j.debug.DisassembleResponse;
import org.eclipse.lsp4j.debug.DisconnectArguments;
import org.eclipse.lsp4j.debug.EvaluateArguments;
import org.eclipse.lsp4j.debug.EvaluateResponse;
import org.eclipse.lsp4j.debug.ExceptionInfoArguments;
import org.eclipse.lsp4j.debug.ExceptionInfoResponse;
import org.eclipse.lsp4j.debug.GotoArguments;
import org.eclipse.lsp4j.debug.GotoTargetsArguments;
import org.eclipse.lsp4j.debug.GotoTargetsResponse;
import org.eclipse.lsp4j.debug.InitializeRequestArguments;
import org.eclipse.lsp4j.debug.LoadedSourcesArguments;
import org.eclipse.lsp4j.debug.LoadedSourcesResponse;
import org.eclipse.lsp4j.debug.ModulesArguments;
import org.eclipse.lsp4j.debug.ModulesResponse;
import org.eclipse.lsp4j.debug.NextArguments;
import org.eclipse.lsp4j.debug.PauseArguments;
import org.eclipse.lsp4j.debug.ReadMemoryArguments;
import org.eclipse.lsp4j.debug.ReadMemoryResponse;
import org.eclipse.lsp4j.debug.RestartArguments;
import org.eclipse.lsp4j.debug.RestartFrameArguments;
import org.eclipse.lsp4j.debug.ReverseContinueArguments;
import org.eclipse.lsp4j.debug.ScopesArguments;
import org.eclipse.lsp4j.debug.ScopesResponse;
import org.eclipse.lsp4j.debug.SetBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetDataBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetDataBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetExceptionBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetExceptionBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetExpressionArguments;
import org.eclipse.lsp4j.debug.SetExpressionResponse;
import org.eclipse.lsp4j.debug.SetFunctionBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetFunctionBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetInstructionBreakpointsArguments;
import org.eclipse.lsp4j.debug.SetInstructionBreakpointsResponse;
import org.eclipse.lsp4j.debug.SetVariableArguments;
import org.eclipse.lsp4j.debug.SetVariableResponse;
import org.eclipse.lsp4j.debug.SourceArguments;
import org.eclipse.lsp4j.debug.SourceResponse;
import org.eclipse.lsp4j.debug.StackTraceArguments;
import org.eclipse.lsp4j.debug.StackTraceResponse;
import org.eclipse.lsp4j.debug.StepBackArguments;
import org.eclipse.lsp4j.debug.StepInArguments;
import org.eclipse.lsp4j.debug.StepInTargetsArguments;
import org.eclipse.lsp4j.debug.StepInTargetsResponse;
import org.eclipse.lsp4j.debug.StepOutArguments;
import org.eclipse.lsp4j.debug.TerminateArguments;
import org.eclipse.lsp4j.debug.TerminateThreadsArguments;
import org.eclipse.lsp4j.debug.ThreadsResponse;
import org.eclipse.lsp4j.debug.VariablesArguments;
import org.eclipse.lsp4j.debug.VariablesResponse;
import org.eclipse.lsp4j.debug.WriteMemoryArguments;
import org.eclipse.lsp4j.debug.WriteMemoryResponse;
import org.eclipse.lsp4j.debug.services.IDebugProtocolServer;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import static com.falsepattern.zigbrains.zig.debugger.dap.Util.get;
@SuppressWarnings("unused")
@RequiredArgsConstructor
public class WrappedDebugServer<T extends IDebugProtocolServer> implements IDebugProtocolServer {
protected final T server;
public void cancelNow(CancelArguments args) throws ExecutionException {
get(cancel(args));
}
public Capabilities initializeNow(InitializeRequestArguments args) throws ExecutionException {
return get(initialize(args));
}
public void configurationDoneNow(ConfigurationDoneArguments args) throws ExecutionException {
get(configurationDone(args));
}
public void launchNow(Map<String, Object> args) throws ExecutionException {
get(launch(args));
}
public void attachNow(Map<String, Object> args) throws ExecutionException {
get(attach(args));
}
public void restartNow(RestartArguments args) throws ExecutionException {
get(restart(args));
}
public void disconnectNow(DisconnectArguments args) throws ExecutionException {
get(disconnect(args));
}
public void terminateNow(TerminateArguments args) throws ExecutionException {
get(terminate(args));
}
public BreakpointLocationsResponse breakpointLocationsNow(BreakpointLocationsArguments args) throws ExecutionException {
return get(breakpointLocations(args));
}
public SetBreakpointsResponse setBreakpointsNow(SetBreakpointsArguments args) throws ExecutionException {
return get(setBreakpoints(args));
}
public SetFunctionBreakpointsResponse setFunctionBreakpointsNow(SetFunctionBreakpointsArguments args) throws ExecutionException {
return get(setFunctionBreakpoints(args));
}
public SetExceptionBreakpointsResponse setExceptionBreakpointsNow(SetExceptionBreakpointsArguments args) throws ExecutionException {
return get(setExceptionBreakpoints(args));
}
public DataBreakpointInfoResponse dataBreakpointInfoNow(DataBreakpointInfoArguments args) throws ExecutionException {
return get(dataBreakpointInfo(args));
}
public SetDataBreakpointsResponse setDataBreakpointsNow(SetDataBreakpointsArguments args) throws ExecutionException {
return get(setDataBreakpoints(args));
}
public SetInstructionBreakpointsResponse setInstructionBreakpointsNow(SetInstructionBreakpointsArguments args) throws ExecutionException {
return get(setInstructionBreakpoints(args));
}
public ContinueResponse continueNow(ContinueArguments args) throws ExecutionException {
return get(continue_(args));
}
public void nextNow(NextArguments args) throws ExecutionException {
get(next(args));
}
public void stepInNow(StepInArguments args) throws ExecutionException {
get(stepIn(args));
}
public void stepOutNow(StepOutArguments args) throws ExecutionException {
get(stepOut(args));
}
public void stepBackNow(StepBackArguments args) throws ExecutionException {
get(stepBack(args));
}
public void reverseContinueNow(ReverseContinueArguments args) throws ExecutionException {
get(reverseContinue(args));
}
public void restartFrameNow(RestartFrameArguments args) throws ExecutionException {
get(restartFrame(args));
}
public void gotoNow(GotoArguments args) throws ExecutionException {
get(goto_(args));
}
public void pauseNow(PauseArguments args) throws ExecutionException {
get(pause(args));
}
public StackTraceResponse stackTraceNow(StackTraceArguments args) throws ExecutionException {
return get(stackTrace(args));
}
public ScopesResponse scopesNow(ScopesArguments args) throws ExecutionException {
return get(scopes(args));
}
public VariablesResponse variablesNow(VariablesArguments args) throws ExecutionException {
return get(variables(args));
}
public SetVariableResponse setVariableNow(SetVariableArguments args) throws ExecutionException {
return get(setVariable(args));
}
public SourceResponse sourceNow(SourceArguments args) throws ExecutionException {
return get(source(args));
}
public ThreadsResponse threadsNow() throws ExecutionException {
return get(threads());
}
public void terminateThreadsNow(TerminateThreadsArguments args) throws ExecutionException {
get(terminateThreads(args));
}
public ModulesResponse modulesNow(ModulesArguments args) throws ExecutionException {
return get(modules(args));
}
public LoadedSourcesResponse loadedSourcesNow(LoadedSourcesArguments args) throws ExecutionException {
return get(loadedSources(args));
}
public EvaluateResponse evaluateNow(EvaluateArguments args) throws ExecutionException {
return get(evaluate(args));
}
public SetExpressionResponse setExpressionNow(SetExpressionArguments args) throws ExecutionException {
return get(setExpression(args));
}
public StepInTargetsResponse stepInTargetsNow(StepInTargetsArguments args) throws ExecutionException {
return get(stepInTargets(args));
}
public GotoTargetsResponse gotoTargetsNow(GotoTargetsArguments args) throws ExecutionException {
return get(gotoTargets(args));
}
public CompletionsResponse completionsNow(CompletionsArguments args) throws ExecutionException {
return get(completions(args));
}
public ExceptionInfoResponse exceptionInfoNow(ExceptionInfoArguments args) throws ExecutionException {
return get(exceptionInfo(args));
}
public ReadMemoryResponse readMemoryNow(ReadMemoryArguments args) throws ExecutionException {
return get(readMemory(args));
}
public WriteMemoryResponse writeMemoryNow(WriteMemoryArguments args) throws ExecutionException {
return get(writeMemory(args));
}
public DisassembleResponse disassembleNow(DisassembleArguments args) throws ExecutionException {
return get(disassemble(args));
}
@Override
@JsonRequest
public CompletableFuture<Void> cancel(CancelArguments args) {
return server.cancel(args);
}
@Override
@JsonRequest
public CompletableFuture<Capabilities> initialize(InitializeRequestArguments args) {
return server.initialize(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> configurationDone(ConfigurationDoneArguments args) {
return server.configurationDone(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> launch(Map<String, Object> args) {
return server.launch(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> attach(Map<String, Object> args) {
return server.attach(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> restart(RestartArguments args) {
return server.restart(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> disconnect(DisconnectArguments args) {
return server.disconnect(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> terminate(TerminateArguments args) {
return server.terminate(args);
}
@Override
@JsonRequest
public CompletableFuture<BreakpointLocationsResponse> breakpointLocations(BreakpointLocationsArguments args) {
return server.breakpointLocations(args);
}
@Override
@JsonRequest
public CompletableFuture<SetBreakpointsResponse> setBreakpoints(SetBreakpointsArguments args) {
return server.setBreakpoints(args);
}
@Override
@JsonRequest
public CompletableFuture<SetFunctionBreakpointsResponse> setFunctionBreakpoints(SetFunctionBreakpointsArguments args) {
return server.setFunctionBreakpoints(args);
}
@Override
@JsonRequest
public CompletableFuture<SetExceptionBreakpointsResponse> setExceptionBreakpoints(SetExceptionBreakpointsArguments args) {
return server.setExceptionBreakpoints(args);
}
@Override
@JsonRequest
public CompletableFuture<DataBreakpointInfoResponse> dataBreakpointInfo(DataBreakpointInfoArguments args) {
return server.dataBreakpointInfo(args);
}
@Override
@JsonRequest
public CompletableFuture<SetDataBreakpointsResponse> setDataBreakpoints(SetDataBreakpointsArguments args) {
return server.setDataBreakpoints(args);
}
@Override
@JsonRequest
public CompletableFuture<SetInstructionBreakpointsResponse> setInstructionBreakpoints(SetInstructionBreakpointsArguments args) {
return server.setInstructionBreakpoints(args);
}
@Override
@JsonRequest("continue")
public CompletableFuture<ContinueResponse> continue_(ContinueArguments args) {
return server.continue_(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> next(NextArguments args) {
return server.next(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> stepIn(StepInArguments args) {
return server.stepIn(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> stepOut(StepOutArguments args) {
return server.stepOut(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> stepBack(StepBackArguments args) {
return server.stepBack(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> reverseContinue(ReverseContinueArguments args) {
return server.reverseContinue(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> restartFrame(RestartFrameArguments args) {
return server.restartFrame(args);
}
@Override
@JsonRequest("goto")
public CompletableFuture<Void> goto_(GotoArguments args) {
return server.goto_(args);
}
@Override
@JsonRequest
public CompletableFuture<Void> pause(PauseArguments args) {
return server.pause(args);
}
@Override
@JsonRequest
public CompletableFuture<StackTraceResponse> stackTrace(StackTraceArguments args) {
return server.stackTrace(args);
}
@Override
@JsonRequest
public CompletableFuture<ScopesResponse> scopes(ScopesArguments args) {
return server.scopes(args);
}
@Override
@JsonRequest
public CompletableFuture<VariablesResponse> variables(VariablesArguments args) {
return server.variables(args);
}
@Override
@JsonRequest
public CompletableFuture<SetVariableResponse> setVariable(SetVariableArguments args) {
return server.setVariable(args);
}
@Override
@JsonRequest
public CompletableFuture<SourceResponse> source(SourceArguments args) {
return server.source(args);
}
@Override
@JsonRequest
public CompletableFuture<ThreadsResponse> threads() {
return server.threads();
}
@Override
@JsonRequest
public CompletableFuture<Void> terminateThreads(TerminateThreadsArguments args) {
return server.terminateThreads(args);
}
@Override
@JsonRequest
public CompletableFuture<ModulesResponse> modules(ModulesArguments args) {
return server.modules(args);
}
@Override
@JsonRequest
public CompletableFuture<LoadedSourcesResponse> loadedSources(LoadedSourcesArguments args) {
return server.loadedSources(args);
}
@Override
@JsonRequest
public CompletableFuture<EvaluateResponse> evaluate(EvaluateArguments args) {
return server.evaluate(args);
}
@Override
@JsonRequest
public CompletableFuture<SetExpressionResponse> setExpression(SetExpressionArguments args) {
return server.setExpression(args);
}
@Override
@JsonRequest
public CompletableFuture<StepInTargetsResponse> stepInTargets(StepInTargetsArguments args) {
return server.stepInTargets(args);
}
@Override
@JsonRequest
public CompletableFuture<GotoTargetsResponse> gotoTargets(GotoTargetsArguments args) {
return server.gotoTargets(args);
}
@Override
@JsonRequest
public CompletableFuture<CompletionsResponse> completions(CompletionsArguments args) {
return server.completions(args);
}
@Override
@JsonRequest
public CompletableFuture<ExceptionInfoResponse> exceptionInfo(ExceptionInfoArguments args) {
return server.exceptionInfo(args);
}
@Override
@JsonRequest
public CompletableFuture<ReadMemoryResponse> readMemory(ReadMemoryArguments args) {
return server.readMemory(args);
}
@Override
@JsonRequest
public CompletableFuture<WriteMemoryResponse> writeMemory(WriteMemoryArguments args) {
return server.writeMemory(args);
}
@Override
@JsonRequest
public CompletableFuture<DisassembleResponse> disassemble(DisassembleArguments args) {
return server.disassemble(args);
}
}

View file

@ -0,0 +1,89 @@
/*
* 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.debugger.runner.base;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.jetbrains.cidr.execution.Installer;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@RequiredArgsConstructor
public abstract class ZigDebugEmitBinaryInstaller<ProfileState extends ProfileStateBase<?>> implements Installer {
protected final String kind;
protected final ProfileState profileState;
protected final AbstractZigToolchain toolchain;
private File executableFile;
@Override
public @NotNull GeneralCommandLine install() throws ExecutionException {
val commandLine = profileState.getCommandLine(toolchain, true);
final Path tmpDir;
try {
tmpDir = Files.createTempDirectory("zigbrains_debug").toAbsolutePath();
} catch (IOException e) {
throw new ExecutionException("Failed to create temporary directory for " + kind + " binary", e);
}
val exe = tmpDir.resolve("executable").toFile();
commandLine.addParameters("-femit-bin=" + exe.getAbsolutePath());
val outputOpt = CLIUtil.execute(commandLine, Integer.MAX_VALUE);
if (outputOpt.isEmpty()) {
throw new ExecutionException("Failed to execute \"zig " + commandLine.getParametersList().getParametersString() + "\"!");
}
val output = outputOpt.get();
if (output.getExitCode() != 0) {
throw new ExecutionException("Zig compilation failed with exit code " + output.getExitCode() + "\nError output:\n" + output.getStdout() + "\n" + output.getStderr());
}
//Find our binary
try (val stream = Files.list(tmpDir)){
executableFile = stream.filter(file -> !file.getFileName().toString().endsWith(".o"))
.map(Path::toFile)
.filter(File::canExecute)
.findFirst()
.orElseThrow(() -> new IOException("No executable file present in temporary directory \"" +
tmpDir + "\""));
} catch (Exception e) {
throw new ExecutionException("Failed to find compiled binary", e);
}
//Construct new command line
val cfg = profileState.configuration();
val cli = new GeneralCommandLine().withExePath(executableFile.getAbsolutePath());
cfg.getWorkingDirectory().getPath().ifPresent(x -> cli.setWorkDirectory(x.toFile()));
cli.addParameters(getExeArgs());
cli.withCharset(StandardCharsets.UTF_8);
cli.withRedirectErrorStream(true);
return cli;
}
public abstract String[] getExeArgs();
@Override
public @NotNull File getExecutableFile() {
return executableFile;
}
}

View file

@ -14,10 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger.base; package com.falsepattern.zigbrains.zig.debugger.runner.base;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase; import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.intellij.util.system.CpuArch;
import com.jetbrains.cidr.ArchitectureType;
import com.jetbrains.cidr.execution.RunParameters; import com.jetbrains.cidr.execution.RunParameters;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration; import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -38,6 +40,6 @@ public abstract class ZigDebugParametersBase<ProfileState extends ProfileStateBa
@Override @Override
public @Nullable String getArchitectureId() { public @Nullable String getArchitectureId() {
return null; return ArchitectureType.forVmCpuArch(CpuArch.CURRENT).getId();
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger.base; package com.falsepattern.zigbrains.zig.debugger.runner.base;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase; import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.runconfig.ZigProgramRunnerBase; import com.falsepattern.zigbrains.project.runconfig.ZigProgramRunnerBase;

View file

@ -0,0 +1,54 @@
/*
* 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.debugger.runner.binary;
import com.falsepattern.zigbrains.project.execution.binary.ProfileStateBinary;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugParametersBase;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.TrivialInstaller;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public class ZigDebugParametersBinary extends ZigDebugParametersBase<ProfileStateBinary> {
public ZigDebugParametersBinary(DebuggerDriverConfiguration driverConfiguration, AbstractZigToolchain toolchain, ProfileStateBinary profileStateBinary) {
super(driverConfiguration, toolchain, profileStateBinary);
}
@Override
public @NotNull Installer getInstaller() {
return new Installer() {
private File executableFile;
@Override
public @NotNull GeneralCommandLine install() throws ExecutionException {
val cli = profileState.getCommandLine(toolchain, true);
executableFile = profileState.configuration().getExePath().getPathOrThrow().toFile();
return cli;
}
@Override
public @NotNull File getExecutableFile() {
return executableFile;
}
};
}
}

View file

@ -0,0 +1,61 @@
/*
* 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.debugger.runner.binary;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.binary.ProfileStateBinary;
import com.falsepattern.zigbrains.project.execution.binary.ZigExecConfigBinary;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugRunnerBase;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ZigDebugRunnerBinary extends ZigDebugRunnerBase<ProfileStateBinary> {
@Override
public @NotNull @NonNls String getRunnerId() {
return "ZigDebugRunnerBinary";
}
@Override
public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
return this.executorId.equals(executorId) &&
(profile instanceof ZigExecConfigBinary);
}
@Override
protected @Nullable ZigDebugParametersBase<ProfileStateBinary> getDebugParameters(ProfileStateBinary profileStateBinary, ExecutionEnvironment environment, DebuggerDriverConfiguration debuggerDriver, AbstractZigToolchain toolchain$) {
if (!(toolchain$ instanceof LocalZigToolchain toolchain)) {
Notifications.Bus.notify(new Notification("ZigBrains.Debugger.Error", "The debugger only supports local zig toolchains!", NotificationType.ERROR));
return null;
}
return new ZigDebugParametersBinary(debuggerDriver, toolchain, profileStateBinary);
}
@Override
protected @Nullable ProfileStateBinary castProfileState(ProfileStateBase<?> state) {
return state instanceof ProfileStateBinary state$ ? state$ : null;
}
}

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.zig.debugger.runner.build;
import com.falsepattern.zigbrains.project.execution.build.ProfileStateBuild;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugParametersBase;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class ZigDebugParametersBuild extends ZigDebugParametersBase<ProfileStateBuild> {
public ZigDebugParametersBuild(DebuggerDriverConfiguration driverConfiguration, AbstractZigToolchain toolchain, ProfileStateBuild profileStateBuild) {
super(driverConfiguration, toolchain, profileStateBuild);
}
@Override
public @NotNull Installer getInstaller() {
return new Installer() {
private File executableFile;
@Override
public @NotNull GeneralCommandLine install() throws ExecutionException {
val exePath = profileState.configuration().getExePath().getPath();
if (exePath.isEmpty()) {
throw new ExecutionException("Please specify the output exe path to debug \"zig build\" tasks!");
}
Path exe = exePath.get();
val commandLine = profileState.getCommandLine(toolchain, true);
val outputOpt = CLIUtil.execute(commandLine, Integer.MAX_VALUE);
if (outputOpt.isEmpty()) {
throw new ExecutionException("Failed to execute \"zig " + commandLine.getParametersList().getParametersString() + "\"!");
}
val output = outputOpt.get();
if (output.getExitCode() != 0) {
throw new ExecutionException("Zig compilation failed with exit code " + output.getExitCode() + "\nError output:\n" + output.getStdout() + "\n" + output.getStderr());
}
if (!Files.exists(exe) || !Files.isExecutable(exe)) {
throw new ExecutionException("File " + exe + " does not exist or is not executable!");
}
executableFile = exe.toFile();
//Construct new command line
val cfg = profileState.configuration();
val cli = new GeneralCommandLine().withExePath(executableFile.getAbsolutePath());
cfg.getWorkingDirectory().getPath().ifPresent(x -> cli.setWorkDirectory(x.toFile()));
cli.withCharset(StandardCharsets.UTF_8);
cli.withRedirectErrorStream(true);
return cli;
}
@Override
public @NotNull File getExecutableFile() {
return executableFile;
}
};
}
}

View file

@ -0,0 +1,61 @@
/*
* 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.debugger.runner.build;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.build.ProfileStateBuild;
import com.falsepattern.zigbrains.project.execution.build.ZigExecConfigBuild;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugRunnerBase;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ZigDebugRunnerBuild extends ZigDebugRunnerBase<ProfileStateBuild> {
@Override
public @NotNull @NonNls String getRunnerId() {
return "ZigDebugRunnerBuild";
}
@Override
public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
return this.executorId.equals(executorId) &&
(profile instanceof ZigExecConfigBuild);
}
@Override
protected @Nullable ZigDebugParametersBase<ProfileStateBuild> getDebugParameters(ProfileStateBuild profileStateBuild, ExecutionEnvironment environment, DebuggerDriverConfiguration debuggerDriver, AbstractZigToolchain toolchain$) {
if (!(toolchain$ instanceof LocalZigToolchain toolchain)) {
Notifications.Bus.notify(new Notification("ZigBrains.Debugger.Error", "The debugger only supports local zig toolchains!", NotificationType.ERROR));
return null;
}
return new ZigDebugParametersBuild(debuggerDriver, toolchain, profileStateBuild);
}
@Override
protected @Nullable ProfileStateBuild castProfileState(ProfileStateBase<?> state) {
return state instanceof ProfileStateBuild state$ ? state$ : null;
}
}

View file

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger.run; package com.falsepattern.zigbrains.zig.debugger.runner.run;
import com.falsepattern.zigbrains.project.execution.run.ProfileStateRun; import com.falsepattern.zigbrains.project.execution.run.ProfileStateRun;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugParametersBase; import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugEmitBinaryInstaller;
import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugParametersBase;
import com.jetbrains.cidr.execution.Installer; import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.TrivialInstaller;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration; import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -31,6 +31,11 @@ public class ZigDebugParametersRun extends ZigDebugParametersBase<ProfileStateRu
@Override @Override
public @NotNull Installer getInstaller() { public @NotNull Installer getInstaller() {
return new TrivialInstaller(profileState.getCommandLine(toolchain)); return new ZigDebugEmitBinaryInstaller<>("run", profileState, toolchain) {
@Override
public String[] getExeArgs() {
return profileState.configuration().getExeArgs().args;
}
};
} }
} }

View file

@ -14,14 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger.run; package com.falsepattern.zigbrains.zig.debugger.runner.run;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase; import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.run.ProfileStateRun; import com.falsepattern.zigbrains.project.execution.run.ProfileStateRun;
import com.falsepattern.zigbrains.project.execution.run.ZigExecConfigRun; import com.falsepattern.zigbrains.project.execution.run.ZigExecConfigRun;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain; import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugRunnerBase; import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugRunnerBase;
import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.notification.Notification; import com.intellij.notification.Notification;

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.debugger.runner.test;
import com.falsepattern.zigbrains.project.execution.test.ProfileStateTest;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugEmitBinaryInstaller;
import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugParametersBase;
import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import org.jetbrains.annotations.NotNull;
public class ZigDebugParametersTest extends ZigDebugParametersBase<ProfileStateTest> {
public ZigDebugParametersTest(DebuggerDriverConfiguration driverConfiguration, AbstractZigToolchain toolchain, ProfileStateTest profileState) {
super(driverConfiguration, toolchain, profileState);
}
@Override
public @NotNull Installer getInstaller() {
return new ZigDebugEmitBinaryInstaller<>("test", profileState, toolchain) {
@Override
public String[] getExeArgs() {
return new String[0];
}
};
}
}

View file

@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package com.falsepattern.zigbrains.zig.debugger.test; package com.falsepattern.zigbrains.zig.debugger.runner.test;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase; import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.test.ProfileStateTest; import com.falsepattern.zigbrains.project.execution.test.ProfileStateTest;
import com.falsepattern.zigbrains.project.execution.test.ZigExecConfigTest; import com.falsepattern.zigbrains.project.execution.test.ZigExecConfigTest;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain; import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugParametersBase; import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugRunnerBase; import com.falsepattern.zigbrains.zig.debugger.runner.base.ZigDebugRunnerBase;
import com.intellij.execution.configurations.RunProfile; import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.notification.Notification; import com.intellij.notification.Notification;

View file

@ -1,100 +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.debugger.test;
import com.falsepattern.zigbrains.project.execution.test.ProfileStateTest;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugParametersBase;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessListener;
import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
import java.util.stream.Collectors;
public class ZigDebugParametersTest extends ZigDebugParametersBase<ProfileStateTest> {
public ZigDebugParametersTest(DebuggerDriverConfiguration driverConfiguration, AbstractZigToolchain toolchain, ProfileStateTest profileState) {
super(driverConfiguration, toolchain, profileState);
}
@Override
public @NotNull Installer getInstaller() {
return new ZigTestInstaller();
}
private class ZigTestInstaller implements Installer {
private File executableFile;
@Override
public @NotNull GeneralCommandLine install() throws ExecutionException {
val commandLine = profileState.getCommandLine(toolchain);
final Path tmpDir;
try {
tmpDir = Files.createTempDirectory("zigbrains_debug").toAbsolutePath();
} catch (IOException e) {
throw new ExecutionException("Failed to create temporary directory for test binary", e);
}
val exe = tmpDir.resolve("executable").toFile();
commandLine.addParameters("--test-no-exec", "-femit-bin=" + exe.getAbsolutePath());
val outputOpt = CLIUtil.execute(commandLine, Integer.MAX_VALUE);
if (outputOpt.isEmpty()) {
throw new ExecutionException("Failed to start \"zig test\"!");
}
val output = outputOpt.get();
if (output.getExitCode() != 0) {
throw new ExecutionException("Zig test compilation failed with exit code " + output.getExitCode() + "\nError output:\n" + output.getStdout() + "\n" + output.getStderr());
}
//Find our binary
try (val stream = Files.list(tmpDir)){
executableFile = stream.filter(file -> !file.getFileName().toString().endsWith(".o"))
.map(Path::toFile)
.filter(File::canExecute)
.findFirst()
.orElseThrow(() -> new IOException("No executable file present in temporary directory \"" +
tmpDir + "\""));
} catch (Exception e) {
throw new ExecutionException("Failed to find compiled test binary", e);
}
//Construct new command line
val cfg = profileState.configuration();
val cli = new GeneralCommandLine().withExePath(executableFile.getAbsolutePath());
if (cfg.workingDirectory != null) {
cli.withWorkDirectory(cfg.workingDirectory.toString());
}
cli.withCharset(StandardCharsets.UTF_8);
cli.withRedirectErrorStream(true);
return cli;
}
@Override
public @NotNull File getExecutableFile() {
return executableFile;
}
}
}

View file

@ -0,0 +1,169 @@
/*
* 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.debugger.win;
import com.falsepattern.zigbrains.zig.debugger.dap.DAPDebuggerDriverConfiguration;
import com.falsepattern.zigbrains.zig.debugger.dap.DAPDriver;
import com.falsepattern.zigbrains.zig.debugger.dap.WrappedDebugServer;
import com.intellij.execution.ExecutionException;
import com.intellij.util.system.CpuArch;
import com.jetbrains.cidr.ArchitectureType;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerCommandException;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.eclipse.lsp4j.debug.Capabilities;
import org.eclipse.lsp4j.debug.OutputEventArguments;
import org.eclipse.lsp4j.debug.services.IDebugProtocolServer;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.jsonrpc.MessageIssueException;
import org.eclipse.lsp4j.jsonrpc.debug.messages.DebugResponseMessage;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
import org.eclipse.lsp4j.util.ToStringBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import java.util.concurrent.CompletableFuture;
import java.util.zip.Inflater;
public class WinDAPDriver extends DAPDriver<
IDebugProtocolServer,
WrappedDebugServer<IDebugProtocolServer>,
WinDAPDriver.WinDAPDebuggerClient
> {
private final CompletableFuture<HandshakeResponse> handshakeFuture = new CompletableFuture<>();
public WinDAPDriver(@NotNull Handler handler, WinDebuggerDriverConfiguration config) throws ExecutionException {
super(handler, config);
DAPDriver$postConstructor();
}
@Override
public void DAPDriver$postConstructor$invoke() {
}
@Override
protected MessageConsumer wrapMessageConsumer(MessageConsumer messageConsumer) {
return new MessageConsumer() {
private boolean verifyHandshake = true;
@Override
public void consume(Message message) throws MessageIssueException, JsonRpcException {
if (verifyHandshake && message instanceof DebugResponseMessage res && res.getMethod().equals("handshake")) {
verifyHandshake = false;
res.setResponseId(1);
}
messageConsumer.consume(message);
}
};
}
@Override
protected Class<IDebugProtocolServer> getServerInterface() {
return IDebugProtocolServer.class;
}
@Override
protected WrappedDebugServer<IDebugProtocolServer> wrapDebugServer(IDebugProtocolServer remoteProxy) {
return new WrappedDebugServer<>(remoteProxy);
}
@Override
protected WinDAPDebuggerClient createDebuggerClient() {
return this.new WinDAPDebuggerClient();
}
@Override
protected CompletableFuture<?> wrapInitialize(CompletableFuture<Capabilities> capabilitiesCompletableFuture) {
return capabilitiesCompletableFuture.thenCombine(handshakeFuture, (res, hs) -> res);
}
@SuppressWarnings("unused")
protected class WinDAPDebuggerClient extends DAPDebuggerClient {
@Override
public void output(OutputEventArguments args) {
if ("telemetry".equals(args.getCategory())) {
return;
}
super.output(args);
}
@JsonRequest
public CompletableFuture<HandshakeResponse> handshake(HandshakeRequest handshake) {
return CompletableFuture.supplyAsync(() -> {
try {
val hasher = MessageDigest.getInstance("SHA-256");
hasher.update(handshake.getValue().getBytes(StandardCharsets.UTF_8));
var inflater = new Inflater(true);
val coconut = DAPDebuggerClient.class.getResourceAsStream("/coconut.jpg").readAllBytes();
inflater.setInput(coconut, coconut.length - 80, 77);
inflater.finished();
var b = new byte[1];
while (inflater.inflate(b) > 0) {
hasher.update(b);
}
return new HandshakeResponse(new String(coconut, coconut.length - 3, 3) + Base64.getEncoder().encodeToString(hasher.digest()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}).thenApply(handshakeResponse -> {
handshakeFuture.complete(handshakeResponse);
return handshakeResponse;
});
}
}
@Override
public @Nullable String getArchitecture() throws ExecutionException {
return ArchitectureType.forVmCpuArch(CpuArch.CURRENT).getId();
}
@Data
@NoArgsConstructor
public static class HandshakeRequest {
@NonNull
private String value;
@Override
public String toString() {
val b = new ToStringBuilder(this);
b.add("value", this.value);
return b.toString();
}
}
@Data
@NoArgsConstructor
@RequiredArgsConstructor
public static class HandshakeResponse {
@NonNull
private String signature;
@Override
public String toString() {
val b = new ToStringBuilder(this);
b.add("signature", this.signature);
return b.toString();
}
}
}

View file

@ -0,0 +1,139 @@
/*
* 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.debugger.win;
import com.falsepattern.zigbrains.zig.debugger.win.config.WinDebuggerConfigService;
import com.falsepattern.zigbrains.zig.debugger.dap.DAPDebuggerDriverConfiguration;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.jetbrains.cidr.ArchitectureType;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver;
import lombok.val;
import org.apache.commons.io.file.PathUtils;
import org.eclipse.lsp4j.debug.InitializeRequestArguments;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.lang.ref.Cleaner;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Map;
public class WinDebuggerDriverConfiguration extends DAPDebuggerDriverConfiguration {
private static void extractVSDebugger(Path pathToPluginFile, Path pathToExtracted) throws IOException {
if (pathToPluginFile == null) {
throw new IllegalArgumentException("Please set the debugger inside Build, Execution, Deployment | Debugger | Zig (Windows)");
}
if (!Files.isRegularFile(pathToPluginFile) || !pathToPluginFile.getFileName().toString().endsWith(".vsix")) {
throw new IllegalArgumentException("Invalid debugger file path! Please check Build, Execution, Deployment | Debugger | Zig (Windows) again! The file MUST be a .vsix file!");
}
URI uri;
try {
uri = new URI("jar:" + pathToPluginFile.toAbsolutePath().toUri().toASCIIString());
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Could not parse plugin file path: " + pathToPluginFile);
}
try (val fs = FileSystems.newFileSystem(uri, Map.of())) {
val basePath = fs.getPath("/extension/debugAdapters/vsdbg/bin");
try (val walk = Files.walk(basePath)) {
walk.forEach(path -> {
if (!Files.isRegularFile(path))
return;
val relPath = Path.of(basePath.relativize(path).toString());
val resPath = pathToExtracted.resolve(relPath);
try {
Files.createDirectories(resPath.getParent());
Files.copy(path, resPath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
}
@Override
public @NotNull GeneralCommandLine createDriverCommandLine(@NotNull DebuggerDriver debuggerDriver, @NotNull ArchitectureType architectureType) {
val pathToPluginFileStr = WinDebuggerConfigService.getInstance().cppToolsPath;
if (pathToPluginFileStr == null || pathToPluginFileStr.isBlank()) {
throw new IllegalArgumentException("Please set the debugger inside Build, Execution, Deployment | Debugger | Zig (Windows)");
}
Path pathToPluginFile;
try {
pathToPluginFile = Path.of(pathToPluginFileStr);
} catch (InvalidPathException e) {
throw new IllegalArgumentException("Invalid debugger path " + pathToPluginFileStr + "! Make sure it points to the .vsix file!");
}
Path pathToExtracted;
try {
val tmpDir = Path.of(System.getProperty("java.io.tmpdir"));
int i = 0;
do {
pathToExtracted = tmpDir.resolve("zb-windbg-" + i++);
} while (Files.exists(pathToExtracted.resolve(".lock")) || Files.isRegularFile(pathToExtracted));
if (Files.exists(pathToExtracted)) {
PathUtils.deleteDirectory(pathToExtracted);
}
extractVSDebugger(pathToPluginFile, pathToExtracted);
} catch (IOException e) {
throw new RuntimeException(e);
}
Path finalPathToExtracted = pathToExtracted;
val lockFile = finalPathToExtracted.resolve(".lock");
try {
Files.createFile(lockFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
Cleaner.create().register(debuggerDriver, () -> {
try {
Files.delete(lockFile);
} catch (IOException e) {
e.printStackTrace();
}
});
val cli = new GeneralCommandLine();
cli.setExePath(finalPathToExtracted.resolve("vsdbg.exe").toString());
cli.setCharset(StandardCharsets.UTF_8);
cli.addParameters("--interpreter=vscode", "--extConfigDir=%USERPROFILE%\\.cppvsdbg\\extensions");
cli.setWorkDirectory(finalPathToExtracted.toString());
return cli;
}
@Override
public @NotNull String getDriverName() {
return "WinDAPDriver";
}
@Override
public @NotNull DebuggerDriver createDriver(DebuggerDriver.@NotNull Handler handler, @NotNull ArchitectureType architectureType)
throws ExecutionException {
return new WinDAPDriver(handler, this);
}
@Override
public void customizeInitializeArguments(InitializeRequestArguments initArgs) {
initArgs.setPathFormat("path");
initArgs.setAdapterID("cppvsdbg");
}
}

View file

@ -0,0 +1,46 @@
/*
* 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.debugger.win.config;
import com.intellij.openapi.application.ApplicationManager;
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.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull;
@Service(Service.Level.APP)
@State(name = "CPPToolsSettings",
storages = @Storage("zigbrains.xml"))
public final class WinDebuggerConfigService implements PersistentStateComponent<WinDebuggerConfigService> {
public String cppToolsPath = "";
public static WinDebuggerConfigService getInstance() {
return ApplicationManager.getApplication().getService(WinDebuggerConfigService.class);
}
@Override
public @NotNull WinDebuggerConfigService getState() {
return this;
}
@Override
public void loadState(@NotNull WinDebuggerConfigService state) {
XmlSerializerUtil.copyBean(state, this);
}
}

View file

@ -0,0 +1,89 @@
/*
* 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.debugger.win.config;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.ui.JBColor;
import com.intellij.util.system.OS;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import javax.swing.JComponent;
import java.util.Objects;
import static com.intellij.ui.dsl.builder.BuilderKt.panel;
public class WinDebuggerConfigurable implements Configurable {
private WinDebuggerSettingsPanel settingsPanel;
@Override
public @NlsContexts.ConfigurableName String getDisplayName() {
return "Zig Debugger (Windows)";
}
@Override
public @Nullable JComponent createComponent() {
settingsPanel = new WinDebuggerSettingsPanel();
return panel((p) -> {
if (OS.CURRENT != OS.Windows) {
p.row("This menu has no effect on linux/macos/non-windows systems, use the C++ toolchains (see plugin description).", (r) -> null);
p.row("For completeness' sake, here is what you would need to configure on Windows:", (r) -> null);
p.separator(JBColor.foreground());
}
settingsPanel.attachPanelTo(p);
return null;
});
}
@Override
public boolean isModified() {
if (settingsPanel == null)
return false;
var cppSettings = WinDebuggerConfigService.getInstance();
var settingsData = settingsPanel.getData();
return !Objects.equals(settingsData.cppToolsPath(), cppSettings.cppToolsPath);
}
@Override
public void apply() throws ConfigurationException {
if (settingsPanel == null)
return;
var cppSettings = WinDebuggerConfigService.getInstance();
var settingsData = settingsPanel.getData();
cppSettings.cppToolsPath = settingsData.cppToolsPath();
}
@Override
public void reset() {
if (settingsPanel == null)
return;
val cppSettings = WinDebuggerConfigService.getInstance();
settingsPanel.setData(new WinDebuggerSettingsPanel.SettingsData(cppSettings.cppToolsPath));
}
@Override
public void disposeUIResources() {
if (settingsPanel == null)
return;
Disposer.dispose(settingsPanel);
settingsPanel = null;
}
}

View file

@ -0,0 +1,99 @@
/*
* 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.debugger.win.config;
import com.falsepattern.zigbrains.common.util.StringUtil;
import com.falsepattern.zigbrains.common.util.TextFieldUtil;
import com.falsepattern.zigbrains.project.openapi.MyDisposable;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.ui.dsl.builder.AlignY;
import com.intellij.ui.dsl.builder.Panel;
import lombok.Getter;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import javax.swing.JLabel;
import java.util.Optional;
public class WinDebuggerSettingsPanel implements MyDisposable {
@Getter
private boolean disposed = false;
public record SettingsData(@Nullable String cppToolsPath) {}
@SuppressWarnings("DialogTitleCapitalization")
private final TextFieldWithBrowseButton pathToArchive = TextFieldUtil.pathToFileTextField(this,
"Path to \"cpptools-win**.vsix\"",
() -> {});
public SettingsData getData() {
return new SettingsData(StringUtil.blankToNull(pathToArchive.getText()));
}
public void setData(SettingsData value) {
pathToArchive.setText(Optional.ofNullable(value.cppToolsPath()).orElse(""));
}
private static HyperlinkLabel link(String url) {
val href = new HyperlinkLabel(url);
href.setHyperlinkTarget(url);
return href;
}
public void attachPanelTo(Panel panel) {
panel.panel(p -> {
p.row("Debugging Zig on Windows requires you to manually install the MSVC toolchain.",
(r) -> null);
p.row("To install the MSVC debugger, follow setup 3 under Prerequisites on the following page:",
(r) -> null);
return null;
});
panel.panel(p -> {
p.row((JLabel) null, (r) -> {
r.cell(link("https://code.visualstudio.com/docs/cpp/config-msvc"));
return null;
});
return null;
});
panel.panel(p -> {
p.row("After you've installed MSVC, you also need download the vscode plugin with the debugger adapter.", (r) -> null);
p.row("Latest known working version: 1.19.6. Newer versions may or may not work.", (r) -> null);
return null;
});
panel.panel(p -> {
p.row("You can download the latest version here:", (r) -> {
r.cell(link("https://github.com/microsoft/vscode-cpptools/releases"));
return null;
});
p.row("Put the path to the downloaded file here:", (r) -> {
r.cell(pathToArchive).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
return null;
});
}
@Override
public void dispose() {
disposed = true;
Disposer.dispose(pathToArchive);
}
}

View file

@ -19,10 +19,14 @@
<resource-bundle>zigbrains.zig.debugger.Bundle</resource-bundle> <resource-bundle>zigbrains.zig.debugger.Bundle</resource-bundle>
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<programRunner implementation="com.falsepattern.zigbrains.zig.debugger.run.ZigDebugRunnerRun" <programRunner implementation="com.falsepattern.zigbrains.zig.debugger.runner.run.ZigDebugRunnerRun"
id="ZigDebugRunnerRun"/> id="ZigDebugRunnerRun"/>
<programRunner implementation="com.falsepattern.zigbrains.zig.debugger.test.ZigDebugRunnerTest" <programRunner implementation="com.falsepattern.zigbrains.zig.debugger.runner.test.ZigDebugRunnerTest"
id="ZigDebugRunnerTest"/> id="ZigDebugRunnerTest"/>
<programRunner implementation="com.falsepattern.zigbrains.zig.debugger.runner.build.ZigDebugRunnerBuild"
id="ZigDebugRunnerBuild"/>
<programRunner implementation="com.falsepattern.zigbrains.zig.debugger.runner.binary.ZigDebugRunnerBinary"
id="ZigDebugRunnerBinary"/>
<notificationGroup displayType="BALLOON" <notificationGroup displayType="BALLOON"
bundle="zigbrains.zig.debugger.Bundle" bundle="zigbrains.zig.debugger.Bundle"
@ -32,6 +36,9 @@
bundle="zigbrains.zig.debugger.Bundle" bundle="zigbrains.zig.debugger.Bundle"
key="notif-debug-warn" key="notif-debug-warn"
id="ZigBrains.Debugger.Warn"/> id="ZigBrains.Debugger.Warn"/>
<applicationConfigurable parentId="project.propDebugger"
instance="com.falsepattern.zigbrains.zig.debugger.win.config.WinDebuggerConfigurable"
displayName="Zig (Windows)"/>
</extensions> </extensions>
<extensions defaultExtensionNs="cidr.debugger"> <extensions defaultExtensionNs="cidr.debugger">

View file

@ -40,7 +40,7 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
@Nullable @Nullable
private ProcessBuilder builder; private ProcessBuilder builder;
@Nullable @Nullable
private Process process = null; protected Process process = null;
private List<String> commands; private List<String> commands;
private String workingDir; private String workingDir;
@ -55,7 +55,7 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
} }
public void start() throws IOException { public void start() throws IOException {
if ((workingDir == null || commands == null || commands.isEmpty() || commands.contains(null)) && builder == null) { if ((workingDir == null || commands == null || commands.isEmpty()) && builder == null) {
throw new IOException("Unable to start language server: " + this.toString()); throw new IOException("Unable to start language server: " + this.toString());
} }
ProcessBuilder builder = createProcessBuilder(); ProcessBuilder builder = createProcessBuilder();
@ -86,6 +86,12 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
return process != null ? process.getInputStream() : null; return process != null ? process.getInputStream() : null;
} }
@Nullable
@Override
public InputStream getErrorStream() {
return process != null ? process.getErrorStream() : null;
}
@Nullable @Nullable
@Override @Override
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
@ -98,6 +104,12 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
} }
} }
public void onExit(Runnable runnable) {
if (process != null) {
process.onExit().thenRun(runnable);
}
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof ProcessStreamConnectionProvider) { if (obj instanceof ProcessStreamConnectionProvider) {

View file

@ -15,6 +15,7 @@
*/ */
package com.falsepattern.zigbrains.lsp.common.connection; package com.falsepattern.zigbrains.lsp.common.connection;
import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -25,6 +26,10 @@ public interface StreamConnectionProvider {
InputStream getInputStream(); InputStream getInputStream();
default @Nullable InputStream getErrorStream() {
return null;
}
OutputStream getOutputStream(); OutputStream getOutputStream();
void stop(); void stop();

View file

@ -131,8 +131,7 @@ import static com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus.
* The implementation of a LanguageServerWrapper (specific to a serverDefinition and a project) * The implementation of a LanguageServerWrapper (specific to a serverDefinition and a project)
*/ */
public class LanguageServerWrapper { public class LanguageServerWrapper {
public final LanguageServerDefinition serverDefinition;
public LanguageServerDefinition serverDefinition;
private final LSPExtensionManager extManager; private final LSPExtensionManager extManager;
private final Project project; private final Project project;
private final HashSet<Editor> toConnect = new HashSet<>(); private final HashSet<Editor> toConnect = new HashSet<>();

View file

@ -17,57 +17,64 @@
package com.falsepattern.zigbrains.project.console; package com.falsepattern.zigbrains.project.console;
import com.falsepattern.zigbrains.common.util.FileUtil; import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.project.openapi.module.ZigModuleType;
import com.intellij.execution.filters.Filter; import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.OpenFileHyperlinkInfo; import com.intellij.execution.filters.OpenFileHyperlinkInfo;
import com.intellij.ide.impl.ProjectUtil;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import kotlin.Pair;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ZigSourceFileFilter implements Filter { public class ZigSourceFileFilter implements Filter {
private final Project project; private final Project project;
private final Pattern LEN_REGEX = Pattern.compile(":(\\d+):(\\d+)");
private Pair<Path, Integer> findLongestParsablePathFromOffset(String line, int end, String projectPath) {
int longestStart = -1;
Path longest = null;
for (int i = end - 1; i >= 0; i--) {
try {
val pathStr = line.substring(i, end);
var path = Path.of(pathStr);
if (!Files.exists(path) || !Files.isRegularFile(path)) {
path = Path.of(projectPath, pathStr);
if (!Files.exists(path) || !Files.isRegularFile(path))
continue;
}
longest = path;
longestStart = i;
} catch (InvalidPathException ignored){}
}
return new Pair<>(longest, longestStart);
}
@Nullable @Nullable
@Override @Override
public Result applyFilter(@NotNull String line, int entireLength) { public Result applyFilter(@NotNull String line, int entireLength) {
val lineStart = entireLength - line.length(); val lineStart = entireLength - line.length();
int splitA, splitB, splitC; val projectPath = project.getBasePath();
splitA = line.indexOf(':'); val results = new ArrayList<ResultItem>();
if (splitA < 0) val matcher = LEN_REGEX.matcher(line);
return null; while (matcher.find()) {
splitB = line.indexOf(':', splitA + 1); val end = matcher.start();
if (splitB < 0) val pair = findLongestParsablePathFromOffset(line, end, projectPath);
return null; val path = pair.getFirst();
splitC = line.indexOf(':', splitB + 1); if (path == null)
if (splitC < 0)
return null;
final int lineNumber, lineOffset;
try {
lineNumber = Math.max(Integer.parseInt(line, splitA + 1, splitB, 10) - 1, 0);
lineOffset = Math.max(Integer.parseInt(line, splitB + 1, splitC, 10) - 1, 0);
} catch (NumberFormatException ignored) {
return null;
}
val pathStr = line.substring(0, splitA);
var path = Path.of(pathStr);
if (!Files.exists(path) || !Files.isRegularFile(path)) {
val projectPath = project.getBasePath();
if (projectPath == null)
return null;
path = Path.of(projectPath, pathStr);
if (!Files.exists(path) || !Files.isRegularFile(path))
return null; return null;
val lineNumber = Math.max(Integer.parseInt(matcher.group(1)) - 1, 0);
val lineOffset = Math.max(Integer.parseInt(matcher.group(2)) - 1, 0);
val file = FileUtil.virtualFileFromURI(path.toUri());
results.add(new ResultItem(lineStart + pair.getSecond(), lineStart + matcher.end(), new OpenFileHyperlinkInfo(project, file, lineNumber, lineOffset)));
} }
val file = FileUtil.virtualFileFromURI(path.toUri()); return new Result(results);
return new Result(lineStart, lineStart + splitC, new OpenFileHyperlinkInfo(project, file, lineNumber, lineOffset));
} }
} }

View file

@ -27,6 +27,8 @@ import com.intellij.psi.PsiElement;
import lombok.val; import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends LazyRunConfigurationProducer<T> { public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends LazyRunConfigurationProducer<T> {
@NotNull @NotNull
@Override @Override
@ -47,7 +49,7 @@ public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends
return false; return false;
} }
var theFile = psiFile.getVirtualFile(); var theFile = psiFile.getVirtualFile();
var filePath = theFile.getPath(); var filePath = theFile.toNioPath();
return setupConfigurationFromContext(configuration, element, filePath, theFile); return setupConfigurationFromContext(configuration, element, filePath, theFile);
} }
@ -65,7 +67,7 @@ public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends
return false; return false;
} }
val vFile = file.getVirtualFile(); val vFile = file.getVirtualFile();
val filePath = vFile.getPath(); val filePath = vFile.toNioPath();
return isConfigurationFromContext(configuration, filePath, vFile, element); return isConfigurationFromContext(configuration, filePath, vFile, element);
} }
@ -95,6 +97,6 @@ public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends
} }
*/ */
protected abstract boolean setupConfigurationFromContext(@NotNull T configuration, PsiElement element, String filePath, VirtualFile theFile); protected abstract boolean setupConfigurationFromContext(@NotNull T configuration, PsiElement element, Path filePath, VirtualFile theFile);
protected abstract boolean isConfigurationFromContext(@NotNull T configuration, String filePath, VirtualFile vFile, PsiElement element); protected abstract boolean isConfigurationFromContext(@NotNull T configuration, Path filePath, VirtualFile vFile, PsiElement element);
} }

View file

@ -0,0 +1,24 @@
/*
* 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.execution.base;
public enum OptimizationLevel {
Debug,
ReleaseFast,
ReleaseSafe,
ReleaseSmall
}

View file

@ -16,7 +16,6 @@
package com.falsepattern.zigbrains.project.execution.base; package com.falsepattern.zigbrains.project.execution.base;
import com.falsepattern.zigbrains.project.execution.ZigCapturingProcessHandler;
import com.falsepattern.zigbrains.project.runconfig.ZigProcessHandler; import com.falsepattern.zigbrains.project.runconfig.ZigProcessHandler;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain; import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.ProjectUtil; import com.falsepattern.zigbrains.project.util.ProjectUtil;
@ -44,18 +43,20 @@ public abstract class ProfileStateBase<T extends ZigExecConfigBase<T>> extends C
@Override @Override
protected @NotNull ProcessHandler startProcess() throws ExecutionException { protected @NotNull ProcessHandler startProcess() throws ExecutionException {
return new ZigProcessHandler(getCommandLine(ProjectUtil.getToolchain(getEnvironment().getProject()))); return new ZigProcessHandler(getCommandLine(ProjectUtil.getToolchain(getEnvironment().getProject()), false));
} }
public GeneralCommandLine getCommandLine(AbstractZigToolchain toolchain) { public GeneralCommandLine getCommandLine(AbstractZigToolchain toolchain, boolean debug) throws ExecutionException {
val workingDirectory = configuration.workingDirectory; val workingDirectory = configuration.getWorkingDirectory();
val zigExecutablePath = toolchain.pathToExecutable("zig"); val zigExecutablePath = toolchain.pathToExecutable("zig");
return new GeneralCommandLine().withExePath(zigExecutablePath.toString()) val cli = new GeneralCommandLine();
.withWorkDirectory(workingDirectory.toString()) cli.setExePath(zigExecutablePath.toString());
.withCharset(StandardCharsets.UTF_8) workingDirectory.getPath().ifPresent(x -> cli.setWorkDirectory(x.toFile()));
.withRedirectErrorStream(true) cli.setCharset(StandardCharsets.UTF_8);
.withParameters(configuration.buildCommandLineArgs()); cli.setRedirectErrorStream(true);
cli.addParameters(debug ? configuration.buildDebugCommandLineArgs() : configuration.buildCommandLineArgs());
return cli;
} }
public T configuration() { public T configuration() {
@ -65,7 +66,7 @@ public abstract class ProfileStateBase<T extends ZigExecConfigBase<T>> extends C
public DefaultExecutionResult executeCommandLine(GeneralCommandLine commandLine, ExecutionEnvironment environment) public DefaultExecutionResult executeCommandLine(GeneralCommandLine commandLine, ExecutionEnvironment environment)
throws ExecutionException { throws ExecutionException {
val handler = startProcess(commandLine); val handler = startProcess(commandLine);
val console = new BuildTextConsoleView(environment.getProject(), true, Collections.emptyList()); val console = new BuildTextConsoleView(environment.getProject(), false, Collections.emptyList());
console.attachToProcess(handler); console.attachToProcess(handler);
return new DefaultExecutionResult(console, handler); return new DefaultExecutionResult(console, handler);
} }

View file

@ -18,137 +18,463 @@ package com.falsepattern.zigbrains.project.execution.base;
import com.falsepattern.zigbrains.project.ui.WorkingDirectoryComponent; import com.falsepattern.zigbrains.project.ui.WorkingDirectoryComponent;
import com.falsepattern.zigbrains.project.ui.ZigFilePathPanel; import com.falsepattern.zigbrains.project.ui.ZigFilePathPanel;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.falsepattern.zigbrains.project.util.ElementUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.ui.LabeledComponent; import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.dsl.builder.AlignX; import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.ui.dsl.builder.AlignY; import com.intellij.ui.dsl.builder.AlignY;
import com.intellij.ui.dsl.builder.Panel; import com.intellij.ui.dsl.builder.Panel;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.val; import lombok.val;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.JComponent; import javax.swing.JComponent;
import java.io.Serializable;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import static com.intellij.ui.dsl.builder.BuilderKt.panel; import static com.intellij.ui.dsl.builder.BuilderKt.panel;
public class ZigConfigEditor<T extends ZigExecConfigBase<T>> extends SettingsEditor<T> { public class ZigConfigEditor<T extends ZigExecConfigBase<T>> extends SettingsEditor<T> {
private final List<ZigConfigModule<T>> modules; private final ZigExecConfigBase<T> state;
private final List<ZigConfigurable.ZigConfigModule<?>> configModules = new ArrayList<>();
public ZigConfigEditor(List<ZigConfigModule<T>> modules) { public ZigConfigEditor(ZigExecConfigBase<T> state) {
this.modules = new ArrayList<>(modules); this.state = state;
} }
@Override @Override
protected void applyEditorTo(@NotNull T s) throws ConfigurationException { protected void applyEditorTo(@NotNull T s) throws ConfigurationException {
for (val module: modules) { try {
module.applyTo(s); outer:
for (val cfg : s.getConfigurables()) {
for (val module : configModules) {
if (module.tryApply(cfg))
continue outer;
}
System.err.println("EEE");
}
} catch (Exception e) {
e.printStackTrace();
throw e;
} }
} }
@Override @Override
protected void resetEditorFrom(@NotNull T s) { protected void resetEditorFrom(@NotNull T s) {
for (val module: modules) { outer:
module.resetFrom(s); for (val cfg: s.getConfigurables()) {
for (val module: configModules) {
if (module.tryReset(cfg))
continue outer;
}
} }
} }
@Override @Override
protected final @NotNull JComponent createEditor() { protected final @NotNull JComponent createEditor() {
configModules.clear();
configModules.addAll(state.getConfigurables().stream().map(ZigConfigurable::createEditor).toList());
return panel((p) -> { return panel((p) -> {
for (val module: modules) { for (val module: configModules) {
module.construct(p); module.construct(p);
} }
return null; return null;
}); });
} }
public interface ZigConfigModule<T> { @Override
void applyTo(@NotNull T s) throws ConfigurationException; protected void disposeEditor() {
void resetFrom(@NotNull T s); for (val module: configModules) {
void construct(Panel p); module.dispose();
}
configModules.clear();
} }
public static class WorkingDirectoryModule<T extends ZigExecConfigBase<T>> implements ZigConfigModule<T> { public interface ZigConfigurable<T extends ZigConfigurable<T>> extends Serializable, Cloneable {
protected final LabeledComponent<TextFieldWithBrowseButton> workingDirectoryComponent = void readExternal(@NotNull Element element);
new WorkingDirectoryComponent(); void writeExternal(@NotNull Element element);
ZigConfigModule<T> createEditor();
T clone();
@Override interface ZigConfigModule<T extends ZigConfigurable<T>> extends Disposable {
public void applyTo(@NotNull T s) { @Nullable T tryMatch(ZigConfigurable<?> cfg);
s.workingDirectory = Paths.get(workingDirectoryComponent.getComponent().getText()); void apply(T configurable) throws ConfigurationException;
} void reset(T configurable);
default boolean tryApply(ZigConfigurable<?> cfg) throws ConfigurationException {
val x = tryMatch(cfg);
if (x != null) {
apply(x);
return true;
}
return false;
}
@Override default boolean tryReset(ZigConfigurable<?> cfg) {
public void resetFrom(@NotNull T s) { val x = tryMatch(cfg);
workingDirectoryComponent.getComponent().setText(Objects.requireNonNullElse(s.workingDirectory, "").toString()); if (x != null) {
} reset(x);
return true;
@Override }
public void construct(Panel p) { return false;
p.row(workingDirectoryComponent.getLabel(), (r) -> { }
r.cell(workingDirectoryComponent).resizableColumn().align(AlignX.FILL).align(AlignY.FILL); void construct(Panel p);
return null;
});
} }
} }
public static class FilePathModule<T extends ZigExecConfigBase<T> & FilePathModule.Carrier> implements ZigConfigModule<T> { public static abstract class PathConfigurable<T extends PathConfigurable<T>> implements ZigConfigurable<T> {
private final ZigFilePathPanel filePathPanel = new ZigFilePathPanel(); private @Nullable Path path = null;
@Override public Optional<Path> getPath() {
public void applyTo(@NotNull T s) { return Optional.ofNullable(path);
s.setFilePath(filePathPanel.getText()); }
public @NotNull Path getPathOrThrow() {
return getPath().orElseThrow(() -> new IllegalArgumentException("Empty file path!"));
}
public void setPath(@Nullable Path path) {
this.path = path;
} }
@Override @Override
public void resetFrom(@NotNull T s) { public void readExternal(@NotNull Element element) {
filePathPanel.setText(Objects.requireNonNullElse(s.getFilePath(), "")); try {
ElementUtil.readString(element, getSerializedName()).map(Paths::get).ifPresent(x -> path = x);
} catch (InvalidPathException ignored){}
} }
@Override @Override
public void construct(Panel p) { public void writeExternal(@NotNull Element element) {
p.row("Target file", (r) -> { ElementUtil.writeString(element, getSerializedName(), path == null ? null : path.toString());
r.cell(filePathPanel).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
} }
public interface Carrier { @Override
void setFilePath(String path); @SneakyThrows
String getFilePath(); public T clone() {
return (T) super.clone();
}
protected abstract String getSerializedName();
public abstract static class PathConfigModule<T extends PathConfigurable<T>> implements ZigConfigModule<T> {
@Override
public void apply(T s) throws ConfigurationException {
try {
s.setPath(Paths.get(getString()));
} catch (InvalidPathException e) {
throw new ConfigurationException(e.getMessage(), e, "Invalid Path");
}
}
@Override
public void reset(T s) {
setString(s.getPath().map(Path::toString).orElse(""));
}
protected abstract String getString();
protected abstract void setString(String str);
} }
} }
public static class ColoredModule<T extends ZigExecConfigBase<T> & ColoredModule.Carrier> implements ZigConfigModule<T> { @Getter(AccessLevel.PROTECTED)
private final JBCheckBox checkBox = new JBCheckBox(); @RequiredArgsConstructor
public static class WorkDirectoryConfigurable extends PathConfigurable<WorkDirectoryConfigurable> {
private transient final String serializedName;
@Override @Override
public void applyTo(@NotNull T s) throws ConfigurationException { public WorkDirectoryConfigModule createEditor() {
s.setColored(checkBox.isSelected()); return new WorkDirectoryConfigModule(serializedName);
}
@RequiredArgsConstructor
public static class WorkDirectoryConfigModule extends PathConfigModule<WorkDirectoryConfigurable> {
private final String serializedName;
@Override
public @Nullable WorkDirectoryConfigurable tryMatch(ZigConfigurable<?> cfg) {
return cfg instanceof WorkDirectoryConfigurable cfg$ && cfg$.serializedName.equals(serializedName) ? cfg$ : null;
}
@Override
protected String getString() {
return workingDirectoryComponent.getComponent().getText();
}
@Override
protected void setString(String str) {
workingDirectoryComponent.getComponent().setText(str);
}
private final WorkingDirectoryComponent workingDirectoryComponent = new WorkingDirectoryComponent(this);
@Override
public void construct(Panel p) {
p.row(workingDirectoryComponent.getLabel(), (r) -> {
r.cell(workingDirectoryComponent).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
}
@Override
public void dispose() {
workingDirectoryComponent.dispose();
}
}
}
@RequiredArgsConstructor
public static class FilePathConfigurable extends PathConfigurable<FilePathConfigurable> {
@Getter(AccessLevel.PROTECTED)
private transient final String serializedName;
private transient final String guiLabel;
@Override
public FilePathConfigModule createEditor() {
return new FilePathConfigModule(serializedName, guiLabel);
}
@RequiredArgsConstructor
public static class FilePathConfigModule extends PathConfigModule<FilePathConfigurable> {
private final String serializedName;
private final String label;
private final ZigFilePathPanel filePathPanel = new ZigFilePathPanel();
@Override
public @Nullable FilePathConfigurable tryMatch(ZigConfigurable<?> cfg) {
return cfg instanceof FilePathConfigurable cfg$ && cfg$.serializedName.equals(serializedName) ? cfg$ : null;
}
@Override
protected String getString() {
return filePathPanel.getText();
}
@Override
protected void setString(String str) {
filePathPanel.setText(str);
}
@Override
public void construct(Panel p) {
p.row(label, (r) -> {
r.cell(filePathPanel).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
}
@Override
public void dispose() {
}
}
}
@RequiredArgsConstructor
public static class ColoredConfigurable implements ZigConfigurable<ColoredConfigurable> {
private transient final String serializedName;
public boolean colored = true;
@Override
public void readExternal(@NotNull Element element) {
ElementUtil.readBoolean(element, serializedName).ifPresent(x -> colored = x);
} }
@Override @Override
public void resetFrom(@NotNull T s) { public void writeExternal(@NotNull Element element) {
checkBox.setSelected(s.isColored()); ElementUtil.writeBoolean(element, serializedName, colored);
} }
@Override @Override
public void construct(Panel p) { public ColoredConfigModule createEditor() {
p.row("Colored terminal", (r) -> { return new ColoredConfigModule(serializedName);
r.cell(checkBox); }
return null;
@Override
@SneakyThrows
public ColoredConfigurable clone() {
return (ColoredConfigurable) super.clone();
}
@RequiredArgsConstructor
public static class ColoredConfigModule implements ZigConfigModule<ColoredConfigurable> {
private final String serializedName;
private final JBCheckBox checkBox = new JBCheckBox();
@Override
public @Nullable ColoredConfigurable tryMatch(ZigConfigurable<?> cfg) {
return cfg instanceof ColoredConfigurable cfg$ && cfg$.serializedName.equals(serializedName) ? cfg$ : null;
}
@Override
public void apply(ColoredConfigurable s) throws ConfigurationException {
s.colored = checkBox.isSelected();
}
@Override
public void reset(ColoredConfigurable s) {
checkBox.setSelected(s.colored);
}
@Override
public void construct(Panel p) {
p.row("Colored terminal", (r) -> {
r.cell(checkBox);
return null;
});
}
@Override
public void dispose() {
}
}
}
@RequiredArgsConstructor
public static class OptimizationConfigurable implements ZigConfigurable<OptimizationConfigurable> {
private transient final String serializedName;
public OptimizationLevel level = OptimizationLevel.Debug;
public boolean forced = false;
@Override
public void readExternal(@NotNull Element element) {
ElementUtil.readChild(element, serializedName).ifPresent(child -> {
ElementUtil.readEnum(child, "level", OptimizationLevel.class).ifPresent(x -> level = x);
ElementUtil.readBoolean(child,"forced").ifPresent(x -> forced = x);
}); });
} }
@Override
public void writeExternal(@NotNull Element element) {
val child = ElementUtil.writeChild(element, serializedName);
ElementUtil.writeEnum(child, "level", level);
ElementUtil.writeBoolean(child, "forced", forced);
}
public interface Carrier { @Override
void setColored(boolean color); public OptimizationConfigModule createEditor() {
boolean isColored(); return new OptimizationConfigModule(serializedName);
}
@Override
@SneakyThrows
public OptimizationConfigurable clone() {
return (OptimizationConfigurable) super.clone();
}
@RequiredArgsConstructor
public static class OptimizationConfigModule implements ZigConfigModule<OptimizationConfigurable> {
private final String serializedName;
private final ComboBox<OptimizationLevel> levels = new ComboBox<>(OptimizationLevel.values());
private final JBCheckBox forced = new JBCheckBox("Force even in debug runs");
@Override
public @Nullable OptimizationConfigurable tryMatch(ZigConfigurable<?> cfg) {
return cfg instanceof OptimizationConfigurable cfg$ && cfg$.serializedName.equals(serializedName) ? cfg$ : null;
}
@Override
public void apply(OptimizationConfigurable s) throws ConfigurationException {
s.level = levels.getItem();
s.forced = forced.isSelected();
}
@Override
public void reset(OptimizationConfigurable s) {
levels.setItem(s.level);
forced.setSelected(s.forced);
}
@Override
public void construct(Panel p) {
p.row("Optimization level", (r) -> {
r.cell(levels);
r.cell(forced);
return null;
});
}
@Override
public void dispose() {
}
}
}
@RequiredArgsConstructor
public static class ArgsConfigurable implements ZigConfigurable<ArgsConfigurable> {
private transient final String serializedName;
private transient final String guiName;
public String[] args = new String[0];
@Override
public void readExternal(@NotNull Element element) {
ElementUtil.readStrings(element, serializedName).ifPresent(x -> args = x);
}
@Override
public void writeExternal(@NotNull Element element) {
ElementUtil.writeStrings(element, serializedName, args);
}
@Override
public ArgsConfigModule createEditor() {
return new ArgsConfigModule(serializedName, guiName);
}
@Override
@SneakyThrows
public ArgsConfigurable clone() {
return (ArgsConfigurable) super.clone();
}
@RequiredArgsConstructor
public static class ArgsConfigModule implements ZigConfigModule<ArgsConfigurable> {
private final String serializedName;
private final String guiName;
private final JBTextField argsField = new JBTextField();
@Override
public @Nullable ArgsConfigurable tryMatch(ZigConfigurable<?> cfg) {
return cfg instanceof ArgsConfigurable cfg$ && cfg$.serializedName.equals(serializedName) ? cfg$ : null;
}
@Override
public void apply(ArgsConfigurable s) throws ConfigurationException {
s.args = CLIUtil.translateCommandline(argsField.getText());
}
@Override
public void reset(ArgsConfigurable s) {
argsField.setText(String.join(" ", s.args));
}
@Override
public void construct(Panel p) {
p.row(guiName, (r) -> {
r.cell(argsField).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
}
@Override
public void dispose() {
}
} }
} }
} }

View file

@ -17,17 +17,18 @@
package com.falsepattern.zigbrains.project.execution.base; package com.falsepattern.zigbrains.project.execution.base;
import com.falsepattern.zigbrains.project.util.ElementUtil; import com.falsepattern.zigbrains.project.util.ElementUtil;
import com.google.common.collect.Lists;
import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionException;
import com.intellij.execution.Executor; import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.configurations.LocatableConfigurationBase; import com.intellij.execution.configurations.LocatableConfigurationBase;
import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.configurations.RunConfigurationBase;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.NlsActions; import com.intellij.openapi.util.NlsActions;
import lombok.Getter;
import lombok.val;
import org.jdom.Element; import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -35,44 +36,50 @@ import org.jetbrains.annotations.Nullable;
import java.nio.file.InvalidPathException; import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@Getter
public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends LocatableConfigurationBase<ProfileStateBase<T>> { public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends LocatableConfigurationBase<ProfileStateBase<T>> {
public @Nullable Path workingDirectory; private ZigConfigEditor.WorkDirectoryConfigurable workingDirectory = new ZigConfigEditor.WorkDirectoryConfigurable("workingDirectory");
public ZigExecConfigBase(@NotNull Project project, @NotNull ConfigurationFactory factory, @Nullable String name) { public ZigExecConfigBase(@NotNull Project project, @NotNull ConfigurationFactory factory, @Nullable String name) {
super(project, factory, name); super(project, factory, name);
workingDirectory = project.isDefault() ? null : Optional.ofNullable(project.getBasePath()) workingDirectory.setPath(project.isDefault() ? null : Optional.ofNullable(project.getBasePath())
.map(Path::of) .map(Path::of)
.orElse(null); .orElse(null));
} }
@Override @Override
public @NotNull SettingsEditor<? extends RunConfiguration> getConfigurationEditor() { public @NotNull ZigConfigEditor<T> getConfigurationEditor() {
return new ZigConfigEditor<>(getEditorConfigModules()); return new ZigConfigEditor<>(this);
} }
@Override @Override
public void readExternal(@NotNull Element element) throws InvalidDataException { public void readExternal(@NotNull Element element) throws InvalidDataException {
super.readExternal(element); super.readExternal(element);
ElementUtil.readString(element, "workingDirectory").ifPresent(dir -> { getConfigurables().forEach(cfg -> cfg.readExternal(element));
try {
workingDirectory = Path.of(dir);
} catch (InvalidPathException ignored) {}
});
} }
@Override @Override
public void writeExternal(@NotNull Element element) { public void writeExternal(@NotNull Element element) {
super.writeExternal(element); super.writeExternal(element);
if (workingDirectory != null) getConfigurables().forEach(cfg -> cfg.writeExternal(element));
ElementUtil.writeString(element, "workingDirectory", workingDirectory.toString());
} }
public abstract String[] buildCommandLineArgs(); public abstract String[] buildCommandLineArgs();
public String[] buildDebugCommandLineArgs() {
return buildCommandLineArgs();
}
@Override
public T clone() {
val myClone = (ZigExecConfigBase<?>) super.clone();
myClone.workingDirectory = workingDirectory.clone();
return (T) myClone;
}
@Override @Override
public abstract @Nullable @NlsActions.ActionText String suggestedName(); public abstract @Nullable @NlsActions.ActionText String suggestedName();
@ -80,7 +87,7 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
public abstract @Nullable ProfileStateBase<T> getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) public abstract @Nullable ProfileStateBase<T> getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment)
throws ExecutionException; throws ExecutionException;
public @NotNull List<ZigConfigEditor.@NotNull ZigConfigModule<T>> getEditorConfigModules() { public @NotNull List<ZigConfigEditor.@NotNull ZigConfigurable<?>> getConfigurables() {
return new ArrayList<>(List.of(new ZigConfigEditor.WorkingDirectoryModule<>())); return List.of(workingDirectory);
} }
} }

View file

@ -0,0 +1,49 @@
/*
* 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.execution.binary;
import com.falsepattern.zigbrains.zig.Icons;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.configurations.ConfigurationTypeBase;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class ConfigTypeBinary extends ConfigurationTypeBase {
public static final String IDENTIFIER = "ZIGBRAINS_BINARY";
public ConfigTypeBinary() {
super(IDENTIFIER, "Zig-compiled native executable", "Binary executable compiled from zig code", Icons.ZIG);
addFactory(new ConfigFactoryBinary());
}
public class ConfigFactoryBinary extends ConfigurationFactory {
public ConfigFactoryBinary() {
super(ConfigTypeBinary.this);
}
@Override
public @NotNull RunConfiguration createTemplateConfiguration(@NotNull Project project) {
return new ZigExecConfigBinary(project, this);
}
@Override
public @NotNull @NonNls String getId() {
return IDENTIFIER;
}
}
}

View file

@ -0,0 +1,44 @@
/*
* 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.execution.binary;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.runners.ExecutionEnvironment;
import lombok.val;
import java.nio.charset.StandardCharsets;
public class ProfileStateBinary extends ProfileStateBase<ZigExecConfigBinary> {
public ProfileStateBinary(ExecutionEnvironment environment, ZigExecConfigBinary configuration) {
super(environment, configuration);
}
@Override
public GeneralCommandLine getCommandLine(AbstractZigToolchain toolchain, boolean debug) throws ExecutionException {
val cli = new GeneralCommandLine();
val cfg = configuration();
cfg.getWorkingDirectory().getPath().ifPresent(dir -> cli.setWorkDirectory(dir.toFile()));
cli.setExePath(cfg.getExePath().getPath().orElseThrow(() -> new ExecutionException("Missing executable path")).toString());
cli.setCharset(StandardCharsets.UTF_8);
cli.setRedirectErrorStream(true);
cli.addParameters(cfg.getArgs().args);
return cli;
}
}

View file

@ -0,0 +1,71 @@
/*
* 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.execution.binary;
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.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.project.Project;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@Getter
public class ZigExecConfigBinary extends ZigExecConfigBase<ZigExecConfigBinary> {
private ZigConfigEditor.FilePathConfigurable exePath = new ZigConfigEditor.FilePathConfigurable("exePath", "Executable program path (not the zig compiler)");
private ZigConfigEditor.ArgsConfigurable args = new ZigConfigEditor.ArgsConfigurable("args", "Command line arguments");
public ZigExecConfigBinary(@NotNull Project project, @NotNull ConfigurationFactory factory) {
super(project, factory, "Zig-compiled native executable");
}
@Override
public String[] buildCommandLineArgs() {
return args.args;
}
@Override
public @Nullable String suggestedName() {
return "Executable";
}
@Override
public ZigExecConfigBinary clone() {
val clone = super.clone();
clone.exePath = exePath.clone();
clone.args = args.clone();
return clone;
}
@Override
public @NotNull List<ZigConfigEditor.ZigConfigurable<?>> getConfigurables() {
return CollectionUtil.concat(super.getConfigurables(), exePath, args);
}
@Override
public @Nullable ProfileStateBinary getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) {
return new ProfileStateBinary(environment, this);
}
}

View file

@ -20,8 +20,11 @@ import com.falsepattern.zigbrains.project.execution.base.ConfigProducerBase;
import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import lombok.val;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild> { public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild> {
@Override @Override
public @NotNull ConfigurationFactory getConfigurationFactory() { public @NotNull ConfigurationFactory getConfigurationFactory() {
@ -29,7 +32,7 @@ public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild>
} }
@Override @Override
protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigBuild configuration, PsiElement element, String filePath, VirtualFile theFile) { protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigBuild configuration, PsiElement element, Path filePath, VirtualFile theFile) {
if (ZigLineMarkerBuild.UTILITY_INSTANCE.elementMatches(element)) { if (ZigLineMarkerBuild.UTILITY_INSTANCE.elementMatches(element)) {
configuration.setName("Build"); configuration.setName("Build");
return true; return true;
@ -38,7 +41,11 @@ public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild>
} }
@Override @Override
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigBuild configuration, String filePath, VirtualFile vFile, PsiElement element) { protected boolean isConfigurationFromContext(@NotNull ZigExecConfigBuild configuration, Path filePath, VirtualFile vFile, PsiElement element) {
return true; val p = configuration.getWorkingDirectory().getPath();
if (p.isEmpty())
return false;
val path = p.get();
return filePath.getParent().equals(path);
} }
} }

View file

@ -16,48 +16,34 @@
package com.falsepattern.zigbrains.project.execution.build; package com.falsepattern.zigbrains.project.execution.build;
import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor; import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase; import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.project.util.ElementUtil;
import com.intellij.execution.Executor; import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.dsl.builder.AlignX;
import com.intellij.ui.dsl.builder.AlignY;
import com.intellij.ui.dsl.builder.Panel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.val; import lombok.val;
import org.apache.groovy.util.Arrays;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
import java.util.Objects;
@Getter @Getter
@Setter public class ZigExecConfigBuild extends ZigExecConfigBase<ZigExecConfigBuild> {
public class ZigExecConfigBuild extends ZigExecConfigBase<ZigExecConfigBuild> implements private ZigConfigEditor.ArgsConfigurable extraArgs = new ZigConfigEditor.ArgsConfigurable("extraArgs", "Extra command line arguments");
ZigConfigEditor.ColoredModule.Carrier { private ZigConfigEditor.ColoredConfigurable colored = new ZigConfigEditor.ColoredConfigurable("colored");
public String extraArguments = ""; private ZigConfigEditor.FilePathConfigurable exePath = new ZigConfigEditor.FilePathConfigurable("exePath", "Output executable created by the build (for debugging)");
public boolean colored = true;
public ZigExecConfigBuild(@NotNull Project project, @NotNull ConfigurationFactory factory) { public ZigExecConfigBuild(@NotNull Project project, @NotNull ConfigurationFactory factory) {
super(project, factory, "Zig Build"); super(project, factory, "Zig Build");
} }
@Override @Override
public String[] buildCommandLineArgs() { public String[] buildCommandLineArgs() {
val base = new String[]{"build", "--color", colored ? "on" : "off"}; val base = new String[]{"build", "--color", colored.colored ? "on" : "off"};
if (extraArguments.isBlank()) { return CollectionUtil.concat(base, extraArgs.args);
return base;
} else {
return Arrays.concat(base, extraArguments.split(" "));
}
} }
@Override @Override
@ -66,53 +52,21 @@ public class ZigExecConfigBuild extends ZigExecConfigBase<ZigExecConfigBuild> im
} }
@Override @Override
public @NotNull List<ZigConfigEditor.ZigConfigModule<ZigExecConfigBuild>> getEditorConfigModules() { public @NotNull List<ZigConfigEditor.ZigConfigurable<?>> getConfigurables() {
val arr = super.getEditorConfigModules(); return CollectionUtil.concat(super.getConfigurables(), extraArgs, colored, exePath);
arr.add(new ExtraArgsModule()); }
arr.add(new ZigConfigEditor.ColoredModule<>());
return arr; @Override
public ZigExecConfigBuild clone() {
val clone = super.clone();
clone.extraArgs = extraArgs.clone();
clone.colored = colored.clone();
clone.exePath = exePath.clone();
return clone;
} }
@Override @Override
public @Nullable ProfileStateBuild getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) { public @Nullable ProfileStateBuild getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) {
return new ProfileStateBuild(environment, this); return new ProfileStateBuild(environment, this);
} }
@Override
public void readExternal(@NotNull Element element) throws InvalidDataException {
super.readExternal(element);
ElementUtil.readString(element, "extraArguments").ifPresent(x -> extraArguments = x);
ElementUtil.readBoolean(element, "colored").ifPresent(x -> colored = x);
}
@Override
public void writeExternal(@NotNull Element element) {
super.writeExternal(element);
ElementUtil.writeString(element, "extraArguments", extraArguments);
ElementUtil.writeBoolean(element, "colored", colored);
}
public static class ExtraArgsModule implements ZigConfigEditor.ZigConfigModule<ZigExecConfigBuild> {
private final JBTextField extraArgs = new JBTextField();
@Override
public void applyTo(@NotNull ZigExecConfigBuild s) throws ConfigurationException {
s.extraArguments = extraArgs.getText();
}
@Override
public void resetFrom(@NotNull ZigExecConfigBuild s) {
extraArgs.setText(Objects.requireNonNullElse(s.extraArguments, ""));
}
@Override
public void construct(Panel p) {
p.row("Extra arguments", (r) -> {
r.cell(extraArgs).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
}
}
} }

View file

@ -23,6 +23,9 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.Objects;
public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> { public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> {
@Override @Override
public @NotNull ConfigurationFactory getConfigurationFactory() { public @NotNull ConfigurationFactory getConfigurationFactory() {
@ -30,9 +33,9 @@ public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> {
} }
@Override @Override
protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigRun configuration, PsiElement element, String filePath, VirtualFile theFile) { protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigRun configuration, PsiElement element, Path filePath, VirtualFile theFile) {
if (ZigLineMarkerRun.UTILITY_INSTANCE.elementMatches(element)) { if (ZigLineMarkerRun.UTILITY_INSTANCE.elementMatches(element)) {
configuration.filePath = filePath; configuration.getFilePath().setPath(filePath);
configuration.setName(theFile.getPresentableName()); configuration.setName(theFile.getPresentableName());
return true; return true;
} }
@ -40,8 +43,8 @@ public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> {
} }
@Override @Override
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigRun configuration, String filePath, VirtualFile vFile, PsiElement element) { protected boolean isConfigurationFromContext(@NotNull ZigExecConfigRun configuration, Path filePath, VirtualFile vFile, PsiElement element) {
return configuration.filePath.equals(filePath); return Objects.equals(configuration.getFilePath().getPath().orElse(null), filePath);
} }
@Override @Override

View file

@ -16,36 +16,43 @@
package com.falsepattern.zigbrains.project.execution.run; package com.falsepattern.zigbrains.project.execution.run;
import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase; import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor; import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.util.ElementUtil;
import com.intellij.execution.Executor; import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.val; import lombok.val;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
@Setter
@Getter @Getter
public class ZigExecConfigRun extends ZigExecConfigBase<ZigExecConfigRun> implements public class ZigExecConfigRun extends ZigExecConfigBase<ZigExecConfigRun> {
ZigConfigEditor.FilePathModule.Carrier, ZigConfigEditor.ColoredModule.Carrier { private ZigConfigEditor.FilePathConfigurable filePath = new ZigConfigEditor.FilePathConfigurable("filePath", "File Path");
public String filePath = ""; private ZigConfigEditor.ColoredConfigurable colored = new ZigConfigEditor.ColoredConfigurable("colored");
public boolean colored = true; private ZigConfigEditor.OptimizationConfigurable optimization = new ZigConfigEditor.OptimizationConfigurable("optimization");
private ZigConfigEditor.ArgsConfigurable exeArgs = new ZigConfigEditor.ArgsConfigurable("exeArgs", "Arguments for the compile exe");
public ZigExecConfigRun(@NotNull Project project, @NotNull ConfigurationFactory factory) { public ZigExecConfigRun(@NotNull Project project, @NotNull ConfigurationFactory factory) {
super(project, factory, "Zig Run"); super(project, factory, "Zig Run");
} }
@Override @Override
public String[] buildCommandLineArgs() { public String[] buildCommandLineArgs() {
return new String[]{"run", "--color", colored ? "on" : "off", filePath}; return CollectionUtil.concat(new String[]{"run", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString(), "-O", optimization.level.name(), "--"}, exeArgs.args);
}
@Override
public String[] buildDebugCommandLineArgs() {
if (optimization.forced) {
return new String[]{"build-exe", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString(), "-O", optimization.level.name()};
} else {
return new String[]{"build-exe", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString()};
}
} }
@Override @Override
@ -54,31 +61,22 @@ public class ZigExecConfigRun extends ZigExecConfigBase<ZigExecConfigRun> implem
} }
@Override @Override
public @NotNull List<ZigConfigEditor.ZigConfigModule<ZigExecConfigRun>> getEditorConfigModules() { public ZigExecConfigRun clone() {
val modules = super.getEditorConfigModules(); val clone = super.clone();
modules.add(new ZigConfigEditor.FilePathModule<>()); clone.filePath = filePath.clone();
modules.add(new ZigConfigEditor.ColoredModule<>()); clone.colored = colored.clone();
return modules; clone.optimization = optimization.clone();
clone.exeArgs = exeArgs.clone();
return clone;
}
@Override
public @NotNull List<ZigConfigEditor.ZigConfigurable<?>> getConfigurables() {
return CollectionUtil.concat(super.getConfigurables(), filePath, optimization, colored);
} }
@Override @Override
public @Nullable ProfileStateRun getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) { public @Nullable ProfileStateRun getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) {
return new ProfileStateRun(environment, this); return new ProfileStateRun(environment, this);
} }
@Override
public void readExternal(@NotNull Element element) throws InvalidDataException {
super.readExternal(element);
ElementUtil.readString(element, "filePath").ifPresent(x -> filePath = x);
ElementUtil.readBoolean(element, "colored").ifPresent(x -> colored = x);
}
@Override
public void writeExternal(@NotNull Element element) {
super.writeExternal(element);
ElementUtil.writeString(element, "filePath", filePath);
ElementUtil.writeBoolean(element, "colored", colored);
}
} }

View file

@ -24,6 +24,9 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.Objects;
public class ConfigProducerTest extends ConfigProducerBase<ZigExecConfigTest> { public class ConfigProducerTest extends ConfigProducerBase<ZigExecConfigTest> {
@Override @Override
public @NotNull ConfigurationFactory getConfigurationFactory() { public @NotNull ConfigurationFactory getConfigurationFactory() {
@ -31,9 +34,9 @@ public class ConfigProducerTest extends ConfigProducerBase<ZigExecConfigTest> {
} }
@Override @Override
protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigTest configuration, PsiElement element, String filePath, VirtualFile theFile) { protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigTest configuration, PsiElement element, Path filePath, VirtualFile theFile) {
if (ZigLineMarkerTest.UTILITY_INSTANCE.elementMatches(element)) { if (ZigLineMarkerTest.UTILITY_INSTANCE.elementMatches(element)) {
configuration.filePath = filePath; configuration.getFilePath().setPath(filePath);
configuration.setName("all tests in " + theFile.getPresentableName()); configuration.setName("all tests in " + theFile.getPresentableName());
return true; return true;
} }
@ -41,8 +44,8 @@ public class ConfigProducerTest extends ConfigProducerBase<ZigExecConfigTest> {
} }
@Override @Override
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigTest configuration, String filePath, VirtualFile vFile, PsiElement element) { protected boolean isConfigurationFromContext(@NotNull ZigExecConfigTest configuration, Path filePath, VirtualFile vFile, PsiElement element) {
return configuration.filePath.equals(filePath); return Objects.equals(configuration.getFilePath().getPath().orElse(null), filePath);
} }
@Override @Override

View file

@ -16,15 +16,15 @@
package com.falsepattern.zigbrains.project.execution.test; package com.falsepattern.zigbrains.project.execution.test;
import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.OptimizationLevel;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase; import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase; import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor; import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.util.ElementUtil; import com.falsepattern.zigbrains.project.util.ElementUtil;
import com.intellij.execution.Executor; import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory; import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.InvalidDataException;
import lombok.Getter; import lombok.Getter;
@ -37,18 +37,26 @@ import org.jetbrains.annotations.Nullable;
import java.util.List; import java.util.List;
@Getter @Getter
@Setter public class ZigExecConfigTest extends ZigExecConfigBase<ZigExecConfigTest> {
public class ZigExecConfigTest extends ZigExecConfigBase<ZigExecConfigTest> implements ZigConfigEditor.FilePathModule.Carrier, private ZigConfigEditor.FilePathConfigurable filePath = new ZigConfigEditor.FilePathConfigurable("filePath", "File path");
ZigConfigEditor.ColoredModule.Carrier { private ZigConfigEditor.ColoredConfigurable colored = new ZigConfigEditor.ColoredConfigurable("colored");
public String filePath = ""; private ZigConfigEditor.OptimizationConfigurable optimization = new ZigConfigEditor.OptimizationConfigurable("optimization");
public boolean colored = true;
public ZigExecConfigTest(@NotNull Project project, @NotNull ConfigurationFactory factory) { public ZigExecConfigTest(@NotNull Project project, @NotNull ConfigurationFactory factory) {
super(project, factory, "Zig Test"); super(project, factory, "Zig Test");
} }
@Override @Override
public String[] buildCommandLineArgs() { public String[] buildCommandLineArgs() {
return new String[]{"test", "--color", colored ? "on" : "off", filePath}; return new String[]{"test", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString(), "-O", optimization.level.name()};
}
@Override
public String[] buildDebugCommandLineArgs() {
if (optimization.forced) {
return new String[]{"test", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString(), "--test-no-exec", "-O", optimization.level.name()};
} else {
return new String[]{"test", "--color", colored.colored ? "on" : "off", filePath.getPathOrThrow().toString(), "--test-no-exec"};
}
} }
@Override @Override
@ -62,26 +70,16 @@ public class ZigExecConfigTest extends ZigExecConfigBase<ZigExecConfigTest> impl
} }
@Override @Override
public void readExternal(@NotNull Element element) throws InvalidDataException { public ZigExecConfigTest clone() {
super.readExternal(element); val clone = super.clone();
clone.filePath = filePath.clone();
ElementUtil.readString(element, "filePath").ifPresent(x -> filePath = x); clone.colored = colored.clone();
ElementUtil.readBoolean(element, "colored").ifPresent(x -> colored = x); clone.optimization = optimization.clone();
return clone;
} }
@Override @Override
public void writeExternal(@NotNull Element element) { public @NotNull List<ZigConfigEditor.ZigConfigurable<?>> getConfigurables() {
super.writeExternal(element); return CollectionUtil.concat(super.getConfigurables(), filePath, optimization, colored);
ElementUtil.writeString(element, "filePath", filePath);
ElementUtil.writeBoolean(element, "colored", colored);
}
@Override
public @NotNull List<ZigConfigEditor.ZigConfigModule<ZigExecConfigTest>> getEditorConfigModules() {
val modules = super.getEditorConfigModules();
modules.add(new ZigConfigEditor.FilePathModule<>());
modules.add(new ZigConfigEditor.ColoredModule<>());
return modules;
} }
} }

View file

@ -52,6 +52,6 @@ public class ZigRegularRunner extends ZigProgramRunnerBase<ProfileStateBase<?>>
@Override @Override
protected @Nullable RunContentDescriptor doExecute(ProfileStateBase<?> state, AbstractZigToolchain toolchain, ExecutionEnvironment environment) protected @Nullable RunContentDescriptor doExecute(ProfileStateBase<?> state, AbstractZigToolchain toolchain, ExecutionEnvironment environment)
throws ExecutionException { throws ExecutionException {
return DefaultProgramRunnerKt.showRunContent(state.executeCommandLine(state.getCommandLine(toolchain), environment), environment); return DefaultProgramRunnerKt.showRunContent(state.executeCommandLine(state.getCommandLine(toolchain, false), environment), environment);
} }
} }

View file

@ -17,17 +17,24 @@
package com.falsepattern.zigbrains.project.ui; package com.falsepattern.zigbrains.project.ui;
import com.intellij.execution.ExecutionBundle; import com.intellij.execution.ExecutionBundle;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.ui.LabeledComponent; import com.intellij.openapi.ui.LabeledComponent;
import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.openapi.ui.TextFieldWithBrowseButton;
public class WorkingDirectoryComponent extends LabeledComponent<TextFieldWithBrowseButton> { public class WorkingDirectoryComponent extends LabeledComponent<TextFieldWithBrowseButton> implements Disposable {
public WorkingDirectoryComponent() { private final TextFieldWithBrowseButton field;
var component = new TextFieldWithBrowseButton(); public WorkingDirectoryComponent(Disposable parent) {
field = new TextFieldWithBrowseButton(null, parent);
var fileChooser = FileChooserDescriptorFactory.createSingleFolderDescriptor(); var fileChooser = FileChooserDescriptorFactory.createSingleFolderDescriptor();
fileChooser.setTitle(ExecutionBundle.message("select.working.directory.message")); fileChooser.setTitle(ExecutionBundle.message("select.working.directory.message"));
component.addBrowseFolderListener(null, null, null, fileChooser); field.addBrowseFolderListener(null, null, null, fileChooser);
setComponent(component); setComponent(field);
setText(ExecutionBundle.message("run.configuration.working.directory.label")); setText(ExecutionBundle.message("run.configuration.working.directory.label"));
} }
@Override
public void dispose() {
field.dispose();
}
} }

View file

@ -20,12 +20,16 @@ import com.falsepattern.zigbrains.project.execution.ZigCapturingProcessHandler;
import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.CapturingProcessHandler; import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessOutput; import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.ProgressManager;
import lombok.val; import lombok.val;
import org.apache.tools.ant.BuildException;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Optional; import java.util.Optional;
import java.util.StringTokenizer;
public class CLIUtil { public class CLIUtil {
public static Optional<ProcessOutput> execute(GeneralCommandLine cli, int timeoutMillis) { public static Optional<ProcessOutput> execute(GeneralCommandLine cli, int timeoutMillis) {
@ -48,4 +52,72 @@ public class CLIUtil {
return handler.runProcess(); return handler.runProcess();
} }
} }
//From Apache Ant
/**
* Crack a command line.
* @param toProcess the command line to process.
* @return the command line broken into strings.
* An empty or null toProcess parameter results in a zero sized array.
*/
public static String[] translateCommandline(String toProcess) throws ConfigurationException {
if (toProcess == null || toProcess.isEmpty()) {
//no command? no string
return new String[0];
}
// parse with a simple finite state machine
final int normal = 0;
final int inQuote = 1;
final int inDoubleQuote = 2;
int state = normal;
final StringTokenizer tok = new StringTokenizer(toProcess, "\"' ", true);
final ArrayList<String> result = new ArrayList<>();
final StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
while (tok.hasMoreTokens()) {
String nextTok = tok.nextToken();
switch (state) {
case inQuote:
if ("'".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
case inDoubleQuote:
if ("\"".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
default:
if ("'".equals(nextTok)) {
state = inQuote;
} else if ("\"".equals(nextTok)) {
state = inDoubleQuote;
} else if (" ".equals(nextTok)) {
if (lastTokenHasBeenQuoted || current.length() > 0) {
result.add(current.toString());
current.setLength(0);
}
} else {
current.append(nextTok);
}
lastTokenHasBeenQuoted = false;
break;
}
}
if (lastTokenHasBeenQuoted || current.length() > 0) {
result.add(current.toString());
}
if (state == inQuote || state == inDoubleQuote) {
throw new ConfigurationException("unbalanced quotes in " + toProcess);
}
return result.toArray(new String[0]);
}
} }

View file

@ -18,8 +18,8 @@ package com.falsepattern.zigbrains.project.util;
import lombok.val; import lombok.val;
import org.jdom.Element; import org.jdom.Element;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
public class ElementUtil { public class ElementUtil {
@ -34,10 +34,52 @@ public class ElementUtil {
.map(it -> it.getAttributeValue("value")); .map(it -> it.getAttributeValue("value"));
} }
public static Optional<Boolean> readBoolean(Element element, String name) {
return readString(element, name).map(Boolean::parseBoolean);
}
public static <T extends Enum<T>> Optional<T> readEnum(Element element, String name, Class<T> enumClass) {
return readString(element, name).map(value -> {
try {
val field = enumClass.getDeclaredField(value);
//noinspection unchecked
return (T) field.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
return null;
}
});
}
public static Optional<Element> readChild(Element element, String name) {
return element.getChildren()
.stream()
.filter(it -> it.getName()
.equals("ZigBrainsNestedOption") &&
it.getAttributeValue("name")
.equals(name))
.findAny();
}
public static Optional<String[]> readStrings(Element element, String name) {
return element.getChildren()
.stream()
.filter(it -> it.getName()
.equals("ZigBrainsArrayOption") &&
it.getAttributeValue("name")
.equals(name))
.findAny()
.map(it -> it.getChildren()
.stream()
.filter(it2 -> it2.getName()
.equals("ZigBrainsArrayEntry"))
.map(it2 -> it2.getAttributeValue("value"))
.toArray(String[]::new));
}
public static void writeString(Element element, String name, String value) { public static void writeString(Element element, String name, String value) {
val option = new Element("ZigBrainsOption"); val option = new Element("ZigBrainsOption");
option.setAttribute("name", name); option.setAttribute("name", name);
option.setAttribute("value", value); option.setAttribute("value", Objects.requireNonNullElse(value, ""));
element.addContent(option); element.addContent(option);
} }
@ -46,7 +88,25 @@ public class ElementUtil {
writeString(element, name, Boolean.toString(state)); writeString(element, name, Boolean.toString(state));
} }
public static Optional<Boolean> readBoolean(Element element, String name) { public static <T extends Enum<T>> void writeEnum(Element element, String name, T value) {
return readString(element, name).map(Boolean::parseBoolean); writeString(element, name, value.name());
}
public static void writeStrings(Element element, String name, String... values) {
val arr = new Element("ZigBrainsArrayOption");
arr.setAttribute("name", name);
for (val value: values) {
val subElem = new Element("ZigBrainsArrayEntry");
subElem.setAttribute("value", value);
arr.addContent(subElem);
}
element.addContent(arr);
}
public static Element writeChild(Element element, String name) {
val child = new Element("ZigBrainsNestedOption");
child.setAttribute("name", name);
element.addContent(child);
return child;
} }
} }

View file

@ -32,6 +32,8 @@
<runLineMarkerContributor language="Zig" <runLineMarkerContributor language="Zig"
implementationClass="com.falsepattern.zigbrains.project.execution.build.ZigLineMarkerBuild"/> implementationClass="com.falsepattern.zigbrains.project.execution.build.ZigLineMarkerBuild"/>
<configurationType implementation="com.falsepattern.zigbrains.project.execution.binary.ConfigTypeBinary"/>
<directoryProjectGenerator implementation="com.falsepattern.zigbrains.project.platform.ZigDirectoryProjectGenerator"/> <directoryProjectGenerator implementation="com.falsepattern.zigbrains.project.platform.ZigDirectoryProjectGenerator"/>
<newProjectWizard.languageGenerator implementation="com.falsepattern.zigbrains.project.ide.newproject.ZigNewProjectWizard"/> <newProjectWizard.languageGenerator implementation="com.falsepattern.zigbrains.project.ide.newproject.ZigNewProjectWizard"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB