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>
<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.23 增加 行末注释 kotlin 注解注释
<li>2.22 增加 文件树注释 ollama models 文件夹从 manifests 获取 blobs 文件注释

View File

@@ -1,12 +1,10 @@
package io.github.linwancen.plugin.show.cache;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.LineExtensionInfo;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
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.bean.LineInfo;
import org.jetbrains.annotations.NotNull;
@@ -19,7 +17,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ScheduledFuture;
public class LineEndCacheUtils {
@@ -28,6 +26,7 @@ public class LineEndCacheUtils {
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, ScheduledFuture<?>> taskMap = new ConcurrentHashMap<>();
public static @Nullable Collection<LineExtensionInfo> get(@NotNull LineInfo info) {
try {
@@ -39,7 +38,7 @@ public class LineEndCacheUtils {
@NotNull LineInfo oldInfo = lineCache.info;
lineCache.info = info;
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);
// load from other line
if (list == null && info.lineCount != oldInfo.lineCount) {
@@ -66,87 +65,55 @@ public class LineEndCacheUtils {
}
}
private static volatile boolean isRun = false;
private static void checkScheduleAndInit(@NotNull Project project) {
if (!isRun) {
if (DumbService.isDumb(project)) {
return;
}
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);
private static void cacheUpdate(Project project, Map<VirtualFile, Map<Integer, LineEndCache>> fileMap) {
try {
fileMap.forEach((file, lineMap) -> lineMap.forEach((lineNumber, lineCache) -> {
@NotNull LineInfo info = lineCache.info;
@Nullable List<LineExtensionInfo> list = lineCache.map.get(info.text);
if (!(lineCache.needUpdate() || list == null)) {
return;
}
fileMap.forEach((file, lineMap) -> lineMap.forEach((lineNumber, lineCache) -> {
@NotNull LineInfo info = lineCache.info;
@Nullable List<LineExtensionInfo> list = lineCache.map.get(info.text);
if (!(lineCache.needUpdate() || list == null)) {
try {
if (project.isDisposed() || DumbService.isDumb(project) || !file.isValid()) {
return;
}
try {
if (project.isDisposed() || DumbService.isDumb(project)) {
return;
}
@Nullable LineExtensionInfo lineExt = LineEnd.lineExt(info);
@Nullable LineInfo info2 = LineInfo.of(info, lineNumber);
if (info2 == null || !info2.text.equals(info.text)) {
return;
}
@Nullable LineExtensionInfo lineExt = LineEnd.lineExt(info);
@Nullable LineInfo info2 = LineInfo.of(info, lineNumber);
if (info2 == null || !info2.text.equals(info.text)) {
return;
}
if (list != null) {
list.clear();
}
// 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) {
list.clear();
}
// 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) {
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);
list.add(lineExt);
} else {
@NotNull ArrayList<LineExtensionInfo> lineExtList = new ArrayList<>(1);
lineExtList.add(lineExt);
lineCache.map.put(info.text, lineExtList);
}
}
}));
} 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);
}
});
lineCache.updated();
} catch (ProcessCanceledException ignored) {
} 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);
}
}
}));
} 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;
import com.intellij.ide.projectView.ProjectViewNode;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.util.concurrency.AppExecutorUtil;
import io.github.linwancen.plugin.show.Tree;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -14,7 +12,7 @@ import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ScheduledFuture;
public class TreeCacheUtils {
@@ -23,6 +21,7 @@ public class TreeCacheUtils {
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, ScheduledFuture<?>> taskMap = new ConcurrentHashMap<>();
@Nullable
public static String treeDoc(@NotNull ProjectViewNode<?> node, @NotNull Project project) {
@@ -31,9 +30,9 @@ public class TreeCacheUtils {
.computeIfAbsent(project, a -> new ConcurrentHashMap<>())
.computeIfAbsent(node, a -> new TreeCache());
treeCache.needUpdate = true;
checkScheduleAndInit(project);
TaskUtils.init(taskMap, project, cache, a -> cacheUpdate(project, a));
return treeCache.doc;
} catch (ProcessCanceledException e) {
} catch (ProcessCanceledException ignored) {
return null;
} catch (Throwable 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 checkScheduleAndInit(@NotNull Project project) {
if (!isRun) {
if (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);
private static void cacheUpdate(Project project, Map<ProjectViewNode<?>, TreeCache> nodeCache) {
try {
nodeCache.forEach((node, treeCache) -> {
if (treeCache.needUpdate) {
try {
if (project.isDisposed() || DumbService.isDumb(project)) {
return;
}
}, 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);
}
});
}
}