Skip to content

Commit

Permalink
Structural rewrite for better file data loading
Browse files Browse the repository at this point in the history
  • Loading branch information
tandemdude committed Jun 21, 2024
1 parent ec7d255 commit 12f5b20
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ protected void addCompletions(
return;
}

var moduleLightbulbData = LightbulbPackageManagerListener.getDataFor(sdk);
var dataService = parameters.getOriginalFile().getProject().getService(ProjectDataService.class);
var moduleLightbulbData = dataService.getLightbulbData(sdk);
if (moduleLightbulbData == null) {
// We don't have the data for the specified module - maybe it isn't part of this project?
// Alternatively, lightbulb may not be installed - a filesystem listener will load the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,109 +1,18 @@
package io.github.tandemdude.hklbsupport;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.search.FileTypeIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.jetbrains.python.packaging.PyPackageManager;
import com.jetbrains.python.packaging.common.PythonPackageManagementListener;
import com.jetbrains.python.sdk.PythonSdkUtil;
import io.github.tandemdude.hklbsupport.utils.Notifier;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.NotNull;

public class LightbulbPackageManagerListener implements PythonPackageManagementListener {
private static final ObjectMapper MAPPER = new ObjectMapper();

public record ParamData(Map<String, String> required, Map<String, String> optional) {}

public record LightbulbData(String version, Map<String, ParamData> paramData) {}

private static final ConcurrentHashMap<Sdk, LightbulbData> sdkLightbulbData = new ConcurrentHashMap<>();

public static LightbulbData getDataFor(Sdk sdk) {
return sdkLightbulbData.get(sdk);
}

public static void flush() {
sdkLightbulbData.clear();
}

LightbulbData readMetaparamsFile(String version, VirtualFile vf) {
try {
var parsedParamData = MAPPER.readValue(vf.getInputStream(), new TypeReference<Map<String, ParamData>>() {});
return new LightbulbData(version, parsedParamData);
} catch (IOException e) {
return null;
}
}

@Override
public void packagesChanged(@NotNull Sdk sdk) {
ApplicationManager.getApplication().runReadAction(() -> {
var packages = PyPackageManager.getInstance(sdk).getPackages();
if (packages == null) {
return;
}

var lightbulb = packages.stream()
.filter(p -> p.getName().equals("hikari-lightbulb"))
.findFirst();

if (lightbulb.isEmpty()) {
return;
}

var existingData = sdkLightbulbData.get(sdk);
if (existingData != null
&& existingData.version().equals(lightbulb.get().getVersion())) {
// The cache is still up-to-date - do not refresh
return;
}

var lightbulbLocation = lightbulb.get().getLocation();
if (lightbulbLocation == null) {
return;
}

var searchScopes = new ArrayList<GlobalSearchScope>();
for (var project : ProjectManager.getInstance().getOpenProjects()) {
Arrays.stream(ModuleManager.getInstance(project).getModules()).forEach(module -> {
var maybeSdk = PythonSdkUtil.findPythonSdk(module);
if (maybeSdk == sdk) {
searchScopes.add(GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module));
}
});
project.getService(ProjectDataService.class).notifyChange(sdk);
}

FileTypeIndex.processFiles(
FileTypeManager.getInstance().getFileTypeByExtension("json"),
file -> {
if (file.getPath().endsWith("metaparams.json")
&& file.getPath().contains("lightbulb")
&& file.getPath().contains(lightbulbLocation)) {
var data = readMetaparamsFile(lightbulb.get().getVersion(), file);
if (data == null) {
return false;
}

sdkLightbulbData.put(sdk, data);
Notifier.notifyInformation(
null, "Lightbulb configuration loaded successfully (%s)", sdk.getName());
return false;
}
return true;
},
GlobalSearchScope.union(searchScopes));
});
}
}
103 changes: 103 additions & 0 deletions src/main/java/io/github/tandemdude/hklbsupport/ProjectDataService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package io.github.tandemdude.hklbsupport;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.search.FileTypeIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.jetbrains.python.sdk.PythonSdkUtil;
import io.github.tandemdude.hklbsupport.models.LightbulbData;
import io.github.tandemdude.hklbsupport.models.ParamData;
import io.github.tandemdude.hklbsupport.utils.Notifier;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

@Service(Service.Level.PROJECT)
public final class ProjectDataService {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final Pattern VERSION_PATTERN = Pattern.compile("__version__\\s*=\\s*\"([^\"]+)\"");

private final Project project;

private final ConcurrentHashMap<Sdk, LightbulbData> sdkCache = new ConcurrentHashMap<>();

public ProjectDataService(Project project) {
this.project = project;
}

public void loadModules() {
Arrays.stream(ModuleManager.getInstance(project).getModules()).forEach(module -> {
var maybeSdk = PythonSdkUtil.findPythonSdk(module);
if (maybeSdk != null) {
sdkCache.put(maybeSdk, new LightbulbData("-1", Collections.emptyMap()));
}
});
}

public void flush() {
this.sdkCache.clear();
}

public LightbulbData getLightbulbData(Sdk sdk) {
return sdkCache.get(sdk);
}

LightbulbData readMetaparamsFile(String version, VirtualFile vf) throws IOException {
var parsedParamData = MAPPER.readValue(vf.getInputStream(), new TypeReference<Map<String, ParamData>>() {});
return new LightbulbData(version, parsedParamData);
}

public void notifyChange(Sdk sdk) {
if (!sdkCache.containsKey(sdk)) {
return;
}

FileTypeIndex.processFiles(
FileTypeManager.getInstance().getFileTypeByExtension("json"),
file -> {
if (!file.getName().equals("metaparams.json")
|| !file.getParent().getName().equals("lightbulb")) {
return true;
}

var initFile = file.getParent().findChild("__init__.py");
if (initFile == null) {
return true;
}

try {
var matcher = VERSION_PATTERN.matcher(new String(initFile.contentsToByteArray()));
String version = null;
while (matcher.find()) {
version = matcher.group(1);
}

if (version == null) {
return true;
}

if (version.equals(sdkCache.get(sdk).version())) {
return false;
}

var data = readMetaparamsFile(version, file);
sdkCache.put(sdk, data);
Notifier.notifyInformation(project, "Lightbulb configuration loaded successfully (%s)", sdk.getName());
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
},
GlobalSearchScope.allScope(project));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.module.ModuleManager;
import com.jetbrains.python.sdk.PythonSdkUtil;
import io.github.tandemdude.hklbsupport.LightbulbPackageManagerListener;
import io.github.tandemdude.hklbsupport.ProjectDataService;
import org.jetbrains.annotations.NotNull;

public class CacheRefreshAction extends AnAction {
Expand All @@ -20,14 +20,14 @@ public void actionPerformed(@NotNull AnActionEvent e) {
return;
}

LightbulbPackageManagerListener.flush();
var listenerInstance = new LightbulbPackageManagerListener();
var dataService = e.getProject().getService(ProjectDataService.class);
dataService.flush();

var modules = ModuleManager.getInstance(e.getProject()).getModules();
for (var module : modules) {
var sdk = PythonSdkUtil.findPythonSdk(module);
if (sdk != null) {
listenerInstance.packagesChanged(sdk);
dataService.notifyChange(sdk);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.tandemdude.hklbsupport.models;

import java.util.Map;

public record LightbulbData(String version, Map<String, ParamData> paramData) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.tandemdude.hklbsupport.models;

import java.util.Map;

public record ParamData(Map<String, String> required, Map<String, String> optional) {}
12 changes: 5 additions & 7 deletions src/main/java/io/github/tandemdude/hklbsupport/utils/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import com.jetbrains.python.psi.PyKeywordArgument;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.sdk.PythonSdkUtil;
import io.github.tandemdude.hklbsupport.LightbulbPackageManagerListener;
import io.github.tandemdude.hklbsupport.ProjectDataService;
import io.github.tandemdude.hklbsupport.models.LightbulbData;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
Expand All @@ -29,9 +30,7 @@ public class Utils {
* @return the Lightbulb superclass as a {@link PyClass}, or {@code null} if none was found.
*/
public static @Nullable PyClass getLightbulbSuperclass(
@NotNull TypeEvalContext context,
@NotNull PyClass pyClass,
@NotNull LightbulbPackageManagerListener.LightbulbData moduleData) {
@NotNull TypeEvalContext context, @NotNull PyClass pyClass, @NotNull LightbulbData moduleData) {
PyClass commandSuperClass = null;
for (var superClass : pyClass.getSuperClasses(context)) {
if (moduleData.paramData().containsKey(superClass.getQualifiedName())) {
Expand All @@ -42,8 +41,7 @@ public class Utils {
return commandSuperClass;
}

public static @Nullable LightbulbPackageManagerListener.LightbulbData getLightbulbDataForNode(
@NotNull PyClass node) {
public static @Nullable LightbulbData getLightbulbDataForNode(@NotNull PyClass node) {
var module = ModuleUtilCore.findModuleForFile(node.getContainingFile());
if (module == null) {
return null;
Expand All @@ -54,7 +52,7 @@ public class Utils {
return null;
}

return LightbulbPackageManagerListener.getDataFor(sdk);
return module.getProject().getService(ProjectDataService.class).getLightbulbData(sdk);
}

public static Map<String, PyExpression> getKeywordSuperclassExpressions(PyClass node) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.github.tandemdude.hklbsupport

import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity

/**
* Startup activity that causes module Lightbulb configurations to attempt to be loaded when the
* user opens a new project.
*/
class StartupActivity : ProjectActivity {
override suspend fun execute(project: Project) {
project.getService(ProjectDataService::class.java).loadModules()
}
}
1 change: 1 addition & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<!-- Extension points defined by the plugin.
Read more: https://plugins.jetbrains.com/docs/intellij/plugin-extension-points.html -->
<extensions defaultExtensionNs="com.intellij">
<postStartupActivity implementation="io.github.tandemdude.hklbsupport.StartupActivity"/>
<notificationGroup displayType="BALLOON" id="Hikari Lightbulb Support"/>

<completion.contributor
Expand Down

0 comments on commit 12f5b20

Please sign in to comment.