How to get the "current" file(s) like the navigation bar does

Answered

The NavBar changes based on whether I'm in an editor, focus something in the project view, the database view or any other tool window. I would like my plugin to be notified whenever the "selection" changes, ideally supporting multiple elements.

From what I can understand, the NavBar uses periodically gets the focused element and then uses IdeFocusManger.getInstance(project).doWhenFocusSettlesDown and uses the DataContext of the focused element. Is there something more convenient to use?

The approach the NavBar uses seems to be rather complicated and is considering many different cases and intimate knowledge about the UI structure.

Basically, I'd I want to update a tool window based on some selection item(s)/active editor. I would ignore focus changes if my own tool window is currently focused of course.

 

---------------

Interesting places in the code I've found:

NavBarUpdateQueue:

private void processUserActivity() {
if (myPanel == null || !myPanel.isShowing()) {
return;
}

final Project project = myPanel.getProject();
IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(() -> {
Window wnd = SwingUtilities.windowForComponent(myPanel);
if (wnd == null) return;

Component focus = null;

if (!wnd.isActive()) {
IdeFrame frame = UIUtil.getParentOfType(IdeFrame.class, myPanel);
if (frame != null) {
focus = IdeFocusManager.getInstance(project).getLastFocusedFor(frame);
}
}
else {
final Window window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
if (window instanceof Dialog) {
final Dialog dialog = (Dialog)window;
if (dialog.isModal() && !SwingUtilities.isDescendingFrom(myPanel, dialog)) {
return;
}
}
}

if (focus != null && focus.isShowing()) {
if (!myPanel.isFocused() && !myPanel.isNodePopupActive()) {
requestModelUpdate(DataManager.getInstance().getDataContext(focus), null, false);
}
}
else if (wnd.isActive()) {
if (myPanel.allowNavItemsFocus() && (myPanel.isFocused() || myPanel.isNodePopupActive())) {
return;
}
requestModelUpdate(null, myPanel.getContextObject(), false);
}
});
}


 

private void requestModelUpdate(@Nullable final DataContext context, final @Nullable Object object, boolean requeue) {
if (myModelUpdating.getAndSet(true) && !requeue) return;

cancelAllUpdates();

queue(new AfterModelUpdate(ID.MODEL) {
@Override
public void run() {
if (context != null || object != null) {
requestModelUpdateFromContextOrObject(context, object);
} else {
DataManager.getInstance().getDataContextFromFocusAsync().onSuccess(
dataContext -> requestModelUpdateFromContextOrObject(dataContext, null));
}
}

@Override
public void setRejected() {
super.setRejected();
myModelUpdating.set(false);
}
});
}

 

From what I could gather, this is how the NavBar detects a "selection change":

IdeEventQueue.getInstance().addActivityListener(() -> restartRebuild(), panel);

This will start an update of the NavBar via a MergingUpdateQeueu, which is then throttled? using an Alarm
(cancelAllUpdates(), with myUserActivityAlarmRunnable -> processUserActivity)

Within processUserActivity, IdeFocusManager.getInstance(project).doWhenFocusSettlesDown is used (probably to only have the NavBar update once the user has "decided" on an item and isn't just using the keyboard to navigate over tons of items, in which case the timer would fire and the NavBar would display the current selection the user just happened to be over etc)

If some element is found, DataManager.getInstance().getDataContext(focus) is used for requestModelUpdate which calls

requestModelUpdateFromContextOrObject(context, object);

 

DataManager.getInstance.getDataContextFromFocusAsync()

 

List of interesting things I found:

MergingUpdateQueue
NavBarUpdateQueue (requestModelUpdate, requestModelUpdateFromContextOrObject, processUserActivity)
com.intellij.openapi.wm.IdeFocusManager#doWhenFocusSettlesDown
com.intellij.util.Alarm
KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow()

 

 

1 comment
Avatar
Vassiliy Kudryashov
Comment actions Permalink

You're right, the code is pretty complicated, probably it's caused by "feature evolution" when you have to obtain more with existing architecture and API again and again. Good research by the way!

As for hints: you can make things simplier if you watch on active editor only. It's like this:

MessageBusConnection connection = myProject.getMessageBus().connect(someDisposable);
connection.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorListener() {
selectionChanged(FileEditorManagerEvent event) {
VirtualFile[] selection = ((FileEditorManager)event.getSource()).getSelectedFiles();
if (selection.length > 0) {
VirtualFile mostRecent = selection[0];
updateToolWindow(mostRecent);
}
}
});
0

Please sign in to leave a comment.