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
This commit is contained in:
Sergei Tachenov
2023-09-03 10:52:35 +00:00
committed by intellij-monorepo-bot
parent d41e9b9412
commit b5fbea99c5
9 changed files with 415 additions and 36 deletions
@@ -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) {
@@ -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) {
@@ -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<TreePath> 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<? extends TreePath> 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;
}
@@ -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);
}
@@ -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<ProjectView>() 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<SelectInTarget>, 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<ProjectView>() as ProjectViewImpl?)?.currentSelectInTarget ?: return
val currentTarget = (project.serviceOrNull<ProjectView>() 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 : FileEditor> T.nullIfInvalid(): T? = if (isValid) this else null
@@ -319,4 +423,4 @@ private fun <T : Editor> T.nullIfDisposed(): T? = if (isDisposed) null else this
private fun <T : VirtualFile> T.nullIfInvalid(): T? = if (isValid) this else null
private val PsiElement.virtualFile: VirtualFile? get() = PsiUtilCore.getVirtualFile(this)
private val LOG = logger<SelectInProjectViewImpl>()
internal val LOG = logger<SelectInProjectViewImpl>()
@@ -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() {
@@ -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<PsiElement> 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() + "}";
}
}
@@ -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 +
'}';
}
}
@@ -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();
}
}