/* * Copyright 2003-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.ide.findusages.view; import com.intellij.icons.AllIcons.Actions; import com.intellij.icons.AllIcons.General; import com.intellij.icons.AllIcons.Toolwindows; import com.intellij.ide.actions.PinActiveTabAction; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.components.StoragePathMacros; import com.intellij.openapi.progress.PerformInBackgroundOption; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task.Backgroundable; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.wm.ToolWindowAnchor; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentManager; import jetbrains.mps.ide.ThreadUtils; import jetbrains.mps.ide.actions.MPSActions; import jetbrains.mps.ide.actions.MPSCommonDataKeys; import jetbrains.mps.ide.findusages.CantLoadSomethingException; import jetbrains.mps.ide.findusages.CantSaveSomethingException; import jetbrains.mps.ide.findusages.model.IResultProvider; import jetbrains.mps.ide.findusages.model.SearchQuery; import jetbrains.mps.ide.findusages.model.SearchResult; import jetbrains.mps.ide.findusages.model.SearchResults; import jetbrains.mps.ide.findusages.view.UsagesView.RebuildAction; import jetbrains.mps.ide.findusages.view.UsagesView.RerunAction; import jetbrains.mps.ide.findusages.view.UsagesView.SearchTaskImpl; import jetbrains.mps.ide.findusages.view.treeholder.tree.DataTreeChangesNotifier; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.openapi.navigation.EditorNavigator; import jetbrains.mps.progress.ProgressMonitorAdapter; import jetbrains.mps.smodel.RepoListenerRegistrar; import jetbrains.mps.util.annotation.ToRemove; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import org.jetbrains.mps.openapi.module.SRepository; import javax.swing.Icon; import javax.swing.JComponent; import java.util.ArrayList; import java.util.List; @State( name = "UsagesViewTool", storages = @Storage(StoragePathMacros.WORKSPACE_FILE) ) public class UsagesViewTool extends TabbedUsagesTool implements PersistentStateComponent<Element> { private static final String VERSION_NUMBER = "1"; private static final String VERSION = "version"; private static final String ID = "id"; private static final String TAB = "tab"; private static final String TABS = "tabs"; private static final String DEFAULT_VIEW_OPTIONS = "default_view_options"; private static final String TOOL_WINDOW_ID = "Usages"; private List<UsageViewData> myUsageViewsData = new ArrayList<>(); private jetbrains.mps.ide.findusages.view.treeholder.treeview.ViewOptions myDefaultViewOptions = new jetbrains.mps.ide.findusages.view.treeholder.treeview.ViewOptions(); private final DataTreeChangesNotifier myChangeTracker = new DataTreeChangesNotifier(); //----CONSTRUCT STUFF---- public UsagesViewTool(Project project) { super(project, TOOL_WINDOW_ID, 3, Toolwindows.ToolWindowFind, ToolWindowAnchor.BOTTOM, true); } @Override protected UsagesView getUsagesView(int index) { return myUsageViewsData.get(index).myUsagesView; } private void register(UsageViewData viewData) { if (myUsageViewsData.isEmpty()) { new RepoListenerRegistrar(ProjectHelper.getProjectRepository(getProject()), myChangeTracker).attach(); } myUsageViewsData.add(viewData); } @Override protected void onRemove(int index) { myUsageViewsData.remove(index); if (myUsageViewsData.isEmpty()) { new RepoListenerRegistrar(ProjectHelper.getProjectRepository(getProject()), myChangeTracker).detach(); } } //----TOOL STUFF---- public int getPriority() { return 0; } @Override protected boolean isInitiallyAvailable() { return true; } //---FIND USAGES STUFF---- /** * Display usages in a tool window of a respective project, according to options supplied. */ public static void showUsages(@NotNull Project project, @NotNull IResultProvider provider, @NotNull SearchQuery query, @NotNull UsageToolOptions options) { project.getComponent(UsagesViewTool.class).findUsages(provider, query, options); } /** * @deprecated Use {@link #showUsages(com.intellij.openapi.project.Project, jetbrains.mps.ide.findusages.model.IResultProvider, jetbrains.mps.ide.findusages.model.SearchQuery, UsageToolOptions)} instead */ @Deprecated @ToRemove(version = 3.3) public void findUsages(IResultProvider provider, SearchQuery query, boolean isRerunnable, boolean showOne, boolean forceNewTab, String notFoundMsg) { findUsages(provider, query, new UsageToolOptions().allowRunAgain(isRerunnable).navigateIfSingle(!showOne).forceNewTab(forceNewTab).notFoundMessage(notFoundMsg)); } private void findUsages(IResultProvider provider, final SearchQuery query, final UsageToolOptions options) { final SearchTaskImpl searchTask = new SearchTaskImpl(provider, query); ThreadUtils.runInUIThreadNoWait(new Runnable() { @Override public void run() { new Backgroundable(getProject(), "Searching", true, PerformInBackgroundOption.DEAF) { private SearchResults searchResults; @Override public void run(@NotNull final ProgressIndicator indicator) { searchResults = searchTask.execute(ProjectHelper.toMPSProject(getProject()).getModelAccess(), new ProgressMonitorAdapter(indicator)); } @Override public void onSuccess() { showResults(searchTask, searchResults, options); } }.queue(); } }); } public void show(SearchResults results, String notFoundMsg) { ThreadUtils.assertEDT(); showResults(null, results, new UsageToolOptions().navigateIfSingle(false).forceNewTab(true).allowRunAgain(false).notFoundMessage(notFoundMsg)); } private void showResults(SearchTaskImpl searchTask, final SearchResults<?> searchResults, UsageToolOptions options) { final jetbrains.mps.project.Project mpsProject = ProjectHelper.toMPSProject(getProject()); int resCount = searchResults.getSearchResults().size(); if (resCount == 0) { final ToolWindowManager manager = ToolWindowManager.getInstance(getProject()); manager.notifyByBalloon(TOOL_WINDOW_ID, MessageType.INFO, options.myNotFoundMessage, null, null); return; } else if (resCount == 1 && options.myNavigateIfSingle) { final SearchResult<?> searchResult = searchResults.getSearchResults().get(0); if (searchResult.getObject() instanceof SNode) { final SNode node = (SNode) searchResult.getObject(); new EditorNavigator(mpsProject).shallFocus(true).selectIfChild().open(node.getReference()); return; } // FALL THROUGH (single result we can't navigate to) } int index = getCurrentTabIndex(); UsagesView usagesView = createUsageView(options.myRunAgain ? searchTask : null); UsageViewData usageViewData = new UsageViewData(usagesView, options.myRunAgain ? searchTask : null); usageViewData.setTransientView(options.myTransientView); register(usageViewData); usagesView.setContents(searchResults); Icon icon = usagesView.getIcon(); String caption = usagesView.getCaption(); JComponent component = usagesView.getComponent(); Content content = addContent(component, caption, icon, true); getContentManager().setSelectedContent(content); if (!options.myForceNewTab) { closeLastUnpinnedTab(index); } openTool(true); } //---END FIND STUFF---- private void read(Element element, jetbrains.mps.project.Project project) { Element versionXML = element.getChild(VERSION); if (versionXML == null) { return; } String version = versionXML.getAttribute(ID).getValue(); if (!VERSION_NUMBER.equals(version)) { return; } Element tabsXML = element.getChild(TABS); if (tabsXML != null) { for (Element tabXML : tabsXML.getChildren()) { final UsageViewData usageViewData; try { usageViewData = UsageViewData.read(this, tabXML, project); } catch (CantLoadSomethingException e) { continue; } register(usageViewData); ApplicationManager.getApplication().invokeLater(() -> { final String caption = usageViewData.myUsagesView.getCaption(); final Icon icon = usageViewData.myUsagesView.getIcon(); addContent(usageViewData.myUsagesView.getComponent(), caption, icon, true); }); } } Element defaultViewOptionsXML = element.getChild(DEFAULT_VIEW_OPTIONS); myDefaultViewOptions.read(defaultViewOptionsXML, project); ApplicationManager.getApplication().invokeLater(() -> { ContentManager cm = getContentManager(); if (cm == null) { return; } if (cm.getContentCount() == 0) { makeUnavailableLater(); } }); } private void write(Element element, jetbrains.mps.project.Project project) { Element versionXML = new Element(VERSION); versionXML.setAttribute(ID, VERSION_NUMBER); element.addContent(versionXML); Element tabsXML = new Element(TABS); for (UsageViewData usageViewData : myUsageViewsData) { if (usageViewData.isTransientView()) { continue; } Element tabXML = new Element(TAB); try { usageViewData.write(tabXML, project); tabsXML.addContent(tabXML); } catch (CantSaveSomethingException e) { // ignore } } element.addContent(tabsXML); Element defaultViewOptionsXML = new Element(DEFAULT_VIEW_OPTIONS); myDefaultViewOptions.write(defaultViewOptionsXML, project); element.addContent(defaultViewOptionsXML); } @Override public Element getState() { final jetbrains.mps.project.Project mpsProject = ProjectHelper.toMPSProject(getProject()); final Element state = new Element("state"); mpsProject.getModelAccess().runReadAction(() -> write(state, mpsProject)); return state; } @Override public void loadState(final Element state) { //startup manager is needed cause the contract is that you can't use read and write locks //on component load - it can cause a deadlock (MPS-2811) StartupManager.getInstance(getProject()).runWhenProjectIsInitialized(() -> { if (getProject().isDisposed()) { return; } final jetbrains.mps.project.Project mpsProject = ProjectHelper.toMPSProject(getProject()); mpsProject.getModelAccess().runReadAction(() -> read(state, mpsProject)); }); } private UsagesView createUsageView(@Nullable SearchTaskImpl searchTask) { jetbrains.mps.project.Project mpsProject = ProjectHelper.fromIdeaProject(getProject()); final UsagesView view = new UsagesView(mpsProject, myDefaultViewOptions, myChangeTracker); ArrayList<AnAction> actions = new ArrayList<>(); if (searchTask != null) { final RerunAction rerunAction = new RerunAction(view, "Run again"); rerunAction.setRunOptions(searchTask); actions.add(rerunAction); } actions.add(new RebuildAction(view)); actions.add(new AnAction("Close", "", Actions.Cancel) { @Override public void actionPerformed(@NotNull AnActionEvent e) { int i = 0; for (UsageViewData vd : myUsageViewsData) { if (vd.myUsagesView == view) { UsagesViewTool.this.closeTab(i); break; } i++; } } }); actions.add(new PinActiveTabAction.TW()); if (ActionManager.getInstance().getAction(MPSActions.FIND_USAGES_WITH_DIALOG_ACTION) != null && searchTask != null) { actions.add(new FindUsagesWithDialogAction(mpsProject.getRepository(), searchTask)); } view.setActions(actions); return view; } /** * Tracks result presentation and optional task to re-populate the view. * Persists state */ private static class UsageViewData { private static final String USAGE_VIEW = "usage_view"; private static final String USAGE_VIEW_OPTIONS = "usage_view_options"; public final UsagesView myUsagesView; public final SearchTaskImpl mySearchTask; private boolean myIsTransientView = false; // now it's not in use, but will be used to implement constructable finders // private FindUsagesOptions myOptions = new FindUsagesOptions(); public UsageViewData(@NotNull UsagesView view, @Nullable SearchTaskImpl searchTask) { myUsagesView = view; mySearchTask = searchTask; } /*package*/void setTransientView(boolean isTransientView) { myIsTransientView = isTransientView; } /*package*/boolean isTransientView() { return myIsTransientView; } @NotNull public static UsageViewData read(UsagesViewTool tool, Element element, jetbrains.mps.project.Project project) throws CantLoadSomethingException { final SearchTaskImpl task = SearchTaskImpl.read(element, project); final UsagesView usageView = tool.createUsageView(task); Element usageViewXML = element.getChild(USAGE_VIEW); usageView.read(usageViewXML, project); // Element usageViewOptionsXML = element.getChild(USAGE_VIEW_OPTIONS); // myOptions = new FindUsagesOptions(usageViewOptionsXML, project); return new UsageViewData(usageView, task); } public void write(Element element, jetbrains.mps.project.Project project) throws CantSaveSomethingException { //this is to partially fix MPS-14671 if (myUsagesView.getTreeComponent().getAllResultNodes().size() > 500) { throw new CantSaveSomethingException("usages view size too big to save"); } if (mySearchTask != null) { mySearchTask.write(element, project); } Element usageViewXML = new Element(USAGE_VIEW); myUsagesView.write(usageViewXML, project); element.addContent(usageViewXML); // Element usageViewOptionsXML = new Element(USAGE_VIEW_OPTIONS); // myOptions.write(usageViewOptionsXML, project); // element.addContent(usageViewOptionsXML); } } private static class FindUsagesWithDialogAction extends AnAction { private final SRepository myRepository; private final SearchTaskImpl mySearchTask; public FindUsagesWithDialogAction(@NotNull SRepository repository, @NotNull SearchTaskImpl searchTask) { super("Settings...", "Show find usages settings dialog", General.ProjectSettings); myRepository = repository; mySearchTask = searchTask; } @Override public void update(AnActionEvent e) { e.getPresentation().setEnabled(ActionManager.getInstance().getAction(MPSActions.FIND_USAGES_WITH_DIALOG_ACTION) != null); } @Override public void actionPerformed(final AnActionEvent e) { if (!mySearchTask.canExecute()) { return; } if (!(mySearchTask.getSearchObject() instanceof SNodeReference)) { return; //object of an incompatible kind (see #getData() below) } final SNodeReference searchedNode = (SNodeReference) mySearchTask.getSearchObject(); DataContext dataContext = new DataContext() { private final DataContext myDelegate = e.getDataContext(); @Nullable @Override public Object getData(@NonNls String dataId) { if (MPSCommonDataKeys.CONTEXT_MODEL.is(dataId)) { SNode resolved = searchedNode.resolve(myRepository); return resolved == null ? null : resolved.getModel(); } // if a caller asks for an SNode, I assume it has appropriate model read, otherwise what would be SNode for? if (MPSCommonDataKeys.NODE.is(dataId)) { return searchedNode.resolve(myRepository); } return myDelegate.getData(dataId); } }; AnActionEvent event = new AnActionEvent(e.getInputEvent(), dataContext, e.getPlace(), e.getPresentation(), e.getActionManager(), e.getModifiers()); AnAction action = ActionManager.getInstance().getAction(MPSActions.FIND_USAGES_WITH_DIALOG_ACTION); action.actionPerformed(event); } } }