fix: line doc cache invalidation after closing the first open project

This commit is contained in:
林万程
2025-08-18 22:55:18 +08:00
parent c27a07c598
commit b416235be4
4 changed files with 122 additions and 136 deletions

View File

@@ -69,7 +69,7 @@ patchPluginXml {
<h2>中文更新说明:</h2> <h2>中文更新说明:</h2>
<ul> <ul>
<li>2.21 增加 文件树注释 用 xml/html/vue 第 1 或第 2 行注释当文件注释 <li>2.25 增加 文件树注释 用 xml/html/vue 第 1 或第 2 行注释当文件注释
<li>2.24 增加 行末注释 Maven pom.xml \${} 注释 <li>2.24 增加 行末注释 Maven pom.xml \${} 注释
<li>2.23 增加 行末注释 kotlin 注解注释 <li>2.23 增加 行末注释 kotlin 注解注释
<li>2.22 增加 文件树注释 ollama models 文件夹从 manifests 获取 blobs 文件注释 <li>2.22 增加 文件树注释 ollama models 文件夹从 manifests 获取 blobs 文件注释

View File

@@ -1,12 +1,10 @@
package io.github.linwancen.plugin.show.cache; package io.github.linwancen.plugin.show.cache;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.LineExtensionInfo; import com.intellij.openapi.editor.LineExtensionInfo;
import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.concurrency.AppExecutorUtil;
import io.github.linwancen.plugin.show.LineEnd; import io.github.linwancen.plugin.show.LineEnd;
import io.github.linwancen.plugin.show.bean.LineInfo; import io.github.linwancen.plugin.show.bean.LineInfo;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -19,7 +17,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.ScheduledFuture;
public class LineEndCacheUtils { public class LineEndCacheUtils {
@@ -28,6 +26,7 @@ public class LineEndCacheUtils {
private static final Logger LOG = LoggerFactory.getLogger(LineEndCacheUtils.class); private static final Logger LOG = LoggerFactory.getLogger(LineEndCacheUtils.class);
public static final Map<Project, Map<VirtualFile, Map<Integer, LineEndCache>>> cache = new ConcurrentHashMap<>(); public static final Map<Project, Map<VirtualFile, Map<Integer, LineEndCache>>> cache = new ConcurrentHashMap<>();
public static final Map<Project, ScheduledFuture<?>> taskMap = new ConcurrentHashMap<>();
public static @Nullable Collection<LineExtensionInfo> get(@NotNull LineInfo info) { public static @Nullable Collection<LineExtensionInfo> get(@NotNull LineInfo info) {
try { try {
@@ -39,7 +38,7 @@ public class LineEndCacheUtils {
@NotNull LineInfo oldInfo = lineCache.info; @NotNull LineInfo oldInfo = lineCache.info;
lineCache.info = info; lineCache.info = info;
lineCache.show = true; lineCache.show = true;
checkScheduleAndInit(info.project); TaskUtils.init(taskMap, info.project, cache, a -> cacheUpdate(info.project, a));
@Nullable List<LineExtensionInfo> list = lineCache.map.get(info.text); @Nullable List<LineExtensionInfo> list = lineCache.map.get(info.text);
// load from other line // load from other line
if (list == null && info.lineCount != oldInfo.lineCount) { if (list == null && info.lineCount != oldInfo.lineCount) {
@@ -66,87 +65,55 @@ public class LineEndCacheUtils {
} }
} }
private static volatile boolean isRun = false; private static void cacheUpdate(Project project, Map<VirtualFile, Map<Integer, LineEndCache>> fileMap) {
try {
private static void checkScheduleAndInit(@NotNull Project project) { fileMap.forEach((file, lineMap) -> lineMap.forEach((lineNumber, lineCache) -> {
if (!isRun) { @NotNull LineInfo info = lineCache.info;
if (DumbService.isDumb(project)) { @Nullable List<LineExtensionInfo> list = lineCache.map.get(info.text);
return; if (!(lineCache.needUpdate() || list == null)) {
}
synchronized (LineEndCacheUtils.class) {
if (!isRun) {
isRun = true;
AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay(() -> {
try {
ReadAction.nonBlocking(LineEndCacheUtils::cacheUpdate)
.inSmartMode(project)
.submit(AppExecutorUtil.getAppExecutorService());
} catch (ProcessCanceledException ignored) {
} catch (Throwable e) {
LOG.info("LineEndCacheUtils checkScheduleAndInit catch Throwable but log to record.", e);
}
}, 0L, 1L, TimeUnit.SECONDS);
}
}
}
}
private static void cacheUpdate() {
cache.forEach((project, fileMap) -> {
try {
if (project.isDisposed()) {
cache.remove(project);
return; return;
} }
fileMap.forEach((file, lineMap) -> lineMap.forEach((lineNumber, lineCache) -> { try {
@NotNull LineInfo info = lineCache.info; if (project.isDisposed() || DumbService.isDumb(project) || !file.isValid()) {
@Nullable List<LineExtensionInfo> list = lineCache.map.get(info.text);
if (!(lineCache.needUpdate() || list == null)) {
return; return;
} }
try { @Nullable LineExtensionInfo lineExt = LineEnd.lineExt(info);
if (project.isDisposed() || DumbService.isDumb(project)) { @Nullable LineInfo info2 = LineInfo.of(info, lineNumber);
return; if (info2 == null || !info2.text.equals(info.text)) {
} return;
@Nullable LineExtensionInfo lineExt = LineEnd.lineExt(info); }
@Nullable LineInfo info2 = LineInfo.of(info, lineNumber); if (list != null) {
if (info2 == null || !info2.text.equals(info.text)) { list.clear();
return; }
} // fix delete line get doc from before line because PsiFile be not updated
if ("}".equals(info.text.trim())) {
return;
}
if (lineExt != null) {
if (list != null) { if (list != null) {
list.clear(); list.add(lineExt);
} } else {
// fix delete line get doc from before line because PsiFile be not updated @NotNull ArrayList<LineExtensionInfo> lineExtList = new ArrayList<>(1);
if ("}".equals(info.text.trim())) { lineExtList.add(lineExt);
return; lineCache.map.put(info.text, lineExtList);
}
if (lineExt != null) {
if (list != null) {
list.add(lineExt);
} else {
@NotNull ArrayList<LineExtensionInfo> lineExtList = new ArrayList<>(1);
lineExtList.add(lineExt);
lineCache.map.put(info.text, lineExtList);
}
}
lineCache.updated();
} catch (ProcessCanceledException ignore) {
// ignore
} catch (Throwable e) {
@Nullable String msg = e.getMessage();
if (msg == null || !msg.contains("File is not valid")) {
LOG.info("LineEndCacheUtils lineMap.forEach catch Throwable but log to record.", e);
} }
} }
})); lineCache.updated();
} catch (ProcessCanceledException ignored) { } catch (ProcessCanceledException ignored) {
} catch (IllegalStateException ignore) { } catch (Throwable e) {
// ignore inSmartMode(project) throw: @Nullable String msg = e.getMessage();
// @NotNull method com/intellij/openapi/project/impl/ProjectImpl.getEarlyDisposable must not if (msg == null || !msg.contains("File is not valid")) {
// return null LOG.info("LineEndCacheUtils lineMap.forEach catch Throwable but log to record.", e);
} catch (Throwable e) { }
LOG.info("LineEndCacheUtils cache.forEach catch Throwable but log to record.", e); }
} }));
}); } catch (ProcessCanceledException ignored) {
} catch (IllegalStateException ignore) {
// ignore inSmartMode(project) throw:
// @NotNull method com/intellij/openapi/project/impl/ProjectImpl.getEarlyDisposable must not
// return null
} catch (Throwable e) {
LOG.info("LineEndCacheUtils cache.forEach catch Throwable but log to record.", e);
}
} }
} }

View File

@@ -0,0 +1,51 @@
package io.github.linwancen.plugin.show.cache;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.util.concurrency.AppExecutorUtil;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class TaskUtils {
private static final Logger LOG = LoggerFactory.getLogger(TaskUtils.class);
public static <T> void init(
@NotNull Map<Project, ScheduledFuture<?>> taskMap,
@NotNull Project project,
@NotNull Map<Project, T> cache,
@NotNull Consumer<T> func
) {
taskMap.computeIfAbsent(project,
project1 -> AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay(() -> {
try {
ReadAction.nonBlocking(() -> {
if (project.isDisposed()) {
cache.remove(project);
ScheduledFuture<?> task = taskMap.remove(project);
if (task != null) {
task.cancel(true);
}
return;
}
T t = cache.get(project);
if (t == null) {
return;
}
func.accept(t);
})
.inSmartMode(project)
.submit(AppExecutorUtil.getAppExecutorService());
} catch (ProcessCanceledException ignored) {
} catch (Throwable e) {
LOG.info("TaskUtils init catch Throwable but log to record.", e);
}
}, 0L, 1L, TimeUnit.SECONDS));
}
}

View File

@@ -1,11 +1,9 @@
package io.github.linwancen.plugin.show.cache; package io.github.linwancen.plugin.show.cache;
import com.intellij.ide.projectView.ProjectViewNode; import com.intellij.ide.projectView.ProjectViewNode;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.util.concurrency.AppExecutorUtil;
import io.github.linwancen.plugin.show.Tree; import io.github.linwancen.plugin.show.Tree;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -14,7 +12,7 @@ import org.slf4j.LoggerFactory;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.ScheduledFuture;
public class TreeCacheUtils { public class TreeCacheUtils {
@@ -23,6 +21,7 @@ public class TreeCacheUtils {
private static final Logger LOG = LoggerFactory.getLogger(TreeCacheUtils.class); private static final Logger LOG = LoggerFactory.getLogger(TreeCacheUtils.class);
public static final Map<Project, Map<ProjectViewNode<?>, TreeCache>> cache = new ConcurrentHashMap<>(); public static final Map<Project, Map<ProjectViewNode<?>, TreeCache>> cache = new ConcurrentHashMap<>();
public static final Map<Project, ScheduledFuture<?>> taskMap = new ConcurrentHashMap<>();
@Nullable @Nullable
public static String treeDoc(@NotNull ProjectViewNode<?> node, @NotNull Project project) { public static String treeDoc(@NotNull ProjectViewNode<?> node, @NotNull Project project) {
@@ -31,9 +30,9 @@ public class TreeCacheUtils {
.computeIfAbsent(project, a -> new ConcurrentHashMap<>()) .computeIfAbsent(project, a -> new ConcurrentHashMap<>())
.computeIfAbsent(node, a -> new TreeCache()); .computeIfAbsent(node, a -> new TreeCache());
treeCache.needUpdate = true; treeCache.needUpdate = true;
checkScheduleAndInit(project); TaskUtils.init(taskMap, project, cache, a -> cacheUpdate(project, a));
return treeCache.doc; return treeCache.doc;
} catch (ProcessCanceledException e) { } catch (ProcessCanceledException ignored) {
return null; return null;
} catch (Throwable e) { } catch (Throwable e) {
LOG.info("TreeCacheUtils catch Throwable but log to record.", e); LOG.info("TreeCacheUtils catch Throwable but log to record.", e);
@@ -41,59 +40,28 @@ public class TreeCacheUtils {
} }
} }
private static volatile boolean isRun = false; private static void cacheUpdate(Project project, Map<ProjectViewNode<?>, TreeCache> nodeCache) {
try {
private static void checkScheduleAndInit(@NotNull Project project) { nodeCache.forEach((node, treeCache) -> {
if (!isRun) { if (treeCache.needUpdate) {
if (DumbService.isDumb(project)) { try {
return; if (project.isDisposed() || DumbService.isDumb(project)) {
} return;
synchronized (TreeCacheUtils.class) {
if (!isRun) {
isRun = true;
AppExecutorUtil.getAppScheduledExecutorService().scheduleWithFixedDelay(() -> {
try {
ReadAction.nonBlocking(TreeCacheUtils::cacheUpdate)
.inSmartMode(project)
.submit(AppExecutorUtil.getAppExecutorService());
} catch (Throwable e) {
LOG.info("TreeCacheUtils checkScheduleAndInit catch Throwable but log to record.", e);
} }
}, 0L, 1L, TimeUnit.SECONDS); treeCache.doc = Tree.treeDoc(node, project);
treeCache.needUpdate = false;
} catch (ProcessCanceledException ignored) {
} catch (Throwable e) {
LOG.info("TreeCacheUtils nodeCache.forEach catch Throwable but log to record.", e);
}
} }
} });
} catch (ProcessCanceledException ignored) {
} catch (IllegalStateException ignore) {
// ignore inSmartMode(project) throw:
// @NotNull method com/intellij/openapi/project/impl/ProjectImpl.getEarlyDisposable must not return null
} catch (Throwable e) {
LOG.info("TreeCacheUtils cache.forEach catch Throwable but log to record.", e);
} }
} }
private static void cacheUpdate() {
cache.forEach((project, nodeCache) -> {
try {
if (project.isDisposed()) {
cache.remove(project);
return;
}
nodeCache.forEach((node, treeCache) -> {
if (treeCache.needUpdate) {
try {
if (project.isDisposed() || DumbService.isDumb(project)) {
return;
}
treeCache.doc = Tree.treeDoc(node, project);
treeCache.needUpdate = false;
} catch (ProcessCanceledException ignore) {
// ignore
} catch (Throwable e) {
LOG.info("TreeCacheUtils nodeCache.forEach catch Throwable but log to record.", e);
}
}
});
} catch (ProcessCanceledException ignored) {
} catch (IllegalStateException ignore) {
// ignore inSmartMode(project) throw:
// @NotNull method com/intellij/openapi/project/impl/ProjectImpl.getEarlyDisposable must not return null
} catch (Throwable e) {
LOG.info("TreeCacheUtils cache.forEach catch Throwable but log to record.", e);
}
});
}
} }