From b5fbea99c5229074cfeeafd092f6faed35fbbed3 Mon Sep 17 00:00:00 2001 From: Sergei Tachenov Date: Sun, 3 Sep 2023 11:43:48 +0300 Subject: [PATCH] IDEA-331018 Implement select in project view logging Log every step of select in logic starting from SelectInProjectViewImpl.selectInCurrentTarget, which is the main entry point into the whole "scroll from source" business. Implementation note: everything is logged using the same logger to avoid complicated setup on the user side, as the logic is spread out through a lot of classes. The only thing that's not covered by this logging is visitors, those are used in a lot of places and logging a lot of stuff there would spam a lot of unrelated messages. GitOrigin-RevId: e8d907811706cfa22c3234671f77f439dfb6ba5f --- .../ide/impl/ProjectViewSelectInTarget.java | 49 ++++++- .../ide/impl/SelectInTargetPsiWrapper.java | 47 ++++++- .../impl/AsyncProjectViewSupport.java | 50 ++++++- .../ide/projectView/impl/ProjectViewImpl.java | 36 +++++ .../impl/SelectInProjectViewImpl.kt | 128 ++++++++++++++++-- .../scopeView/ScopePaneSelectInTarget.java | 27 +++- .../intellij/ide/scopeView/ScopeViewPane.java | 98 +++++++++++--- .../com/intellij/ide/FileSelectInContext.java | 9 ++ .../intellij/ide/SmartSelectInContext.java | 7 + 9 files changed, 415 insertions(+), 36 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/ide/impl/ProjectViewSelectInTarget.java b/platform/lang-impl/src/com/intellij/ide/impl/ProjectViewSelectInTarget.java index 3edadf619311..c3011194167b 100644 --- a/platform/lang-impl/src/com/intellij/ide/impl/ProjectViewSelectInTarget.java +++ b/platform/lang-impl/src/com/intellij/ide/impl/ProjectViewSelectInTarget.java @@ -9,6 +9,7 @@ import com.intellij.ide.projectView.SelectableTreeStructureProvider; import com.intellij.ide.projectView.TreeStructureProvider; import com.intellij.ide.projectView.impl.AbstractProjectViewPane; import com.intellij.ide.projectView.impl.SelectInProjectViewImpl; +import com.intellij.ide.projectView.impl.SelectInProjectViewImplKt; import com.intellij.notebook.editor.BackedVirtualFile; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.DumbService; @@ -54,8 +55,20 @@ public abstract class ProjectViewSelectInTarget extends SelectInTargetPsiWrapper @Nullable final String subviewId, final VirtualFile virtualFile, final boolean requestFocus) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug( + "ProjectViewSelectInTarget.select: " + + "project=" + project + + ", toSelect=" + toSelect + + ", viewId=" + viewId + + ", subviewId=" + subviewId + + ", virtualFile=" + virtualFile + + ", requestFocus=" + requestFocus + ); + } ProjectView projectView = ProjectView.getInstance(project); if (projectView == null) { + SelectInProjectViewImplKt.getLOG().debug("Not selecting anything because there is no project view"); return ActionCallback.REJECTED; } @@ -63,6 +76,9 @@ public abstract class ProjectViewSelectInTarget extends SelectInTargetPsiWrapper if (ApplicationManager.getApplication().isUnitTestMode()) { AbstractProjectViewPane pane = projectView.getProjectViewPaneById(id); if (pane != null) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Switching to pane " + pane); + } pane.select(toSelect, virtualFile, requestFocus); } return ActionCallback.DONE; @@ -74,20 +90,30 @@ public abstract class ProjectViewSelectInTarget extends SelectInTargetPsiWrapper ToolWindow projectViewToolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.PROJECT_VIEW); if (projectViewToolWindow == null) { + SelectInProjectViewImplKt.getLOG().debug("Not selecting anything because there is no project view tool window"); return ActionCallback.REJECTED; } ActionCallback result = new ActionCallback(); Runnable runnable = () -> { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug( + (requestFocus ? "Activated" : "Shown") + + ". Changing project view to " + id + " / " + subviewId + ", will continue once changed" + ); + } projectView.changeViewCB(id, subviewId).doWhenProcessed(() -> { + SelectInProjectViewImplKt.getLOG().debug("Changed. Delegating to SelectInProjectViewImpl to continue"); project.getService(SelectInProjectViewImpl.class).ensureSelected(id, virtualFile, toSelectSupplier, requestFocus, true, result); }); }; if (requestFocus) { + SelectInProjectViewImplKt.getLOG().debug("Activating the project view tool window, will continue once activated"); projectViewToolWindow.activate(runnable, true); } else { + SelectInProjectViewImplKt.getLOG().debug("Showing the project view tool window, will continue once shown"); projectViewToolWindow.show(runnable); } @@ -114,7 +140,18 @@ public abstract class ProjectViewSelectInTarget extends SelectInTargetPsiWrapper protected boolean canSelect(PsiFileSystemItem file) { VirtualFile vFile = PsiUtilCore.getVirtualFile(file); vFile = vFile == null ? null : BackedVirtualFile.getOriginFileIfBacked(vFile); - if (vFile == null || !vFile.isValid()) return false; + if (vFile == null) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Can NOT select " + file + " because its virtual file is null"); + } + return false; + } + else if (!vFile.isValid()) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Can NOT select " + file + " because its virtual file " + vFile + " is invalid"); + } + return false; + } return canBeSelectedInProjectView(myProject, vFile); } @@ -126,6 +163,13 @@ public abstract class ProjectViewSelectInTarget extends SelectInTargetPsiWrapper @Override public void select(PsiElement element, final boolean requestFocus) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug( + "ProjectViewSelectInTarget.selectIn: Select in " + this + + ", requestFocus=" + requestFocus + + ", element=" + element + ); + } PsiUtilCore.ensureValid(element); PsiElement toSelect = null; for (TreeStructureProvider provider : getProvidersDumbAware()) { @@ -140,6 +184,9 @@ public abstract class ProjectViewSelectInTarget extends SelectInTargetPsiWrapper } } + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Top level element is " + toSelect); + } toSelect = findElementToSelect(element, toSelect); if (toSelect != null) { diff --git a/platform/lang-impl/src/com/intellij/ide/impl/SelectInTargetPsiWrapper.java b/platform/lang-impl/src/com/intellij/ide/impl/SelectInTargetPsiWrapper.java index d7f6bf040387..1a67b746c201 100644 --- a/platform/lang-impl/src/com/intellij/ide/impl/SelectInTargetPsiWrapper.java +++ b/platform/lang-impl/src/com/intellij/ide/impl/SelectInTargetPsiWrapper.java @@ -3,6 +3,7 @@ package com.intellij.ide.impl; import com.intellij.ide.SelectInContext; import com.intellij.ide.SelectInTarget; +import com.intellij.ide.projectView.impl.SelectInProjectViewImplKt; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; @@ -32,14 +33,28 @@ public abstract class SelectInTargetPsiWrapper implements SelectInTarget { protected boolean canSelectInner(@NotNull SelectInContext context) { PsiFileSystemItem psiFile = getContextPsiFile(context); - return psiFile != null && canSelect(psiFile); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("PSI file for context " + context + " is " + psiFile); + } + if (psiFile == null) { + return false; + } + boolean canSelect = canSelect(psiFile); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Can " + (canSelect ? "select" : "NOT select") + " file " + psiFile + " in " + this); + } + return canSelect; } private boolean isContextValid(SelectInContext context) { if (myProject.isDisposed()) return false; VirtualFile virtualFile = context.getVirtualFile(); - return virtualFile.isValid(); + boolean valid = virtualFile.isValid(); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("File " + virtualFile + " is " + (valid ? "valid" : "NOT valid")); + } + return valid; } @Nullable @@ -61,10 +76,23 @@ public abstract class SelectInTargetPsiWrapper implements SelectInTarget { @Override public final void selectIn(@NotNull SelectInContext context, boolean requestFocus) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug( + "SelectInTargetPsiWrapper.selectIn: Select in " + this + + ", requestFocus=" + requestFocus + + " using context " + context + ); + } VirtualFile file = context.getVirtualFile(); Object selector = context.getSelectorInFile(); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("File is " + file + ", selector is " + selector); + } if (selector == null) { selector = PsiUtilCore.findFileSystemItem(myProject, file); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Falling back to selector " + selector); + } } if (selector instanceof PsiElement) { @@ -74,10 +102,16 @@ public abstract class SelectInTargetPsiWrapper implements SelectInTarget { throw new PsiInvalidElementAccessException(original, "Returned by " + selector + " of " + selector.getClass()); } try (var ignored = SlowOperations.startSection(SlowOperations.ACTION_PERFORM)) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Selecting " + original); + } select(original, requestFocus); } } else { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Selecting non-PSI selector " + selector + " in file " + file); + } select(selector, file, requestFocus); } } @@ -93,12 +127,18 @@ public abstract class SelectInTargetPsiWrapper implements SelectInTarget { if (toSelect == null) { if (element instanceof PsiFile || element instanceof PsiDirectory) { toSelect = element; + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Will select PSI file/dir " + toSelect); + } } else { PsiFile containingFile = element.getContainingFile(); if (containingFile != null) { FileViewProvider viewProvider = containingFile.getViewProvider(); toSelect = viewProvider.getPsi(viewProvider.getBaseLanguage()); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Will select PSI element " + toSelect + " provided by " + viewProvider); + } } } } @@ -107,6 +147,9 @@ public abstract class SelectInTargetPsiWrapper implements SelectInTarget { PsiElement originalElement = null; try { originalElement = toSelect.getOriginalElement(); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Original element to select is " + originalElement); + } } catch (IndexNotReadyException ignored) { } if (originalElement != null) { diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java index 781c619b8913..9cef492bf9aa 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java @@ -144,9 +144,19 @@ public final class AsyncProjectViewSupport { } public ActionCallback select(JTree tree, Object object, VirtualFile file) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug( + "AsyncProjectViewSupport.select: " + + "object=" + object + + ", file=" + file + ); + } if (object instanceof AbstractTreeNode node) { object = node.getValue(); LOG.debug("select AbstractTreeNode"); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Retrieved the value from the node: " + object); + } } PsiElement element = object instanceof PsiElement ? (PsiElement)object : null; LOG.debug("select object: ", object, " in file: ", file); @@ -156,22 +166,47 @@ public final class AsyncProjectViewSupport { ActionCallback callback = new ActionCallback(); //noinspection CodeBlock2Expr + SelectInProjectViewImplKt.getLOG().debug("Updating nodes before selecting"); myNodeUpdater.updateImmediately(() -> expand(tree, promise -> { + SelectInProjectViewImplKt.getLOG().debug("Updated nodes"); promise.onSuccess(o -> callback.setDone()); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Collecting paths to select"); + } acceptOnEDT(visitor, () -> { - if (selectPaths(tree, pathsToSelect, visitor) || + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Collected paths to the element: " + pathsToSelect); + } + boolean selected = selectPaths(tree, pathsToSelect, visitor); + if (selected || element == null || file == null || Registry.is("async.project.view.support.extra.select.disabled")) { + if (selected) { + SelectInProjectViewImplKt.getLOG().debug("Selected successfully. Done"); + } + else { + SelectInProjectViewImplKt.getLOG().debug("Couldn't select, but there's nothing else to do. Done"); + } promise.setResult(null); } else { + SelectInProjectViewImplKt.getLOG().debug("Couldn't select the element, falling back to selecting the file"); // try to search the specified file instead of element, // because Kotlin files cannot represent containing functions pathsToSelect.clear(); TreeVisitor fileVisitor = AbstractProjectViewPane.createVisitor(null, file, pathsToSelect); acceptOnEDT(fileVisitor, () -> { - selectPaths(tree, pathsToSelect, fileVisitor); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Collected paths to the file: " + pathsToSelect); + } + boolean selectedFile = selectPaths(tree, pathsToSelect, fileVisitor); + if (selectedFile) { + SelectInProjectViewImplKt.getLOG().debug("Selected successfully. Done"); + } + else { + SelectInProjectViewImplKt.getLOG().debug("Couldn't select, but there's nothing else to do. Done"); + } promise.setResult(null); }); } @@ -185,7 +220,10 @@ public final class AsyncProjectViewSupport { } private static boolean selectPaths(@NotNull JTree tree, @NotNull List paths, @NotNull TreeVisitor visitor) { - if (paths.isEmpty()) return false; + if (paths.isEmpty()) { + SelectInProjectViewImplKt.getLOG().debug("Nothing to select"); + return false; + } if (paths.size() > 1) { if (visitor instanceof ProjectViewNodeVisitor nodeVisitor) { return selectPaths(tree, new SelectionDescriptor(nodeVisitor.getElement(), nodeVisitor.getFile(), paths)); @@ -197,6 +235,9 @@ public final class AsyncProjectViewSupport { TreePath path = paths.get(0); tree.expandPath(path); // request to expand found path TreeUtil.selectPaths(tree, path); // select and scroll to center + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Selected the only path: " + path); + } return true; } @@ -204,6 +245,9 @@ public final class AsyncProjectViewSupport { List adjustedPaths = ProjectViewPaneSelectionHelper.getAdjustedPaths(selectionDescriptor); adjustedPaths.forEach(it -> tree.expandPath(it)); TreeUtil.selectPaths(tree, adjustedPaths); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Selected paths adjusted according to " + selectionDescriptor + ": " + adjustedPaths); + } return true; } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java index b73666c60b02..36c1f4289e18 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewImpl.java @@ -151,6 +151,7 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo getDefaultState().setAutoscrollFromSource(selected); getGlobalOptions().setAutoscrollFromSource(selected); if (selected && !myAutoScrollFromSourceHandler.isCurrentProjectViewPaneFocused()) { + SelectInProjectViewImplKt.getLOG().debug("Invoking scroll from source because Always Select Opened File has been turned on"); myAutoScrollFromSourceHandler.scrollFromSource(false); } } @@ -580,6 +581,7 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo if (ToolWindowId.PROJECT_VIEW.equals(toolWindow.getId())) { AbstractProjectViewPane currentProjectViewPane = getCurrentProjectViewPane(); if (currentProjectViewPane != null && isAutoscrollFromSource(currentProjectViewPane.getId())) { + SelectInProjectViewImplKt.getLOG().debug("Invoking scroll from source because the project view is shown"); myAutoScrollFromSourceHandler.scrollFromSource(false); } } @@ -877,6 +879,9 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo newPane.restoreExpandedPaths(); if (selectedUserObject != null && newSubId != null) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Re-selecting " + selectedUserObject + " after switching to " + myCurrentViewId); + } myProject.getService(SelectInProjectViewImpl.class).ensureSelected( myCurrentViewId, null, @@ -973,6 +978,7 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo JComponent component = editor.getComponent(); for (FileEditor fileEditor : FileEditorManager.getInstance(myProject).getAllEditors()) { if (SwingUtilities.isDescendingFrom(component, fileEditor.getComponent())) { + SelectInProjectViewImplKt.getLOG().debug("Invoking scroll from source because the editor has gained focus"); myAutoScrollFromSourceHandler.scrollFromSource(false); break; } @@ -1075,6 +1081,7 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo target.setSubId(subId); } if (isAutoscrollFromSource(id)) { + SelectInProjectViewImplKt.getLOG().debug("Invoking scroll from source because the project view has changed panes"); myAutoScrollFromSourceHandler.scrollFromSource(false); } } @@ -1152,9 +1159,16 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo @Override public void select(final Object element, VirtualFile file, boolean requestFocus) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("select: element=" + element + ", file=" + file + ", requestFocus=" + requestFocus); + } final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); if (viewPane != null) { myAutoScrollOnFocusEditor.set(!requestFocus); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Delegating to AbstractProjectViewPane, auto scroll enabled=" + + myAutoScrollOnFocusEditor.get()); + } viewPane.select(element, file, requestFocus); } } @@ -1165,11 +1179,24 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo boolean requestFocus, @Nullable ActionCallback result ) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug( + "ProjectViewImpl.select: " + + "elementSupplier=" + elementSupplier + + ", file=" + virtualFile + + ", requestFocus=" + requestFocus + + ", result=" + result + ); + } + SelectInProjectViewImplKt.getLOG().debug("Starting a read action in background to retrieve the element from the supplier"); ReadAction .nonBlocking(elementSupplier::get) .finishOnUiThread( ModalityState.defaultModalityState(), element -> { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Retrieved the element from the supplier: " + element); + } var callback = selectCB(element, virtualFile, requestFocus); if (result != null) { callback.notify(result); @@ -1182,9 +1209,16 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo @NotNull @Override public ActionCallback selectCB(Object element, VirtualFile file, boolean requestFocus) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("ProjectViewImpl.selectCB: element=" + element + ", file=" + file + ", requestFocus=" + requestFocus); + } final AbstractProjectViewPane viewPane = getCurrentProjectViewPane(); if (viewPane instanceof AbstractProjectViewPaneWithAsyncSupport) { myAutoScrollOnFocusEditor.set(!requestFocus); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Delegating to AbstractProjectViewPaneWithAsyncSupport, auto scroll enabled=" + + myAutoScrollOnFocusEditor.get()); + } return ((AbstractProjectViewPaneWithAsyncSupport)viewPane).selectCB(element, file, requestFocus); } select(element, file, requestFocus); @@ -1745,6 +1779,7 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo protected void selectElementFromEditor(@NotNull FileEditor fileEditor) { if (myProject.isDisposed() || !myViewContentPanel.isShowing()) return; if (isAutoscrollFromSource(getCurrentViewId()) && !isCurrentProjectViewPaneFocused()) { + SelectInProjectViewImplKt.getLOG().debug("Invoking scroll from source because the selected editor tab has been changed"); scrollFromSource(fileEditor, false); } } @@ -1833,6 +1868,7 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo } void selectOpenedFile() { + SelectInProjectViewImplKt.getLOG().debug("Invoking scroll from source because Select Opened File was performed manually"); myAutoScrollFromSourceHandler.scrollFromSource(true); } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/SelectInProjectViewImpl.kt b/platform/lang-impl/src/com/intellij/ide/projectView/impl/SelectInProjectViewImpl.kt index 0007486cca39..ea689b386fae 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/SelectInProjectViewImpl.kt +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/SelectInProjectViewImpl.kt @@ -48,6 +48,9 @@ internal class SelectInProjectViewImpl( } private fun invokeWithSemaphore(taskName: String, task: suspend () -> Unit, onDone: (() -> Unit)?) { + if (LOG.isDebugEnabled) { + LOG.debug("Attempting to start $taskName") + } coroutineScope.launch( CoroutineName(taskName), start = CoroutineStart.UNDISPATCHED @@ -56,6 +59,9 @@ internal class SelectInProjectViewImpl( tasks.incrementAndGet() semaphore.withPermit { yield() // Ensure the coroutine is redispatched, even if withPermit() didn't suspend, to free the EDT. + if (LOG.isDebugEnabled) { + LOG.debug("Started $taskName") + } task() } } @@ -65,6 +71,9 @@ internal class SelectInProjectViewImpl( } finally { tasks.decrementAndGet() + if (LOG.isDebugEnabled) { + LOG.debug("Finished $taskName") + } } } } @@ -77,6 +86,9 @@ internal class SelectInProjectViewImpl( } private suspend fun doSelectInCurrentTarget(fileEditor: FileEditor?, invokedManually: Boolean) { + if (LOG.isDebugEnabled) { + LOG.debug("doSelectInCurrentTarget: fileEditor=$fileEditor, invokedManually=$invokedManually") + } val editorsToCheck = if (fileEditor == null) allEditors() else listOf(fileEditor) selectInCurrentTarget(invokedManually, editorsToCheck) } @@ -88,12 +100,22 @@ internal class SelectInProjectViewImpl( AdvancedSettings.getBoolean("project.view.do.not.autoscroll.to.libraries") && readAction { fileEditor.file?.let { file -> ProjectFileIndex.getInstance(project).isInLibrary(file) } == true } ) { + if (LOG.isDebugEnabled) { + LOG.debug("Skipping $fileEditor because the file is in a library and autoscroll to libraries is off") + } continue } val psiFilePointer = getPsiFilePointer(fileEditor) if (psiFilePointer != null) { + if (LOG.isDebugEnabled) { + LOG.debug("Trying to select using $fileEditor with psiFilePointer=$psiFilePointer") + } withContext(Dispatchers.EDT) { - createSelectInContext(psiFilePointer, fileEditor).selectInCurrentTarget(requestFocus = invokedManually) + val selectInContext = createSelectInContext(psiFilePointer, fileEditor) + if (LOG.isDebugEnabled) { + LOG.debug("Created select-in context and delegating to it: $selectInContext") + } + selectInContext.selectInCurrentTarget(requestFocus = invokedManually) } break } @@ -169,20 +191,48 @@ internal class SelectInProjectViewImpl( allowSubIdChange: Boolean, result: ActionCallback?, ) { + if (LOG.isDebugEnabled) { + LOG.debug("doEnsureSelected: " + + "paneId=$paneId, " + + "virtualFile=$virtualFile, " + + "elementSupplier=$elementSupplier, " + + "requestFocus=$requestFocus, " + + "allowSubIdChange=$allowSubIdChange, " + + "result=$result" + ) + } val projectView = project.serviceOrNull() as ProjectViewImpl? if (projectView == null) { + LOG.debug("Not selecting anything because there is no project view") result?.setRejected() return } val pane = if (requestFocus) null else projectView.getProjectViewPaneById(paneId) val target = if (pane == null) null else projectView.getProjectViewSelectInTarget(pane) if (!allowSubIdChange) { + if (LOG.isDebugEnabled) { + LOG.debug("SubId change not allowed, checking: " + + "pane=$pane, " + + "target=$target, " + + "virtualFile=$virtualFile, " + + "and isSubIdSelectable" + ) + } val isSelectableInCurrentSubId = - pane != null && - target != null && - virtualFile != null && - readAction { - target.isSubIdSelectable(pane.subId, FileSelectInContext(project, virtualFile, null)) + if (pane == null || target == null || virtualFile == null) { + if (LOG.isDebugEnabled) { + LOG.debug("File $virtualFile is NOT selectable because there's not enough non-null parameters to go on, not selecting anything") + } + false + } + else { + val isSubIdSelectable = readAction { + target.isSubIdSelectable(pane.subId, FileSelectInContext(project, virtualFile, null)) + } + if (LOG.isDebugEnabled && !isSubIdSelectable) { + LOG.debug("File $virtualFile is NOT selectable in $pane with target $target and changing subId is not allowed, not selecting anything") + } + isSubIdSelectable } if (!isSelectableInCurrentSubId) { return @@ -191,6 +241,9 @@ internal class SelectInProjectViewImpl( val visibleAndSelectedUserObject = withContext(Dispatchers.EDT) { pane?.visibleAndSelectedUserObject } + if (LOG.isDebugEnabled && !requestFocus) { // if requestFocus, pane is always null + LOG.debug("Currently visible and selected node is $visibleAndSelectedUserObject") + } data class SelectionContext( val isAlreadyVisibleAndSelected: Boolean, @@ -198,17 +251,30 @@ internal class SelectInProjectViewImpl( ) val context = readAction { - val elementToSelect = elementSupplier.get() ?: virtualFile ?: return@readAction null + val suppliedElement = elementSupplier.get() + val elementToSelect = suppliedElement ?: virtualFile ?: return@readAction null + if (LOG.isDebugEnabled) { + LOG.debug("Element to select is $elementToSelect (from ${if (suppliedElement == null) "virtual file" else "supplier"})") + } + val isAlreadyVisibleAndSelected = visibleAndSelectedUserObject != null && visibleAndSelectedUserObject.canRepresent(elementToSelect) + if (LOG.isDebugEnabled && isAlreadyVisibleAndSelected) { + LOG.debug("This element is already visible and selected") + } SelectionContext( - visibleAndSelectedUserObject != null && visibleAndSelectedUserObject.canRepresent(elementToSelect), + isAlreadyVisibleAndSelected, virtualFile ?: (elementToSelect as? PsiElement)?.virtualFile ) } + if (LOG.isDebugEnabled) { + LOG.debug("The selection context is $context") + } if (context == null || context.isAlreadyVisibleAndSelected || context.virtualFile == null) { + LOG.debug("Nothing to do") result?.setDone() return } withContext(Dispatchers.EDT) { + LOG.debug("Delegating back to the project view") projectView.select(elementSupplier, context.virtualFile, requestFocus, result) } } @@ -220,9 +286,19 @@ internal class SelectInProjectViewImpl( } private suspend fun doSelectInAnyTarget(context: SelectInContext, targets: Collection, requestFocus: Boolean) { + if (LOG.isDebugEnabled) { + LOG.debug("doSelectInAnyTarget: context=$context, targets=$targets, requestFocus=$requestFocus") + } for (target in targets) { - if (readAction { target.canSelect(context) }) { + val canSelect = readAction { target.canSelect(context) } + if (LOG.isDebugEnabled) { + LOG.debug("${if (canSelect) "Can" else "Can NOT"} select $context in $target") + } + if (canSelect) { withContext(Dispatchers.EDT) { + if (LOG.isDebugEnabled) { + LOG.debug("Selecting $context in $target") + } target.selectIn(context, requestFocus) } return @@ -242,12 +318,18 @@ internal class SelectInProjectViewImpl( file: VirtualFile, requestFocus: Boolean, ) { + if (LOG.isDebugEnabled()) { + LOG.debug("doSelectInScopeViewPane: pane=$pane, pointer=$pointer, file=$file, requestFocus=$requestFocus") + } val currentFilter = pane.getCurrentFilter() val allFilters = pane.filters.toMutableList() allFilters.remove(currentFilter) allFilters.add(0, currentFilter) // Start with the current filter and then fall back to others. for (filter in allFilters) { if (readAction { filter.accept(file) }) { + if (LOG.isDebugEnabled()) { + LOG.debug("The file $file has been accepted by the filter $filter, delegating to the pane") + } withContext(Dispatchers.EDT) { pane.select(pointer, file, requestFocus, filter) } @@ -269,10 +351,18 @@ private open class SimpleSelectInContext( } open suspend fun selectInCurrentTarget(requestFocus: Boolean) { - val currentTarget = (project.serviceOrNull() as ProjectViewImpl?)?.currentSelectInTarget ?: return + val currentTarget = (project.serviceOrNull() as ProjectViewImpl?)?.currentSelectInTarget + if (LOG.isDebugEnabled) { + LOG.debug("The current target is $currentTarget") + } + if (currentTarget == null) return currentTarget.selectIn(this, requestFocus) } + override fun toString(): String { + return "SimpleSelectInContext(project=$project) ${super.toString()}" + } + } private class EditorSelectInContext( @@ -287,12 +377,22 @@ private class EditorSelectInContext( withContext(Dispatchers.EDT) { while (true) { if (editor.isDisposed) { + LOG.debug("Not selecting anything because the editor is disposed") break } val offset = editor.caretModel.offset - constrainedReadAction(ReadConstraint.withDocumentsCommitted(project)) { + if (LOG.isDebugEnabled) { + LOG.debug("Looking for the element at offset $offset") + } + val element = constrainedReadAction(ReadConstraint.withDocumentsCommitted(project)) { psiFile?.findElementAt(offset) } + // No, the fact that the element is only used for logging isn't a bug: + // the whole point of the read action above is to ensure the document is committed + // and parsed before selecting the current element, otherwise it may be outdated. + if (LOG.isDebugEnabled) { + LOG.debug("The element is $element") + } if (editor.caretModel.offset == offset && PsiDocumentManager.getInstance(project).isCommitted(editor.document)) { super.selectInCurrentTarget(requestFocus) break @@ -312,6 +412,10 @@ private class EditorSelectInContext( } return file } + + override fun toString(): String { + return "EditorSelectInContext(editor=$editor) ${super.toString()}" + } } private fun T.nullIfInvalid(): T? = if (isValid) this else null @@ -319,4 +423,4 @@ private fun T.nullIfDisposed(): T? = if (isDisposed) null else this private fun T.nullIfInvalid(): T? = if (isValid) this else null private val PsiElement.virtualFile: VirtualFile? get() = PsiUtilCore.getVirtualFile(this) -private val LOG = logger() +internal val LOG = logger() diff --git a/platform/lang-impl/src/com/intellij/ide/scopeView/ScopePaneSelectInTarget.java b/platform/lang-impl/src/com/intellij/ide/scopeView/ScopePaneSelectInTarget.java index ab652561442f..f8d4e305132d 100644 --- a/platform/lang-impl/src/com/intellij/ide/scopeView/ScopePaneSelectInTarget.java +++ b/platform/lang-impl/src/com/intellij/ide/scopeView/ScopePaneSelectInTarget.java @@ -7,6 +7,7 @@ import com.intellij.ide.SelectInContext; import com.intellij.ide.StandardTargetWeights; import com.intellij.ide.impl.ProjectViewSelectInTarget; import com.intellij.ide.projectView.ProjectView; +import com.intellij.ide.projectView.impl.SelectInProjectViewImplKt; import com.intellij.notebook.editor.BackedVirtualFile; import com.intellij.openapi.extensions.AreaInstance; import com.intellij.openapi.project.Project; @@ -51,8 +52,12 @@ public final class ScopePaneSelectInTarget extends ProjectViewSelectInTarget { @Override public void select(PsiElement element, boolean requestFocus) { if (getSubId() == null) { + SelectInProjectViewImplKt.getLOG().debug("getSubId() == null, looking for a fallback"); PsiFile file = element.getContainingFile(); NamedScopeFilter filter = getContainingFilter(file == null ? null : file.getVirtualFile()); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("The fallback is " + filter); + } if (filter == null) return; setSubId(filter.toString()); } @@ -71,11 +76,29 @@ public final class ScopePaneSelectInTarget extends ProjectViewSelectInTarget { @Override public boolean isSubIdSelectable(@NotNull String subId, @NotNull SelectInContext context) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("ScopePaneSelectInTarget.isSubIdSelectable: subId=" + subId + ", context is " + context); + } PsiFileSystemItem file = getContextPsiFile(context); - if (!(file instanceof PsiFile)) return false; + if (!(file instanceof PsiFile)) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Can NOT select " + file + " because it's not a PsiFile"); + } + return false; + } ScopeViewPane pane = getScopeViewPane(); NamedScopeFilter filter = pane == null ? null : pane.getFilter(subId); - return filter != null && filter.accept(file.getVirtualFile()); + if (filter == null) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Can NOT select " + file + " because filter is null"); + } + return false; + } + boolean accept = filter.accept(file.getVirtualFile()); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("The filter " + filter + (accept ? "accepts" : "does NOT accept") + " file " + file); + } + return accept; } private ScopeViewPane getScopeViewPane() { diff --git a/platform/lang-impl/src/com/intellij/ide/scopeView/ScopeViewPane.java b/platform/lang-impl/src/com/intellij/ide/scopeView/ScopeViewPane.java index 3c3b6dda7244..75861e0aaca5 100644 --- a/platform/lang-impl/src/com/intellij/ide/scopeView/ScopeViewPane.java +++ b/platform/lang-impl/src/com/intellij/ide/scopeView/ScopeViewPane.java @@ -237,11 +237,17 @@ public final class ScopeViewPane extends AbstractProjectViewPane { @Override public void select(Object object, VirtualFile file, boolean requestFocus) { if (myTreeModel.get() == null) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Can NOT select " + object + " / " + file + " in " + this + " because the scope pane isn't initialized yet"); + } // not initialized yet return; } if (file == null) { LOG.warn(new IllegalArgumentException("ScopeViewPane.select: file==null, object=" + object)); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Can NOT select " + object + " / " + file + " in " + this + " because the file is null"); + } return; // Filters don't accept null files anyway, so just do nothing. } @@ -255,6 +261,9 @@ public final class ScopeViewPane extends AbstractProjectViewPane { LOG.warn("ScopeViewPane.select(object=" + object + ",file=" + file + ",requestFocus=" + requestFocus + "): element invalidated"); } } + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Select " + object + " / " + file + " in " + this); + } myProject.getService(SelectInProjectViewImpl.class).selectInScopeViewPane(this, pointer, file, requestFocus); } @@ -267,33 +276,85 @@ public final class ScopeViewPane extends AbstractProjectViewPane { @ApiStatus.Internal public void select(@Nullable SmartPsiElementPointer pointer, VirtualFile file, boolean requestFocus, VirtualFileFilter filter) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug( + "ScopeViewPane.select: " + + "pane=" + this + + ", pointer=" + pointer + + ", file=" + file + + ", requestFocus=" + requestFocus + + ", filter=" + filter + ); + } String subId = filter.toString(); if (!Objects.equals(subId, getSubId())) { - if (!requestFocus) return; - selectScopeView(subId); + if (requestFocus) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug( + "Selected subId=" + getSubId() + + ", requested subId=" + subId + + ", changing the scope" + ); + } + selectScopeView(subId); + } + else { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug( + "Selected subId=" + getSubId() + + ", requested subId=" + subId + + ", changing not allowed because requestFocus=false, aborting" + ); + } + return; + } } if (LOG.isDebugEnabled()) { LOG.debug("select element: ", (pointer == null ? null : pointer.getElement()), " in file: ", file); } TreeVisitor visitor = AbstractProjectViewPane.createVisitorByPointer(pointer, file); - if (visitor == null) return; + if (visitor == null) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Not selecting anything because both the pointer and file are null"); + } + return; + } JTree tree = myTree; - myTreeModel.get().getUpdater().updateImmediately(() -> TreeState.expand(tree, promise -> TreeUtil.visit(tree, visitor, path -> { - if (selectPath(tree, path) || pointer == null || Registry.is("async.project.view.support.extra.select.disabled")) { - promise.setResult(null); - } - else { - // try to search the specified file instead of element, - // because Kotlin files cannot represent containing functions - TreeUtil.visit(tree, AbstractProjectViewPane.createVisitor(file), path2 -> { - selectPath(tree, path2); + SelectInProjectViewImplKt.getLOG().debug("Start updating the tree. Will continue once updated"); + myTreeModel.get().getUpdater().updateImmediately(() -> { + SelectInProjectViewImplKt.getLOG().debug("Updated. Start expanding the tree and looking for the path to select"); + TreeState.expand(tree, promise -> TreeUtil.visit(tree, visitor, path -> { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Expanded. The path to select is " + path); + } + if (selectPath(tree, path) || pointer == null || Registry.is("async.project.view.support.extra.select.disabled")) { promise.setResult(null); - }); - } - }))); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Selected. Done"); + } + } + else { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Not selected. Trying to look for the file without the pointer instead"); + } + // try to search the specified file instead of element, + // because Kotlin files cannot represent containing functions + TreeUtil.visit(tree, AbstractProjectViewPane.createVisitor(file), path2 -> { + selectPath(tree, path2); + promise.setResult(null); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Found and selected " + path2); + } + }); + } + })); + }); } - private static boolean selectPath(@NotNull JTree tree, TreePath path) { + private boolean selectPath(@NotNull JTree tree, TreePath path) { + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("selectPath: " + path + " in " + this); + } if (path == null) { return false; } @@ -444,4 +505,9 @@ public final class ScopeViewPane extends AbstractProjectViewPane { public boolean supportsShowModules() { return PlatformUtils.isIntelliJ(); } + + @Override + public String toString() { + return "ScopeViewPane{id=" + getId() + ",subId=" + getSubId() + "}"; + } } diff --git a/platform/platform-api/src/com/intellij/ide/FileSelectInContext.java b/platform/platform-api/src/com/intellij/ide/FileSelectInContext.java index ad7e3f80aa38..adaaa0ce2af3 100644 --- a/platform/platform-api/src/com/intellij/ide/FileSelectInContext.java +++ b/platform/platform-api/src/com/intellij/ide/FileSelectInContext.java @@ -57,4 +57,13 @@ public class FileSelectInContext implements SelectInContext { FileEditorManager manager = FileEditorManager.getInstance(project); return manager == null ? null : () -> getFirstElement(manager.openFile(file, false)); } + + @Override + public String toString() { + return "FileSelectInContext{" + + "myProject=" + myProject + + ", myFile=" + myFile + + ", myProvider=" + myProvider + + '}'; + } } diff --git a/platform/platform-api/src/com/intellij/ide/SmartSelectInContext.java b/platform/platform-api/src/com/intellij/ide/SmartSelectInContext.java index dc2ab0169ede..c7aef6808ace 100644 --- a/platform/platform-api/src/com/intellij/ide/SmartSelectInContext.java +++ b/platform/platform-api/src/com/intellij/ide/SmartSelectInContext.java @@ -41,4 +41,11 @@ public class SmartSelectInContext extends FileSelectInContext { Object selector = pointer.getElement(); return selector instanceof PsiFile ? (PsiFile)selector : null; } + + @Override + public String toString() { + return "SmartSelectInContext{" + + "pointer=" + pointer + + "} " + super.toString(); + } }