backport: master

fix: Local variables now show up in the debugger properly

chore: The flattening

chore: Remove unnecessary plugin xmls

docs: Update module tree

chore: Remove flexmark, replace with builtin intellij apis

fix: wrong lsp4j package (used IJ shipped lsp4j instead of ours)

(cherry picked from commit 49483a9f9c)

ci: fix deps

(cherry picked from commit 1ff8616d26)

chore: better project path management

(cherry picked from commit 0ebbe1cc9b)

ci: Slightly better git version management

(cherry picked from commit 6fbf826574)

chore: Optimize imports

(cherry picked from commit 92c5e57a03)

chore: Require restart unconditionally

(cherry picked from commit 9e90502b05)

docs: Update readme

(cherry picked from commit 95799c627b)

docs: Update changelog

(cherry picked from commit 45b153f7c4)

feat: Better configurability

(cherry picked from commit ec5c07c0a1)

fix: Annotator jank breaking diagnostics

(cherry picked from commit 85bd68393c)

chore: move some misplaced xml entires

(cherry picked from commit 7b2ad7c324)

feat!: Huge debugging refactor

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

This is a squashed commit so ignore the weird author date

(cherry picked from commit 3d0dbb8e36)

fix: NPE in go to definition

(cherry picked from commit b4539c0aa9)

fix: Builds on windows again

(cherry picked from commit 733f0b2622)

feat: Improved docs, more reliable file sync

(cherry picked from commit 23b72086bc)

chore: LanguageServerDefinition remove ancient obsolete logic

(cherry picked from commit 8a0c862446)

chore: Move lsp connection logic to lsp-common

(cherry picked from commit 3287051e3d)

chore: Move ApplicationUtil to common

(cherry picked from commit 845af09e29)

feat!: Colored builds and clickable file path references

(cherry picked from commit 66aef224b2)
This commit is contained in:
FalsePattern 2024-03-04 16:55:25 +01:00
parent ff29b663dc
commit 59d4dcc8bf
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
114 changed files with 5178 additions and 882 deletions

View file

@ -22,6 +22,7 @@ Changelog structure reference:
- Debugging
- Debugging support for tests when launched using the ZigTest task type (and with the gutter icons in the editor)
- Debugging support on Windows systems
- Project
- Added `zig init` as a new project creation option
@ -30,6 +31,9 @@ Changelog structure reference:
- Zig
- Updated semantic highlighting to latest ZLS protocol
- ZLS
- ZLS configuration is now partially editable through the GUI
### Fixed
- Project
@ -38,6 +42,14 @@ Changelog structure reference:
- Plugin
- Removed a bunch of write action locking, the editor should feel more responsive now
- Zig
- Error highlighting was breaking all the time
### Removed
- Project
- !!!BREAKING CHANGE!!! There is now no arbitrary "zig execution" task, all zig tasks have been categorized into Zig run/build/test tasks respectively.
## [12.0.0]
### Added

View file

@ -21,10 +21,13 @@ IC modules MUST NOT depend on CL modules, as this violates the restrictions set
### Common (IC)
### LSP (IC)
### LSP-Common (IC)
- LSP4J (EXT)
- Flexmark (EXT)
### LSP (IC)
- Apache Commons Lang 3 (EXT)
- Common (IC)
- LSP-Common (IC)
### Zig (IC)
- Grammarkit (EXT)
@ -39,6 +42,8 @@ IC modules MUST NOT depend on CL modules, as this violates the restrictions set
- Grammarkit (EXT)
- Common (IC)
### Debugger (CL)
### Debugger (IU/CL)
- Common (IC)
- LSP-Common (IC)
- Zig (IC)
- Project (IC)

View file

@ -13,11 +13,12 @@ complain about missing files
## Special Thanks
- The [ZigTools](https://github.com/zigtools/) team for developing the Zig Language Server.
- [HTGAzureX1212](https://github.com/HTGAzureX1212) for developing [intellij-zig](https://github.com/intellij-zig/intellij-zig),
which served as a fantastic reference for deep IDE integration features
- The members of the `Zig Programming Language` discord server's `#tooling-dev` channel for providing encouragement and
feedback
- The members of the `Zig Programming Language` discord server's `#tooling-dev` channel for providing encouragement,
feedback, and lots of bug reports.
- The Ballerina Platform developers for `lsp4intellij`, the language server connector between the IntelliJ platform
and the Eclipse LSP4J project
@ -80,7 +81,16 @@ LSP server is running.
## Debugging
ZigBrains uses the CLion C++ toolchains (Settings | Build, Execution, Deployment | Toolchains) for debugging purposes,
### Windows
Due to technical limitations, the C++ toolchains cannot be used for debugging zig code on windows.
Go to `Settings | Build, Execution, Deployment | Debugger | Zig (Windows)` and follow the steps shown there to set up a
zig-compatible debugger.
### Linux / MacOS / Unix
ZigBrains uses the CLion C++ toolchains `Settings | Build, Execution, Deployment | Toolchains` for debugging purposes,
and it is fully compatible with both GDB and LLDB debuggers.
Additionally, ZigBrains will prioritize a toolchain if it is called `Zig`, otherwise it will use the default toolchain.

View file

@ -8,7 +8,7 @@
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
>
<defs
id="defs36">
<linearGradient

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

View file

@ -43,15 +43,26 @@ tasks {
}
}
fun pluginVersion(): Provider<String> {
fun pluginVersionGit(): Provider<String> {
return provider {
System.getenv("RELEASE_VERSION")
}.orElse(provider {
try {
gitVersion()
} catch (_: java.lang.Exception) {
error("Git version not found and RELEASE_VERSION environment variable is not set!")
}
}
}
fun pluginVersion(): Provider<String> {
return provider {
System.getenv("RELEASE_VERSION")
}.orElse(pluginVersionGit().map {
val suffix = "-" + properties("pluginSinceBuild").get()
if (it.endsWith(suffix)) {
it.substring(0, it.length - suffix.length)
} else {
it
}
})
}
@ -97,6 +108,11 @@ allprojects {
targetCompatibility = javaVersion
}
tasks.withType(JavaCompile::class) {
options.encoding = "UTF-8"
}
group = properties("pluginGroup").get()
version = pluginVersionFull().get()
@ -169,6 +185,10 @@ project(":debugger") {
dependencies {
implementation(project(":zig"))
implementation(project(":project"))
implementation(project(":common"))
implementation(project(":lsp-common"))
implementation(project(":lsp"))
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j.debug:0.22.0")
}
intellij {
version = clionVersion
@ -176,13 +196,22 @@ project(":debugger") {
}
}
project(":lsp-common") {
apply {
plugin("java-library")
}
dependencies {
api("org.eclipse.lsp4j:org.eclipse.lsp4j:0.22.0")
}
}
project(":lsp") {
apply {
plugin("java-library")
}
dependencies {
api("org.eclipse.lsp4j:org.eclipse.lsp4j:0.22.0")
implementation("com.vladsch.flexmark:flexmark:0.64.8")
implementation(project(":common"))
api(project(":lsp-common"))
api("org.apache.commons:commons-lang3:3.14.0")
}
}

View file

@ -13,18 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.utils;
package com.falsepattern.zigbrains.common.util;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.project.NoAccessDuringPsiEvents;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Condition;
import lombok.val;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ApplicationUtils {
public class ApplicationUtil {
private final static ExecutorService EXECUTOR_SERVICE;
@ -50,16 +49,7 @@ public class ApplicationUtils {
}
static public <T> T computableReadAction(Computable<T> computable) {
if (ApplicationManager.getApplication().isDispatchThread() ||
ApplicationManagerEx.getApplicationEx().holdsReadLock()) {
return ApplicationManager.getApplication().runReadAction(computable);
} else {
var result = new Object() {
T value = null;
};
ApplicationManager.getApplication().invokeAndWait(() -> result.value = ApplicationManager.getApplication().runReadAction(computable));
return result.value;
}
return ApplicationManager.getApplication().runReadAction(computable);
}
static public void writeAction(Runnable runnable) {
@ -70,15 +60,15 @@ public class ApplicationUtils {
return ApplicationManager.getApplication().runWriteAction(computable);
}
static public void invokeAfterPsiEvents(Runnable runnable) {
static public void invokeAfterPsiEvents(Runnable runnable, boolean readLock, boolean writeLock) {
Runnable wrapper = () -> {
if (NoAccessDuringPsiEvents.isInsideEventProcessing()) {
invokeAfterPsiEvents(runnable);
invokeAfterPsiEvents(runnable, readLock, writeLock);
} else {
runnable.run();
}
};
ApplicationManager.getApplication().invokeLater(wrapper, (Condition<Void>) value -> false);
val app = ApplicationManager.getApplication();
app.invokeLater(writeLock ? wrapper : () -> app.executeOnPooledThread(readLock ? () -> app.runReadAction(wrapper) : wrapper));
}
}

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

@ -0,0 +1,134 @@
/*
* 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 com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
public class FileUtil {
private static final Logger LOG = Logger.getInstance(FileUtil.class);
public final static String SPACE_ENCODED = "%20";
private final static OS os = (System.getProperty("os.name").toLowerCase().contains("win")) ? OS.WINDOWS : OS.UNIX;
private final static String COLON_ENCODED = "%3A";
private final static String URI_FILE_BEGIN = "file:";
private final static String URI_VALID_FILE_BEGIN = "file:///";
private final static char URI_PATH_SEP = '/';
/**
* Fixes common problems in uri, mainly related to Windows
*
* @param uri The uri to sanitize
* @return The sanitized uri
*/
public static String sanitizeURI(String uri) {
if (uri != null) {
StringBuilder reconstructed = new StringBuilder();
String uriCp = uri.replaceAll(" ", SPACE_ENCODED); //Don't trust servers
if (!uri.startsWith(URI_FILE_BEGIN)) {
LOG.warn("Malformed uri : " + uri);
return uri; //Probably not an uri
} else {
uriCp = uriCp.substring(URI_FILE_BEGIN.length());
while (uriCp.startsWith(Character.toString(URI_PATH_SEP))) {
uriCp = uriCp.substring(1);
}
reconstructed.append(URI_VALID_FILE_BEGIN);
if (os == OS.UNIX) {
return reconstructed.append(uriCp).toString();
} else {
reconstructed.append(uriCp.substring(0, uriCp.indexOf(URI_PATH_SEP)));
char driveLetter = reconstructed.charAt(URI_VALID_FILE_BEGIN.length());
if (Character.isLowerCase(driveLetter)) {
reconstructed.setCharAt(URI_VALID_FILE_BEGIN.length(), Character.toUpperCase(driveLetter));
}
if (reconstructed.toString().endsWith(COLON_ENCODED)) {
reconstructed.delete(reconstructed.length() - 3, reconstructed.length());
}
if (!reconstructed.toString().endsWith(":")) {
reconstructed.append(":");
}
return reconstructed.append(uriCp.substring(uriCp.indexOf(URI_PATH_SEP))).toString();
}
}
} else {
return null;
}
}
/**
* Returns the URI string corresponding to a VirtualFileSystem file
*
* @param file The file
* @return the URI
*/
public static String URIFromVirtualFile(VirtualFile file) {
return file == null? null : pathToUri(file.getPath());
}
/**
* Transforms an URI string into a VFS file
*
* @param uri The uri
* @return The virtual file
*/
public static VirtualFile virtualFileFromURI(URI uri) {
return LocalFileSystem.getInstance().findFileByIoFile(new File(uri));
}
/**
* Transforms an URI string into a VFS file
*
* @param uri The uri
* @return The virtual file
*/
public static VirtualFile virtualFileFromURI(String uri) {
try {
return virtualFileFromURI(new URI(sanitizeURI(uri)));
} catch (URISyntaxException e) {
LOG.warn(e);
return null;
}
}
/**
* Transforms a path into an URI string
*
* @param path The path
* @return The uri
*/
public static String pathToUri(@Nullable String path) {
return path != null ? sanitizeURI(new File(path).toURI().toString()) : null;
}
public static String pathToUri(@Nullable Path path) {
return path != null ? sanitizeURI(path.toUri().toString()) : null;
}
/**
* Object representing the OS type (Windows or Unix)
*/
public enum OS {
WINDOWS, UNIX
}
}

View file

@ -16,6 +16,10 @@
package com.falsepattern.zigbrains.common.util;
import lombok.val;
import java.util.Arrays;
public class StringUtil {
public static String blankToNull(String value) {
return value == null || value.isBlank() ? null : value;
@ -24,4 +28,56 @@ public class StringUtil {
public static String orEmpty(String value) {
return value == null ? "" : value;
}
private static final char[] VT100_CHARS = new char[256];
static {
Arrays.fill(VT100_CHARS, ' ');
VT100_CHARS[0x6A] = '┘';
VT100_CHARS[0x6B] = '┐';
VT100_CHARS[0x6C] = '┌';
VT100_CHARS[0x6D] = '└';
VT100_CHARS[0x6E] = '┼';
VT100_CHARS[0x71] = '─';
VT100_CHARS[0x74] = '├';
VT100_CHARS[0x75] = '┤';
VT100_CHARS[0x76] = '┴';
VT100_CHARS[0x77] = '┬';
VT100_CHARS[0x78] = '│';
}
private static final String VT100_BEGIN_SEQ = "\u001B(0";
private static final String VT100_END_SEQ = "\u001B(B";
private static final int VT100_BEGIN_SEQ_LENGTH = VT100_BEGIN_SEQ.length();
private static final int VT100_END_SEQ_LENGTH = VT100_END_SEQ.length();
public static String translateVT100Escapes(String text) {
int offset = 0;
val result = new StringBuilder();
val textLength = text.length();
while (offset < textLength) {
val startIndex = text.indexOf(VT100_BEGIN_SEQ, offset);
if (startIndex < 0) {
result.append(text.substring(offset, textLength).replace(VT100_END_SEQ, ""));
break;
}
result.append(text, offset, startIndex);
val blockOffset = startIndex + VT100_BEGIN_SEQ_LENGTH;
var endIndex = text.indexOf(VT100_END_SEQ, blockOffset);
if (endIndex < 0) {
endIndex = textLength;
}
for (int i = blockOffset; i < endIndex; i++) {
val c = text.charAt(i);
if (c >= 256) {
result.append(c);
} else {
result.append(VT100_CHARS[c]);
}
}
offset = endIndex + VT100_END_SEQ_LENGTH;
}
return result.toString();
}
}

View file

@ -40,6 +40,15 @@ public class TextFieldUtil {
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,
Disposable disposable,
@NlsContexts.DialogTitle String dialogTitle,

View file

@ -1,17 +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.
-->
<idea-plugin/>

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

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

View file

@ -14,9 +14,9 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.cpp;
package com.falsepattern.zigbrains.cpp;
import com.falsepattern.zigbrains.zig.debugbridge.DebuggerDriverProvider;
import com.falsepattern.zigbrains.debugbridge.DebuggerDriverProvider;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.jetbrains.cidr.cpp.execution.debugger.backend.CLionGDBDriverConfiguration;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugbridge;
package com.falsepattern.zigbrains.debugbridge;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;

View file

@ -14,13 +14,15 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugger;
package com.falsepattern.zigbrains.debugger;
import com.falsepattern.zigbrains.zig.debugbridge.DebuggerDriverProvider;
import com.falsepattern.zigbrains.debugbridge.DebuggerDriverProvider;
import com.falsepattern.zigbrains.debugger.win.WinDebuggerDriverConfiguration;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
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.lldb.LLDBDriverConfiguration;
import lombok.val;
@ -28,6 +30,9 @@ import org.jetbrains.annotations.Nullable;
public class Utils {
public static @Nullable DebuggerDriverConfiguration getDebuggerConfiguration(Project project) {
if (OS.CURRENT == OS.Windows) {
return new WinDebuggerDriverConfiguration();
}
val providedDebugger = DebuggerDriverProvider.findDebuggerConfigurations(project)
.filter(x -> x instanceof DebuggerDriverConfiguration)
.map(x -> (DebuggerDriverConfiguration)x)

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugger;
package com.falsepattern.zigbrains.debugger;
import com.jetbrains.cidr.execution.debugger.CidrDebuggerEditorsExtensionBase;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugger;
package com.falsepattern.zigbrains.debugger;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver;
import lombok.AccessLevel;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugger;
package com.falsepattern.zigbrains.debugger;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.intellij.execution.configurations.RunProfile;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugger;
package com.falsepattern.zigbrains.debugger;
import com.falsepattern.zigbrains.zig.ZigFileType;
import com.intellij.openapi.fileTypes.FileType;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugger;
package com.falsepattern.zigbrains.debugger;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.filters.Filter;

View file

@ -0,0 +1,47 @@
/*
* 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.debugger;
import com.intellij.openapi.project.Project;
import com.intellij.xdebugger.XSourcePosition;
import com.jetbrains.cidr.execution.debugger.backend.LLValue;
import com.jetbrains.cidr.execution.debugger.evaluation.LocalVariablesFilterHandler;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class ZigVariablesFilterHandler implements LocalVariablesFilterHandler {
@NotNull
@Override
public CompletableFuture<List<LLValue>> filterVars(@NotNull Project project, @NotNull XSourcePosition xSourcePosition, @NotNull List<? extends LLValue> list) {
return CompletableFuture.supplyAsync(() -> {
val vf = xSourcePosition.getFile();
if ("zig".equals(vf.getExtension())) {
return new ArrayList<>(list);
}
return List.of();
});
}
@Override
public boolean canFilterAtPos(@NotNull Project proj, @NotNull XSourcePosition pos) {
return "zig".equals(pos.getFile().getExtension());
}
}

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.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.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.debugger.dap;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.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.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.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.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.
*/
package com.falsepattern.zigbrains.zig.debugger.base;
package com.falsepattern.zigbrains.debugger.runner.base;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
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.debugger.backend.DebuggerDriverConfiguration;
import lombok.RequiredArgsConstructor;
@ -38,6 +40,6 @@ public abstract class ZigDebugParametersBase<ProfileState extends ProfileStateBa
@Override
public @Nullable String getArchitectureId() {
return null;
return ArchitectureType.forVmCpuArch(CpuArch.CURRENT).getId();
}
}

View file

@ -14,13 +14,13 @@
* limitations under the License.
*/
package com.falsepattern.zigbrains.zig.debugger.base;
package com.falsepattern.zigbrains.debugger.runner.base;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.runconfig.ZigProgramRunnerBase;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.Utils;
import com.falsepattern.zigbrains.zig.debugger.ZigLocalDebugProcess;
import com.falsepattern.zigbrains.debugger.Utils;
import com.falsepattern.zigbrains.debugger.ZigLocalDebugProcess;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.executors.DefaultDebugExecutor;

View file

@ -0,0 +1,53 @@
/*
* Copyright 2023-2024 FalsePattern
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.debugger.runner.binary;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.project.execution.binary.ProfileStateBinary;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
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;
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.debugger.runner.binary;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase;
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.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.debugger.runner.build;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.project.execution.build.ProfileStateBuild;
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 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.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.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.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.
*/
package com.falsepattern.zigbrains.zig.debugger.run;
package com.falsepattern.zigbrains.debugger.runner.run;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.project.execution.run.ProfileStateRun;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugParametersBase;
import com.jetbrains.cidr.execution.Installer;
import com.jetbrains.cidr.execution.TrivialInstaller;
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration;
import org.jetbrains.annotations.NotNull;
@ -31,6 +31,11 @@ public class ZigDebugParametersRun extends ZigDebugParametersBase<ProfileStateRu
@Override
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.
*/
package com.falsepattern.zigbrains.zig.debugger.run;
package com.falsepattern.zigbrains.debugger.runner.run;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.run.ProfileStateRun;
import com.falsepattern.zigbrains.project.execution.run.ZigExecConfigRun;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugRunnerBase;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.runners.ExecutionEnvironment;
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.debugger.runner.test;
import com.falsepattern.zigbrains.project.execution.test.ProfileStateTest;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller;
import com.falsepattern.zigbrains.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.
*/
package com.falsepattern.zigbrains.zig.debugger.test;
package com.falsepattern.zigbrains.debugger.runner.test;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.test.ProfileStateTest;
import com.falsepattern.zigbrains.project.execution.test.ZigExecConfigTest;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.zig.debugger.base.ZigDebugRunnerBase;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase;
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.notification.Notification;

View file

@ -0,0 +1,166 @@
/*
* 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.debugger.win;
import com.falsepattern.zigbrains.debugger.dap.DAPDriver;
import com.falsepattern.zigbrains.debugger.dap.WrappedDebugServer;
import com.intellij.execution.ExecutionException;
import com.intellij.util.system.CpuArch;
import com.jetbrains.cidr.ArchitectureType;
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.jsonrpc.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.debugger.win;
import com.falsepattern.zigbrains.debugger.dap.DAPDebuggerDriverConfiguration;
import com.falsepattern.zigbrains.debugger.win.config.WinDebuggerConfigService;
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.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,88 @@
/*
* 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.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.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

@ -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

@ -14,10 +14,10 @@
~ limitations under the License.
-->
<idea-plugin package="com.falsepattern.zigbrains.zig.cpp">
<idea-plugin package="com.falsepattern.zigbrains.cpp">
<depends>com.intellij.modules.clion</depends>
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
<debuggerDriverProvider implementation="com.falsepattern.zigbrains.zig.cpp.CPPDebuggerDriverProvider"/>
<debuggerDriverProvider implementation="com.falsepattern.zigbrains.cpp.CPPDebuggerDriverProvider"/>
</extensions>
</idea-plugin>

View file

@ -14,29 +14,37 @@
~ limitations under the License.
-->
<idea-plugin package="com.falsepattern.zigbrains.zig.debugger">
<idea-plugin package="com.falsepattern.zigbrains.debugger">
<depends>com.intellij.modules.cidr.debugger</depends>
<resource-bundle>zigbrains.zig.debugger.Bundle</resource-bundle>
<resource-bundle>zigbrains.debugger.Bundle</resource-bundle>
<extensions defaultExtensionNs="com.intellij">
<programRunner implementation="com.falsepattern.zigbrains.zig.debugger.run.ZigDebugRunnerRun"
<programRunner implementation="com.falsepattern.zigbrains.debugger.runner.run.ZigDebugRunnerRun"
id="ZigDebugRunnerRun"/>
<programRunner implementation="com.falsepattern.zigbrains.zig.debugger.test.ZigDebugRunnerTest"
<programRunner implementation="com.falsepattern.zigbrains.debugger.runner.test.ZigDebugRunnerTest"
id="ZigDebugRunnerTest"/>
<programRunner implementation="com.falsepattern.zigbrains.debugger.runner.build.ZigDebugRunnerBuild"
id="ZigDebugRunnerBuild"/>
<programRunner implementation="com.falsepattern.zigbrains.debugger.runner.binary.ZigDebugRunnerBinary"
id="ZigDebugRunnerBinary"/>
<notificationGroup displayType="BALLOON"
bundle="zigbrains.zig.debugger.Bundle"
bundle="zigbrains.debugger.Bundle"
key="notif-debug-error"
id="ZigBrains.Debugger.Error"/>
<notificationGroup displayType="BALLOON"
bundle="zigbrains.zig.debugger.Bundle"
bundle="zigbrains.debugger.Bundle"
key="notif-debug-warn"
id="ZigBrains.Debugger.Warn"/>
<applicationConfigurable parentId="project.propDebugger"
instance="com.falsepattern.zigbrains.debugger.win.config.WinDebuggerConfigurable"
displayName="Zig (Windows)"/>
</extensions>
<extensions defaultExtensionNs="cidr.debugger">
<languageSupport language="Zig" implementationClass="com.falsepattern.zigbrains.zig.debugger.ZigDebuggerLanguageSupport"/>
<editorsExtension language="Zig" implementationClass="com.falsepattern.zigbrains.zig.debugger.ZigDebuggerEditorsExtension"/>
<lineBreakpointFileTypesProvider implementation="com.falsepattern.zigbrains.zig.debugger.ZigLineBreakpointFileTypesProvider"/>
<languageSupport language="Zig" implementationClass="com.falsepattern.zigbrains.debugger.ZigDebuggerLanguageSupport"/>
<editorsExtension language="Zig" implementationClass="com.falsepattern.zigbrains.debugger.ZigDebuggerEditorsExtension"/>
<lineBreakpointFileTypesProvider implementation="com.falsepattern.zigbrains.debugger.ZigLineBreakpointFileTypesProvider"/>
<localVariablesFilterHandler implementation="com.falsepattern.zigbrains.debugger.ZigVariablesFilterHandler"/>
</extensions>
</idea-plugin>

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.client.connection;
package com.falsepattern.zigbrains.lspcommon.connection;
import com.intellij.openapi.diagnostic.Logger;
import org.jetbrains.annotations.NotNull;
@ -40,7 +40,7 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
@Nullable
private ProcessBuilder builder;
@Nullable
private Process process = null;
protected Process process = null;
private List<String> commands;
private String workingDir;
@ -55,7 +55,7 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
}
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());
}
ProcessBuilder builder = createProcessBuilder();
@ -86,6 +86,12 @@ public class ProcessStreamConnectionProvider implements StreamConnectionProvider
return process != null ? process.getInputStream() : null;
}
@Nullable
@Override
public InputStream getErrorStream() {
return process != null ? process.getErrorStream() : null;
}
@Nullable
@Override
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
public boolean equals(Object obj) {
if (obj instanceof ProcessStreamConnectionProvider) {

View file

@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.falsepattern.zigbrains.lsp.client.connection;
package com.falsepattern.zigbrains.lspcommon.connection;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -25,6 +26,10 @@ public interface StreamConnectionProvider {
InputStream getInputStream();
default @Nullable InputStream getErrorStream() {
return null;
}
OutputStream getOutputStream();
void stop();

View file

@ -15,6 +15,7 @@
*/
package com.falsepattern.zigbrains.lsp;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition.LanguageServerDefinition;
import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper;
@ -27,6 +28,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import lombok.val;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
@ -43,7 +45,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.pool;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.pool;
public class IntellijLanguageClient {
@ -330,9 +332,14 @@ public class IntellijLanguageClient {
public static void removeWrapper(LanguageServerWrapper wrapper) {
if (wrapper.getProject() != null) {
String[] extensions = wrapper.getServerDefinition().ext.split(LanguageServerDefinition.SPLIT_CHAR);
val rootPath = wrapper.getProjectRootPath();
if (rootPath == null) {
LOG.error("Project root path is null");
return;
}
val absolutePath = FileUtil.pathToUri(rootPath);
for (String ext : extensions) {
MutablePair<String, String> extProjectPair = new MutablePair<>(ext, FileUtils.pathToUri(
new File(wrapper.getProjectRootPath()).getAbsolutePath()));
MutablePair<String, String> extProjectPair = new MutablePair<>(ext, absolutePath);
extToLanguageWrapper.remove(extProjectPair);
extToServerDefinition.remove(extProjectPair);
}

View file

@ -19,13 +19,10 @@ package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.requests.ReformatHandler;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;

View file

@ -20,13 +20,9 @@ import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.intellij.codeInsight.hint.actions.ShowImplementationsAction;
import com.intellij.codeInsight.navigation.CtrlMouseAction;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
@ -58,6 +54,8 @@ public class LSPGotoDefinitionAction extends ShowImplementationsAction {
super.actionPerformed(e);
return;
}
manager.gotoDefinition(psiElement);
if (!manager.gotoDefinition(psiElement)) {
super.actionPerformed(e);
}
}
}

View file

@ -15,14 +15,12 @@
*/
package com.falsepattern.zigbrains.lsp.actions;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.requests.ReformatHandler;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.intellij.codeInsight.actions.ReformatCodeAction;
import com.intellij.lang.LanguageFormatting;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
@ -51,7 +49,7 @@ public class LSPReformatAction extends ReformatCodeAction implements DumbAware {
super.actionPerformed(e);
return;
}
ApplicationUtils.writeAction(() -> FileDocumentManager.getInstance().saveDocument(editor.getDocument()));
ApplicationUtil.writeAction(() -> FileDocumentManager.getInstance().saveDocument(editor.getDocument()));
// if editor hasSelection, only reformat selection, not reformat the whole file
if (editor.getSelectionModel().hasSelection()) {
ReformatHandler.reformatSelection(editor);

View file

@ -22,7 +22,6 @@ import com.intellij.codeInsight.actions.LayoutCodeDialog;
import com.intellij.codeInsight.actions.LayoutCodeOptions;
import com.intellij.codeInsight.actions.ShowReformatFileDialog;
import com.intellij.codeInsight.actions.TextRangeType;
import com.intellij.lang.LanguageFormatting;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.diagnostic.Logger;

View file

@ -15,10 +15,10 @@
*/
package com.falsepattern.zigbrains.lsp.client;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.requests.WorkspaceEditHandler;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationAction;
import com.intellij.notification.NotificationGroup;
@ -123,7 +123,7 @@ public class DefaultLanguageClient implements LanguageClient {
@Override
public void publishDiagnostics(PublishDiagnosticsParams publishDiagnosticsParams) {
String uri = FileUtils.sanitizeURI(publishDiagnosticsParams.getUri());
String uri = FileUtil.sanitizeURI(publishDiagnosticsParams.getUri());
List<Diagnostic> diagnostics = publishDiagnosticsParams.getDiagnostics();
EditorEventManagerBase.diagnostics(uri, diagnostics);
}
@ -134,7 +134,7 @@ public class DefaultLanguageClient implements LanguageClient {
String message = messageParams.getMessage();
if (isModal) {
ApplicationUtils.invokeLater(() -> {
ApplicationUtil.invokeLater(() -> {
MessageType msgType = messageParams.getType();
switch (msgType) {
case Error:

View file

@ -15,7 +15,7 @@
*/
package com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition;
import com.falsepattern.zigbrains.lsp.client.connection.StreamConnectionProvider;
import com.falsepattern.zigbrains.lspcommon.connection.StreamConnectionProvider;
import com.intellij.openapi.diagnostic.Logger;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
@ -24,7 +24,6 @@ import org.eclipse.lsp4j.InitializeParams;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -32,7 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
/**
* A trait representing a ServerDefinition
*/
public class LanguageServerDefinition {
public abstract class LanguageServerDefinition {
private static final Logger LOG = Logger.getInstance(LanguageServerDefinition.class);
@ -75,18 +74,6 @@ public class LanguageServerDefinition {
}
}
/**
* Returns the initialization options for the given uri.
*
* @param uri file URI
* @return initialization options
* @deprecated use {@link #customizeInitializeParams(InitializeParams)} instead
*/
@Deprecated
public Object getInitializationOptions(URI uri) {
return null;
}
/**
* Use this method to modify the {@link InitializeParams} that was initialized by this library. The values
* assigned to the passed {@link InitializeParams} after this method ends will be the ones sent to the LSP server.
@ -107,9 +94,7 @@ public class LanguageServerDefinition {
* @param workingDir The root directory
* @return The stream connection provider
*/
public StreamConnectionProvider createConnectionProvider(String workingDir) {
throw new UnsupportedOperationException();
}
public abstract StreamConnectionProvider createConnectionProvider(String workingDir);
public ServerListener getServerListener() {
return ServerListener.DEFAULT;

View file

@ -15,8 +15,8 @@
*/
package com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition;
import com.falsepattern.zigbrains.lsp.client.connection.ProcessStreamConnectionProvider;
import com.falsepattern.zigbrains.lsp.client.connection.StreamConnectionProvider;
import com.falsepattern.zigbrains.lspcommon.connection.ProcessStreamConnectionProvider;
import com.falsepattern.zigbrains.lspcommon.connection.StreamConnectionProvider;
import java.util.Collections;
import java.util.Map;

View file

@ -15,8 +15,8 @@
*/
package com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition;
import com.falsepattern.zigbrains.lsp.client.connection.ProcessStreamConnectionProvider;
import com.falsepattern.zigbrains.lsp.client.connection.StreamConnectionProvider;
import com.falsepattern.zigbrains.lspcommon.connection.ProcessStreamConnectionProvider;
import com.falsepattern.zigbrains.lspcommon.connection.StreamConnectionProvider;
import java.util.Arrays;
import java.util.Collections;

View file

@ -15,6 +15,8 @@
*/
package com.falsepattern.zigbrains.lsp.client.languageserver.wrapper;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.client.DefaultLanguageClient;
import com.falsepattern.zigbrains.lsp.client.ServerWrapperBaseClientContext;
@ -23,7 +25,6 @@ import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.DefaultRequestManager;
import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.RequestManager;
import com.falsepattern.zigbrains.lsp.client.languageserver.serverdefinition.LanguageServerDefinition;
import com.falsepattern.zigbrains.lsp.editor.DocumentEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.extensions.LSPExtensionManager;
@ -33,7 +34,6 @@ import com.falsepattern.zigbrains.lsp.requests.Timeout;
import com.falsepattern.zigbrains.lsp.requests.Timeouts;
import com.falsepattern.zigbrains.lsp.statusbar.LSPServerStatusWidget;
import com.falsepattern.zigbrains.lsp.statusbar.LSPServerStatusWidgetFactory;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.falsepattern.zigbrains.lsp.utils.LSPException;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
@ -43,6 +43,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
@ -51,6 +52,7 @@ import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.remoteServer.util.CloudNotifier;
import com.intellij.util.PlatformIcons;
import lombok.val;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.lsp4j.ClientCapabilities;
@ -104,6 +106,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -131,12 +134,12 @@ import static com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus.
* The implementation of a LanguageServerWrapper (specific to a serverDefinition and a project)
*/
public class LanguageServerWrapper {
public LanguageServerDefinition serverDefinition;
public final LanguageServerDefinition serverDefinition;
private final LSPExtensionManager extManager;
private final Project project;
private final HashSet<Editor> toConnect = new HashSet<>();
private final String projectRootPath;
@Nullable
private final Path projectRootPath;
private final HashSet<String> urisUnderLspControl = new HashSet<>();
private final HashSet<Editor> connectedEditors = new HashSet<>();
private final Map<String, Set<EditorEventManager>> uriToEditorManagers = new HashMap<>();
@ -167,7 +170,12 @@ public class LanguageServerWrapper {
this.project = project;
// We need to keep the project rootPath in addition to the project instance, since we cannot get the project
// base path if the project is disposed.
this.projectRootPath = project.getBasePath();
val projectDir = ProjectUtil.guessProjectDir(project);
if (projectDir != null) {
this.projectRootPath = projectDir.toNioPath();
} else {
this.projectRootPath = null;
}
this.extManager = extManager;
projectToLanguageServerWrapper.put(project, this);
}
@ -182,7 +190,7 @@ public class LanguageServerWrapper {
}
public static LanguageServerWrapper forVirtualFile(VirtualFile file, Project project) {
return uriToLanguageServerWrapper.get(new ImmutablePair<>(FileUtils.VFSToURI(file), FileUtils.projectToUri(project)));
return uriToLanguageServerWrapper.get(new ImmutablePair<>(FileUtil.URIFromVirtualFile(file), FileUtils.projectToUri(project)));
}
/**
@ -201,7 +209,7 @@ public class LanguageServerWrapper {
return serverDefinition;
}
public String getProjectRootPath() {
public Path getProjectRootPath() {
return projectRootPath;
}
@ -237,7 +245,7 @@ public class LanguageServerWrapper {
String msg = String.format("%s \n is not initialized after %d seconds",
serverDefinition.toString(), Timeout.getTimeout(Timeouts.INIT) / 1000);
LOG.warn(msg, e);
ApplicationUtils.invokeLater(() -> {
ApplicationUtil.invokeLater(() -> {
if (!alreadyShownTimeout) {
notifier.showMessage(msg, MessageType.WARNING);
alreadyShownTimeout = true;
@ -280,7 +288,7 @@ public class LanguageServerWrapper {
return null;
}
VirtualFile currentOpenFile = selectedEditor.getFile();
VirtualFile requestedFile = FileUtils.virtualFileFromURI(uri);
VirtualFile requestedFile = FileUtil.virtualFileFromURI(uri);
if (currentOpenFile == null || requestedFile == null) {
return null;
}
@ -396,7 +404,7 @@ public class LanguageServerWrapper {
}
// Triggers annotators since this is the first editor which starts the LS
// and annotators are executed before LS is bootstrap to provide diagnostics.
ApplicationUtils.computableReadAction(() -> {
ApplicationUtil.computableReadAction(() -> {
PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (psiFile != null) {
DaemonCodeAnalyzer.getInstance(project).restart(psiFile);
@ -449,7 +457,7 @@ public class LanguageServerWrapper {
launcherFuture.cancel(true);
}
if (serverDefinition != null) {
serverDefinition.stop(projectRootPath);
serverDefinition.stop(projectRootPath != null ? projectRootPath.toString() : null);
}
for (Editor ed : new HashSet<>(connectedEditors)) {
disconnect(ed);
@ -496,7 +504,7 @@ public class LanguageServerWrapper {
if (status == STOPPED && !alreadyShownCrash && !alreadyShownTimeout) {
setStatus(STARTING);
try {
Pair<InputStream, OutputStream> streams = serverDefinition.start(projectRootPath);
Pair<InputStream, OutputStream> streams = serverDefinition.start(projectRootPath != null ? projectRootPath.toString() : null);
InputStream inputStream = streams.getKey();
OutputStream outputStream = streams.getValue();
InitializeParams initParams = getInitParams();
@ -540,7 +548,7 @@ public class LanguageServerWrapper {
});
} catch (LSPException | IOException | URISyntaxException e) {
LOG.warn(e);
ApplicationUtils.invokeLater(() ->
ApplicationUtil.invokeLater(() ->
notifier.showMessage(String.format("Can't start server due to %s", e.getMessage()),
MessageType.WARNING));
removeServerWrapper();
@ -550,7 +558,7 @@ public class LanguageServerWrapper {
private InitializeParams getInitParams() throws URISyntaxException {
InitializeParams initParams = new InitializeParams();
String projectRootUri = FileUtils.pathToUri(projectRootPath);
String projectRootUri = FileUtil.pathToUri(projectRootPath);
WorkspaceFolder workspaceFolder = new WorkspaceFolder(projectRootUri, this.project.getName());
initParams.setWorkspaceFolders(Collections.singletonList(workspaceFolder));
@ -591,8 +599,6 @@ public class LanguageServerWrapper {
new ClientCapabilities(workspaceClientCapabilities, textDocumentClientCapabilities, null));
initParams.setClientInfo(new ClientInfo(ApplicationInfo.getInstance().getVersionName(), ApplicationInfo.getInstance().getFullVersion()));
// custom initialization options and initialize params provided by users
initParams.setInitializationOptions(serverDefinition.getInitializationOptions(URI.create(initParams.getWorkspaceFolders().get(0).getUri())));
serverDefinition.customizeInitializeParams(initParams);
return initParams;
}
@ -639,7 +645,7 @@ public class LanguageServerWrapper {
if (crashCount <= 3) {
reconnect();
} else {
ApplicationUtils.invokeLater(() -> {
ApplicationUtil.invokeLater(() -> {
if (alreadyShownCrash) {
reconnect();
} else {
@ -679,7 +685,7 @@ public class LanguageServerWrapper {
List<String> connected = new ArrayList<>();
urisUnderLspControl.forEach(s -> {
try {
connected.add(new URI(FileUtils.sanitizeURI(s)).toString());
connected.add(new URI(FileUtil.sanitizeURI(s)).toString());
} catch (URISyntaxException e) {
LOG.warn(e);
}
@ -733,7 +739,7 @@ public class LanguageServerWrapper {
* @param projectUri The project root uri
*/
public void disconnect(String uri, String projectUri) {
uriToLanguageServerWrapper.remove(new ImmutablePair<>(FileUtils.sanitizeURI(uri), FileUtils.sanitizeURI(projectUri)));
uriToLanguageServerWrapper.remove(new ImmutablePair<>(FileUtil.sanitizeURI(uri), FileUtil.sanitizeURI(projectUri)));
Set<EditorEventManager> managers = uriToEditorManagers.get(uri);
if (managers == null) {
@ -752,7 +758,7 @@ public class LanguageServerWrapper {
}
}
urisUnderLspControl.remove(uri);
uriToLanguageServerWrapper.remove(new ImmutablePair<>(FileUtils.sanitizeURI(uri), FileUtils.sanitizeURI(projectUri)));
uriToLanguageServerWrapper.remove(new ImmutablePair<>(FileUtil.sanitizeURI(uri), FileUtil.sanitizeURI(projectUri)));
}
if (connectedEditors.isEmpty()) {
stop(true);
@ -787,7 +793,7 @@ public class LanguageServerWrapper {
* Reset language server wrapper state so it can be started again if it was failed earlier.
*/
public void restart() {
ApplicationUtils.pool(() -> {
ApplicationUtil.pool(() -> {
if (isRestartable()) {
alreadyShownCrash = false;
alreadyShownTimeout = false;

View file

@ -16,13 +16,14 @@
package com.falsepattern.zigbrains.lsp.contributors;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.requests.HoverHandler;
import com.falsepattern.zigbrains.lsp.requests.Timeout;
import com.falsepattern.zigbrains.lsp.requests.Timeouts;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter;
import com.intellij.model.Pointer;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
@ -33,6 +34,7 @@ import com.intellij.platform.backend.presentation.TargetPresentation;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiFileRange;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
@ -44,7 +46,6 @@ import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
public class LSPDocumentationTargetProvider implements DocumentationTargetProvider {
@Override
@ -86,7 +87,7 @@ public class LSPDocumentationTargetProvider implements DocumentationTargetProvid
return null;
}
var caretPos = editor.offsetToLogicalPosition(offset);
var serverPos = ApplicationUtils.computableReadAction(() -> DocumentUtils.logicalToLSPPos(caretPos, editor));
var serverPos = ApplicationUtil.computableReadAction(() -> DocumentUtils.logicalToLSPPos(caretPos, editor));
return DocumentationResult.asyncDocumentation(() -> {
var identifier = manager.getIdentifier();
var request = wrapper.getRequestManager().hover(new HoverParams(identifier, serverPos));
@ -102,13 +103,15 @@ public class LSPDocumentationTargetProvider implements DocumentationTargetProvid
return null;
}
String string = HoverHandler.getHoverString(hover);
val markdown = HoverHandler.getHoverString(hover);
val string = ApplicationUtil.computableReadAction(() -> DocMarkdownToHtmlConverter
.convert(manager.getProject(), markdown));
if (StringUtils.isEmpty(string)) {
LOG.warn(String.format("Hover string returned is empty for file %s and pos (%d;%d)",
identifier.getUri(), serverPos.getLine(), serverPos.getCharacter()));
return null;
}
return DocumentationResult.documentation(string.lines().collect(Collectors.joining("<br>\n")));
return DocumentationResult.documentation(string);
} catch (TimeoutException e) {
LOG.warn(e);
wrapper.notifyFailure(Timeouts.HOVER);

View file

@ -25,11 +25,10 @@ import com.intellij.codeInsight.hints.declarative.InlayTreeSink;
import com.intellij.codeInsight.hints.declarative.InlineInlayPosition;
import com.intellij.codeInsight.hints.declarative.OwnBypassCollector;
import com.intellij.codeInsight.hints.declarative.impl.DeclarativeInlayHintsPassFactory;
import com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.psi.PsiFile;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -92,12 +91,10 @@ public class LSPInlayHintProvider implements InlayHintsProvider {
tooltipText = switch (markup.getKind()) {
case "markdown" -> {
var markedContent = markup.getValue();
if (markedContent.isEmpty()) {
if (markedContent.isBlank()) {
yield "";
}
Parser parser = Parser.builder().build();
HtmlRenderer renderer = HtmlRenderer.builder().build();
yield "<html>" + renderer.render(parser.parse(markedContent)) + "</html>";
yield DocMarkdownToHtmlConverter.convert(manager.getProject(), markedContent);
}
default -> markup.getValue();
};

View file

@ -15,6 +15,7 @@
*/
package com.falsepattern.zigbrains.lsp.contributors.annotator;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper;
@ -32,8 +33,10 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.util.SmartList;
import lombok.val;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.DiagnosticSeverity;
import org.eclipse.lsp4j.DiagnosticTag;
@ -103,7 +106,7 @@ public class LSPAnnotator extends ExternalAnnotator<Object, Object> {
VirtualFile virtualFile = file.getVirtualFile();
if (FileUtils.isFileSupported(virtualFile) && IntellijLanguageClient.isExtensionSupported(virtualFile)) {
String uri = FileUtils.VFSToURI(virtualFile);
String uri = FileUtil.URIFromVirtualFile(virtualFile);
// TODO annotations are applied to a file / document not to an editor. so store them by file and not by editor..
EditorEventManager eventManager = EditorEventManagerBase.forUri(uri);
if (eventManager == null) {
@ -161,14 +164,17 @@ public class LSPAnnotator extends ExternalAnnotator<Object, Object> {
}
@Nullable
protected AnnotationBuilder createAnnotation(Editor editor, AnnotationHolder holder, Diagnostic diagnostic) {
protected TextRange getTextRange(Editor editor, Diagnostic diagnostic) {
final int start = DocumentUtils.LSPPosToOffset(editor, diagnostic.getRange().getStart());
final int end = DocumentUtils.LSPPosToOffset(editor, diagnostic.getRange().getEnd());
if (start >= end) {
if (start > end) {
return null;
}
final TextRange range = new TextRange(start, end);
return new TextRange(start, end);
}
@NotNull
protected AnnotationBuilder createAnnotation(@NotNull TextRange range, AnnotationHolder holder, Diagnostic diagnostic) {
return holder.newAnnotation(lspToIntellijAnnotationsMap.get(diagnostic.getSeverity()), diagnostic.getMessage())
.range(range);
}
@ -176,18 +182,47 @@ public class LSPAnnotator extends ExternalAnnotator<Object, Object> {
private void createAnnotations(AnnotationHolder holder, EditorEventManager eventManager) {
final List<Diagnostic> diagnostics = eventManager.getDiagnostics();
final Editor editor = eventManager.editor;
PsiFile file;
val document = editor.getDocument();
if (document != null) {
file = PsiDocumentManager.getInstance(eventManager.getProject()).getPsiFile(document);
} else {
file = null;
}
List<Annotation> annotations = new ArrayList<>();
diagnostics.forEach(d -> {
var annotation = createAnnotation(editor, holder, d);
if (annotation != null) {
if (d.getTags() != null && d.getTags().contains(DiagnosticTag.Deprecated)) {
annotation = annotation.highlightType(ProblemHighlightType.LIKE_DEPRECATED);
var range = getTextRange(editor, d);
if (range == null)
return;
blk:
if (file != null && range.getLength() == 0) {
val psiElement = file.findElementAt(range.getStartOffset());
if (psiElement != null && !psiElement.getText().equals(".")) {
range = psiElement.getTextRange();
break blk;
}
val psiElementForward = file.findElementAt(range.getStartOffset() + 1);
if (psiElementForward != null) {
range = psiElementForward.getTextRange();
break blk;
}
val psiElementBack = file.findElementAt(range.getStartOffset() - 1);
if (psiElementBack != null) {
range = psiElementBack.getTextRange();
break blk;
}
if (psiElement != null) {
range = psiElement.getTextRange();
}
annotation.create();
var theList = (SmartList<Annotation>) holder;
annotations.add(theList.get(theList.size() - 1));
}
var annotation = createAnnotation(range, holder, d);
if (d.getTags() != null && d.getTags().contains(DiagnosticTag.Deprecated)) {
annotation = annotation.highlightType(ProblemHighlightType.LIKE_DEPRECATED);
}
annotation.create();
var theList = (SmartList<Annotation>) holder;
annotations.add(theList.get(theList.size() - 1));
});
eventManager.setAnnotations(annotations);

View file

@ -15,7 +15,7 @@
*/
package com.falsepattern.zigbrains.lsp.contributors.psi;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
@ -700,7 +700,7 @@ public class LSPPsiElement extends PsiElementBase implements PsiNameIdentifierOw
if (editor == null) {
OpenFileDescriptor descriptor = new OpenFileDescriptor(getProject(), getContainingFile().getVirtualFile(),
getTextOffset());
ApplicationUtils.invokeLater(() -> ApplicationUtils
ApplicationUtil.invokeLater(() -> ApplicationUtil
.writeAction(() -> FileEditorManager.getInstance(getProject()).openTextEditor(descriptor, false)));
}
}

View file

@ -15,6 +15,7 @@
*/
package com.falsepattern.zigbrains.lsp.contributors.symbol;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.RequestManager;
@ -70,7 +71,7 @@ public class WorkspaceSymbolProvider {
final SymbolInformation information = (result.getSymbolInformation() != null) ?
result.getSymbolInformation() : from(result.getWorkspaceSymbol());
final Location location = information.getLocation();
final VirtualFile file = FileUtils.URIToVFS(location.getUri());
final VirtualFile file = FileUtil.virtualFileFromURI(location.getUri());
if (file != null) {
final LSPIconProvider iconProviderFor = GUIUtils.getIconProviderFor(result.getDefinition());

View file

@ -15,23 +15,18 @@
*/
package com.falsepattern.zigbrains.lsp.editor;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.util.text.StringUtil;
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.TextDocumentItem;
@ -78,49 +73,50 @@ public class DocumentEventManager {
DidChangeTextDocumentParams changesParams = new DidChangeTextDocumentParams(new VersionedTextDocumentIdentifier(),
Collections.singletonList(new TextDocumentContentChangeEvent()));
changesParams.getTextDocument().setUri(identifier.getUri());
changesParams.getTextDocument().setVersion(++version);
if (syncKind == TextDocumentSyncKind.Incremental) {
TextDocumentContentChangeEvent changeEvent = changesParams.getContentChanges().get(0);
CharSequence newText = event.getNewFragment();
int offset = event.getOffset();
int newTextLength = event.getNewLength();
EditorEventManager editorEventManager = EditorEventManagerBase.forUri(FileUtils.documentToUri(document));
if (editorEventManager == null) {
LOG.warn("no editor associated with document");
return;
}
Editor editor = editorEventManager.editor;
Position lspPosition = DocumentUtils.offsetToLSPPos(editor, offset);
if (lspPosition == null) {
return;
}
int startLine = lspPosition.getLine();
int startColumn = lspPosition.getCharacter();
CharSequence oldText = event.getOldFragment();
//if text was deleted/replaced, calculate the end position of inserted/deleted text
int endLine, endColumn;
if (oldText.length() > 0) {
endLine = startLine + StringUtil.countNewLines(oldText);
String content = oldText.toString();
String[] oldLines = content.split("\n");
int oldTextLength = oldLines.length == 0 ? 0 : oldLines[oldLines.length - 1].length();
endColumn = content.endsWith("\n") ? 0 : oldLines.length == 1 ? startColumn + oldTextLength : oldTextLength;
} else { //if insert or no text change, the end position is the same
endLine = startLine;
endColumn = startColumn;
}
Range range = new Range(new Position(startLine, startColumn), new Position(endLine, endColumn));
changeEvent.setRange(range);
changeEvent.setText(newText.toString());
} else if (syncKind == TextDocumentSyncKind.Full) {
// TODO this incremental update logic is kinda broken, investigate later...
// if (syncKind == TextDocumentSyncKind.Incremental) {
// TextDocumentContentChangeEvent changeEvent = changesParams.getContentChanges().get(0);
// CharSequence newText = event.getNewFragment();
// int offset = event.getOffset();
// int newTextLength = event.getNewLength();
//
// EditorEventManager editorEventManager = EditorEventManagerBase.forUri(FileUtils.documentToUri(document));
// if (editorEventManager == null) {
// LOG.warn("no editor associated with document");
// return;
// }
// Editor editor = editorEventManager.editor;
// Position lspPosition = DocumentUtils.offsetToLSPPos(editor, offset);
// if (lspPosition == null) {
// return;
// }
// int startLine = lspPosition.getLine();
// int startColumn = lspPosition.getCharacter();
// CharSequence oldText = event.getOldFragment();
//
// //if text was deleted/replaced, calculate the end position of inserted/deleted text
// int endLine, endColumn;
// if (oldText.length() > 0) {
// endLine = startLine + StringUtil.countNewLines(oldText);
// String content = oldText.toString();
// String[] oldLines = content.split("\n");
// int oldTextLength = oldLines.length == 0 ? 0 : oldLines[oldLines.length - 1].length();
// endColumn = content.endsWith("\n") ? 0 : oldLines.length == 1 ? startColumn + oldTextLength : oldTextLength;
// } else { //if insert or no text change, the end position is the same
// endLine = startLine;
// endColumn = startColumn;
// }
// Range range = new Range(new Position(startLine, startColumn), new Position(endLine, endColumn));
// changeEvent.setRange(range);
// changeEvent.setText(newText.toString());
// } else if (syncKind == TextDocumentSyncKind.Full) {
if (syncKind != TextDocumentSyncKind.None) {
changesParams.getContentChanges().get(0).setText(document.getText());
}
ApplicationUtils.pool(() -> wrapper.getRequestManager().didChange(changesParams));
// }
ApplicationUtil.pool(() -> wrapper.getRequestManager().didChange(changesParams));
}
public void documentOpened() {

View file

@ -15,6 +15,8 @@
*/
package com.falsepattern.zigbrains.lsp.editor;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.actions.LSPReferencesAction;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerOptions;
import com.falsepattern.zigbrains.lsp.client.languageserver.requestmanager.RequestManager;
@ -25,11 +27,9 @@ import com.falsepattern.zigbrains.lsp.contributors.icon.LSPIconProvider;
import com.falsepattern.zigbrains.lsp.contributors.psi.LSPPsiElement;
import com.falsepattern.zigbrains.lsp.contributors.rename.LSPRenameProcessor;
import com.falsepattern.zigbrains.lsp.listeners.LSPCaretListenerImpl;
import com.falsepattern.zigbrains.lsp.requests.HoverHandler;
import com.falsepattern.zigbrains.lsp.requests.Timeout;
import com.falsepattern.zigbrains.lsp.requests.Timeouts;
import com.falsepattern.zigbrains.lsp.requests.WorkspaceEditHandler;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.falsepattern.zigbrains.lsp.utils.GUIUtils;
@ -54,15 +54,10 @@ import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.event.EditorMouseEvent;
import com.intellij.openapi.editor.event.EditorMouseListener;
import com.intellij.openapi.editor.event.EditorMouseMotionListener;
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
@ -72,7 +67,6 @@ import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@ -96,8 +90,6 @@ import org.eclipse.lsp4j.DocumentFormattingParams;
import org.eclipse.lsp4j.DocumentRangeFormattingParams;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.FormattingOptions;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.HoverParams;
import org.eclipse.lsp4j.InlayHint;
import org.eclipse.lsp4j.InlayHintParams;
import org.eclipse.lsp4j.InsertReplaceEdit;
@ -124,7 +116,6 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.Tuple;
import org.eclipse.lsp4j.services.TextDocumentService;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.Icon;
import java.awt.Point;
@ -144,15 +135,14 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.computableReadAction;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.computableWriteAction;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.invokeLater;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.pool;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.writeAction;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.computableReadAction;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.computableWriteAction;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.invokeLater;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.pool;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.writeAction;
import static com.falsepattern.zigbrains.lsp.utils.DocumentUtils.toEither;
import static com.falsepattern.zigbrains.lsp.utils.GUIUtils.createAndShowEditorHint;
@ -370,8 +360,8 @@ public class EditorEventManager {
res.forEach(l -> {
Position start = l.getRange().getStart();
Position end = l.getRange().getEnd();
String uri = FileUtils.sanitizeURI(l.getUri());
VirtualFile file = FileUtils.virtualFileFromURI(uri);
String uri = FileUtil.sanitizeURI(l.getUri());
VirtualFile file = FileUtil.virtualFileFromURI(uri);
if (fast) {
if (file == null)
return;
@ -1228,7 +1218,7 @@ public class EditorEventManager {
}
public List<InlayHint> inlayHint() {
var range = ApplicationUtils.computableReadAction(() -> {
var range = ApplicationUtil.computableReadAction(() -> {
var start = DocumentUtils.offsetToLSPPos(editor, 0);
var end = DocumentUtils.offsetToLSPPos(editor, editor.getDocument().getTextLength());
return new Range(start, end);
@ -1295,14 +1285,16 @@ public class EditorEventManager {
}
}
// Tries to go to definition
public void gotoDefinition(PsiElement element) {
public boolean gotoDefinition(PsiElement element) {
if (editor.isDisposed()) {
return;
return false;
}
val sourceOffset = element.getTextOffset();
val loc = requestDefinition(DocumentUtils.offsetToLSPPos(editor, sourceOffset));
if (loc == null)
return false;
gotoLocation(loc);
return gotoLocation(loc);
}
// Tries to go to declaration / show usages based on the element which is
@ -1322,7 +1314,7 @@ public class EditorEventManager {
return;
}
String locUri = FileUtils.sanitizeURI(loc.getUri());
String locUri = FileUtil.sanitizeURI(loc.getUri());
if (identifier.getUri().equals(locUri)
&& sourceOffset >= DocumentUtils.LSPPosToOffset(editor, loc.getRange().getStart())
@ -1337,7 +1329,7 @@ public class EditorEventManager {
}
}
public void gotoLocation(Location loc) {
public boolean gotoLocation(Location loc) {
VirtualFile file = null;
try {
file = VfsUtil.findFileByURL(new URL(loc.getUri()));
@ -1359,9 +1351,11 @@ public class EditorEventManager {
}
}
});
return true;
} else {
LOG.warn("Empty file for " + loc.getUri());
}
return false;
}
public void requestAndShowCodeActions() {

View file

@ -15,15 +15,12 @@
*/
package com.falsepattern.zigbrains.lsp.editor;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.falsepattern.zigbrains.lsp.utils.OSUtils;
import com.intellij.openapi.editor.Editor;
import lombok.val;
import org.eclipse.lsp4j.Diagnostic;
import java.awt.KeyboardFocusManager;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -86,7 +83,7 @@ public class EditorEventManagerBase {
if (runOnRegistry.containsKey(manager.editor)) {
var tasks = runOnRegistry.remove(manager.editor);
for (var task: tasks) {
ApplicationUtils.invokeLater(task);
ApplicationUtil.invokeLater(task);
}
}
}
@ -97,7 +94,7 @@ public class EditorEventManagerBase {
var manager = forEditor(editor);
if (manager != null) {
for (var task: runnables) {
ApplicationUtils.invokeLater(task);
ApplicationUtil.invokeLater(task);
}
} else {
runOnRegistry.computeIfAbsent(editor, (ignored) -> new ArrayList<>()).addAll(List.of(runnables));

View file

@ -15,11 +15,12 @@
*/
package com.falsepattern.zigbrains.lsp.listeners;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.client.languageserver.ServerStatus;
import com.falsepattern.zigbrains.lsp.client.languageserver.wrapper.LanguageServerWrapper;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
@ -27,7 +28,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileMoveEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent;
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
import org.eclipse.lsp4j.FileChangeType;
import org.eclipse.lsp4j.FileEvent;
@ -51,7 +52,7 @@ class LSPFileEventManager {
* @param doc The document
*/
static void willSave(Document doc) {
String uri = FileUtils.VFSToURI(FileDocumentManager.getInstance().getFile(doc));
String uri = FileUtil.URIFromVirtualFile(FileDocumentManager.getInstance().getFile(doc));
EditorEventManagerBase.willSave(uri);
}
@ -72,16 +73,16 @@ class LSPFileEventManager {
if (!FileUtils.isFileSupported(file)) {
return;
}
String uri = FileUtils.VFSToURI(file);
String uri = FileUtil.URIFromVirtualFile(file);
if (uri == null) {
return;
}
ApplicationUtils.invokeAfterPsiEvents(() -> {
ApplicationUtil.invokeAfterPsiEvents(() -> {
EditorEventManagerBase.documentSaved(uri);
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri,
FileUtils.projectToUri(p), FileChangeType.Changed));
});
FileUtils.findProjectsFor(file)
.forEach(p -> changedConfiguration(uri, FileUtils.projectToUri(p), FileChangeType.Changed));
}, false, false);
}
/**
@ -89,19 +90,19 @@ class LSPFileEventManager {
*
* @param event The file move event
*/
static void fileMoved(VirtualFileMoveEvent event) {
static void fileMoved(VFileMoveEvent event) {
try {
VirtualFile file = event.getFile();
if (!FileUtils.isFileSupported(file)) {
return;
}
String newFileUri = FileUtils.VFSToURI(file);
String oldParentUri = FileUtils.VFSToURI(event.getOldParent());
String newFileUri = FileUtil.URIFromVirtualFile(file);
String oldParentUri = FileUtil.URIFromVirtualFile(event.getOldParent());
if (newFileUri == null || oldParentUri == null) {
return;
}
String oldFileUri = String.format("%s/%s", oldParentUri, event.getFileName());
String oldFileUri = String.format("%s/%s", oldParentUri, event.getFile().getName());
closeAndReopenAffectedFile(file, oldFileUri);
} catch (Exception e) {
LOG.warn("LSP file move event failed due to :", e);
@ -117,14 +118,14 @@ class LSPFileEventManager {
if (!FileUtils.isFileSupported(file)) {
return;
}
String uri = FileUtils.VFSToURI(file);
String uri = FileUtil.URIFromVirtualFile(file);
if (uri == null) {
return;
}
ApplicationUtils.invokeAfterPsiEvents(() -> {
ApplicationUtil.invokeAfterPsiEvents(() -> {
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri,
FileUtils.projectToUri(p), FileChangeType.Deleted));
});
}, true, true);
}
/**
@ -134,7 +135,7 @@ class LSPFileEventManager {
* @param newFileName the new file name
*/
static void fileRenamed(String oldFileName, String newFileName) {
ApplicationUtils.invokeAfterPsiEvents(() -> {
ApplicationUtil.invokeAfterPsiEvents(() -> {
try {
// Getting the right file is not trivial here since we only have the file name. Since we have to iterate over
// all opened projects and filter based on the file name.
@ -142,22 +143,24 @@ class LSPFileEventManager {
.flatMap(p -> searchFiles(newFileName, p).stream())
.collect(Collectors.toSet());
for (VirtualFile file : files) {
if (!FileUtils.isFileSupported(file)) {
continue;
ApplicationUtil.invokeLater(() -> {
for (VirtualFile file : files) {
if (!FileUtils.isFileSupported(file)) {
continue;
}
String newFileUri = FileUtil.URIFromVirtualFile(file);
String oldFileUri = newFileUri.replace(file.getName(), oldFileName);
closeAndReopenAffectedFile(file, oldFileUri);
}
String newFileUri = FileUtils.VFSToURI(file);
String oldFileUri = newFileUri.replace(file.getName(), oldFileName);
closeAndReopenAffectedFile(file, oldFileUri);
}
});
} catch (Exception e) {
LOG.warn("LSP file rename event failed due to : ", e);
}
});
}, true, false);
}
private static void closeAndReopenAffectedFile(VirtualFile file, String oldFileUri) {
String newFileUri = FileUtils.VFSToURI(file);
String newFileUri = FileUtil.URIFromVirtualFile(file);
// Notifies the language server.
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(oldFileUri,
@ -172,7 +175,7 @@ class LSPFileEventManager {
if (!newFileUri.equals(oldFileUri)) {
// Re-open file to so that the new editor will be connected to the language server.
FileEditorManager fileEditorManager = FileEditorManager.getInstance(p);
ApplicationUtils.invokeLater(() -> {
ApplicationUtil.invokeLater(() -> {
fileEditorManager.closeFile(file);
fileEditorManager.openFile(file, true);
});
@ -189,17 +192,17 @@ class LSPFileEventManager {
if (!FileUtils.isFileSupported(file)) {
return;
}
String uri = FileUtils.VFSToURI(file);
String uri = FileUtil.URIFromVirtualFile(file);
if (uri != null) {
ApplicationUtils.invokeAfterPsiEvents(() -> {
ApplicationUtil.invokeAfterPsiEvents(() -> {
FileUtils.findProjectsFor(file).forEach(p -> changedConfiguration(uri,
FileUtils.projectToUri(p), FileChangeType.Created));
});
}, true, true);
}
}
private static void changedConfiguration(String uri, String projectUri, FileChangeType typ) {
ApplicationUtils.pool(() -> {
ApplicationUtil.pool(() -> {
DidChangeWatchedFilesParams params = getDidChangeWatchedFilesParams(uri, typ);
Set<LanguageServerWrapper> wrappers = IntellijLanguageClient.getAllServerWrappersFor(projectUri);
if (wrappers == null) {

View file

@ -16,14 +16,42 @@
package com.falsepattern.zigbrains.lsp.listeners;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileCopyEvent;
import com.intellij.openapi.vfs.VirtualFileEvent;
import com.intellij.openapi.vfs.VirtualFileListener;
import com.intellij.openapi.vfs.VirtualFileMoveEvent;
import com.intellij.openapi.vfs.VirtualFilePropertyEvent;
import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent;
import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent;
import lombok.val;
import org.jetbrains.annotations.NotNull;
public class VFSListener implements VirtualFileListener {
import java.util.List;
public class VFSListener implements BulkFileListener {
@Override
public void before(@NotNull List<? extends @NotNull VFileEvent> events) {
}
@Override
public void after(@NotNull List<? extends @NotNull VFileEvent> events) {
for (val event: events) {
if (event instanceof VFilePropertyChangeEvent propEvent)
propertyChanged(propEvent);
else if (event instanceof VFileContentChangeEvent changeEvent)
contentsChanged(changeEvent);
else if (event instanceof VFileDeleteEvent deleteEvent)
fileDeleted(deleteEvent);
else if (event instanceof VFileMoveEvent moveEvent)
fileMoved(moveEvent);
else if (event instanceof VFileCopyEvent copyEvent)
fileCopied(copyEvent);
else if (event instanceof VFileCreateEvent createEvent)
fileCreated(createEvent);
}
}
/**
* Fired when a virtual file is renamed from within IDEA, or its writable status is changed.
@ -31,8 +59,7 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void propertyChanged(@NotNull VirtualFilePropertyEvent event) {
public void propertyChanged(@NotNull VFilePropertyChangeEvent event) {
if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) {
LSPFileEventManager.fileRenamed((String) event.getOldValue(), (String) event.getNewValue());
}
@ -43,8 +70,7 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void contentsChanged(@NotNull VirtualFileEvent event) {
public void contentsChanged(@NotNull VFileContentChangeEvent event) {
LSPFileEventManager.fileChanged(event.getFile());
}
@ -53,8 +79,7 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void fileDeleted(@NotNull VirtualFileEvent event) {
public void fileDeleted(@NotNull VFileDeleteEvent event) {
LSPFileEventManager.fileDeleted(event.getFile());
}
@ -63,8 +88,7 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void fileMoved(@NotNull VirtualFileMoveEvent event) {
public void fileMoved(@NotNull VFileMoveEvent event) {
LSPFileEventManager.fileMoved(event);
}
@ -73,9 +97,8 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void fileCopied(@NotNull VirtualFileCopyEvent event) {
fileCreated(event);
public void fileCopied(@NotNull VFileCopyEvent event) {
LSPFileEventManager.fileCreated(event.findCreatedFile());
}
/**
@ -83,44 +106,7 @@ public class VFSListener implements VirtualFileListener {
*
* @param event the event object containing information about the change.
*/
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
public void fileCreated(@NotNull VFileCreateEvent event) {
LSPFileEventManager.fileCreated(event.getFile());
}
/**
* Fired before the change of a name or writable status of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforePropertyChange(@NotNull VirtualFilePropertyEvent event) {
}
/**
* Fired before the change of contents of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforeContentsChange(@NotNull VirtualFileEvent event) {
}
/**
* Fired before the deletion of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforeFileDeletion(@NotNull VirtualFileEvent event) {
}
/**
* Fired before the movement of a file is processed.
*
* @param event the event object containing information about the change.
*/
@Override
public void beforeFileMovement(@NotNull VirtualFileMoveEvent event) {
}
}

View file

@ -16,9 +16,6 @@
package com.falsepattern.zigbrains.lsp.requests;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.ui.UIUtil;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.MarkedString;
import org.eclipse.lsp4j.MarkupContent;
@ -60,13 +57,9 @@ public class HoverHandler {
"```" + markedString.getLanguage() + " " + markedString.getValue() + "```" :
"";
}
Parser parser = Parser.builder().build();
HtmlRenderer renderer = HtmlRenderer.builder().build();
if (!string.isEmpty()) {
result.add(renderer.render(parser.parse(string)));
}
result.add(string);
}
return "<html>" + String.join("\n\n", result) + "</html>";
return String.join("\n", result);
} else {
return "";
}
@ -75,9 +68,7 @@ public class HoverHandler {
if (markedContent.isEmpty()) {
return "";
}
Parser parser = Parser.builder().build();
HtmlRenderer renderer = HtmlRenderer.builder().build();
return "<html>" + renderer.render(parser.parse(markedContent)) + "</html>";
return markedContent;
} else {
return "";
}

View file

@ -15,10 +15,11 @@
*/
package com.falsepattern.zigbrains.lsp.requests;
import com.falsepattern.zigbrains.common.util.ApplicationUtil;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.contributors.psi.LSPPsiElement;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManager;
import com.falsepattern.zigbrains.lsp.editor.EditorEventManagerBase;
import com.falsepattern.zigbrains.lsp.utils.ApplicationUtils;
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
import com.intellij.openapi.command.CommandProcessor;
@ -57,8 +58,8 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.invokeLater;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.writeAction;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.invokeLater;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.writeAction;
import static com.falsepattern.zigbrains.lsp.utils.DocumentUtils.toEither;
/**
@ -81,9 +82,9 @@ public class WorkspaceEditHandler {
TextEdit edit = new TextEdit(lspRange, newName);
String uri = null;
try {
uri = FileUtils.sanitizeURI(
new URL(ui.getVirtualFile().getUrl().replace(" ", FileUtils.SPACE_ENCODED)).toURI()
.toString());
uri = FileUtil.sanitizeURI(
new URL(ui.getVirtualFile().getUrl().replace(" ", FileUtil.SPACE_ENCODED)).toURI()
.toString());
} catch (MalformedURLException | URISyntaxException e) {
LOG.warn(e);
}
@ -131,7 +132,7 @@ public class WorkspaceEditHandler {
TextDocumentEdit textEdit = tEdit.getLeft();
VersionedTextDocumentIdentifier doc = textEdit.getTextDocument();
int version = doc.getVersion() != null ? doc.getVersion() : Integer.MAX_VALUE;
String uri = FileUtils.sanitizeURI(doc.getUri());
String uri = FileUtil.sanitizeURI(doc.getUri());
EditorEventManager manager = EditorEventManagerBase.forUri(uri);
if (manager != null) {
curProject[0] = manager.editor.getProject();
@ -151,7 +152,7 @@ public class WorkspaceEditHandler {
} else if (changes != null) {
changes.forEach((key, lChanges) -> {
String uri = FileUtils.sanitizeURI(key);
String uri = FileUtil.sanitizeURI(key);
EditorEventManager manager = EditorEventManagerBase.forUri(uri);
if (manager != null) {
@ -198,18 +199,18 @@ public class WorkspaceEditHandler {
Project[] projects = ProjectManager.getInstance().getOpenProjects();
//Infer the project from the uri
Project project = Stream.of(projects)
.map(p -> new ImmutablePair<>(FileUtils.VFSToURI(ProjectUtil.guessProjectDir(p)), p))
.map(p -> new ImmutablePair<>(FileUtil.URIFromVirtualFile(ProjectUtil.guessProjectDir(p)), p))
.filter(p -> uri.startsWith(p.getLeft())).sorted(Collections.reverseOrder())
.map(ImmutablePair::getRight).findFirst().orElse(projects[0]);
VirtualFile file = null;
try {
file = LocalFileSystem.getInstance().findFileByIoFile(new File(new URI(FileUtils.sanitizeURI(uri))));
file = LocalFileSystem.getInstance().findFileByIoFile(new File(new URI(FileUtil.sanitizeURI(uri))));
} catch (URISyntaxException e) {
LOG.warn(e);
}
FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
OpenFileDescriptor descriptor = new OpenFileDescriptor(project, file);
Editor editor = ApplicationUtils
Editor editor = ApplicationUtil
.computableWriteAction(() -> fileEditorManager.openTextEditor(descriptor, false));
openedEditors.add(file);
curProject[0] = editor.getProject();

View file

@ -31,7 +31,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Collectors;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.computableReadAction;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.computableReadAction;
import static java.lang.Math.max;
import static java.lang.Math.min;

View file

@ -15,6 +15,7 @@
*/
package com.falsepattern.zigbrains.lsp.utils;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.falsepattern.zigbrains.lsp.IntellijLanguageClient;
import com.falsepattern.zigbrains.lsp.extensions.LSPExtensionManager;
import com.intellij.openapi.diagnostic.Logger;
@ -27,21 +28,19 @@ import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.LightVirtualFileBase;
import lombok.val;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -50,19 +49,12 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.computableReadAction;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.computableReadAction;
/**
* Various file / uri related methods
*/
public class FileUtils {
private final static OS os = (System.getProperty("os.name").toLowerCase().contains("win")) ? OS.WINDOWS : OS.UNIX;
private final static String COLON_ENCODED = "%3A";
public final static String SPACE_ENCODED = "%20";
private final static String URI_FILE_BEGIN = "file:";
private final static String URI_VALID_FILE_BEGIN = "file:///";
private final static char URI_PATH_SEP = '/';
private static final Logger LOG = Logger.getInstance(FileUtils.class);
public static List<Editor> getAllOpenedEditors(Project project) {
@ -83,7 +75,7 @@ public class FileUtils {
}
public static List<Editor> getAllOpenedEditorsForUri(@NotNull Project project, String uri) {
VirtualFile file = virtualFileFromURI(uri);
VirtualFile file = FileUtil.virtualFileFromURI(uri);
if (file == null)
return Collections.emptyList();
return getAllOpenedEditorsForVirtualFile(project, file);
@ -106,38 +98,13 @@ public class FileUtils {
});
}
/**
* This can be used to instantly apply a language server definition without restarting the IDE.
*/
public static void reloadAllEditors() {
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
for (Project project : openProjects) {
reloadEditors(project);
}
}
/**
* This can be used to instantly apply a project-specific language server definition without restarting the
* project/IDE.
*
* @param project The project instance which need to be restarted
*/
public static void reloadEditors(@NotNull Project project) {
try {
List<Editor> allOpenedEditors = FileUtils.getAllOpenedEditors(project);
allOpenedEditors.forEach(IntellijLanguageClient::editorClosed);
allOpenedEditors.forEach(IntellijLanguageClient::editorOpened);
} catch (Exception e) {
LOG.warn(String.format("Refreshing project: %s is failed due to: ", project.getName()), e);
}
}
public static Editor editorFromPsiFile(PsiFile psiFile) {
return editorFromVirtualFile(psiFile.getVirtualFile(), psiFile.getProject());
}
public static Editor editorFromUri(String uri, Project project) {
return editorFromVirtualFile(virtualFileFromURI(uri), project);
return editorFromVirtualFile(FileUtil.virtualFileFromURI(uri), project);
}
@Nullable
@ -149,15 +116,6 @@ public class FileUtils {
return null;
}
public static VirtualFile virtualFileFromURI(String uri) {
try {
return LocalFileSystem.getInstance().findFileByIoFile(new File(new URI(sanitizeURI(uri))));
} catch (URISyntaxException e) {
LOG.warn(e);
return null;
}
}
/**
* Returns a file type given an editor
*
@ -185,79 +143,14 @@ public class FileUtils {
* @return The URI
*/
public static String editorToURIString(Editor editor) {
return sanitizeURI(VFSToURI(FileDocumentManager.getInstance().getFile(editor.getDocument())));
return FileUtil.sanitizeURI(
FileUtil.URIFromVirtualFile(FileDocumentManager.getInstance().getFile(editor.getDocument())));
}
public static VirtualFile virtualFileFromEditor(Editor editor) {
return FileDocumentManager.getInstance().getFile(editor.getDocument());
}
/**
* Returns the URI string corresponding to a VirtualFileSystem file
*
* @param file The file
* @return the URI
*/
public static String VFSToURI(VirtualFile file) {
return file == null? null : pathToUri(file.getPath());
}
/**
* Fixes common problems in uri, mainly related to Windows
*
* @param uri The uri to sanitize
* @return The sanitized uri
*/
public static String sanitizeURI(String uri) {
if (uri != null) {
StringBuilder reconstructed = new StringBuilder();
String uriCp = uri.replaceAll(" ", SPACE_ENCODED); //Don't trust servers
if (!uri.startsWith(URI_FILE_BEGIN)) {
LOG.warn("Malformed uri : " + uri);
return uri; //Probably not an uri
} else {
uriCp = uriCp.substring(URI_FILE_BEGIN.length());
while (uriCp.startsWith(Character.toString(URI_PATH_SEP))) {
uriCp = uriCp.substring(1);
}
reconstructed.append(URI_VALID_FILE_BEGIN);
if (os == OS.UNIX) {
return reconstructed.append(uriCp).toString();
} else {
reconstructed.append(uriCp.substring(0, uriCp.indexOf(URI_PATH_SEP)));
char driveLetter = reconstructed.charAt(URI_VALID_FILE_BEGIN.length());
if (Character.isLowerCase(driveLetter)) {
reconstructed.setCharAt(URI_VALID_FILE_BEGIN.length(), Character.toUpperCase(driveLetter));
}
if (reconstructed.toString().endsWith(COLON_ENCODED)) {
reconstructed.delete(reconstructed.length() - 3, reconstructed.length());
}
if (!reconstructed.toString().endsWith(":")) {
reconstructed.append(":");
}
return reconstructed.append(uriCp.substring(uriCp.indexOf(URI_PATH_SEP))).toString();
}
}
} else {
return null;
}
}
/**
* Transforms an URI string into a VFS file
*
* @param uri The uri
* @return The virtual file
*/
public static VirtualFile URIToVFS(String uri) {
try {
return LocalFileSystem.getInstance().findFileByIoFile(new File(new URI(sanitizeURI(uri))));
} catch (URISyntaxException e) {
LOG.warn(e);
return null;
}
}
/**
* Returns the project base dir uri given an editor
*
@ -265,42 +158,82 @@ public class FileUtils {
* @return The project whose the editor belongs
*/
public static String editorToProjectFolderUri(Editor editor) {
return pathToUri(editorToProjectFolderPath(editor));
return FileUtil.pathToUri(editorToProjectFolderPath(editor));
}
public static String editorToProjectFolderPath(Editor editor) {
if (editor != null && editor.getProject() != null && editor.getProject().getBasePath() != null) {
return new File(editor.getProject().getBasePath()).getAbsolutePath();
}
return null;
}
if (editor == null)
return null;
/**
* Transforms a path into an URI string
*
* @param path The path
* @return The uri
*/
public static String pathToUri(@Nullable String path) {
return path != null ? sanitizeURI(new File(path).toURI().toString()) : null;
val project = editor.getProject();
if (project == null)
return null;
val projectDir = ProjectUtil.guessProjectDir(editor.getProject());
if (projectDir == null)
return null;
return projectDir.toNioPath().toAbsolutePath().toString();
}
public static String projectToUri(Project project) {
if (project != null && project.getBasePath() != null) {
return pathToUri(new File(project.getBasePath()).getAbsolutePath());
}
return null;
if (project == null)
return null;
val path = ProjectUtil.guessProjectDir(project);
if (path == null)
return null;
return FileUtil.pathToUri(path.toNioPath());
}
public static String documentToUri(Document document) {
return sanitizeURI(VFSToURI(FileDocumentManager.getInstance().getFile(document)));
return FileUtil.sanitizeURI(FileUtil.URIFromVirtualFile(FileDocumentManager.getInstance().getFile(document)));
}
/**
* Object representing the OS type (Windows or Unix)
* Find projects which contains the given file. This search runs among all open projects.
*/
public enum OS {
WINDOWS, UNIX
@NotNull
public static Set<Project> findProjectsFor(@NotNull VirtualFile file) {
return Arrays.stream(ProjectManager.getInstance().getOpenProjects())
.filter(p -> searchFiles(file.getName(), p).stream().anyMatch(f -> f.getPath().equals(file.getPath())))
.collect(Collectors.toSet());
}
public static Collection<VirtualFile> searchFiles(String fileName, Project p) {
try {
return computableReadAction(() -> FilenameIndex.getVirtualFilesByName(fileName, GlobalSearchScope.projectScope(p)));
} catch (Throwable t) {
// Todo - Find a proper way to handle when IDEA file indexing is in-progress.
return Collections.emptyList();
}
}
/**
* This can be used to instantly apply a language server definition without restarting the IDE.
*/
public static void reloadAllEditors() {
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
for (Project project : openProjects) {
reloadEditors(project);
}
}
/**
* This can be used to instantly apply a project-specific language server definition without restarting the
* project/IDE.
*
* @param project The project instance which need to be restarted
*/
public static void reloadEditors(@NotNull Project project) {
try {
List<Editor> allOpenedEditors = FileUtils.getAllOpenedEditors(project);
allOpenedEditors.forEach(IntellijLanguageClient::editorClosed);
allOpenedEditors.forEach(IntellijLanguageClient::editorOpened);
} catch (Exception e) {
LOG.warn(String.format("Refreshing project: %s is failed due to: ", project.getName()), e);
}
}
/**
@ -322,25 +255,6 @@ public class FileUtils {
return IntellijLanguageClient.isExtensionSupported(file);
}
/**
* Find projects which contains the given file. This search runs among all open projects.
*/
@NotNull
public static Set<Project> findProjectsFor(@NotNull VirtualFile file) {
return Arrays.stream(ProjectManager.getInstance().getOpenProjects())
.filter(p -> searchFiles(file.getName(), p).stream().anyMatch(f -> f.getPath().equals(file.getPath())))
.collect(Collectors.toSet());
}
public static Collection<VirtualFile> searchFiles(String fileName, Project p) {
try {
return computableReadAction(() -> FilenameIndex.getVirtualFilesByName(fileName, GlobalSearchScope.projectScope(p)));
} catch (Throwable t) {
// Todo - Find a proper way to handle when IDEA file indexing is in-progress.
return Collections.emptyList();
}
}
/**
* Checks if the file in editor is supported by this LS client library.
*/

View file

@ -46,7 +46,7 @@ import java.net.URISyntaxException;
import java.util.Objects;
import java.util.Optional;
import static com.falsepattern.zigbrains.lsp.utils.ApplicationUtils.writeAction;
import static com.falsepattern.zigbrains.common.util.ApplicationUtil.writeAction;
public final class GUIUtils {
private static final LSPDefaultIconProvider DEFAULT_ICON_PROVIDER = new LSPDefaultIconProvider();

View file

@ -1,17 +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.
-->
<idea-plugin/>

View file

@ -0,0 +1,29 @@
/*
* 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.console;
import com.intellij.execution.filters.ConsoleFilterProvider;
import com.intellij.execution.filters.Filter;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
public class ZigConsoleFilterProvider implements ConsoleFilterProvider {
@Override
public Filter @NotNull [] getDefaultFilters(@NotNull Project project) {
return new Filter[]{new ZigSourceFileFilter(project)};
}
}

View file

@ -0,0 +1,83 @@
/*
* 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.console;
import com.falsepattern.zigbrains.common.util.FileUtil;
import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.OpenFileHyperlinkInfo;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.vfs.VirtualFile;
import kotlin.Pair;
import lombok.RequiredArgsConstructor;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Optional;
import java.util.regex.Pattern;
@RequiredArgsConstructor
public class ZigSourceFileFilter implements Filter {
private final Project project;
private final Pattern LEN_REGEX = Pattern.compile(":(\\d+):(\\d+)");
private Pair<Path, Integer> findLongestParsablePathFromOffset(String line, int end, Path 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)) && projectPath != null) {
path = projectPath.resolve(pathStr);
if (!Files.exists(path) || !Files.isRegularFile(path))
continue;
}
longest = path;
longestStart = i;
} catch (InvalidPathException ignored){}
}
return new Pair<>(longest, longestStart);
}
@Nullable
@Override
public Result applyFilter(@NotNull String line, int entireLength) {
val lineStart = entireLength - line.length();
val projectPath = Optional.ofNullable(ProjectUtil.guessProjectDir(project)).map(VirtualFile::toNioPath).orElse(null);
val results = new ArrayList<ResultItem>();
val matcher = LEN_REGEX.matcher(line);
while (matcher.find()) {
val end = matcher.start();
val pair = findLongestParsablePathFromOffset(line, end, projectPath);
val path = pair.getFirst();
if (path == 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)));
}
return new Result(results);
}
}

View file

@ -18,13 +18,13 @@ package com.falsepattern.zigbrains.project.execution;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.CapturingAnsiEscapesAwareProcessHandler;
import com.intellij.util.io.BaseOutputReader;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public class ZigCapturingProcessHandler extends CapturingProcessHandler {
public class ZigCapturingProcessHandler extends CapturingAnsiEscapesAwareProcessHandler {
public static Optional<ZigCapturingProcessHandler> startProcess(GeneralCommandLine commandLine) {
try {
return Optional.of(new ZigCapturingProcessHandler(commandLine));

View file

@ -16,7 +16,6 @@
package com.falsepattern.zigbrains.project.execution.base;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.zig.parser.ZigFile;
import com.intellij.execution.actions.ConfigurationContext;
import com.intellij.execution.actions.LazyRunConfigurationProducer;
@ -27,6 +26,8 @@ import com.intellij.psi.PsiElement;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends LazyRunConfigurationProducer<T> {
@NotNull
@Override
@ -47,7 +48,7 @@ public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends
return false;
}
var theFile = psiFile.getVirtualFile();
var filePath = theFile.getPath();
var filePath = theFile.toNioPath();
return setupConfigurationFromContext(configuration, element, filePath, theFile);
}
@ -65,7 +66,7 @@ public abstract class ConfigProducerBase<T extends ZigExecConfigBase<T>> extends
return false;
}
val vFile = file.getVirtualFile();
val filePath = vFile.getPath();
val filePath = vFile.toNioPath();
return isConfigurationFromContext(configuration, filePath, vFile, element);
}
@ -95,6 +96,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 isConfigurationFromContext(@NotNull T configuration, String filePath, VirtualFile vFile, PsiElement element);
protected abstract boolean setupConfigurationFromContext(@NotNull T configuration, PsiElement element, Path filePath, VirtualFile theFile);
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,10 +16,10 @@
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.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.util.ProjectUtil;
import com.intellij.build.BuildTextConsoleView;
import com.intellij.execution.DefaultExecutionResult;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.CommandLineState;
@ -31,6 +31,7 @@ import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
public abstract class ProfileStateBase<T extends ZigExecConfigBase<T>> extends CommandLineState {
protected final T configuration;
@ -42,18 +43,20 @@ public abstract class ProfileStateBase<T extends ZigExecConfigBase<T>> extends C
@Override
protected @NotNull ProcessHandler startProcess() throws ExecutionException {
return new ZigCapturingProcessHandler(getCommandLine(ProjectUtil.getToolchain(getEnvironment().getProject())));
return new ZigProcessHandler(getCommandLine(ProjectUtil.getToolchain(getEnvironment().getProject()), false));
}
public GeneralCommandLine getCommandLine(AbstractZigToolchain toolchain) {
val workingDirectory = configuration.workingDirectory;
public GeneralCommandLine getCommandLine(AbstractZigToolchain toolchain, boolean debug) throws ExecutionException {
val workingDirectory = configuration.getWorkingDirectory();
val zigExecutablePath = toolchain.pathToExecutable("zig");
return new GeneralCommandLine().withExePath(zigExecutablePath.toString())
.withWorkDirectory(workingDirectory.toString())
.withCharset(StandardCharsets.UTF_8)
.withRedirectErrorStream(true)
.withParameters(configuration.buildCommandLineArgs());
val cli = new GeneralCommandLine();
cli.setExePath(zigExecutablePath.toString());
workingDirectory.getPath().ifPresent(x -> cli.setWorkDirectory(x.toFile()));
cli.setCharset(StandardCharsets.UTF_8);
cli.setRedirectErrorStream(true);
cli.addParameters(debug ? configuration.buildDebugCommandLineArgs() : configuration.buildCommandLineArgs());
return cli;
}
public T configuration() {
@ -63,7 +66,7 @@ public abstract class ProfileStateBase<T extends ZigExecConfigBase<T>> extends C
public DefaultExecutionResult executeCommandLine(GeneralCommandLine commandLine, ExecutionEnvironment environment)
throws ExecutionException {
val handler = startProcess(commandLine);
val console = getConsoleBuilder().getConsole();
val console = new BuildTextConsoleView(environment.getProject(), false, Collections.emptyList());
console.attachToProcess(handler);
return new DefaultExecutionResult(console, handler);
}

View file

@ -18,75 +18,462 @@ package com.falsepattern.zigbrains.project.execution.base;
import com.falsepattern.zigbrains.project.ui.WorkingDirectoryComponent;
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.SettingsEditor;
import com.intellij.openapi.ui.LabeledComponent;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.ui.ComboBox;
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.AlignY;
import com.intellij.ui.dsl.builder.Panel;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.val;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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.util.Objects;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static com.intellij.ui.dsl.builder.BuilderKt.panel;
public class ZigConfigEditor<T extends ZigExecConfigBase<T>> extends SettingsEditor<T> {
protected final LabeledComponent<TextFieldWithBrowseButton> workingDirectoryComponent =
new WorkingDirectoryComponent();
private final ZigExecConfigBase<T> state;
private final List<ZigConfigurable.ZigConfigModule<?>> configModules = new ArrayList<>();
public ZigConfigEditor(ZigExecConfigBase<T> state) {
this.state = state;
}
@Override
protected void applyEditorTo(@NotNull T s) throws ConfigurationException {
s.workingDirectory = Paths.get(workingDirectoryComponent.getComponent().getText());
try {
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
protected void resetEditorFrom(@NotNull T s) {
workingDirectoryComponent.getComponent().setText(Objects.requireNonNullElse(s.workingDirectory, "").toString());
outer:
for (val cfg: s.getConfigurables()) {
for (val module: configModules) {
if (module.tryReset(cfg))
continue outer;
}
}
}
@Override
protected final @NotNull JComponent createEditor() {
configModules.clear();
configModules.addAll(state.getConfigurables().stream().map(ZigConfigurable::createEditor).toList());
return panel((p) -> {
constructPanel(p);
for (val module: configModules) {
module.construct(p);
}
return null;
});
}
protected void constructPanel(Panel p) {
p.row(workingDirectoryComponent.getLabel(), (r) -> {
r.cell(workingDirectoryComponent).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
});
@Override
protected void disposeEditor() {
for (val module: configModules) {
module.dispose();
}
configModules.clear();
}
public static abstract class WithFilePath<T extends ZigExecConfigBase<T>> extends ZigConfigEditor<T> {
private final ZigFilePathPanel filePathPanel = new ZigFilePathPanel();
public interface ZigConfigurable<T extends ZigConfigurable<T>> extends Serializable, Cloneable {
void readExternal(@NotNull Element element);
void writeExternal(@NotNull Element element);
ZigConfigModule<T> createEditor();
T clone();
@Override
protected void applyEditorTo(@NotNull T s) throws ConfigurationException {
super.applyEditorTo(s);
setFilePath(s, filePathPanel.getText());
interface ZigConfigModule<T extends ZigConfigurable<T>> extends Disposable {
@Nullable T tryMatch(ZigConfigurable<?> cfg);
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;
}
default boolean tryReset(ZigConfigurable<?> cfg) {
val x = tryMatch(cfg);
if (x != null) {
reset(x);
return true;
}
return false;
}
void construct(Panel p);
}
}
public static abstract class PathConfigurable<T extends PathConfigurable<T>> implements ZigConfigurable<T> {
private @Nullable Path path = null;
public Optional<Path> getPath() {
return Optional.ofNullable(path);
}
public @NotNull Path getPathOrThrow() {
return getPath().orElseThrow(() -> new IllegalArgumentException("Empty file path!"));
}
public void setPath(@Nullable Path path) {
this.path = path;
}
@Override
protected void resetEditorFrom(@NotNull T s) {
super.resetEditorFrom(s);
filePathPanel.setText(Objects.requireNonNullElse(getFilePath(s), ""));
public void readExternal(@NotNull Element element) {
try {
ElementUtil.readString(element, getSerializedName()).map(Paths::get).ifPresent(x -> path = x);
} catch (InvalidPathException ignored){}
}
@Override
protected void constructPanel(Panel p) {
super.constructPanel(p);
p.row("Target file", (r) -> {
r.cell(filePathPanel).resizableColumn().align(AlignX.FILL).align(AlignY.FILL);
return null;
public void writeExternal(@NotNull Element element) {
ElementUtil.writeString(element, getSerializedName(), path == null ? null : path.toString());
}
@Override
@SneakyThrows
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);
}
}
@Getter(AccessLevel.PROTECTED)
@RequiredArgsConstructor
public static class WorkDirectoryConfigurable extends PathConfigurable<WorkDirectoryConfigurable> {
private transient final String serializedName;
@Override
public WorkDirectoryConfigModule createEditor() {
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
public void writeExternal(@NotNull Element element) {
ElementUtil.writeBoolean(element, serializedName, colored);
}
@Override
public ColoredConfigModule createEditor() {
return new ColoredConfigModule(serializedName);
}
@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);
});
}
protected abstract String getFilePath(T config);
protected abstract void setFilePath(T config, String path);
@Override
public void writeExternal(@NotNull Element element) {
val child = ElementUtil.writeChild(element, serializedName);
ElementUtil.writeEnum(child, "level", level);
ElementUtil.writeBoolean(child, "forced", forced);
}
@Override
public OptimizationConfigModule createEditor() {
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

@ -22,24 +22,60 @@ import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.configurations.LocatableConfigurationBase;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.NlsActions;
import com.intellij.openapi.vfs.VirtualFile;
import lombok.Getter;
import lombok.val;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
@Getter
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) {
super(project, factory, name);
workingDirectory = project.isDefault() ? null : Optional.ofNullable(project.getBasePath())
.map(Path::of)
.orElse(null);
workingDirectory.setPath(getProject().isDefault() ? null : Optional.ofNullable(ProjectUtil.guessProjectDir(getProject()))
.map(VirtualFile::toNioPath)
.orElse(null));
}
@Override
public @NotNull ZigConfigEditor<T> getConfigurationEditor() {
return new ZigConfigEditor<>(this);
}
@Override
public void readExternal(@NotNull Element element) throws InvalidDataException {
super.readExternal(element);
getConfigurables().forEach(cfg -> cfg.readExternal(element));
}
@Override
public void writeExternal(@NotNull Element element) {
super.writeExternal(element);
getConfigurables().forEach(cfg -> cfg.writeExternal(element));
}
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
public abstract @Nullable @NlsActions.ActionText String suggestedName();
@ -47,4 +83,7 @@ public abstract class ZigExecConfigBase<T extends ZigExecConfigBase<T>> extends
public abstract @Nullable ProfileStateBase<T> getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment)
throws ExecutionException;
public @NotNull List<ZigConfigEditor.@NotNull ZigConfigurable<?>> getConfigurables() {
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,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.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.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.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild> {
@Override
public @NotNull ConfigurationFactory getConfigurationFactory() {
@ -29,7 +32,7 @@ public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild>
}
@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)) {
configuration.setName("Build");
return true;
@ -38,7 +41,11 @@ public class ConfigProducerBuild extends ConfigProducerBase<ZigExecConfigBuild>
}
@Override
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigBuild configuration, String filePath, VirtualFile vFile, PsiElement element) {
return true;
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigBuild configuration, Path filePath, VirtualFile vFile, PsiElement element) {
val p = configuration.getWorkingDirectory().getPath();
if (p.isEmpty())
return false;
val path = p.get();
return filePath.getParent().equals(path);
}
}

View file

@ -16,41 +16,33 @@
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.ZigExecConfigBase;
import com.falsepattern.zigbrains.project.util.ElementUtil;
import com.intellij.execution.Executor;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.options.ConfigurationException;
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.val;
import org.apache.groovy.util.Arrays;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.List;
@Getter
public class ZigExecConfigBuild extends ZigExecConfigBase<ZigExecConfigBuild> {
public String extraArguments = "";
private ZigConfigEditor.ArgsConfigurable extraArgs = new ZigConfigEditor.ArgsConfigurable("extraArgs", "Extra command line arguments");
private ZigConfigEditor.ColoredConfigurable colored = new ZigConfigEditor.ColoredConfigurable("colored");
private ZigConfigEditor.FilePathConfigurable exePath = new ZigConfigEditor.FilePathConfigurable("exePath", "Output executable created by the build (for debugging)");
public ZigExecConfigBuild(@NotNull Project project, @NotNull ConfigurationFactory factory) {
super(project, factory, "Zig Build");
}
@Override
public String[] buildCommandLineArgs() {
val base = new String[]{"build"};
if (extraArguments.isBlank()) {
return base;
} else {
return Arrays.concat(base, extraArguments.split(" "));
}
val base = new String[]{"build", "--color", colored.colored ? "on" : "off"};
return CollectionUtil.concat(base, extraArgs.args);
}
@Override
@ -59,54 +51,21 @@ public class ZigExecConfigBuild extends ZigExecConfigBase<ZigExecConfigBuild> {
}
@Override
public @NotNull Editor getConfigurationEditor() {
return new Editor();
public @NotNull List<ZigConfigEditor.ZigConfigurable<?>> getConfigurables() {
return CollectionUtil.concat(super.getConfigurables(), extraArgs, colored, exePath);
}
@Override
public ZigExecConfigBuild clone() {
val clone = super.clone();
clone.extraArgs = extraArgs.clone();
clone.colored = colored.clone();
clone.exePath = exePath.clone();
return clone;
}
@Override
public @Nullable ProfileStateBuild getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) {
return new ProfileStateBuild(environment, this);
}
@Override
public void readExternal(@NotNull Element element) throws InvalidDataException {
super.readExternal(element);
val extraArguments = ElementUtil.readString(element, "extraArguments");
if (extraArguments != null) {
this.extraArguments = extraArguments;
}
}
@Override
public void writeExternal(@NotNull Element element) {
super.writeExternal(element);
ElementUtil.writeString(element, "extraArguments", extraArguments);
}
public static class Editor extends ZigConfigEditor<ZigExecConfigBuild> {
private final JBTextField extraArgs = new JBTextField();
@Override
protected void applyEditorTo(@NotNull ZigExecConfigBuild s) throws ConfigurationException {
super.applyEditorTo(s);
s.extraArguments = extraArgs.getText();
}
@Override
protected void resetEditorFrom(@NotNull ZigExecConfigBuild s) {
super.resetEditorFrom(s);
extraArgs.setText(Objects.requireNonNullElse(s.extraArguments, ""));
}
@Override
protected void constructPanel(Panel p) {
super.constructPanel(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 org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
import java.util.Objects;
public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> {
@Override
public @NotNull ConfigurationFactory getConfigurationFactory() {
@ -30,9 +33,9 @@ public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> {
}
@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)) {
configuration.filePath = filePath;
configuration.getFilePath().setPath(filePath);
configuration.setName(theFile.getPresentableName());
return true;
}
@ -40,8 +43,8 @@ public class ConfigProducerRun extends ConfigProducerBase<ZigExecConfigRun> {
}
@Override
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigRun configuration, String filePath, VirtualFile vFile, PsiElement element) {
return configuration.filePath.equals(filePath);
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigRun configuration, Path filePath, VirtualFile vFile, PsiElement element) {
return Objects.equals(configuration.getFilePath().getPath().orElse(null), filePath);
}
@Override

View file

@ -16,31 +16,42 @@
package com.falsepattern.zigbrains.project.execution.run;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ZigConfigEditor;
import com.falsepattern.zigbrains.project.util.ElementUtil;
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 com.intellij.openapi.util.InvalidDataException;
import lombok.Getter;
import lombok.Setter;
import org.jdom.Element;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Setter
import java.util.List;
@Getter
public class ZigExecConfigRun extends ZigExecConfigBase<ZigExecConfigRun> {
public String filePath = "";
private ZigConfigEditor.FilePathConfigurable filePath = new ZigConfigEditor.FilePathConfigurable("filePath", "File Path");
private ZigConfigEditor.ColoredConfigurable colored = new ZigConfigEditor.ColoredConfigurable("colored");
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) {
super(project, factory, "Zig Run");
}
@Override
public String[] buildCommandLineArgs() {
return new String[]{"run", 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
@ -49,42 +60,22 @@ public class ZigExecConfigRun extends ZigExecConfigBase<ZigExecConfigRun> {
}
@Override
public @NotNull Editor getConfigurationEditor() {
return new Editor();
public ZigExecConfigRun clone() {
val clone = super.clone();
clone.filePath = filePath.clone();
clone.colored = colored.clone();
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
public @Nullable ProfileStateRun getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) {
return new ProfileStateRun(environment, this);
}
@Override
public void readExternal(@NotNull Element element) throws InvalidDataException {
super.readExternal(element);
var filePath = ElementUtil.readString(element, "filePath");
if (filePath != null) {
this.filePath = filePath;
}
}
@Override
public void writeExternal(@NotNull Element element) {
super.writeExternal(element);
ElementUtil.writeString(element, "filePath", filePath);
}
public static class Editor extends ZigConfigEditor.WithFilePath<ZigExecConfigRun> {
@Override
protected String getFilePath(ZigExecConfigRun config) {
return config.filePath;
}
@Override
protected void setFilePath(ZigExecConfigRun config, String path) {
config.filePath = path;
}
}
}

View file

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

View file

@ -16,27 +16,42 @@
package com.falsepattern.zigbrains.project.execution.test;
import com.falsepattern.zigbrains.common.util.CollectionUtil;
import com.falsepattern.zigbrains.project.execution.base.ProfileStateBase;
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfigBase;
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.configurations.RunConfiguration;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.project.Project;
import lombok.Getter;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@Getter
public class ZigExecConfigTest extends ZigExecConfigBase<ZigExecConfigTest> {
public String filePath = "";
private ZigConfigEditor.FilePathConfigurable filePath = new ZigConfigEditor.FilePathConfigurable("filePath", "File path");
private ZigConfigEditor.ColoredConfigurable colored = new ZigConfigEditor.ColoredConfigurable("colored");
private ZigConfigEditor.OptimizationConfigurable optimization = new ZigConfigEditor.OptimizationConfigurable("optimization");
public ZigExecConfigTest(@NotNull Project project, @NotNull ConfigurationFactory factory) {
super(project, factory, "Zig Test");
}
@Override
public String[] buildCommandLineArgs() {
return new String[]{"test", 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
@ -50,20 +65,16 @@ public class ZigExecConfigTest extends ZigExecConfigBase<ZigExecConfigTest> {
}
@Override
public @NotNull SettingsEditor<? extends RunConfiguration> getConfigurationEditor() {
return new Editor();
public ZigExecConfigTest clone() {
val clone = super.clone();
clone.filePath = filePath.clone();
clone.colored = colored.clone();
clone.optimization = optimization.clone();
return clone;
}
public static class Editor extends ZigConfigEditor.WithFilePath<ZigExecConfigTest> {
@Override
protected String getFilePath(ZigExecConfigTest config) {
return config.filePath;
}
@Override
protected void setFilePath(ZigExecConfigTest config, String path) {
config.filePath = path;
}
@Override
public @NotNull List<ZigConfigEditor.ZigConfigurable<?>> getConfigurables() {
return CollectionUtil.concat(super.getConfigurables(), filePath, optimization, colored);
}
}

View file

@ -27,7 +27,6 @@ import com.intellij.ide.util.projectWizard.CustomStepProjectGenerator;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;

View file

@ -16,16 +16,19 @@
package com.falsepattern.zigbrains.project.runconfig;
import com.falsepattern.zigbrains.common.util.StringUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.PtyCommandLine;
import com.intellij.execution.process.KillableProcessHandler;
import com.intellij.execution.process.AnsiEscapeDecoder;
import com.intellij.execution.process.KillableColoredProcessHandler;
import com.intellij.openapi.util.Key;
import com.pty4j.PtyProcess;
import org.jetbrains.annotations.NotNull;
import java.nio.charset.Charset;
public class ZigProcessHandler extends KillableProcessHandler {
public class ZigProcessHandler extends KillableColoredProcessHandler implements AnsiEscapeDecoder.ColoredTextAcceptor {
public ZigProcessHandler(@NotNull GeneralCommandLine commandLine) throws ExecutionException {
super(commandLine);
setHasPty(commandLine instanceof PtyCommandLine);
@ -37,4 +40,9 @@ public class ZigProcessHandler extends KillableProcessHandler {
setHasPty(process instanceof PtyProcess);
setShouldDestroyProcessRecursively(!hasPty());
}
@Override
public void coloredTextAvailable(@NotNull String text, @NotNull Key attributes) {
super.coloredTextAvailable(StringUtil.translateVT100Escapes(text), attributes);
}
}

View file

@ -22,9 +22,9 @@ import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.runners.DefaultProgramRunnerKt;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.execution.runners.DefaultProgramRunnerKt;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -52,6 +52,6 @@ public class ZigRegularRunner extends ZigProgramRunnerBase<ProfileStateBase<?>>
@Override
protected @Nullable RunContentDescriptor doExecute(ProfileStateBase<?> state, AbstractZigToolchain toolchain, ExecutionEnvironment environment)
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

@ -22,19 +22,18 @@ import com.falsepattern.zigbrains.zig.environment.ZLSConfigProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import lombok.val;
import org.jetbrains.annotations.NotNull;
import java.nio.file.Path;
public class ToolchainZLSConfigProvider implements ZLSConfigProvider {
@Override
public @NotNull ZLSConfig getEnvironment(Project project) {
public void getEnvironment(Project project, ZLSConfig.ZLSConfigBuilder builder) {
val projectSettings = ZigProjectSettingsService.getInstance(project);
val toolchain = projectSettings.getToolchain();
if (toolchain == null)
return ZLSConfig.EMPTY;
return;
val projectDir = ProjectUtil.guessProjectDir(project);
val env = toolchain.zig().getEnv(projectDir == null ? Path.of(".") : projectDir.toNioPath());
return env.map(e -> new ZLSConfig(e.zigExecutable(), e.libDirectory())).orElse(ZLSConfig.EMPTY);
env.ifPresent(e -> builder.zig_exe_path(e.zigExecutable()).zig_lib_path(e.libDirectory()));
}
}

View file

@ -18,7 +18,6 @@ package com.falsepattern.zigbrains.project.toolchain.tools;
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain;
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSerializable;
import com.falsepattern.zigbrains.project.util.CLIUtil;
import com.google.gson.Gson;
import com.intellij.execution.process.ProcessOutput;
import org.jetbrains.annotations.Nullable;

View file

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

View file

@ -20,7 +20,6 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.ui.TextBrowseFolderListener;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.ui.TextAccessor;
import com.intellij.ui.components.JBTextField;
import javax.swing.JPanel;
import java.awt.BorderLayout;

View file

@ -20,12 +20,15 @@ import com.falsepattern.zigbrains.project.execution.ZigCapturingProcessHandler;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import lombok.val;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Optional;
import java.util.StringTokenizer;
public class CLIUtil {
public static Optional<ProcessOutput> execute(GeneralCommandLine cli, int timeoutMillis) {
@ -48,4 +51,72 @@ public class CLIUtil {
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,10 +18,12 @@ package com.falsepattern.zigbrains.project.util;
import lombok.val;
import org.jdom.Element;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.Optional;
public class ElementUtil {
public static @Nullable String readString(Element element, String name) {
public static Optional<String> readString(Element element, String name) {
return element.getChildren()
.stream()
.filter(it -> it.getName()
@ -29,15 +31,82 @@ public class ElementUtil {
it.getAttributeValue("name")
.equals(name))
.findAny()
.map(it -> it.getAttributeValue("value"))
.orElse(null);
.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) {
val option = new Element("ZigBrainsOption");
option.setAttribute("name", name);
option.setAttribute("value", value);
option.setAttribute("value", Objects.requireNonNullElse(value, ""));
element.addContent(option);
}
public static void writeBoolean(Element element, String name, boolean state) {
writeString(element, name, Boolean.toString(state));
}
public static <T extends Enum<T>> void writeEnum(Element element, String name, T value) {
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;
}
}

Some files were not shown because too many files have changed in this diff Show more