Future<Collection<Usage>> (@NotNull ShowUsagesParameters parameters, @NotNull ShowUsagesActionHandler actionHandler, @NotNull UsageViewImpl usageView) { Span findUsageSpan = myFindUsagesTracer.spanBuilder("findUsages").startSpan(); Scope opentelemetryScope = findUsageSpan.makeCurrent(); Span popupSpan = myFindUsagesTracer.spanBuilder("findUsage_popup").startSpan(); Span firstUsageSpan = myFindUsagesTracer.spanBuilder("findUsages_firstUsage").startSpan(); Project project = parameters.project; ReadAction.nonBlocking(() -> actionHandler.getEventData()).submit(AppExecutorUtil.getAppExecutorService()).onSuccess( (eventData) -> UsageViewStatisticsCollector.logSearchStarted(project, usageView, CodeNavigateSource.ShowUsagesPopup, eventData)); final SearchScope searchScope = actionHandler.getSelectedScope(); final AtomicInteger outOfScopeUsages = new AtomicInteger(); AtomicBoolean manuallyResized = new AtomicBoolean(); Ref<UsageNode> preselectedRow = new Ref<>(); Predicate<? super Usage> originUsageCheck = originUsageCheck(parameters.editor); ShowUsagesTableCellRenderer renderer = new ShowUsagesTableCellRenderer(originUsageCheck, outOfScopeUsages, searchScope); ShowUsagesTable table = new ShowUsagesTable(renderer, usageView); addUsageNodes(usageView.getRoot(), usageView, new ArrayList<>()); List<Usage> usages = new ArrayList<>(); Set<Usage> visibleUsages = new LinkedHashSet<>(); table.setTableModel(new SmartList<>(new StringNode(UsageViewBundle.message("progress.searching")))); Runnable itemChosenCallback = table.prepareTable( showMoreUsagesRunnable(parameters, actionHandler), showUsagesInMaximalScopeRunnable(parameters, actionHandler), actionHandler ); Consumer<AbstractPopup> tableResizer = popup -> { if (popup != null && popup.isVisible() && !manuallyResized.get()) { PropertiesComponent properties = PropertiesComponent.getInstance(project); int dataSize = table.getModel().getRowCount(); setPopupSize(table, popup, parameters.popupPosition, parameters.minWidth, properties.isValueSet(PREVIEW_PROPERTY_KEY), dataSize); } }; ShowUsagesPopupData showUsagesPopupData = new ShowUsagesPopupData(parameters, table, actionHandler, usageView); AbstractPopup popup = createUsagePopup(usageView, showUsagesPopupData, itemChosenCallback, tableResizer); popup.addResizeListener(() -> manuallyResized.set(true), popup); Ref<Long> popupShownTimeRef = new Ref<>(); popup.addListener(new JBPopupListener() { @Override public void onClosed(@NotNull LightweightWindowEvent event) { @Nullable UsageNode node = getSelectedUsageNode(table); Usage usage = node != null ? node.getUsage() : null; UsageInfo2UsageAdapter usageAdapter = ObjectUtils.tryCast(usage, UsageInfo2UsageAdapter.class); UsageInfo usageInfo = usageAdapter != null ? usageAdapter.getUsageInfo() : null; int preselectedRowNumber = getRowNumber(preselectedRow.get(), table); int selectedRowNumber = getRowNumber(node, table); Long popupShownTime = popupShownTimeRef.get(); Long durationTime = popupShownTime != null ? System.currentTimeMillis() - popupShownTime : null; ReadAction.nonBlocking(() -> actionHandler.buildFinishEventData(usageInfo) ).submit(AppExecutorUtil.getAppExecutorService()).onSuccess((finishEventData) -> { UsageViewStatisticsCollector.logPopupClosed(project, usageView, event.isOk(), preselectedRowNumber, selectedRowNumber, visibleUsages.size(), durationTime, finishEventData ); }); } }); ProgressIndicator indicator = new ProgressIndicatorBase(); if (!popup.isDisposed()) { Disposer.register(popup, usageView); Disposer.register(popup, indicator::cancel); // show popup only if find usages takes more than 300ms, otherwise it would flicker needlessly EdtScheduledExecutorService.getInstance().schedule(() -> { if (!usageView.isDisposed()) { showPopupIfNeedTo(popup, parameters.popupPosition, popupShownTimeRef); popupSpan.end(); } }, ourPopupDelayTimeout, TimeUnit.MILLISECONDS); } UsageNode USAGES_OUTSIDE_SCOPE_NODE = new UsageNode(null, table.USAGES_OUTSIDE_SCOPE_SEPARATOR); UsageNode MORE_USAGES_SEPARATOR_NODE = new UsageNode(null, table.MORE_USAGES_SEPARATOR); PingEDT pingEDT = new PingEDT("Rebuild popup in EDT", () -> popup.isDisposed(), 100, () -> { if (popup.isDisposed()) return; List<UsageNode> nodes = new ArrayList<>(usages.size()); List<Usage> copy; synchronized (usages) { // open up popup as soon as the first usage has been found if (!popup.isVisible() && (usages.isEmpty() || !showPopupIfNeedTo(popup, parameters.popupPosition, popupShownTimeRef))) { return; } addUsageNodes(usageView.getRoot(), usageView, nodes); copy = new ArrayList<>(usages); } boolean shouldShowMoreSeparator = copy.contains(table.MORE_USAGES_SEPARATOR); if (shouldShowMoreSeparator) { nodes.add(MORE_USAGES_SEPARATOR_NODE); } boolean hasOutsideScopeUsages = copy.contains(table.USAGES_OUTSIDE_SCOPE_SEPARATOR); if (hasOutsideScopeUsages && !shouldShowMoreSeparator) { nodes.add(USAGES_OUTSIDE_SCOPE_NODE); } List<UsageNode> data = new ArrayList<>(nodes); int filteredOutCount = getFilteredOutNodeCount(copy, usageView); if (filteredOutCount != 0) { DefaultActionGroup filteringActions = popup.getUserData(DefaultActionGroup.class); if (filteringActions == null) return; List<ToggleAction> unselectedActions = Arrays.stream(filteringActions.getChildren(null)) .filter(action -> action instanceof ToggleAction) .map(action -> (ToggleAction)action) .filter(ta -> !ta.isSelected(fakeEvent(ta))) .filter(ta -> !StringUtil.isEmpty(ta.getTemplatePresentation().getText())) .collect(Collectors.toList()); data.add(new FilteredOutUsagesNode(table.USAGES_FILTERED_OUT_SEPARATOR, UsageViewBundle.message("usages.were.filtered.out", filteredOutCount), UsageViewBundle.message("usages.were.filtered.out.tooltip")) { @Override public void onSelected() { actionHandler.beforeClose(CLOSE_REASON_RESET_FILTERS); // toggle back unselected toggle actions toggleFilters(unselectedActions); // and restart show usages in hope it will show filtered out items now showElementUsages(parameters, actionHandler); } }); } data.sort(new UsageNodeComparator(table)); boolean hasMore = shouldShowMoreSeparator || hasOutsideScopeUsages; int totalCount = copy.size(); int visibleCount = totalCount - filteredOutCount; showUsagesPopupData.header.setStatusText(hasMore, visibleCount, totalCount); rebuildTable(project, originUsageCheck, data, table, popup, parameters.popupPosition, parameters.minWidth, manuallyResized); preselectedRow.set(getSelectedUsageNode(table)); }); MessageBusConnection messageBusConnection = project.getMessageBus().connect(usageView); messageBusConnection.subscribe(UsageFilteringRuleProvider.RULES_CHANGED, () -> rulesChanged(usageView, pingEDT, popup)); AtomicLong firstUsageAddedTS = new AtomicLong(); AtomicBoolean tooManyResults = new AtomicBoolean(); Processor<Usage> collect = usage -> { if (!UsageViewManagerImpl.isInScope(usage, searchScope)) { if (outOfScopeUsages.getAndIncrement() == 0) { visibleUsages.add(USAGES_OUTSIDE_SCOPE_NODE.getUsage()); usages.add(table.USAGES_OUTSIDE_SCOPE_SEPARATOR); } return true; } synchronized (usages) { firstUsageSpan.end(); if (visibleUsages.size() >= parameters.maxUsages) { tooManyResults.set(true); return false; } UsageNode nodes = ReadAction.compute(() -> usageView.doAppendUsage(usage)); usages.add(usage); firstUsageAddedTS.compareAndSet(0, System.nanoTime()); // Successes only once - at first assignment if (nodes != null) { visibleUsages.add(nodes.getUsage()); boolean continueSearch = true; if (visibleUsages.size() == parameters.maxUsages) { visibleUsages.add(MORE_USAGES_SEPARATOR_NODE.getUsage()); usages.add(table.MORE_USAGES_SEPARATOR); continueSearch = false; } pingEDT.ping(); return continueSearch; } } return true; }; UsageSearcher usageSearcher = actionHandler.createUsageSearcher(); long searchStarted = System.nanoTime(); CompletableFuture<Collection<Usage>> result = new CompletableFuture<>(); FindUsagesManager.startProcessUsages(indicator, project, usageSearcher, collect, () -> ApplicationManager.getApplication().invokeLater( () -> { showUsagesPopupData.header.disposeProcessIcon(); pingEDT.ping(); // repaint status synchronized (usages) { findUsageSpan.setAttribute("number", usages.size()); if (visibleUsages.isEmpty()) { if (usages.isEmpty()) { String hint = UsageViewBundle.message("no.usages.found.in", searchScope.getDisplayName()); hint(false, hint, parameters, actionHandler); cancel(popup); } // else all usages filtered out } else if (visibleUsages.size() == 1) { if (usages.size() == 1) { //the only usage Usage usage = visibleUsages.iterator().next(); if (usage == table.USAGES_OUTSIDE_SCOPE_SEPARATOR) { String hint = UsageViewManagerImpl.outOfScopeMessage(outOfScopeUsages.get(), searchScope); hint(true, hint, parameters, actionHandler); cancel(popup); } else { String hint = UsageViewBundle.message("show.usages.only.usage", searchScope.getDisplayName()); navigateAndHint(project, usage, hint, parameters, actionHandler, () -> cancel(popup)); } } else { assert usages.size() > 1 : usages; // usage view can filter usages down to one Usage visibleUsage = visibleUsages.iterator().next(); if (areAllUsagesInOneLine(visibleUsage, usages)) { String hint = UsageViewBundle.message("all.usages.are.in.this.line", usages.size(), searchScope.getDisplayName()); navigateAndHint(project, visibleUsage, hint, parameters, actionHandler, () -> cancel(popup)); cancel(popup); } } } result.complete(usages); } findUsageSpan.end(); long current = System.nanoTime(); UsageViewStatisticsCollector.logSearchFinished(project, usageView, actionHandler.getTargetClass(), searchScope, actionHandler.getTargetLanguage(), visibleUsages.size(), TimeUnit.NANOSECONDS.toMillis(firstUsageAddedTS.get() - searchStarted), TimeUnit.NANOSECONDS.toMillis(current - searchStarted), tooManyResults.get(), indicator.isCanceled(), CodeNavigateSource.ShowUsagesPopup); }, project.getDisposed() )); opentelemetryScope.close(); return result; }