Skip to content

Commit

Permalink
Merge pull request #15296 from geoand/open-in-ide
Browse files Browse the repository at this point in the history
Add the ability to open classes from the DevUI in the IDE
  • Loading branch information
geoand authored Mar 12, 2021
2 parents 42dcea2 + 94f715b commit f1bbb73
Show file tree
Hide file tree
Showing 11 changed files with 663 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.deployment.ide;

import io.quarkus.builder.item.SimpleBuildItem;

/**
* Contains the IDE to be opened when a request to open a class is made
*/
public final class EffectiveIdeBuildItem extends SimpleBuildItem {

private final Ide ide;

public EffectiveIdeBuildItem(Ide ide) {
this.ide = ide;
}

public Ide getIde() {
return ide;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.quarkus.deployment.ide;

public enum Ide {
IDEA,
ECLIPSE,
VSCODE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.deployment.ide;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;

@ConfigRoot
public class IdeConfig {

/**
* The Ide to use to open files from the DevUI.
* {@code auto} means that Quarkus will attempt to determine the Ide being used.
*/
@ConfigItem(defaultValue = "auto")
public Target target;

enum Target {
auto,
idea,
vscode,
eclipse
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.deployment.ide;

import java.util.Set;

import io.quarkus.builder.item.SimpleBuildItem;

/**
* Contains the set of IDEs that could be potentially linked to project based
* on the files present in the project
*/
final class IdeFileBuildItem extends SimpleBuildItem {

private final Set<Ide> detectedIDEs;

IdeFileBuildItem(Set<Ide> detectedIDEs) {
this.detectedIDEs = detectedIDEs;
}

Set<Ide> getDetectedIDEs() {
return detectedIDEs;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package io.quarkus.deployment.ide;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.pkg.builditem.BuildSystemTargetBuildItem;
import io.quarkus.runtime.util.JavaVersionUtil;

public class IdeProcessor {

private static Map<String, List<Ide>> IDE_MARKER_FILES = new HashMap<>();
private static Map<Predicate<ProcessInfo>, Ide> IDE_PROCESSES = new HashMap<>();

static {
IDE_MARKER_FILES.put(".idea", Collections.singletonList(Ide.IDEA));
IDE_MARKER_FILES.put(".project", Arrays.asList(Ide.VSCODE, Ide.ECLIPSE));
IDE_MARKER_FILES = Collections.unmodifiableMap(IDE_MARKER_FILES);

IDE_PROCESSES.put((processInfo -> processInfo.getCommand().contains("idea")), Ide.IDEA);
IDE_PROCESSES.put((processInfo -> processInfo.getCommand().contains("code")), Ide.VSCODE);
IDE_PROCESSES.put((processInfo -> processInfo.getCommand().contains("eclipse")), Ide.ECLIPSE); // TODO: I have no idea if this is correct
IDE_PROCESSES = Collections.unmodifiableMap(IDE_PROCESSES);
}

@BuildStep
public EffectiveIdeBuildItem effectiveIde(IdeConfig ideConfig, IdeFileBuildItem ideFile,
IdeRunningProcessBuildItem ideRunningProcess) {
Ide result = null;
if (ideConfig.target == IdeConfig.Target.auto) {

// the idea here is to auto-detect the special files that IDEs create
// and also the running IDE process if need be

if (ideFile.getDetectedIDEs().size() == 1) {
result = ideFile.getDetectedIDEs().iterator().next();
} else {
Set<Ide> runningIdes = ideRunningProcess.getDetectedIDEs();
if (runningIdes.size() == 1) {
result = runningIdes.iterator().next();
} else {
List<Ide> matches = new ArrayList<>();
for (Ide file : ideFile.getDetectedIDEs()) {
for (Ide process : runningIdes) {
if (file == process) {
matches.add(file);
}
}
}
if (matches.size() == 1) {
result = matches.get(0);
}
}
}
} else {
if (ideConfig.target == IdeConfig.Target.idea) {
result = Ide.IDEA;
} else if (ideConfig.target == IdeConfig.Target.eclipse) {
result = Ide.ECLIPSE;
} else if (ideConfig.target == IdeConfig.Target.vscode) {
result = Ide.VSCODE;
}
}

if (result == null) {
return null;
}

return new EffectiveIdeBuildItem(result);
}

@BuildStep
public IdeFileBuildItem detectIdeFiles(BuildSystemTargetBuildItem buildSystemTarget) {
Set<Ide> result = new HashSet<>(2);
Path projectRoot = buildSystemTarget.getOutputDirectory().getParent();
IDE_MARKER_FILES.forEach((file, ides) -> {
if (Files.exists(projectRoot.resolve(file))) {
result.addAll(ides);
}
});
return new IdeFileBuildItem(result);
}

@BuildStep
public IdeRunningProcessBuildItem detectRunningIdeProcesses() {
Set<Ide> result = new HashSet<>(2);
List<ProcessInfo> processInfos = ProcessUtil.runningProcesses();
for (ProcessInfo processInfo : processInfos) {
for (Map.Entry<Predicate<ProcessInfo>, Ide> entry : IDE_PROCESSES.entrySet()) {
if (entry.getKey().test(processInfo)) {
result.add(entry.getValue());
break;
}
}
}
return new IdeRunningProcessBuildItem(result);
}

// TODO: remove when we move to Java 11 and just call the methods of 'java.lang.ProcessHandle'
private static class ProcessUtil {

/**
* Returns a list of running processes
* Only works for Java 11+
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static List<ProcessInfo> runningProcesses() {
if (!JavaVersionUtil.isJava11OrHigher()) {
return Collections.emptyList();
}
// we can't use ProcessHandle directly as it is Java 9+, so we need to do reflection to the get the info we need
try {
Class processHandlerClass = Class.forName("java.lang.ProcessHandle");
Method allProcessesMethod = processHandlerClass.getMethod("allProcesses");
Method processHandleInfoMethod = processHandlerClass.getMethod("info");
Class processHandleInfoClass = Class.forName("java.lang.ProcessHandle$Info");
Method processHandleInfoCommandMethod = processHandleInfoClass.getMethod("command");
Stream<Object> allProcessesResult = (Stream<Object>) allProcessesMethod.invoke(null);
List<ProcessInfo> result = new ArrayList<>();
allProcessesResult.forEach(o -> {
try {
Object processHandleInfo = processHandleInfoMethod.invoke(o);
Optional<String> command = (Optional<String>) processHandleInfoCommandMethod.invoke(processHandleInfo);
if (command.isPresent()) {
result.add(new ProcessInfo(command.get()));
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
});
return result;
} catch (Exception e) {
throw new RuntimeException("Unable to determine running processes", e);
}
}
}

private static class ProcessInfo {
// the executable pathname of the process.
private final String command;

public ProcessInfo(String command) {
this.command = command;
}

public String getCommand() {
return command;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.deployment.ide;

import java.util.Set;

import io.quarkus.builder.item.SimpleBuildItem;

/**
* Contains the set of IDEs that are running as processes
*/
final class IdeRunningProcessBuildItem extends SimpleBuildItem {

private final Set<Ide> detectedIDEs;

IdeRunningProcessBuildItem(Set<Ide> detectedIDEs) {
this.detectedIDEs = detectedIDEs;
}

Set<Ide> getDetectedIDEs() {
return detectedIDEs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,33 @@
span.larger-badge {
font-size: 0.9em;
}
span.app-class {
cursor:pointer;
color:blue;
text-decoration:underline;
}

{/style}

{#script}
$(document).ready(function(){
if (!ideKnown()) {
return;
}
$(".class-candidate").each(function() {
var className = $(this).text();
if (appClassLang(className)) {
$(this).addClass("app-class");
}
});

$(".app-class").on("click", function() {
openInIDE($(this).text());
});
});

{/script}

{#title}Beans{/title}
{#body}
<table class="table table-striped">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
{#for q in it.nonDefaultQualifiers}
<span class="annotation" title="Qualifier: {q}">{q.simpleName}</span><br/>
{/for}
{it.providerType}
<span class="class-candidate">{it.providerType}</span>
Loading

0 comments on commit f1bbb73

Please sign in to comment.