diff --git a/CHANGELOG.md b/CHANGELOG.md index 9539231..dca639d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [1.0.3] - 2019-05-28 +- Fix multiple rescan requests and add ignoring events while rescan running +- Fix cache invalidation in Bulk mode for files update +- CheckBundle after uploadFiles to ensure no missingFiles left + +## [1.0.2] - 2019-05-25 +- Bugfixing and better support for refactoring + ## [1.0.1] - 2019-05-22 - Updated Java-client and added support for Java 8, required for Android Studio diff --git a/build.gradle b/build.gradle index 65c937f..3fef38e 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { //group 'org.example' -version '1.0.2' +version '1.0.3' sourceCompatibility = 1.8 repositories { @@ -35,6 +35,8 @@ patchPluginXml { untilBuild 202 changeNotes """ + 1.0.3 - Bugfixing and optimization of bulk file changes
+ 1.0.2 - Bugfixing and better support for refactoring
1.0.1 - Updated Java-client and added support for Java 8, required for Android Studio
1.0.0 - First public beta release.
""" diff --git a/src/main/java/ai/deepcode/jbplugin/annotators/DeepCodeAnnotator.java b/src/main/java/ai/deepcode/jbplugin/annotators/DeepCodeAnnotator.java deleted file mode 100644 index 4d69588..0000000 --- a/src/main/java/ai/deepcode/jbplugin/annotators/DeepCodeAnnotator.java +++ /dev/null @@ -1,88 +0,0 @@ -package ai.deepcode.jbplugin.annotators; - -import ai.deepcode.jbplugin.actions.DeepCodeIntentionAction; -import ai.deepcode.jbplugin.core.*; -import com.intellij.lang.annotation.Annotation; -import com.intellij.lang.annotation.AnnotationHolder; -import com.intellij.lang.annotation.Annotator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.util.TextRange; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import org.jetbrains.annotations.NotNull; - -import java.util.List; - -public class DeepCodeAnnotator implements Annotator { - - @Override - public void annotate(@NotNull PsiElement psiElement, @NotNull AnnotationHolder holder) { - if (!(psiElement instanceof PsiFile)) return; - PsiFile psiFile = (PsiFile) psiElement; - if (!DeepCodeUtils.isSupportedFileFormat(psiFile)) return; - final long annotatorId = System.currentTimeMillis(); - DCLogger.info( - "Annotator (" - + annotatorId - + ") requested for file: " - + psiFile.getName() - + " with Holder: " - + holder); - -// RunUtils.runInBackground( -// psiElement.getProject(), -// () -> { - doAnnotate(psiFile, holder, annotatorId); -// }, "DeepCodeAnnotator.annotate" -// ); - } - - private static void doAnnotate( - @NotNull PsiFile psiFile, @NotNull AnnotationHolder holder, long annotatorId) { - DCLogger.info( - "Annotator (" - + annotatorId - + ") started for file: " - + psiFile.getName() - + " with Holder: " - + holder); - AnalysisData.waitForUpdateAnalysisFinish(); - ProgressManager.checkCanceled(); - - List suggestions = AnalysisData.getAnalysis(psiFile); - DCLogger.info( - "Annotator (" + annotatorId + ") suggestions gotten for file: " + psiFile.getName()); - ProgressManager.checkCanceled(); - - for (SuggestionForFile suggestion : suggestions) { - final String message = "DeepCode: " + suggestion.getMessage(); - Annotation annotation; - for (TextRange range : suggestion.getRanges()) { - switch (suggestion.getSeverity()) { - case 1: - annotation = holder.createWeakWarningAnnotation(range, message); - break; - case 2: - annotation = holder.createWarningAnnotation(range, message); - break; - case 3: - annotation = holder.createErrorAnnotation(range, message); - break; - default: - annotation = holder.createInfoAnnotation(range, message); - break; - } - annotation.registerFix( - new DeepCodeIntentionAction(psiFile, range, suggestion.getId(), false)); - annotation.registerFix( - new DeepCodeIntentionAction(psiFile, range, suggestion.getId(), true)); - /* - holder - .newAnnotation(severity, "DeepCode: " + suggestion.getMessage()) - .range(range) - .create(); - */ - } - } - } -} diff --git a/src/main/java/ai/deepcode/jbplugin/core/AnalysisData.java b/src/main/java/ai/deepcode/jbplugin/core/AnalysisData.java index 6428d3e..894d66d 100644 --- a/src/main/java/ai/deepcode/jbplugin/core/AnalysisData.java +++ b/src/main/java/ai/deepcode/jbplugin/core/AnalysisData.java @@ -12,7 +12,6 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; @@ -46,8 +45,7 @@ private AnalysisData() {} // todo: keep few latest file versions (Guava com.google.common.cache.CacheBuilder ?) private static final Map> mapFile2Suggestions = - // deepcode ignore ApiMigration~java.util.Hashtable: we need read and write full data lock - new Hashtable<>(); // new ConcurrentHashMap<>(); + new ConcurrentHashMap<>(); private static final Map mapPsiFile2Hash = new ConcurrentHashMap<>(); @@ -111,6 +109,7 @@ static void removeFilesFromCache(@NotNull Collection files) { info("Request to remove from cache " + files.size() + " files: " + files); // todo: do we really need mutex here? MUTEX.lock(); + info("MUTEX LOCK"); int removeCounter = 0; for (PsiFile file : files) { if (file != null && isFileInCache(file)) { @@ -125,13 +124,12 @@ static void removeFilesFromCache(@NotNull Collection files) { + " files. Were not in cache: " + (files.size() - removeCounter)); } finally { + info("MUTEX RELEASED"); MUTEX.unlock(); } } static void removeProjectFromCache(@NotNull Project project) { - // lets all running ProgressIndicators release MUTEX first - RunUtils.cancelRunningIndicators(project); if (mapProject2BundleId.remove(project) != null) { info("Removed from cache: " + project); } @@ -174,6 +172,7 @@ public static void updateCachedResultsForFiles( Collection filesToProceed = null; try { MUTEX.lock(); + info("MUTEX LOCK"); updateInProgress = true; filesToProceed = // DeepCodeUtils.computeNonBlockingReadAction( @@ -209,7 +208,8 @@ public static void updateCachedResultsForFiles( updateInProgress = false; } finally { - if (filesToProceed != null && !filesToProceed.isEmpty()) info("MUTEX RELEASED"); + // if (filesToProceed != null && !filesToProceed.isEmpty()) + info("MUTEX RELEASED"); MUTEX.unlock(); } } @@ -266,6 +266,10 @@ private static Map> retrieveSuggestions( ProgressManager.checkCanceled(); progress.setFraction(((double) fileCounter++) / totalFiles); progress.setText(PREPARE_FILES_TEXT + fileCounter + " of " + totalFiles + " files done."); + if (!file.isValid()) { + DCLogger.warn("Invalid PsiFile: " + psiFiles); + continue; + } final String path = DeepCodeUtils.getDeepCodedFilePath(file); // info("getHash requested"); final String hash = getHash(file); @@ -285,24 +289,58 @@ private static Map> retrieveSuggestions( CreateBundleResponse createBundleResponse = makeNewBundle(project, mapPath2Hash, filesToRemove); if (isNotSucceed(project, createBundleResponse, "Bad Create/Extend Bundle request: ")) return EMPTY_MAP; + final String bundleId = createBundleResponse.getBundleId(); + List missingFiles = createBundleResponse.getMissingFiles(); info( "--- Create/Extend Bundle took: " + (System.currentTimeMillis() - startTime) - + " milliseconds"); - - final String bundleId = createBundleResponse.getBundleId(); - info("bundleId: " + bundleId); - - final List missingFiles = createBundleResponse.getMissingFiles(); - info("missingFiles: " + missingFiles.size()); + + " milliseconds" + + "\nbundleId: " + + bundleId + + "\nmissingFiles: " + + missingFiles.size()); // ---------------------------------------- Upload Files startTime = System.currentTimeMillis(); progress.setText(UPLOADING_FILES_TEXT); ProgressManager.checkCanceled(); - fileCounter = 0; - totalFiles = missingFiles.size(); + for (int counter = 0; counter < 10; counter++) { + uploadFiles(project, psiFiles, missingFiles, bundleId, progress); + missingFiles = checkBundle(project, bundleId, progress); + if (missingFiles.isEmpty()) { + break; + } else { + warn( + "Check Bundle found some missingFiles to be NOT uploaded, will try to upload " + + (10 - counter) + + " more times:\nmissingFiles = " + + missingFiles); + } + } + // mapPsiFile2Hash.clear(); + mapPsiFile2Content.clear(); + info("--- Upload Files took: " + (System.currentTimeMillis() - startTime) + " milliseconds"); + + // ---------------------------------------- Get Analysis + startTime = System.currentTimeMillis(); + progress.setText(WAITING_FOR_ANALYSIS_TEXT); + ProgressManager.checkCanceled(); + GetAnalysisResponse getAnalysisResponse = doRetrieveSuggestions(project, bundleId, progress); + result = parseGetAnalysisResponse(project, psiFiles, getAnalysisResponse, progress); + info("--- Get Analysis took: " + (System.currentTimeMillis() - startTime) + " milliseconds"); + // progress.stop(); + return result; + } + + private static void uploadFiles( + @NotNull Project project, + @NotNull Collection psiFiles, + @NotNull List missingFiles, + @NotNull String bundleId, + @NotNull ProgressIndicator progress) { + int fileCounter = 0; + int totalFiles = missingFiles.size(); long fileChunkSize = 0; int brokenMissingFilesCount = 0; String brokenMissingFilesMessage = ""; @@ -335,7 +373,7 @@ private static Map> retrieveSuggestions( final long fileSize = psiFile.getVirtualFile().getLength(); if (fileChunkSize + fileSize > MAX_BUNDLE_SIZE) { info("Files-chunk size: " + fileChunkSize); - uploadFiles(project, filesChunk, bundleId, progress); + doUploadFiles(project, filesChunk, bundleId, progress); fileChunkSize = 0; filesChunk.clear(); } @@ -344,21 +382,18 @@ private static Map> retrieveSuggestions( } if (brokenMissingFilesCount > 0) warn(brokenMissingFilesCount + brokenMissingFilesMessage); info("Last files-chunk size: " + fileChunkSize); - uploadFiles(project, filesChunk, bundleId, progress); - - // mapPsiFile2Hash.clear(); - mapPsiFile2Content.clear(); - info("--- Upload Files took: " + (System.currentTimeMillis() - startTime) + " milliseconds"); + doUploadFiles(project, filesChunk, bundleId, progress); + } - // ---------------------------------------- Get Analysis - startTime = System.currentTimeMillis(); - progress.setText(WAITING_FOR_ANALYSIS_TEXT); - ProgressManager.checkCanceled(); - GetAnalysisResponse getAnalysisResponse = doRetrieveSuggestions(project, bundleId, progress); - result = parseGetAnalysisResponse(project, psiFiles, getAnalysisResponse, progress); - info("--- Get Analysis took: " + (System.currentTimeMillis() - startTime) + " milliseconds"); - // progress.stop(); - return result; + @NotNull + private static List checkBundle( + @NotNull Project project, @NotNull String bundleId, @NotNull ProgressIndicator progress) { + CreateBundleResponse checkBundleResponse = + DeepCodeRestApi.checkBundle(DeepCodeParams.getSessionToken(), bundleId); + if (isNotSucceed(project, checkBundleResponse, "Bad CheckBundle request: ")) { + return Collections.emptyList(); + } + return checkBundleResponse.getMissingFiles(); } private static CreateBundleResponse makeNewBundle( @@ -479,7 +514,7 @@ private static String doGetFileContent(@NotNull PsiFile psiFile) { */ } - private static void uploadFiles( + private static void doUploadFiles( @NotNull Project project, @NotNull Collection psiFiles, @NotNull String bundleId, @@ -609,6 +644,8 @@ private static FileContent createFileContent(PsiFile psiFile) { public static Set getAllFilesWithSuggestions(@NotNull final Project project) { return mapFile2Suggestions.entrySet().stream() .filter(e -> e.getKey().getProject().equals(project)) + // otherwise ai.deepcode.jbplugin.ui.TodoTreeBuilder.getAllFiles may fail + .filter(e -> e.getKey().isValid()) .filter(e -> !e.getValue().isEmpty()) .map(Map.Entry::getKey) .collect(Collectors.toSet()); @@ -618,6 +655,7 @@ public static boolean isFileInCache(@NotNull PsiFile psiFile) { return mapFile2Suggestions.containsKey(psiFile); } + /** Remove project from all Caches and CANCEL all background tasks for it */ public static void clearCache(@Nullable final Project project) { info("Cache clearance requested for project: " + project); mapPsiFile2Hash.clear(); @@ -625,6 +663,8 @@ public static void clearCache(@Nullable final Project project) { final Set projects = (project == null) ? getAllCachedProject() : Collections.singleton(project); for (Project prj : projects) { + // lets all running ProgressIndicators release MUTEX first + RunUtils.cancelRunningIndicators(prj); removeProjectFromCache(prj); ServiceManager.getService(prj, myTodoView.class).refresh(); mapProject2analysisUrl.put(prj, ""); diff --git a/src/main/java/ai/deepcode/jbplugin/core/DeepCodeUtils.java b/src/main/java/ai/deepcode/jbplugin/core/DeepCodeUtils.java index 66c58ae..be25b0e 100644 --- a/src/main/java/ai/deepcode/jbplugin/core/DeepCodeUtils.java +++ b/src/main/java/ai/deepcode/jbplugin/core/DeepCodeUtils.java @@ -3,6 +3,7 @@ import ai.deepcode.javaclient.DeepCodeRestApi; import ai.deepcode.javaclient.responses.GetFiltersResponse; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; @@ -42,8 +43,17 @@ static List getAllSupportedFilesInProject(@NotNull Project project) { private static List allProjectFiles(@NotNull Project project) { final PsiManager psiManager = PsiManager.getInstance(project); - final PsiDirectory prjDirectory = psiManager.findDirectory(project.getBaseDir()); - return prjDirectory != null ? getFilesRecursively(prjDirectory) : Collections.emptyList(); + final VirtualFile projectDir = ProjectUtil.guessProjectDir(project); + if (projectDir == null) { + DCLogger.warn("Project directory not found for: " + project); + return Collections.emptyList(); + } + final PsiDirectory prjDirectory = psiManager.findDirectory(projectDir); + if (prjDirectory == null) { + DCLogger.warn("Project directory not found for: " + project); + return Collections.emptyList(); + } + return getFilesRecursively(prjDirectory); } private static List getFilesRecursively(@NotNull PsiDirectory psiDirectory) { diff --git a/src/main/java/ai/deepcode/jbplugin/core/LoginUtils.java b/src/main/java/ai/deepcode/jbplugin/core/LoginUtils.java index 825b647..35c51a0 100644 --- a/src/main/java/ai/deepcode/jbplugin/core/LoginUtils.java +++ b/src/main/java/ai/deepcode/jbplugin/core/LoginUtils.java @@ -28,6 +28,7 @@ private LoginUtils() {} /** network request! */ public static boolean isLogged(@Nullable Project project, boolean userActionNeeded) { final String sessionToken = DeepCodeParams.getSessionToken(); + ProgressManager.checkCanceled(); final EmptyResponse response = DeepCodeRestApi.checkSession(sessionToken); boolean isLogged = response.getStatusCode() == 200; String message = response.getStatusDescription(); diff --git a/src/main/java/ai/deepcode/jbplugin/core/MyBulkFileListener.java b/src/main/java/ai/deepcode/jbplugin/core/MyBulkFileListener.java index cf8bb58..2d5ecc9 100644 --- a/src/main/java/ai/deepcode/jbplugin/core/MyBulkFileListener.java +++ b/src/main/java/ai/deepcode/jbplugin/core/MyBulkFileListener.java @@ -1,11 +1,9 @@ package ai.deepcode.jbplugin.core; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.vfs.newvfs.BulkFileListener; -import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent; -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.*; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.util.PsiTreeUtil; @@ -26,40 +24,52 @@ public class MyBulkFileListener implements BulkFileListener { @Override public void after(@NotNull List events) { // fixme debug only - DCLogger.info("MyBulkFileListener.after begins for: " + events); - for (Project project : AnalysisData.getAllCachedProject()) { - RunUtils.runInBackground( - project, - () -> { - Set filesChangedOrCreated = - RunUtils.computeInReadActionInSmartMode( - project, - () -> - getFilteredFilesByEventTypes( - project, - events, - (psiFile -> - DeepCodeUtils.isSupportedFileFormat(psiFile) - // to prevent updating files already done by - // MyPsiTreeChangeAdapter - // fixme: doesn't work, try to use isFromSave or isFromRefresh - && AnalysisData.isHashChanged(psiFile)), - VFileContentChangeEvent.class, - // fixme doen't work for copy-past file ( VFileMoveEvent ?) - VFileCreateEvent.class)); - if (!filesChangedOrCreated.isEmpty()) { - DCLogger.info( - filesChangedOrCreated.size() + " files changed: " + filesChangedOrCreated); - for (PsiFile psiFile : filesChangedOrCreated) { - RunUtils.runInBackgroundCancellable( - psiFile, - () -> { - AnalysisData.removeFilesFromCache(Collections.singleton(psiFile)); - RunUtils.asyncAnalyseAndUpdatePanel(project, Collections.singleton(psiFile)); - }); - } - } - }); + DCLogger.info("MyBulkFileListener.after begins for " + events.size() + " events " + events); + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + /* + for (Project project : AnalysisData.getAllCachedProject()) { + RunUtils.runInBackground( + project, + () -> { + */ + Set filesChangedOrCreated = + /* + RunUtils.computeInReadActionInSmartMode( + project, + () -> + */ + getFilteredFilesByEventTypes( + project, + events, + (psiFile -> DeepCodeUtils.isSupportedFileFormat(psiFile) + // to prevent updating files already done by + // MyPsiTreeChangeAdapter + // fixme: doesn't work, try to use isFromSave or isFromRefresh + // && AnalysisData.isHashChanged(psiFile) + ), + VFileContentChangeEvent.class, + VFileMoveEvent.class, + VFileCopyEvent.class, + VFileCreateEvent.class); + if (!filesChangedOrCreated.isEmpty()) { + DCLogger.info(filesChangedOrCreated.size() + " files changed: " + filesChangedOrCreated); + if (filesChangedOrCreated.size() > 10) { + // if too many files changed then it's easier to do Bulk Mode full rescan + RunUtils.setBulkMode(project); + // small delay to prevent multiple rescan Background tasks + RunUtils.rescanInBackgroundCancellableDelayed(project, 100, true); + } else { + for (PsiFile psiFile : filesChangedOrCreated) { + RunUtils.runInBackgroundCancellable( + psiFile, + () -> { + AnalysisData.removeFilesFromCache(Collections.singleton(psiFile)); + RunUtils.updateCachedAnalysisResults(project, Collections.singleton(psiFile)); + }); + } + } + } + // }); Set gcignoreChangedFiles = getFilteredFilesByEventTypes( @@ -69,50 +79,67 @@ public void after(@NotNull List events) { VFileContentChangeEvent.class, VFileCreateEvent.class); if (!gcignoreChangedFiles.isEmpty()) { - RunUtils.runInBackground( - project, - () -> { - gcignoreChangedFiles.forEach(DeepCodeIgnoreInfoHolder::update_dcignoreFileContent); - // small delay to prevent duplicated delete with MyPsiTreeChangeAdapter - RunUtils.rescanProject(project, 100); - }); + RunUtils.setBulkMode(project); + for (PsiFile gcignoreFile : gcignoreChangedFiles) { + RunUtils.runInBackgroundCancellable( + gcignoreFile, + () -> DeepCodeIgnoreInfoHolder.update_dcignoreFileContent(gcignoreFile)); + } + // small delay to prevent multiple rescan Background tasks + RunUtils.rescanInBackgroundCancellableDelayed(project, 100, true); } } // fixme debug only - DCLogger.info("MyBulkFileListener.after ends for: " + events); + DCLogger.info("MyBulkFileListener.after ends"); } @Override public void before(@NotNull List events) { - DCLogger.info("MyBulkFileListener.before begins for: " + events); - for (Project project : AnalysisData.getAllCachedProject()) { + DCLogger.info("MyBulkFileListener.before begins for " + events.size() + " events " + events); + for (Project project : ProjectManager.getInstance().getOpenProjects()) { + // for (Project project : AnalysisData.getAllCachedProject()) { if (project.isDisposed()) continue; Set filesRemoved = getFilteredFilesByEventTypes( project, events, DeepCodeUtils::isSupportedFileFormat, VFileDeleteEvent.class); if (!filesRemoved.isEmpty()) { - RunUtils.runInBackground( - project, - () -> { - AnalysisData.removeFilesFromCache(filesRemoved); - RunUtils.asyncAnalyseAndUpdatePanel(project, Collections.emptyList(), filesRemoved); - }); + DCLogger.info("Found " + filesRemoved.size() + " files to remove: " + filesRemoved); + // if too many files removed then it's easier to do full rescan + if (filesRemoved.size() > 10) { + RunUtils.setBulkMode(project); + // small delay to prevent multiple rescan Background tasks + RunUtils.rescanInBackgroundCancellableDelayed(project, 100, true); + } else if (!RunUtils.isFullRescanRequested(project)) { + RunUtils.runInBackground( + project, + () -> { + AnalysisData.removeFilesFromCache(filesRemoved); + RunUtils.updateCachedAnalysisResults( + project, Collections.emptyList(), filesRemoved); + }); + } } Set ignoreFilesToRemove = getFilteredFilesByEventTypes( project, events, DeepCodeIgnoreInfoHolder::is_ignoreFile, VFileDeleteEvent.class); if (!ignoreFilesToRemove.isEmpty()) { - RunUtils.runInBackground( - project, - () -> { - ignoreFilesToRemove.forEach(DeepCodeIgnoreInfoHolder::remove_dcignoreFileContent); - // small delay to prevent duplicated delete with MyPsiTreeChangeAdapter - RunUtils.rescanProject(project, 100); - }); + RunUtils.setBulkMode(project); + // small delay to prevent multiple rescan Background tasks + RunUtils.rescanInBackgroundCancellableDelayed(project, 100, true); + /* + RunUtils.rescanInBackgroundCancellableDelayed( + project, + 100, // small delay to prevent multiple rescan + () -> { + ignoreFilesToRemove.forEach(DeepCodeIgnoreInfoHolder::remove_dcignoreFileContent); + RunUtils.rescanProject(project); + RunUtils.unsetBulkMode(project); + }); + */ } } - DCLogger.info("MyBulkFileListener.before ends for: " + events); + DCLogger.info("MyBulkFileListener.before ends"); } private Set getFilteredFilesByEventTypes( @@ -122,6 +149,8 @@ private Set getFilteredFilesByEventTypes( @NotNull Class... classesOfEventsToFilter) { PsiManager manager = PsiManager.getInstance(project); return events.stream() + // to prevent updating files already done by MyPsiTreeChangeAdapter + .filter(VFileEvent::isFromRefresh) .filter(event -> PsiTreeUtil.instanceOf(event, classesOfEventsToFilter)) .map(VFileEvent::getFile) .filter(Objects::nonNull) diff --git a/src/main/java/ai/deepcode/jbplugin/core/MyProjectManagerListener.java b/src/main/java/ai/deepcode/jbplugin/core/MyProjectManagerListener.java index 3525549..9375a06 100644 --- a/src/main/java/ai/deepcode/jbplugin/core/MyProjectManagerListener.java +++ b/src/main/java/ai/deepcode/jbplugin/core/MyProjectManagerListener.java @@ -2,7 +2,6 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManagerListener; import com.intellij.openapi.vfs.VirtualFile; @@ -32,16 +31,18 @@ public void projectOpened(@NotNull Project project) { @Override public void projectClosing(@NotNull Project project) { - // lets all running ProgressIndicators release MUTEX first - // RunUtils.cancelRunningIndicators(project); - RunUtils.runInBackground(project, () -> AnalysisData.removeProjectFromCache(project)); + RunUtils.runInBackground(project, () -> { + // lets all running ProgressIndicators release MUTEX first + RunUtils.cancelRunningIndicators(project); + AnalysisData.removeProjectFromCache(project); + }); } private static class MyPsiTreeChangeAdapter extends PsiTreeChangeAdapter { @Override public void beforeChildrenChange(@NotNull PsiTreeChangeEvent event) { final PsiFile psiFile = event.getFile(); - if (psiFile == null) return; + if (psiFile == null || RunUtils.inBulkMode(psiFile.getProject())) return; if (AnalysisData.isFileInCache(psiFile)) { // ?? immediate delete for visual updates in Panel, annotations, etc. // should be done in background to wait MUTEX released in case of currently running update @@ -68,6 +69,8 @@ public void beforeChildrenChange(@NotNull PsiTreeChangeEvent event) { public void childrenChanged(@NotNull PsiTreeChangeEvent event) { final PsiFile psiFile = event.getFile(); if (psiFile == null) return; + final Project project = psiFile.getProject(); + if (RunUtils.inBulkMode(project)) return; if (DeepCodeUtils.isSupportedFileFormat(psiFile)) { RunUtils.runInBackgroundCancellable( @@ -79,14 +82,14 @@ public void childrenChanged(@NotNull PsiTreeChangeEvent event) { // but in case of update finished between beforeChildrenChange and now. AnalysisData.removeFilesFromCache(psiFileSet); } - RunUtils.asyncAnalyseAndUpdatePanel(psiFile.getProject(), psiFileSet); + RunUtils.updateCachedAnalysisResults(project, psiFileSet); }); } if (DeepCodeIgnoreInfoHolder.is_dcignoreFile(psiFile)) { DeepCodeIgnoreInfoHolder.update_dcignoreFileContent(psiFile); // delayed to prevent unnecessary updates in case of continuous typing by user - RunUtils.rescanProject(psiFile.getProject(), 1000); + RunUtils.rescanInBackgroundCancellableDelayed(project, 1000, false); } // .gitignore content delay to be parsed https://youtrack.jetbrains.com/issue/IDEA-239773 final VirtualFile virtualFile = psiFile.getVirtualFile(); @@ -95,7 +98,7 @@ public void childrenChanged(@NotNull PsiTreeChangeEvent event) { if (document != null) { FileDocumentManager.getInstance().saveDocument(document); // delayed to let git update it meta-info - RunUtils.rescanProject(psiFile.getProject(), 1000); + RunUtils.rescanInBackgroundCancellableDelayed(project, 1000, false); } } } @@ -103,10 +106,14 @@ public void childrenChanged(@NotNull PsiTreeChangeEvent event) { @Override public void beforeChildRemoval(@NotNull PsiTreeChangeEvent event) { PsiFile psiFile = (event.getChild() instanceof PsiFile) ? (PsiFile) event.getChild() : null; - if (psiFile != null && DeepCodeIgnoreInfoHolder.is_ignoreFile(psiFile)) { + if (psiFile == null) return; + final Project project = psiFile.getProject(); + if (RunUtils.inBulkMode(project)) return; + + if (DeepCodeIgnoreInfoHolder.is_ignoreFile(psiFile)) { DeepCodeIgnoreInfoHolder.remove_dcignoreFileContent(psiFile); - // small delay to prevent duplicated delete with MyBulkFileListener - RunUtils.rescanProject(psiFile.getProject(), 100); + // ??? small delay to prevent duplicated delete with MyBulkFileListener + RunUtils.rescanInBackgroundCancellableDelayed(project, 100, false); } } } diff --git a/src/main/java/ai/deepcode/jbplugin/core/RunUtils.java b/src/main/java/ai/deepcode/jbplugin/core/RunUtils.java index a4ebac9..ae37bd5 100644 --- a/src/main/java/ai/deepcode/jbplugin/core/RunUtils.java +++ b/src/main/java/ai/deepcode/jbplugin/core/RunUtils.java @@ -21,17 +21,44 @@ public class RunUtils { private RunUtils() {} - public static void asyncUpdateCurrentFilePanel(PsiFile psiFile) { - /* - ApplicationManager.getApplication() - .invokeLater( - () -> - WriteCommandAction.runWriteCommandAction( - psiFile.getProject(), - () -> DeepCodeConsoleToolWindowFactory.updateCurrentFilePanel(psiFile))); - */ + private static final Map mapProject2RequestsCounter = new ConcurrentHashMap<>(); + + private static int getBulkRequestsCount(@NotNull Project project) { + return mapProject2RequestsCounter.computeIfAbsent(project, p -> 0); + } + + /** No events for individual files should be processed */ + public static boolean inBulkMode(@NotNull Project project) { + return getBulkRequestsCount(project) > 0; + } + + public static void setBulkMode(@NotNull Project project) { + final int counter = getBulkRequestsCount(project) + 1; + if (counter == 1) { + // cancel all running tasks first + cancelRunningIndicators(project); + } + DCLogger.info("BulkMode ON with " + counter + " total requests"); + mapProject2RequestsCounter.put(project, counter); + } + + public static void unsetBulkMode(@NotNull Project project) { + final int counter = getBulkRequestsCount(project) - 1; + if (counter >= 0) { + mapProject2RequestsCounter.put(project, counter); + DCLogger.info("BulkMode OFF with " + counter + " total requests"); + } else { + DCLogger.warn("BulkMode OFF request with already " + counter + " total requests"); + } + } + + public static void forceUnsetBulkMode(@NotNull Project project) { + mapProject2RequestsCounter.put(project, 0); + DCLogger.info("BulkMode OFF forced"); } + public static void asyncUpdateCurrentFilePanel(PsiFile psiFile) {} + public static T computeInReadActionInSmartMode( @NotNull Project project, @NotNull final Computable computation) { // DCLogger.info("computeInReadActionInSmartMode requested"); @@ -58,20 +85,6 @@ public static void delay(long millis) { ProgressManager.checkCanceled(); } - private static long timeOfLastRescanRequest = 0; - // BackgroundTaskQueue ??? com.intellij.openapi.wm.ex.StatusBarEx#getBackgroundProcesses ??? - public static void rescanProject(@NotNull Project project, long delayMilliseconds) { - runInBackground( - project, - () -> { - long timeOfThisRequest = timeOfLastRescanRequest = System.currentTimeMillis(); - delay(delayMilliseconds); - if (timeOfLastRescanRequest > timeOfThisRequest) return; - AnalysisData.clearCache(project); - asyncAnalyseProjectAndUpdatePanel(project); - }); - } - public static void runInBackground(@NotNull Project project, @NotNull Runnable runnable) { DCLogger.info("runInBackground requested"); final ProgressManager progressManager = ProgressManager.getInstance(); @@ -109,7 +122,8 @@ public void run(@NotNull ProgressIndicator indicator) { private static final Map> mapProject2Indicators = new ConcurrentHashMap<>(); - private static synchronized Set getRunningIndicators(@NotNull Project project) { + private static synchronized Set getRunningIndicators( + @NotNull Project project) { return mapProject2Indicators.computeIfAbsent(project, p -> new HashSet<>()); } @@ -123,8 +137,11 @@ public static void cancelRunningIndicators(@NotNull Project project) { .map(ProgressIndicator::toString) .collect(Collectors.joining("\n")); DCLogger.info("Canceling ProgressIndicators:\n" + indicatorsList); + // in case any indicator holds Bulk mode process + forceUnsetBulkMode(project); getRunningIndicators(project).forEach(ProgressIndicator::cancel); getRunningIndicators(project).clear(); + projectsWithFullRescanRequested.remove(project); } private static final Map mapFileProcessed2CancellableIndicator = @@ -134,13 +151,20 @@ public static void cancelRunningIndicators(@NotNull Project project) { public static void runInBackgroundCancellable( @NotNull PsiFile psiFile, @NotNull Runnable runnable) { - DCLogger.info("runInBackgroundCancellable requested for: " + psiFile.getName()); + final String s = runnable.toString(); + final String runId = s.substring(s.lastIndexOf('/'), s.length() - 1); + DCLogger.info( + "runInBackgroundCancellable requested for: " + + psiFile.getName() + + " with Runnable " + + runId); final VirtualFile virtualFile = psiFile.getVirtualFile(); // To proceed multiple PSI events in a bunch (every 100 milliseconds) Runnable prevRunnable = mapFile2Runnable.put(virtualFile, runnable); if (prevRunnable != null) return; - DCLogger.info("new Background task registered for: " + psiFile.getName()); + DCLogger.info( + "new Background task registered for: " + psiFile.getName() + " with Runnable " + runId); final Project project = psiFile.getProject(); ProgressManager.getInstance() @@ -170,10 +194,13 @@ && getRunningIndicators(project).contains(prevProgressIndicator)) { // small delay to let new consequent requests proceed and cancel current one delay(100); - DCLogger.info("New Process started for " + psiFile.getName()); Runnable actualRunnable = mapFile2Runnable.get(virtualFile); - mapFile2Runnable.remove(virtualFile); if (actualRunnable != null) { + final String s1 = actualRunnable.toString(); + final String runId = s1.substring(s1.lastIndexOf('/'), s1.length() - 1); + DCLogger.info( + "New Process started for " + psiFile.getName() + " with Runnable " + runId); + mapFile2Runnable.remove(virtualFile); actualRunnable.run(); } else { DCLogger.warn("No actual Runnable found for: " + psiFile.getName()); @@ -183,34 +210,128 @@ && getRunningIndicators(project).contains(prevProgressIndicator)) { }); } - public static void asyncAnalyseProjectAndUpdatePanel(@Nullable Project project) { - if (project == null) { - for (Project prj : ProjectManager.getInstance().getOpenProjects()) { - asyncAnalyseAndUpdatePanel(prj, null); + public static boolean isFullRescanRequested(@NotNull Project project) { + return projectsWithFullRescanRequested.contains(project); + } + + private static final Set projectsWithFullRescanRequested = + ContainerUtil.newConcurrentSet(); + + private static final Map mapProject2CancellableIndicator = + new ConcurrentHashMap<>(); + private static final Map mapProject2CancellableRequestId = + new ConcurrentHashMap<>(); + + private static final Map mapProject2RequestId = new ConcurrentHashMap<>(); + private static final Set bulkModeRequests = ContainerUtil.newConcurrentSet(); + + public static void rescanInBackgroundCancellableDelayed( + @NotNull Project project, long delayMilliseconds, boolean inBulkMode) { + final long requestId = System.currentTimeMillis(); + DCLogger.info( + "rescanInBackgroundCancellableDelayed requested for: " + + project.getName() + + "] with RequestId " + + requestId); + projectsWithFullRescanRequested.add(project); + + // To proceed multiple events in a bunch (every ) + Long prevRequestId = mapProject2RequestId.put(project, requestId); + if (inBulkMode) bulkModeRequests.add(requestId); + if (prevRequestId != null) { + if (bulkModeRequests.remove(prevRequestId)) { + RunUtils.unsetBulkMode(project); } - } else asyncAnalyseAndUpdatePanel(project, null); + return; + } + DCLogger.info( + "new Background Rescan task registered for [" + + project.getName() + + "] with RequestId " + + requestId); + + ProgressManager.getInstance() + .run( + new Task.Backgroundable(project, "DeepCode: Analysing Files...") { + @Override + public void run(@NotNull ProgressIndicator indicator) { + + // To let new event cancel the currently running one + ProgressIndicator prevProgressIndicator = + mapProject2CancellableIndicator.put(project, indicator); + if (prevProgressIndicator != null + // can't use prevProgressIndicator.isRunning() due to + // https://youtrack.jetbrains.com/issue/IDEA-241055 + && getRunningIndicators(project).remove(prevProgressIndicator)) { + DCLogger.info( + "Previous Rescan cancelling for " + + project.getName() + + "\nProgressIndicator [" + + prevProgressIndicator.toString() + + "]"); + prevProgressIndicator.cancel(); + } + getRunningIndicators(project).add(indicator); + + // unset BulkMode if cancelled process did run under BulkMode + Long prevRequestId = mapProject2CancellableRequestId.put(project, requestId); + if (prevRequestId != null && bulkModeRequests.remove(prevRequestId)) { + RunUtils.unsetBulkMode(project); + } + + // delay to let new consequent requests proceed and cancel current one + // or to let Idea proceed internal events (.gitignore update) + delay(delayMilliseconds); + + Long actualRequestId = mapProject2RequestId.get(project); + if (actualRequestId != null) { + DCLogger.info( + "New Rescan started for [" + + project.getName() + + "] with RequestId " + + actualRequestId); + mapProject2RequestId.remove(project); + + // actual rescan + AnalysisData.removeProjectFromCache(project); + updateCachedAnalysisResults(project, null); + + if (bulkModeRequests.remove(actualRequestId)) { + RunUtils.unsetBulkMode(project); + } + } else { + DCLogger.warn("No actual RequestId found for: " + project.getName()); + } + projectsWithFullRescanRequested.remove(project); + DCLogger.info("Rescan ending for " + project.getName()); + } + }); + } + + public static void asyncAnalyseProjectAndUpdatePanel(@Nullable Project project) { + final Project[] projects = + (project == null) + ? ProjectManager.getInstance().getOpenProjects() + : new Project[] {project}; + for (Project prj : projects) { + // DumbService.getInstance(project).runWhenSmart(() -> + runInBackground(prj, () -> updateCachedAnalysisResults(prj, null)); + } } - public static void asyncAnalyseAndUpdatePanel( + public static void updateCachedAnalysisResults( @NotNull Project project, @Nullable Collection psiFiles) { - asyncAnalyseAndUpdatePanel(project, psiFiles, Collections.emptyList()); + updateCachedAnalysisResults(project, psiFiles, Collections.emptyList()); } - public static void asyncAnalyseAndUpdatePanel( + public static void updateCachedAnalysisResults( @NotNull Project project, @Nullable Collection psiFiles, @NotNull Collection filesToRemove) { - // DumbService.getInstance(project) - // .runWhenSmart( - // () -> - runInBackground( + AnalysisData.updateCachedResultsForFiles( project, - () -> { - AnalysisData.updateCachedResultsForFiles( - project, - (psiFiles != null) ? psiFiles : DeepCodeUtils.getAllSupportedFilesInProject(project), - filesToRemove); - // StatusBarUtil.setStatusBarInfo(project, message); - }); + (psiFiles != null) ? psiFiles : DeepCodeUtils.getAllSupportedFilesInProject(project), + filesToRemove); + // StatusBarUtil.setStatusBarInfo(project, message); } } diff --git a/src/main/java/ai/deepcode/jbplugin/ui/myTodoView.java b/src/main/java/ai/deepcode/jbplugin/ui/myTodoView.java index 2f01e53..cde63d4 100644 --- a/src/main/java/ai/deepcode/jbplugin/ui/myTodoView.java +++ b/src/main/java/ai/deepcode/jbplugin/ui/myTodoView.java @@ -117,8 +117,7 @@ public void dispose() { public void initToolWindow(@NotNull ToolWindow toolWindow) { // Create panels ContentFactory contentFactory = ContentFactory.SERVICE.getInstance(); - final String projectDisplayName = - IdeUICustomization.getInstance().getProjectDisplayName() + " [ " + myProject.getName() + " ]"; + final String projectDisplayName ="Project [ " + myProject.getName() + " ]"; Content allTodosContent = contentFactory.createContent(null, projectDisplayName, false); toolWindow.setHelpId("find.todoList"); myAllTodos = new TodoPanel(myProject, state.all, false, allTodosContent) { diff --git a/src/main/java/ai/deepcode/jbplugin/ui/nodes/ModuleToDoNode.java b/src/main/java/ai/deepcode/jbplugin/ui/nodes/ModuleToDoNode.java index 3e97783..a8d9932 100644 --- a/src/main/java/ai/deepcode/jbplugin/ui/nodes/ModuleToDoNode.java +++ b/src/main/java/ai/deepcode/jbplugin/ui/nodes/ModuleToDoNode.java @@ -13,11 +13,13 @@ import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.ui.Queryable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.search.TodoItem; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; @@ -82,10 +84,17 @@ public void update(@NotNull PresentationData presentation) { presentation.setPresentableText(newName); } +/* @Override public String getTestPresentation() { return "Module"; } +*/ + + @Override + public String toTestString(@Nullable Queryable.PrintInfo printInfo) { + return "Module"; + } @Override public int getFileCount(Module module) { diff --git a/src/main/java/ai/deepcode/jbplugin/ui/nodes/SummaryNode.java b/src/main/java/ai/deepcode/jbplugin/ui/nodes/SummaryNode.java index 372db76..33b72e1 100644 --- a/src/main/java/ai/deepcode/jbplugin/ui/nodes/SummaryNode.java +++ b/src/main/java/ai/deepcode/jbplugin/ui/nodes/SummaryNode.java @@ -17,11 +17,13 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.ui.Queryable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiFile; import com.intellij.ui.HighlightedRegion; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; @@ -124,10 +126,18 @@ public void update(@NotNull PresentationData presentation) { myBuilder.expandTree(2); } +/* @Override public String getTestPresentation() { return "Summary"; } +*/ + + @Nullable + @Override + public String toTestString(@Nullable Queryable.PrintInfo printInfo) { + return "Summary"; + } @Override public int getFileCount(ToDoSummary summary) { diff --git a/src/main/java/ai/deepcode/jbplugin/ui/nodes/ToDoRootNode.java b/src/main/java/ai/deepcode/jbplugin/ui/nodes/ToDoRootNode.java index e7e4fd1..1b9e104 100644 --- a/src/main/java/ai/deepcode/jbplugin/ui/nodes/ToDoRootNode.java +++ b/src/main/java/ai/deepcode/jbplugin/ui/nodes/ToDoRootNode.java @@ -7,7 +7,9 @@ import ai.deepcode.jbplugin.ui.TodoTreeBuilder; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Queryable; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; @@ -39,10 +41,18 @@ public Object getSummaryNode() { return mySummaryNode; } +/* @Override public String getTestPresentation() { return "Root"; } +*/ + + @Nullable + @Override + public String toTestString(@Nullable Queryable.PrintInfo printInfo) { + return "Root"; + } @Override public int getFileCount(final Object val) { diff --git a/src/main/java/ai/deepcode/jbplugin/ui/nodes/TodoItemNode.java b/src/main/java/ai/deepcode/jbplugin/ui/nodes/TodoItemNode.java index 46463b4..f4285d2 100644 --- a/src/main/java/ai/deepcode/jbplugin/ui/nodes/TodoItemNode.java +++ b/src/main/java/ai/deepcode/jbplugin/ui/nodes/TodoItemNode.java @@ -16,6 +16,7 @@ import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Queryable; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.TextRange; import com.intellij.psi.search.TodoItem; @@ -23,6 +24,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -194,10 +196,17 @@ public int getRowCount() { return myAdditionalLines.size() + 1; } +/* @Override public String getTestPresentation() { return "Item: " + getValue().getTodoItem().getTextRange(); } +*/ + + @Override + public String toTestString(@Nullable Queryable.PrintInfo printInfo) { + return "Item: " + getValue().getTodoItem().getTextRange(); + } @Override public int getWeight() { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 8495a4e..2120afb 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -22,22 +22,7 @@ - +